In the following:
import Data.Bits (popCount, shiftL, (.|.))
import Data.Char (chr, ord)
import Data.Word (Word8)
import qualified Data.ByteString as B
data MByteSequence = MByteSequence [Word8] -- a sequence of map bytes (len 0-15)
data Atom = TerminationAtom |
NonOffsetAtom { nonOffsetGapLength :: Integer -- the number of gap bytes (max 2**53-1)
, nonOffsetGapValue :: Bool -- the value of the gap bytes
, followingMapBytes :: MByteSequence -- a sequence of following map bytes (len 1-15)
} |
OffsetAtom { offsetGapLength :: Integer -- the number of gap bytes (max 2**53-1)
, offsetGapValue :: Bool -- the value of the gap bytes and the majority of the offset byte
, offsetBit :: Int -- the bit with a different value in the offset byte (0-7)
}
getAtomType :: Atom -> Int
getAtomType TerminationAtom = 1
getAtomType (NonOffsetAtom len _ _)
| len <= 3 = fromInteger len
| len > 3 = 4
getAtomType (OffsetAtom len val _)
| len <= 3 && val == False = 5
| len > 3 = 6
| len <= 3 && val == True = 7
getFillField :: Atom -> Int
getFillField TerminationAtom = 0
getFillField (NonOffsetAtom _ val _) = if val then 1 else 0
getFillField (OffsetAtom len val _) = if len > 3 then (if val then 1 else 0) else fromInteger len
getDataField :: Atom -> Int
getDataField TerminationAtom = 0
getDataField (NonOffsetAtom _ val (MByteSequence mbytes))
| length mbytes == 1 &&
head mbytes == (if val then 0 else 0xFF) = 0
| otherwise = length mbytes
getDataField (OffsetAtom _ _ bit) = bit
serialize :: Atom -> Word8
serialize TerminationAtom = 0
serialize (NonOffsetAtom x y z) = fromIntegral $ (getAtomType all) `shiftL` 6 .|.
(getFillField all) `shiftL` 4 .|.
(getDataField all)
where all = NonOffsetAtom x y z
serialize (OffsetAtom x y z) = fromIntegral $ (getAtomType all) `shiftL` 6 .|.
(getFillField all) `shiftL` 3 .|.
(getDataField all)
where all = OffsetAtom x y z
To me, serialize
especially seems pretty ugly; the use of the where
clauses and the similarity of the latter two pattern match cases all seems bad to me, yet I'm unsure what should be done instead. The other functions, while split up into logical blocks, seem perhaps too small? Should I use Word8
as the return type of all the functions (the return values are all small enough to fit)?
I'd welcome any feedback on code style, Haskell best practices (in general!), and any design patterns I should be using here!