module JSONIncrementalDecoder.Parsers where import JSONIncrementalDecoder.Prelude hiding (scanl, isDigit, bool, null, takeWhile) import Data.Attoparsec.ByteString.Char8 import qualified Data.HashMap.Strict import qualified Control.Monad.Par import qualified JSONIncrementalDecoder.Parsers.Aeson as Aeson -- * General Parser ------------------------- -- | -- Composes two parsers to consume the same input. -- Each must consume it in whole. {-# INLINE parallelly #-} parallelly :: Parser a -> Parser b -> Parser (a, b) parallelly parser1 parser2 = {-# SCC "parallelly" #-} do (input2, result1) <- match parser1 result2 <- liftSubparser input2 (parser2 <* endOfInput) return (result1, result2) {-# INLINABLE sequenceParallellyToList #-} sequenceParallellyToList :: [Parser a] -> Parser [a] sequenceParallellyToList = {-# SCC "sequenceParallellyToList" #-} \case head : tail -> fmap (uncurry (:)) (sequenceParallelly head tail) _ -> return [] {-# INLINABLE sequenceParallelly #-} sequenceParallelly :: Traversable t => Parser a -> t (Parser a) -> Parser (a, (t a)) sequenceParallelly primaryParser secondaryParsers = {-# SCC "sequenceParallelly" #-} do (input, primaryResult) <- match primaryParser secondaryResults <- liftSubparsers input (fmap (<* endOfInput) secondaryParsers) return (primaryResult, secondaryResults) {-# INLINE liftSubparsers #-} liftSubparsers :: Traversable t => ByteString -> t (Parser a) -> Parser (t a) liftSubparsers input parsers = {-# SCC "liftSubparsers" #-} traverse liftEither $ parMap parserToEither parsers where parserToEither parser = parseOnly parser input parMap f xs = Control.Monad.Par.runPar $ traverse (Control.Monad.Par.spawn_ . return . f) xs >>= traverse Control.Monad.Par.get {-# INLINE liftSubparser #-} liftSubparser :: ByteString -> Parser a -> Parser a liftSubparser input parser = {-# SCC "liftSubparser" #-} liftEither (parseOnly (parser <* endOfInput) input) {-# INLINE liftEither #-} liftEither :: Either String a -> Parser a liftEither = {-# SCC "liftEither" #-} either fail return -- * Specific ------------------------- null :: Parser () null = {-# SCC "null" #-} stringCI "null" $> () bool :: Parser Bool bool = {-# SCC "bool" #-} stringCI "false" $> False <|> stringCI "true" $> True {-# INLINE stringLitAsText #-} stringLitAsText :: Parser Text stringLitAsText = {-# SCC "stringLitAsText" #-} Aeson.jstring {-# INLINE numberLitAsIntegral #-} numberLitAsIntegral :: Integral a => Parser a numberLitAsIntegral = {-# SCC "numberLitAsIntegral" #-} signed decimal <* shouldFail (char '.') {-# INLINE numberLitAsDouble #-} numberLitAsDouble :: Parser Double numberLitAsDouble = {-# SCC "numberLitAsDouble" #-} signed double {-# INLINE numberLitAsScientific #-} numberLitAsScientific :: Parser Scientific numberLitAsScientific = {-# SCC "numberLitAsScientific" #-} signed scientific -- | -- An optimized parser, which skips the next valid JSON literal. skipJSONLit :: Parser () skipJSONLit = {-# SCC "skipJSONLit" #-} skipStringLit <|> skipNumberLit <|> void bool <|> null <|> skipArrayLit <|> skipObjectLit skipStringLit :: Parser () skipStringLit = {-# SCC "skipStringLit" #-} char '"' *> contents *> char '"' $> () where contents = skipWhile (\c -> c /= '"' && c /= '\\') *> ((escapeSeq *> contents) <|> pure ()) where escapeSeq = char '\\' *> anyChar skipNumberLit :: Parser () skipNumberLit = {-# SCC "skipNumberLit" #-} sign *> oneOrMoreDigits *> pointAndAfter where oneOrMoreDigits = satisfy isDigit *> skipWhile isDigit sign = (satisfy (\c -> c == '-' || c == '+') $> ()) <|> pure () pointAndAfter = (char '.' *> oneOrMoreDigits) <|> pure () skipObjectRow :: Parser () skipObjectRow = {-# SCC "skipObjectRow" #-} skipStringLit *> skipSpace *> char ':' *> skipSpace *> skipJSONLit skipObjectLit :: Parser () skipObjectLit = {-# SCC "skipObjectLit" #-} objectBody (skipSepBy skipObjectRow comma) skipArrayLit :: Parser () skipArrayLit = {-# SCC "skipArrayLit" #-} arrayBody (skipSepBy skipJSONLit comma) objectBody :: Parser a -> Parser a objectBody body = {-# SCC "objectBody" #-} char '{' *> skipSpace *> body <* skipSpace <* char '}' arrayBody :: Parser a -> Parser a arrayBody body = {-# SCC "arrayBody" #-} char '[' *> skipSpace *> body <* skipSpace <* char ']' colon :: Parser () colon = {-# SCC "colon" #-} skipSpace *> char ':' *> skipSpace comma :: Parser () comma = {-# SCC "comma" #-} skipSpace *> char ',' *> skipSpace objectKey :: Parser Text objectKey = {-# SCC "objectKey" #-} stringLitAsText <* skipSpace <* char ':' <* skipSpace