libasterix-0.14.0: src/Asterix/Base.hs
-- |
-- Module: Asterix.Base
--
-- Asterix base data structures and functions.
-- Some constraints seems redundant to ghc, but are actually required.
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}
module Asterix.Base (
-- * RawDatablock
--
-- | A 'RawDatablock' represents a valid 'ByteString', according to the
-- first level of asterix: @[cat|length|records...]@, where the record
-- bytes are not yet fully parsed.
RawDatablock(..)
, rawDatablockCategory
, parseRawDatablock
, parseRawDatablocks
, unparseRawDatablock
, getRawRecords
, processDatablocks
-- * Basic asterix building blocks
, ItemName
, FRN
, RecordItem(..)
-- * Parsing
, KnownParsingMode(..)
, ParsingStore(..)
, ParsingM
, ParsingError(..)
, ParsingMode(..)
, Env(..)
, runParsing
, parseVariation
, parseExpansion
, parseNonSpare
, parseRecord
, parseRecords
, parseRecordsTry
-- * Unparsing
, Unparsing(..)
, unparseToNum
-- * Asterix related support types and functions
, recreateExtended
, mkFspecFx
, mkFspecFixed
, bitsToString
, bitsToInteger
, bitsToDouble
, asUint
, endOffset
, bitsPerChar
, evalNum
-- * Other support types and functions
, intError
, IsEmpty(..)
, KnownBool(..)
, Fst
, Snd
-- * Heterogeneous list
, HList(..)
, hlUncons
, hlHead
, hlTail
, nil
, (*:)
, FoldHList(..)
) where
import Control.Applicative
import Control.Monad
import Control.Monad.Trans.Class
import Control.Monad.Trans.RWS
import Data.Bits (testBit)
import Data.Bool
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.ByteString.Builder as BSB
import Data.Char (chr, ord)
import Data.Coerce
import Data.Kind (Type)
import qualified Data.List as L
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe
import Data.Proxy
import Data.String as S
import Data.Text (Text)
import Data.Word
import GHC.Stack
import Asterix.BitString
import Asterix.Schema
-- | First part of a Pair.
type family Fst t where Fst '(a, b) = a
-- | Second part of a Pair.
type family Snd t where Snd '(a, b) = b
-- | Raise internal error.
intError :: HasCallStack => a
intError = error $ "Internal error, " <> prettyCallStack callStack
-- | Number of bits per character for different string types.
bitsPerChar :: VStringType -> Int
bitsPerChar = \case
GStringAscii -> 8
GStringICAO -> 6
GStringOctal -> 3
-- | Evaluate GZ number to Integer.
evalGz :: VZ -> Integer
evalGz (GZ pm i) = fromIntegral $ i * case pm of
GPlus -> 1
GMinus -> -1
-- | Evaluate VNumber.
evalNum :: Fractional a => VNumber -> a
evalNum = \case
GNumInt gz -> fromIntegral $ evalGz gz
GNumDiv a b -> evalNum a / evalNum b
GNumPow a b -> fromIntegral (evalGz a ^ evalGz b)
-- | Convert structure to unsigned integer, via Bits representation.
asUint :: forall b a. (Unparsing Bits a, Integral b) => a -> b
asUint = bitsToNum . unparse
-- | Convert Bits to String, according to string type rules.
bitsToString :: VStringType -> Int -> Bits -> String
bitsToString st bitSize =
let bpc :: Int
bpc = bitsPerChar st
toChar :: Int -> Char
toChar x = case st of
GStringAscii -> chr x
GStringICAO -> if
| x >= 0x01 && x <= 0x1A -> chr (ord 'A' + x - 0x01)
| x == 0x20 -> ' '
| x >= 0x30 && x <= 0x39 -> chr (ord '0' + x - 0x30)
| otherwise -> '?'
GStringOctal -> chr (ord '0' + x)
p = 2 ^ bpc
n = div bitSize bpc
go :: Int -> Integer -> String
go cnt x
| cnt > 0 =
let (y, i) = divMod x p
c = toChar $ fromIntegral i
in c : go (pred cnt) y
| cnt == 0 = ""
| otherwise = intError
in reverse . go n . bitsToNum @Integer
-- | Map Unsigned values unchanged, apply proper modulo for signed values.
-- This is a helper function when converting unsigned bits to signed integer.
applySignedness :: (Integral b, Num a, Ord a) => GSignedness -> b -> a -> a
applySignedness sig n val = case sig of
GUnsigned -> val
GSigned -> withAssumption (n > 0) $
let half = 2 ^ (n-1)
in case val < half of
True -> val
False -> val - (2 * half)
-- | Convert bits to integer.
bitsToInteger :: VSignedness -> Int -> Bits -> Integer
bitsToInteger sig n
= applySignedness sig n
. bitsToNum @Integer
-- | Convert bits to double.
bitsToDouble :: VSignedness -> Int -> Double -> Bits -> Double
bitsToDouble sig n lsb
= applyLsb
. applySignedness sig n
. bitsToNum @Integer
where
applyLsb = (* lsb) . fromIntegral
-- | Empty structure check.
class IsEmpty t where
isEmpty :: t -> Bool
-- | Conversion from typelevel to value level bool.
class KnownBool t where boolVal :: Proxy t -> Bool
instance KnownBool 'False where boolVal _ = False
instance KnownBool 'True where boolVal _ = True
-- | Heterogeneous list.
data HList t where
HNil :: HList '[]
HCons :: t -> HList ts -> HList (t ': ts)
-- | Empty HList.
nil :: HList '[]
nil = HNil
-- | HList cons operator.
infixr 5 *:
(*:) :: x -> HList xs -> HList (x ': xs)
(*:) = HCons
-- | Uncons head and tail from HList.
hlUncons :: HList (t ': ts) -> (t, HList ts)
hlUncons (HCons x xs) = (x, xs)
-- | Head of HList.
hlHead :: HList (c : ts) -> c
hlHead = fst . hlUncons
-- | Tail of HList.
hlTail :: HList (a : ts) -> HList ts
hlTail = snd . hlUncons
-- | Split HList based on type level index.
class HListSplit (ix :: [Type]) (ts :: [Type]) where
type HListSplitL ix ts :: [Type]
type HListSplitR ix ts :: [Type]
hListSplit :: Proxy ix -> HList ts
-> (HList (HListSplitL ix ts), HList (HListSplitR ix ts))
instance HListSplit '[] ts where
type HListSplitL '[] ts = '[]
type HListSplitR '[] ts = ts
hListSplit _ lst = (nil, lst)
instance
( t1 ~ t2
, HListSplit ts1 ts2
) => HListSplit (t1 ': ts1) (t2 ': ts2) where
type HListSplitL (t1 ': ts1) (t2 ': ts2) = t1 ': HListSplitL ts1 ts2
type HListSplitR (t1 ': ts1) (t2 ': ts2) = HListSplitR ts1 ts2
hListSplit _ (HCons x xs) =
let (a, b) = hListSplit (Proxy @ts1) xs
in (x *: a, b)
-- | Append 2 HLists.
class HListAppend l1 l2 where
type HListAppendR l1 l2 :: [Type]
hListAppend :: HList l1 -> HList l2 -> HList (HListAppendR l1 l2)
instance HListAppend '[] l2 where
type HListAppendR '[] l2 = l2
hListAppend HNil l = l
instance HListAppend ts l2 => HListAppend (t ': ts) l2 where
type HListAppendR (t ': ts) l2 = t ': HListAppendR ts l2
hListAppend (HCons x xs) l2 = HCons x (hListAppend xs l2)
-- | Fold HList by converting each element to a Monoid
-- and combining the results.
-- A 'c' represents a constraint on each element of HList.
-- A caller of 'foldHList' should provide 'c' as type application,
-- for example: foldHList @Show (\x -> [show x]) someHList,
-- would convert HList to [String], given that each element has
-- Show instance.
class FoldHList c t where
foldHList :: Monoid r => (forall a. c a => a -> r) -> HList t -> r
instance FoldHList c '[] where
foldHList _f HNil = mempty
instance
( c t
, FoldHList c ts
) => FoldHList c (t ': ts) where
foldHList f (HCons x xs) = f x <> foldHList @c f xs
-- | Overload 'unparse' function to work on various structures and results.
class Unparsing r t where
unparse :: t -> r
instance Unparsing Bits Bits where
unparse = id
instance Unparsing Bits ByteString where
unparse = byteStringToBits
instance Unparsing SBuilder ByteString where
unparse = fromByteString
-- | If we can unparse to bits, we can also get a number.
unparseToNum :: (Unparsing Bits t, Integral a) => t -> a
unparseToNum = bitsToNum . unparse
-- | Item name alias.
type ItemName = Text
-- | RawDatablock wrapper around ByteString.
newtype RawDatablock = RawDatablock { unRawDatablock :: ByteString }
deriving (Eq, Show)
instance Unparsing SBuilder RawDatablock where
unparse = fromByteString . unRawDatablock
-- | Fspec flag bits, without fx bits and without trailing bits.
newtype Fspec = Fspec { fspecBits :: [Bool] }
deriving Show
-- | Frame number.
type FRN = Word8
-- | Parsing mode.
data ParsingMode
= StrictParsing
| PartialParsing
deriving (Eq, Enum, Bounded, Show)
-- | Parsing mode type to value instances.
class KnownParsingMode t where parsingMode :: Proxy t -> ParsingMode
instance KnownParsingMode 'StrictParsing where parsingMode _ = StrictParsing
instance KnownParsingMode 'PartialParsing where parsingMode _ = PartialParsing
-- | Record item, containing some NonSpare type.
data RecordItem nsp
= RecordItem nsp
| RecordItemSpare
| RecordItemRFS [(FRN, nsp)]
deriving (Eq, Show)
-- | All 'Nothing' items represent FX which must be '1',
-- except for the last which must be '0' (if FX).
recreateExtended :: Unparsing Bits i => [Maybe i] -> Bits
recreateExtended = \case
[] -> intError
[Nothing] -> setFx False
[Just i] -> unparse @Bits i
(x:xs) -> case x of
Nothing -> setFx True `appendBits` recreateExtended xs
Just i -> unparse @Bits i `appendBits` recreateExtended xs
where
setFx = integerToBits 7 1 . bool 0 1
-- | Create raw fspec from given binary flags, put fx in between.
mkFspecFx :: [Bool] -> SBuilder
mkFspecFx
= bitsToSBuilder
. boolsToBits 0
. mconcat
. reverse
. zipWith addFx (False : repeat True)
. keepAtLeastOne
. dropWhile (== replicate 7 False)
. reverse
. splitToGroupsOf7
where
splitToGroupsOf7 :: [Bool] -> [[Bool]]
splitToGroupsOf7 lst
| Prelude.length lst <= 7 = [take 7 (lst <> repeat False)]
| otherwise = take 7 lst : splitToGroupsOf7 (drop 7 lst)
addFx :: Bool -> [Bool] -> [Bool]
addFx fxBit lst = lst <> [fxBit]
keepAtLeastOne :: [[Bool]] -> [[Bool]]
keepAtLeastOne = \case
[] -> [replicate 7 False]
lst -> lst
-- | Create fixed size raw fspec from given binary flags (no fx).
mkFspecFixed
:: Int -- ^ number of resulting bytes
-> [Bool] -- ^ input flags
-> SBuilder
mkFspecFixed nBytes
= bitsToSBuilder
. boolsToBits 0
. take (nBytes * 8)
. (<> repeat False)
-- | Helper structure for parsing.
-- This module implements actual low-level asterix parsing.
-- Each concrete implementation shall provide a structure of this type
-- as a way to specify how the parsing result shall be stored.
-- This mostly depends on a usecase.
data ParsingStore var rv nsp item rec exp = ParsingStore
-- variation
-- Element consumes the actual bits.
{ psElement :: Bits -> var
-- Group consumes actual bits + list of parsed items
, psGroup :: Bits -> [item] -> var
-- Extended consumes actual bits + list of parsed items or fx bits
, psExtended :: Bits -> [Maybe item] -> var
-- Repetitive consumes actual bits + list of variations
, psRepetitive :: Bits -> [var] -> var
-- Explicit consumes all bits + data bits (all bits except first octet)
, psExplicit :: Bits -> Bits -> var
-- Compound consumes (raw fspec, raw items, items)
, psCompound :: Bits -> Bits -> [Maybe nsp] -> var
-- rule variation
, psRuleVar :: var -> rv
-- non-spare
, psNsp :: rv -> nsp
-- items
, psSpare :: Bits -> item
, psItem :: nsp -> item
-- record
, psRecord ::
Bits -- raw fspec
-> Bits -- raw items
-> [Maybe (RecordItem nsp)] -- items
-> rec
-- expansion
, psExpansion ::
Bits -- raw fspec
-> Bits -- raw items
-> [Maybe nsp] -- items
-> exp
}
-- | Parsing environment.
-- This data type is parametrized with several data types,
-- like 'var', 'item', 'rec'..., such that a Coding module can select concrete
-- types to store 'Variation', 'Item', 'Record'... and this module can remain
-- generic.
data Env (pm :: ParsingMode) var rv nsp item rec exp = Env
{ envStore :: ParsingStore var rv nsp item rec exp
, envInput :: ByteString
}
-- | Extract category number from RawDatablock.
rawDatablockCategory :: RawDatablock -> Word8
rawDatablockCategory (RawDatablock bs) = BS.index bs 0
-- | Extract bytes, representing raw records from RawDatablock.
getRawRecords :: RawDatablock -> ByteString
getRawRecords (RawDatablock bs) = BS.drop 3 bs
-- | Parsing error text.
newtype ParsingError = ParsingError Text
deriving (Show, IsString)
-- | Parsing monad. It combines several effects, which are already
-- provided by the RWST type from the 'mtl' library.
-- - reader type is the environment
-- - writer type is not used ()
-- - state type is the current parsing offfset
-- - monad is (Either ParsingError r), where the 'r' represents
-- eventual parsing result
-- All type parameters are required by the environment (see 'Env').
type ParsingM pm a b c d e f = RWST (Env pm a b c d e f) () Offset (Either ParsingError)
-- | Run parsing action.
runParsing :: Monad m => RWST r w s m a -> r -> s -> m (a, s)
runParsing act r s = do
(a, s', _w) <- runRWST act r s
pure (a, s')
-- | Raise parsing error.
parsingError :: ParsingError -> ParsingM pm a b c d e f r
parsingError = lift . Left
-- | Check bit alignment.
aligned :: Offset -> Int -> Bool
aligned o o8 = numBits o == o8
-- | End of a bytestring.
endOffset :: ByteString -> Offset
endOffset = intToNumBits . (*8) . BS.length
-- | End of input.
eof :: ParsingM pm a b c d e f Bool
eof = do
o <- get
o2 <- asks (endOffset . envInput)
case compare o o2 of
LT -> pure False
EQ -> pure True
GT -> intError
-- | Move offset pointer by 'n' bits.
moveOffset :: Size -> ParsingM pm a b c d e f ()
moveOffset n = do
bs <- asks envInput
o <- get
let o' = o + coerce n
when (o' > endOffset bs) $ parsingError "overflow"
put o'
-- | Fetch some number of bits.
parseBits :: Int -> Size -> ParsingM pm a b c d e f Bits
parseBits o8 n = do
bs <- asks envInput
o <- get
withAssumption (aligned o o8) $ do
moveOffset n
pure $ Bits bs o n
-- | Parse Word8.
parseWord8 :: ParsingM pm a b c d e f Word8
parseWord8 = do
bs <- asks envInput
o <- get
withAssumption (aligned o 0) $ do
moveOffset 8
pure $ BS.index bs (numBytes o)
-- | Parse 'n' bytes.
parseBytes :: Int -> ParsingM pm a b c d e f ByteString
parseBytes n = do
bs <- asks envInput
o <- get
withAssumption (aligned o 0) $ do
moveOffset $ intToNumBits $ n * 8
pure $ BS.take n $ BS.drop (numBytes o) bs
-- | Parse 'fx' bit.
parseFx :: ParsingM pm a b c d e f Bool
parseFx = do
Bits bs o _ <- parseBits 7 1
pure $ testBit (BS.index bs (numBytes o)) 0
-- | Parse complete fspec, take fx bits into account
-- The ParsingMode is present in the environment, which is ignored in this
-- function. Explicit parameter is used instead. The reason is that a
-- caller can override mode, for example... fspec in compound is always
-- strict and for the record it depends on user preferences.
parseFspec :: ParsingMode -> Int -> ParsingM pm a b c d e f Fspec
parseFspec pm definedItems = do
result <- go
let n = length result
(a, b) = divMod definedItems 7
maxSize
| b == 0 = definedItems
| otherwise = (a+1) * 7
when (n == 0) $ parsingError "empty fspec"
case pm of
StrictParsing -> when (n > maxSize) $ parsingError "fspec too big"
PartialParsing -> pure ()
pure $ Fspec $ take definedItems result
where
go :: ParsingM pm a b c d e f [Bool]
go = do
w <- word8ToBools <$> parseWord8
let (flags, fx) = (init w, last w)
case fx of
False -> pure flags
True -> (<>) <$> pure flags <*> go
-- | Parse Variation.
parseVariation :: VVariation -> ParsingM pm var b c d e f var
parseVariation sch = ask >>= \env -> case sch of
GElement o n _ruleCont -> do
psElement (envStore env) <$> parseBits o (intToNumBits n)
GGroup _o lst -> do
o1 <- get
items <- go lst
o2 <- get
let bits = Bits (envInput env) o1 (coerce $ o2 - o1)
pure $ psGroup (envStore env) bits items
where
go [] = pure []
go (x:xs) = (:) <$> parseItem x <*> go xs
GExtended lst -> do
o1 <- get
mItems <- go lst
o2 <- get
let bits = Bits (envInput env) o1 (coerce $ o2 - o1)
pure $ psExtended (envStore env) bits mItems
where
go [] = pure []
go (mx:xs) = case mx of
Nothing -> parseFx >>= \case
False -> pure [Nothing]
True -> case xs of
[] -> parsingError "last FX bit is expected to be zero"
_ -> (:) <$> pure Nothing <*> go xs
Just x -> ((:) . Just <$> parseItem x) <*> go xs
GRepetitive rt var -> do
o1 <- get
lst <- case rt of
GRepetitiveRegular rep -> do
parseBytes rep >>= goRegular . byteStringToNum @Int
GRepetitiveFx -> goFx
o2 <- get
let bits = Bits (envInput env) o1 (coerce $ o2 - o1)
pure $ psRepetitive (envStore env) bits lst
where
goRegular = \case
0 -> pure []
n -> (:) <$> parseVariation var <*> goRegular (pred n)
goFx = do
x <- parseVariation var
parseFx >>= \case
False -> pure [x]
True -> (:) <$> pure x <*> goFx
GExplicit _met -> do
o1 <- get
n <- parseWord8
o2 <- get
_ <- case n of
0 -> parsingError "unexpected size of explicit item"
_ -> parseBytes (fromIntegral $ pred n)
o3 <- get
let b1 = Bits (envInput env) o1 (coerce $ o3 - o1)
b2 = Bits (envInput env) o2 (coerce $ o3 - o2)
pure $ psExplicit (envStore env) b1 b2
GCompound lst -> do
o1 <- get
fspec <- parseFspec StrictParsing (length lst)
o2 <- get
items <- go $ zip (fspecBits fspec <> repeat False) lst
o3 <- get
let rawFspec = Bits (envInput env) o1 (coerce $ o2 - o1)
rawItems = Bits (envInput env) o2 (coerce $ o3 - o2)
pure $ psCompound (envStore env) rawFspec rawItems items
where
go :: [(Bool, Maybe VNonSpare)]
-> ParsingM pm a b nsp d e f [Maybe nsp]
go [] = pure mempty
go ((flag,spec) : xs)
| not flag = (:) <$> pure Nothing <*> go xs
| otherwise = case spec of
Nothing -> parsingError "FX bit set for non-defined item"
Just nsp -> ((:) . Just <$> parseNonSpare nsp) <*> go xs
-- | Parse RuleVariation.
parseRuleVariation :: VRule VVariation
-> ParsingM pm a rv c d e f rv
parseRuleVariation sch1 = ask >>= \env -> do
let sch2 = case sch1 of
GContextFree sch -> sch
GDependent _ sch _ -> sch
psRuleVar (envStore env) <$> parseVariation sch2
-- | Parse NonSpare.
parseNonSpare :: VNonSpare -> ParsingM pm var rv nsp d e f nsp
parseNonSpare (GNonSpare _name _title rvSch) = ask >>= \env -> do
psNsp (envStore env) <$> parseRuleVariation rvSch
-- | Parse Item.
parseItem :: VItem -> ParsingM pm a b c item e f item
parseItem sch = ask >>= \env -> case sch of
GSpare o n -> psSpare (envStore env) <$> parseBits o (intToNumBits n)
GItem nsp -> psItem (envStore env) <$> parseNonSpare nsp
-- | Parse Record.
parseRecord :: forall pm a b c d rec f.
( KnownParsingMode pm
) => VRecord -> ParsingM pm a b c d rec f rec
parseRecord (GRecord lst) = ask >>= \env -> do
o1 <- get
fspec <- parseFspec pm (length lst)
o2 <- get
(items, clean) <- goItems (fspecBits fspec) lst
o3 <- get
let rawItems = Bits (envInput env) o2 (coerce $ o3 - o2)
-- We can reuse original bits only if the parsing was 'clean',
-- otherwise we need to recreate the fspec.
rawFspec = case clean of
True -> Bits (envInput env) o1 (coerce $ o2 - o1)
False -> recreateFspec (fmap isJust items)
pure $ psRecord (envStore env) rawFspec rawItems items
where
pm :: ParsingMode
pm = parsingMode @pm Proxy
findSchema :: FRN -> [VUapItem] -> Maybe VNonSpare
findSchema _ [] = Nothing
findSchema n (x:xs)
| n == 0 = case x of
GUapItem nsp -> Just nsp
_ -> Nothing
| otherwise = findSchema (pred n) xs
goRfs :: ParsingM pm a b nsp d e f ([(FRN, nsp)], Bool)
goRfs = parseWord8 >>= go
where
go :: Word8 -> ParsingM pm a b nsp d e f ([(FRN, nsp)], Bool)
go = \case
0 -> pure (mempty, True)
cnt -> do
frn <- parseWord8 >>= \case
0 -> parsingError "invalid FRN"
n -> pure n
nsp <- maybe
(parsingError "RFS subitem not defined")
pure (findSchema (pred frn) lst)
env <- ask
offset <- get
case runParsing (parseNonSpare nsp) env offset of
Left err -> case pm of
StrictParsing -> parsingError err
PartialParsing -> pure (mempty, False)
Right (x, offset') -> do
put offset'
(xs, clean) <- go (pred cnt)
pure ((frn, x) : xs, clean)
goItems :: [Bool] -> [VUapItem]
-> ParsingM pm a b nsp d e f ([Maybe (RecordItem nsp)], Bool)
goItems [] [] = pure (mempty, True)
goItems [] (_:ts) = do
(items, clean) <- goItems [] ts
pure (Nothing : items, clean)
goItems _flags [] = case pm of
StrictParsing -> parsingError "record subitem not defined"
PartialParsing -> pure (mempty, False)
goItems (flag:flags) (spec:specs)
| not flag = do
(items, clean) <- goItems flags specs
pure (Nothing : items, clean)
| otherwise = case spec of
GUapItemSpare -> case pm of
StrictParsing -> parsingError "FX bit set for spare item"
PartialParsing -> pure (mempty, False)
GUapItemRFS -> do
(rfs, clean1) <- goRfs
case clean1 of
False -> pure ([Just (RecordItemRFS rfs)], False)
True -> do
(items, clean2) <- goItems flags specs
pure (Just (RecordItemRFS rfs) : items, clean2)
GUapItem nsp -> do
env <- ask
offset <- get
case runParsing (parseNonSpare nsp) env offset of
Left err -> case pm of
StrictParsing -> parsingError err
PartialParsing -> pure (mempty, False)
Right (x, offset') -> do
put offset'
(items, clean) <- goItems flags specs
pure (Just (RecordItem x) : items, clean)
-- | Recreate Fspec as Bits.
recreateFspec :: [Bool] -> Bits
recreateFspec
= byteStringToBits
. BS.pack
. terminateFx -- set last FX bit to '0'
. fmap ((+ 1) . (* 2) . boolsToNum)
. L.dropWhileEnd (replicate 7 False ==)
. groupsOf False 7
where
groupsOf :: a -> Int -> [a] -> [[a]]
groupsOf a n lst
| m == 0 = []
| m < n = [lst <> replicate (n-m) a]
| m == n = [lst]
| otherwise = L.take n lst : groupsOf a n (L.drop n lst)
where
m = Prelude.length lst
terminateFx :: Num a => [a] -> [a]
terminateFx [] = []
terminateFx lst = init lst <> [last lst - 1]
-- | Parse multiple records of the same UAP.
parseRecords ::
( pm ~ StrictParsing -- only makes sense with StrictParsing
) => VRecord -> ParsingM pm a b c d rec f [rec]
parseRecords sch = eof >>= \case
True -> pure []
False -> (:) <$> parseRecord sch <*> parseRecords sch
-- | Try to parse multiple UAP combinations.
-- This function consumes complete input or fails if maxDepth reached.
parseRecordsTry ::
( pm ~ StrictParsing -- only makes sense with StrictParsing
) => Maybe Int -> [(VText, VRecord)]
-> ParsingM pm a b c d rec f [[(VText, rec)]]
parseRecordsTry mMaxDepth schs = do
env <- ask
offset <- get
let eo = endOffset $ envInput env
result <- go 0 eo env offset
put eo
pure result
where
isOverflow depth = case mMaxDepth of
Nothing -> False
Just maxDepth -> depth > maxDepth
go depth eo env offset
| isOverflow depth = parsingError "max_depth reached"
| offset > eo = intError
| offset == eo = pure [[]]
| otherwise = do
let result1 = do
(name, sch) <- schs
(x, offset') <- either (const empty) pure
(runParsing (parseRecord sch) env offset)
pure (name, x, offset')
result2 <- forM result1 $ \(name, x, offset') -> do
cont <- go (succ depth) eo env offset'
pure (name, x, cont)
pure $ do
(name, x, ys) <- result2
y <- ys
pure ((name, x) : y)
-- | Parse Expansion.
parseExpansion :: VExpansion -> ParsingM pm a b c d e exp exp
parseExpansion (GExpansion mn lst) = ask >>= \env -> do
o1 <- get
fspec <- case mn of
Nothing -> parseFspec StrictParsing (length lst)
Just n -> Fspec . mconcat . fmap word8ToBools . BS.unpack <$> parseBytes n
o2 <- get
items <- go $ zip (fspecBits fspec <> repeat False) lst
o3 <- get
let rawFspec = Bits (envInput env) o1 (coerce $ o2 - o1)
rawItems = Bits (envInput env) o2 (coerce $ o3 - o2)
pure $ psExpansion (envStore env) rawFspec rawItems items
where
go :: [(Bool, Maybe VNonSpare)]
-> ParsingM pm a b nsp d e f [Maybe nsp]
go [] = pure mempty
go ((flag, spec) : xs)
| not flag = (:) <$> pure Nothing <*> go xs
| otherwise = case spec of
Nothing -> parsingError "FX bit set for non-defined item"
Just nsp -> ((:) . Just <$> parseNonSpare nsp) <*> go xs
-- | Parse ByteString to RawDatablock and the remaining ByteString.
parseRawDatablock :: ByteString -> Either ParsingError (RawDatablock, ByteString)
parseRawDatablock bs = do
let n = BS.length bs
when (n < 3) $ Left "overflow datablock header"
let m = fromIntegral (BS.index bs 1) * 256 + fromIntegral (BS.index bs 2)
when (m < 3) $ Left "unexpected length"
when (m > n) $ Left "overflow datablock records"
pure (RawDatablock $ BS.take m bs, BS.drop m bs)
-- | Parse many RawDatablocks.
parseRawDatablocks :: ByteString -> Either ParsingError [RawDatablock]
parseRawDatablocks bs
| BS.null bs = pure []
| otherwise = do
(x, bs') <- parseRawDatablock bs
(:) <$> pure x <*> parseRawDatablocks bs'
-- | Given a map from asterix category to processing function,
-- process known categories and create list of results. The
-- result is 'Nothing' for unknown category.
processDatablocks :: Map Word8 (RawDatablock -> r) -> ByteString
-> Either ParsingError [Maybe r]
processDatablocks mapping bs = fmap go <$> parseRawDatablocks bs
where
go db = do
f <- Map.lookup (rawDatablockCategory db) mapping
pure $ f db
-- | Unparse RawDatablock.
unparseRawDatablock :: RawDatablock -> Builder
unparseRawDatablock = BSB.byteString . unRawDatablock