simple-parser 0.2.2 → 0.3.0
raw patch · 15 files changed
+1597/−775 lines, 15 filesdep +nonempty-containersdep +scientificdep +text-builderPVP ok
version bump matches the API change (PVP)
Dependencies added: nonempty-containers, scientific, text-builder
API changes (from Hackage documentation)
- SimpleParser.Examples.Json: parseJson :: Text -> [Json]
- SimpleParser.Parser: branchParser :: (Foldable f, Monad m) => f (ParserT e s m a) -> ParserT e s m a
- SimpleParser.Parser: filterParser :: Monad m => (a -> Bool) -> ParserT e s m a -> ParserT e s m a
- SimpleParser.Parser: instance Control.Monad.Morph.MFunctor (SimpleParser.Parser.ParserT e s)
- SimpleParser.Parser: instance Control.Monad.Trans.Class.MonadTrans (SimpleParser.Parser.ParserT e s)
- SimpleParser.Parser: instance GHC.Base.Functor m => GHC.Base.Functor (SimpleParser.Parser.ParserT e s m)
- SimpleParser.Parser: instance GHC.Base.Monad m => Control.Monad.Error.Class.MonadError e (SimpleParser.Parser.ParserT e s m)
- SimpleParser.Parser: instance GHC.Base.Monad m => Control.Monad.State.Class.MonadState s (SimpleParser.Parser.ParserT e s m)
- SimpleParser.Parser: instance GHC.Base.Monad m => GHC.Base.Alternative (SimpleParser.Parser.ParserT e s m)
- SimpleParser.Parser: instance GHC.Base.Monad m => GHC.Base.Applicative (SimpleParser.Parser.ParserT e s m)
- SimpleParser.Parser: instance GHC.Base.Monad m => GHC.Base.Monad (SimpleParser.Parser.ParserT e s m)
- SimpleParser.Parser: instance GHC.Base.Monad m => GHC.Base.MonadPlus (SimpleParser.Parser.ParserT e s m)
- SimpleParser.Parser: isolateParser :: Monad m => ParserT e s m a -> ParserT e s m a
- SimpleParser.Parser: reflectParser :: Monad m => ParserT e s m a -> ParserT e s m (ParseValue e a)
- SimpleParser.Parser: suppressParser :: Monad m => ParserT e s m a -> ParserT e s m a
- SimpleParser.Result: ParseResult :: !ParseValue e a -> !s -> ParseResult e s a
- SimpleParser.Result: [prState] :: ParseResult e s a -> !s
- SimpleParser.Result: [prValue] :: ParseResult e s a -> !ParseValue e a
- SimpleParser.Result: data ParseValue e a
- SimpleParser.Result: instance (GHC.Classes.Eq e, GHC.Classes.Eq a) => GHC.Classes.Eq (SimpleParser.Result.ParseValue e a)
- SimpleParser.Result: instance (GHC.Classes.Eq e, GHC.Classes.Eq a, GHC.Classes.Eq s) => GHC.Classes.Eq (SimpleParser.Result.ParseResult e s a)
- SimpleParser.Result: instance (GHC.Show.Show e, GHC.Show.Show a) => GHC.Show.Show (SimpleParser.Result.ParseValue e a)
- SimpleParser.Result: instance (GHC.Show.Show e, GHC.Show.Show a, GHC.Show.Show s) => GHC.Show.Show (SimpleParser.Result.ParseResult e s a)
- SimpleParser.Result: instance Data.Foldable.Foldable (SimpleParser.Result.ParseResult e s)
- SimpleParser.Result: instance Data.Foldable.Foldable (SimpleParser.Result.ParseValue e)
- SimpleParser.Result: instance Data.Traversable.Traversable (SimpleParser.Result.ParseResult e s)
- SimpleParser.Result: instance Data.Traversable.Traversable (SimpleParser.Result.ParseValue e)
- SimpleParser.Result: instance GHC.Base.Functor (SimpleParser.Result.ParseResult e s)
- SimpleParser.Result: instance GHC.Base.Functor (SimpleParser.Result.ParseValue e)
- SimpleParser.Result: parseErrorResult :: e -> s -> ParseResult e s a
- SimpleParser.Result: parseSuccessResult :: a -> s -> ParseResult e s a
- SimpleParser.Result: parseValue :: (e -> r) -> (a -> r) -> ParseValue e a -> r
- SimpleParser.Stream: chunkEmpty :: Chunked chunk token => chunk -> Bool
- SimpleParser.Stream: chunkLength :: Chunked chunk token => chunk -> Int
- SimpleParser.Stream: chunkToTokens :: Chunked chunk token => chunk -> [token]
- SimpleParser.Stream: class Monoid chunk => Chunked chunk token | chunk -> token
- SimpleParser.Stream: consChunk :: Chunked chunk token => token -> chunk -> chunk
- SimpleParser.Stream: instance SimpleParser.Stream.Chunked (Data.Sequence.Internal.Seq a) a
- SimpleParser.Stream: instance SimpleParser.Stream.Chunked Data.Text.Internal.Text GHC.Types.Char
- SimpleParser.Stream: instance SimpleParser.Stream.Chunked [a] a
- SimpleParser.Stream: tokenToChunk :: Chunked chunk token => token -> chunk
- SimpleParser.Stream: tokensToChunk :: Chunked chunk token => [token] -> chunk
- SimpleParser.Stream: unconsChunk :: Chunked chunk token => chunk -> Maybe (token, chunk)
+ SimpleParser.Chunked: buildChunk :: TextualChunked chunk => chunk -> Builder
+ SimpleParser.Chunked: chunkEmpty :: Chunked chunk token => chunk -> Bool
+ SimpleParser.Chunked: chunkLength :: Chunked chunk token => chunk -> Int
+ SimpleParser.Chunked: chunkToTokens :: Chunked chunk token => chunk -> [token]
+ SimpleParser.Chunked: class Monoid chunk => Chunked chunk token | chunk -> token
+ SimpleParser.Chunked: class Chunked chunk Char => TextualChunked chunk
+ SimpleParser.Chunked: consChunk :: Chunked chunk token => token -> chunk -> chunk
+ SimpleParser.Chunked: instance (a GHC.Types.~ GHC.Types.Char) => SimpleParser.Chunked.TextualChunked (Data.Sequence.Internal.Seq a)
+ SimpleParser.Chunked: instance (a GHC.Types.~ GHC.Types.Char) => SimpleParser.Chunked.TextualChunked [a]
+ SimpleParser.Chunked: instance SimpleParser.Chunked.Chunked (Data.Sequence.Internal.Seq a) a
+ SimpleParser.Chunked: instance SimpleParser.Chunked.Chunked Data.Text.Internal.Text GHC.Types.Char
+ SimpleParser.Chunked: instance SimpleParser.Chunked.Chunked [a] a
+ SimpleParser.Chunked: instance SimpleParser.Chunked.TextualChunked Data.Text.Internal.Text
+ SimpleParser.Chunked: packChunk :: TextualChunked chunk => chunk -> Text
+ SimpleParser.Chunked: revTokensToChunk :: Chunked chunk token => [token] -> chunk
+ SimpleParser.Chunked: tokenToChunk :: Chunked chunk token => token -> chunk
+ SimpleParser.Chunked: tokensToChunk :: Chunked chunk token => [token] -> chunk
+ SimpleParser.Chunked: unconsChunk :: Chunked chunk token => chunk -> Maybe (token, chunk)
+ SimpleParser.Chunked: unpackChunk :: TextualChunked chunk => Text -> chunk
+ SimpleParser.Common: CompoundTextLabelCustom :: !l -> CompoundTextLabel l
+ SimpleParser.Common: CompoundTextLabelText :: !TextLabel -> CompoundTextLabel l
+ SimpleParser.Common: TextLabelDigit :: TextLabel
+ SimpleParser.Common: TextLabelHSpace :: TextLabel
+ SimpleParser.Common: TextLabelSpace :: TextLabel
+ SimpleParser.Common: betweenParser :: Monad m => ParserT l s e m () -> ParserT l s e m () -> ParserT l s e m a -> ParserT l s e m a
+ SimpleParser.Common: class EmbedTextLabel l
+ SimpleParser.Common: data CompoundTextLabel l
+ SimpleParser.Common: data TextLabel
+ SimpleParser.Common: decimalParser :: (EmbedTextLabel l, Stream s, Token s ~ Char, Monad m, Num a) => ParserT l s e m a
+ SimpleParser.Common: embedTextLabel :: EmbedTextLabel l => TextLabel -> l
+ SimpleParser.Common: escapedStringParser :: (Stream s, Token s ~ Char, Monad m) => Char -> ParserT l s e m (Chunk s)
+ SimpleParser.Common: getStreamPos :: (Stream s, Monad m) => ParserT l s e m (Pos s)
+ SimpleParser.Common: hspaceParser :: (Stream s, Token s ~ Char, Monad m) => ParserT l s e m ()
+ SimpleParser.Common: hspaceParser1 :: (EmbedTextLabel l, Stream s, Token s ~ Char, Monad m) => ParserT l s e m ()
+ SimpleParser.Common: instance Data.Foldable.Foldable SimpleParser.Common.CompoundTextLabel
+ SimpleParser.Common: instance Data.Traversable.Traversable SimpleParser.Common.CompoundTextLabel
+ SimpleParser.Common: instance GHC.Base.Functor SimpleParser.Common.CompoundTextLabel
+ SimpleParser.Common: instance GHC.Classes.Eq SimpleParser.Common.TextLabel
+ SimpleParser.Common: instance GHC.Classes.Eq l => GHC.Classes.Eq (SimpleParser.Common.CompoundTextLabel l)
+ SimpleParser.Common: instance GHC.Show.Show SimpleParser.Common.TextLabel
+ SimpleParser.Common: instance GHC.Show.Show l => GHC.Show.Show (SimpleParser.Common.CompoundTextLabel l)
+ SimpleParser.Common: instance SimpleParser.Common.EmbedTextLabel (SimpleParser.Common.CompoundTextLabel l)
+ SimpleParser.Common: instance SimpleParser.Common.EmbedTextLabel SimpleParser.Common.TextLabel
+ SimpleParser.Common: lexemeParser :: Monad m => ParserT l s e m () -> ParserT l s e m a -> ParserT l s e m a
+ SimpleParser.Common: newlineParser :: (Stream s, Token s ~ Char, Monad m) => ParserT l s e m ()
+ SimpleParser.Common: scientificParser :: (EmbedTextLabel l, Stream s, Token s ~ Char, Monad m) => ParserT l s e m Scientific
+ SimpleParser.Common: sepByParser :: (Chunked seq elem, Monad m) => ParserT l s e m elem -> ParserT l s e m () -> ParserT l s e m seq
+ SimpleParser.Common: signedNumStartPred :: Char -> Bool
+ SimpleParser.Common: signedParser :: (Stream s, Token s ~ Char, Monad m, Num a) => ParserT l s e m () -> ParserT l s e m a -> ParserT l s e m a
+ SimpleParser.Common: spaceParser :: (Stream s, Token s ~ Char, Monad m) => ParserT l s e m ()
+ SimpleParser.Common: spaceParser1 :: (EmbedTextLabel l, Stream s, Token s ~ Char, Monad m) => ParserT l s e m ()
+ SimpleParser.Common: spanParser :: (Stream s, Monad m) => (Span (Pos s) -> a -> b) -> ParserT l s e m a -> ParserT l s e m b
+ SimpleParser.Examples.Json: JsonNum :: !Scientific -> JsonF a
+ SimpleParser.Examples.Json: jsonParser :: JsonParserC s => JsonParserM s Json
+ SimpleParser.Examples.Json: recJsonParser :: JsonParserC s => JsonParserM s a -> JsonParserM s (JsonF a)
+ SimpleParser.Examples.Json: type JsonParserC s = (TextualStream s, Eq (Chunk s))
+ SimpleParser.Examples.Json: type JsonParserM s a = Parser TextLabel s Void a
+ SimpleParser.Examples.Sexp: AtomFloat :: !Scientific -> Atom
+ SimpleParser.Examples.Sexp: AtomIdent :: !Text -> Atom
+ SimpleParser.Examples.Sexp: AtomInt :: !Integer -> Atom
+ SimpleParser.Examples.Sexp: AtomString :: !Text -> Atom
+ SimpleParser.Examples.Sexp: Sexp :: SexpF Sexp -> Sexp
+ SimpleParser.Examples.Sexp: SexpAtom :: !Atom -> SexpF a
+ SimpleParser.Examples.Sexp: SexpLabelEmbedText :: !TextLabel -> SexpLabel
+ SimpleParser.Examples.Sexp: SexpLabelIdentStart :: SexpLabel
+ SimpleParser.Examples.Sexp: SexpList :: !Seq a -> SexpF a
+ SimpleParser.Examples.Sexp: [unSexp] :: Sexp -> SexpF Sexp
+ SimpleParser.Examples.Sexp: data Atom
+ SimpleParser.Examples.Sexp: data SexpF a
+ SimpleParser.Examples.Sexp: data SexpLabel
+ SimpleParser.Examples.Sexp: instance Data.Foldable.Foldable SimpleParser.Examples.Sexp.SexpF
+ SimpleParser.Examples.Sexp: instance Data.Traversable.Traversable SimpleParser.Examples.Sexp.SexpF
+ SimpleParser.Examples.Sexp: instance GHC.Base.Functor SimpleParser.Examples.Sexp.SexpF
+ SimpleParser.Examples.Sexp: instance GHC.Classes.Eq SimpleParser.Examples.Sexp.Atom
+ SimpleParser.Examples.Sexp: instance GHC.Classes.Eq SimpleParser.Examples.Sexp.Sexp
+ SimpleParser.Examples.Sexp: instance GHC.Classes.Eq SimpleParser.Examples.Sexp.SexpLabel
+ SimpleParser.Examples.Sexp: instance GHC.Classes.Eq a => GHC.Classes.Eq (SimpleParser.Examples.Sexp.SexpF a)
+ SimpleParser.Examples.Sexp: instance GHC.Show.Show SimpleParser.Examples.Sexp.Atom
+ SimpleParser.Examples.Sexp: instance GHC.Show.Show SimpleParser.Examples.Sexp.Sexp
+ SimpleParser.Examples.Sexp: instance GHC.Show.Show SimpleParser.Examples.Sexp.SexpLabel
+ SimpleParser.Examples.Sexp: instance GHC.Show.Show a => GHC.Show.Show (SimpleParser.Examples.Sexp.SexpF a)
+ SimpleParser.Examples.Sexp: instance SimpleParser.Common.EmbedTextLabel SimpleParser.Examples.Sexp.SexpLabel
+ SimpleParser.Examples.Sexp: instance SimpleParser.Explain.ExplainLabel SimpleParser.Examples.Sexp.SexpLabel
+ SimpleParser.Examples.Sexp: newtype Sexp
+ SimpleParser.Examples.Sexp: recSexpParser :: SexpParserC s => SexpParserM s a -> SexpParserM s (SexpF a)
+ SimpleParser.Examples.Sexp: sexpParser :: SexpParserC s => SexpParserM s Sexp
+ SimpleParser.Examples.Sexp: type SexpParserC s = TextualStream s
+ SimpleParser.Examples.Sexp: type SexpParserM s a = Parser SexpLabel s Void a
+ SimpleParser.Explain: ErrorExplanation :: !Builder -> !Maybe Builder -> !Maybe Builder -> ErrorExplanation
+ SimpleParser.Explain: ParseErrorExplanation :: !Span p -> !Seq Builder -> !Maybe Builder -> !ErrorExplanation -> ParseErrorExplanation p
+ SimpleParser.Explain: [eeActual] :: ErrorExplanation -> !Maybe Builder
+ SimpleParser.Explain: [eeExpected] :: ErrorExplanation -> !Maybe Builder
+ SimpleParser.Explain: [eeReason] :: ErrorExplanation -> !Builder
+ SimpleParser.Explain: [peeContext] :: ParseErrorExplanation p -> !Seq Builder
+ SimpleParser.Explain: [peeDetails] :: ParseErrorExplanation p -> !Maybe Builder
+ SimpleParser.Explain: [peeErrExp] :: ParseErrorExplanation p -> !ErrorExplanation
+ SimpleParser.Explain: [peeSpan] :: ParseErrorExplanation p -> !Span p
+ SimpleParser.Explain: buildAllParseErrorExplanations :: Foldable f => f (ParseErrorExplanation LinePos) -> Builder
+ SimpleParser.Explain: buildErrorExplanation :: Maybe Builder -> ErrorExplanation -> [Builder]
+ SimpleParser.Explain: buildParseErrorExplanation :: ParseErrorExplanation LinePos -> Builder
+ SimpleParser.Explain: buildSpan :: Span LinePos -> Builder
+ SimpleParser.Explain: chunkB :: TextualChunked chunk => chunk -> Builder
+ SimpleParser.Explain: class ExplainError e
+ SimpleParser.Explain: class ExplainLabel l
+ SimpleParser.Explain: data ErrorExplanation
+ SimpleParser.Explain: data ParseErrorExplanation p
+ SimpleParser.Explain: endMsg :: Builder
+ SimpleParser.Explain: explainError :: ExplainError e => e -> ErrorExplanation
+ SimpleParser.Explain: explainLabel :: ExplainLabel l => l -> Builder
+ SimpleParser.Explain: explainLabelText :: ExplainLabel l => l -> Text
+ SimpleParser.Explain: explainParseError :: Explainable l s e => ParseError l s e -> ParseErrorExplanation (Pos s)
+ SimpleParser.Explain: instance (SimpleParser.Stream.Token s GHC.Types.~ GHC.Types.Char, SimpleParser.Chunked.TextualChunked (SimpleParser.Stream.Chunk s)) => SimpleParser.Explain.ExplainError (SimpleParser.Result.StreamError s)
+ SimpleParser.Explain: instance (SimpleParser.Stream.Token s GHC.Types.~ GHC.Types.Char, SimpleParser.Chunked.TextualChunked (SimpleParser.Stream.Chunk s), SimpleParser.Explain.ExplainError e) => SimpleParser.Explain.ExplainError (SimpleParser.Result.CompoundError s e)
+ SimpleParser.Explain: instance SimpleParser.Explain.ExplainError Data.Void.Void
+ SimpleParser.Explain: instance SimpleParser.Explain.ExplainLabel Data.Void.Void
+ SimpleParser.Explain: instance SimpleParser.Explain.ExplainLabel SimpleParser.Common.TextLabel
+ SimpleParser.Explain: instance SimpleParser.Explain.ExplainLabel l => SimpleParser.Explain.ExplainLabel (SimpleParser.Common.CompoundTextLabel l)
+ SimpleParser.Explain: mayChunkB :: TextualChunked chunk => Maybe chunk -> Builder
+ SimpleParser.Explain: mayTokB :: Maybe Char -> Builder
+ SimpleParser.Explain: tokB :: Char -> Builder
+ SimpleParser.Explain: type Explainable l s e = (TextualStream s, ExplainLabel l, ExplainError e)
+ SimpleParser.Input: withChunk :: (Stream s, Monad m) => Maybe l -> Int -> (Maybe (Chunk s) -> ParserT l s e m a) -> ParserT l s e m a
+ SimpleParser.Input: withToken :: (Stream s, Monad m) => Maybe l -> (Maybe (Token s) -> ParserT l s e m a) -> ParserT l s e m a
+ SimpleParser.Interactive: parseInteractive :: (s ~ LinePosStream Text, Explainable l s e, Show a) => Parser l s e a -> String -> IO ()
+ SimpleParser.Parser: bindParser :: Monad m => ParserT l s e m a -> (a -> ParserT l s e m b) -> ParserT l s e m b
+ SimpleParser.Parser: catchJustParser :: Monad m => (e -> Maybe b) -> ParserT l s e m a -> (b -> ParserT l s e m a) -> ParserT l s e m a
+ SimpleParser.Parser: catchParser :: Monad m => ParserT l s e m a -> (e -> ParserT l s e m a) -> ParserT l s e m a
+ SimpleParser.Parser: commitParser :: Monad m => ParserT l s e m () -> ParserT l s e m a -> ParserT l s e m a
+ SimpleParser.Parser: emptyParser :: Monad m => ParserT l s e m a
+ SimpleParser.Parser: failParser :: Monad m => Text -> ParserT l s e m a
+ SimpleParser.Parser: hoistParser :: (forall x. m x -> n x) -> ParserT l s e m a -> ParserT l s e n a
+ SimpleParser.Parser: instance (GHC.Classes.Eq a, GHC.Classes.Eq b) => GHC.Classes.Eq (SimpleParser.Parser.SeqPartition a b)
+ SimpleParser.Parser: instance (GHC.Show.Show a, GHC.Show.Show b) => GHC.Show.Show (SimpleParser.Parser.SeqPartition a b)
+ SimpleParser.Parser: instance Control.Monad.Morph.MFunctor (SimpleParser.Parser.ParserT l s e)
+ SimpleParser.Parser: instance Control.Monad.Trans.Class.MonadTrans (SimpleParser.Parser.ParserT l s e)
+ SimpleParser.Parser: instance GHC.Base.Functor m => GHC.Base.Functor (SimpleParser.Parser.ParserT l s e m)
+ SimpleParser.Parser: instance GHC.Base.Monad m => Control.Monad.Error.Class.MonadError e (SimpleParser.Parser.ParserT l s e m)
+ SimpleParser.Parser: instance GHC.Base.Monad m => Control.Monad.Fail.MonadFail (SimpleParser.Parser.ParserT l s e m)
+ SimpleParser.Parser: instance GHC.Base.Monad m => Control.Monad.State.Class.MonadState s (SimpleParser.Parser.ParserT l s e m)
+ SimpleParser.Parser: instance GHC.Base.Monad m => GHC.Base.Alternative (SimpleParser.Parser.ParserT l s e m)
+ SimpleParser.Parser: instance GHC.Base.Monad m => GHC.Base.Applicative (SimpleParser.Parser.ParserT l s e m)
+ SimpleParser.Parser: instance GHC.Base.Monad m => GHC.Base.Monad (SimpleParser.Parser.ParserT l s e m)
+ SimpleParser.Parser: instance GHC.Base.Monad m => GHC.Base.MonadPlus (SimpleParser.Parser.ParserT l s e m)
+ SimpleParser.Parser: liftParser :: Monad m => m a -> ParserT l s e m a
+ SimpleParser.Parser: markParser :: Monad m => Maybe l -> ParserT l s e m a -> ParserT l s e m a
+ SimpleParser.Parser: markWithOptStateParser :: Monad m => Maybe l -> (s -> Maybe (b, s)) -> (Maybe b -> ParserT l s e m a) -> ParserT l s e m a
+ SimpleParser.Parser: markWithStateParser :: Monad m => Maybe l -> (s -> (b, s)) -> (b -> ParserT l s e m a) -> ParserT l s e m a
+ SimpleParser.Parser: onEmptyParser :: Parser l s e a -> Parser l s e a -> Parser l s e a
+ SimpleParser.Parser: orParser :: Monad m => ParserT l s e m a -> ParserT l s e m a -> ParserT l s e m a
+ SimpleParser.Parser: pureParser :: Monad m => a -> ParserT l s e m a
+ SimpleParser.Parser: throwParser :: Monad m => e -> ParserT l s e m a
+ SimpleParser.Parser: unmarkParser :: Monad m => ParserT l s e m a -> ParserT l s e m a
+ SimpleParser.Result: CompoundErrorCustom :: !e -> CompoundError s e
+ SimpleParser.Result: CompoundErrorFail :: !Text -> CompoundError s e
+ SimpleParser.Result: CompoundErrorStream :: !StreamError s -> CompoundError s e
+ SimpleParser.Result: Mark :: !Maybe l -> !s -> Mark l s
+ SimpleParser.Result: ParseResultError :: !NESeq (ParseError l s e) -> ParseResult l s e a
+ SimpleParser.Result: ParseResultSuccess :: !ParseSuccess s a -> ParseResult l s e a
+ SimpleParser.Result: RawErrorAnyChunk :: RawError chunk token
+ SimpleParser.Result: RawErrorAnyToken :: RawError chunk token
+ SimpleParser.Result: RawErrorDropTokensWhile1 :: !Maybe token -> RawError chunk token
+ SimpleParser.Result: RawErrorMatchChunk :: !chunk -> !Maybe chunk -> RawError chunk token
+ SimpleParser.Result: RawErrorMatchEnd :: !token -> RawError chunk token
+ SimpleParser.Result: RawErrorMatchToken :: !token -> !Maybe token -> RawError chunk token
+ SimpleParser.Result: RawErrorSatisfyToken :: !Maybe token -> RawError chunk token
+ SimpleParser.Result: RawErrorTakeTokensWhile1 :: !Maybe token -> RawError chunk token
+ SimpleParser.Result: StreamError :: RawError (Chunk s) (Token s) -> StreamError s
+ SimpleParser.Result: [markLabel] :: Mark l s -> !Maybe l
+ SimpleParser.Result: [markState] :: Mark l s -> !s
+ SimpleParser.Result: [peEndState] :: ParseError l s e -> !s
+ SimpleParser.Result: [peError] :: ParseError l s e -> !CompoundError s e
+ SimpleParser.Result: [peMarkStack] :: ParseError l s e -> !MarkStack l s
+ SimpleParser.Result: [psEndState] :: ParseSuccess s a -> !s
+ SimpleParser.Result: [psValue] :: ParseSuccess s a -> !a
+ SimpleParser.Result: [unStreamError] :: StreamError s -> RawError (Chunk s) (Token s)
+ SimpleParser.Result: data CompoundError s e
+ SimpleParser.Result: data Mark l s
+ SimpleParser.Result: data ParseError l s e
+ SimpleParser.Result: data ParseSuccess s a
+ SimpleParser.Result: data RawError chunk token
+ SimpleParser.Result: instance (GHC.Classes.Eq (SimpleParser.Stream.Token s), GHC.Classes.Eq (SimpleParser.Stream.Chunk s)) => GHC.Classes.Eq (SimpleParser.Result.StreamError s)
+ SimpleParser.Result: instance (GHC.Classes.Eq (SimpleParser.Stream.Token s), GHC.Classes.Eq (SimpleParser.Stream.Chunk s), GHC.Classes.Eq e) => GHC.Classes.Eq (SimpleParser.Result.CompoundError s e)
+ SimpleParser.Result: instance (GHC.Classes.Eq l, GHC.Classes.Eq s) => GHC.Classes.Eq (SimpleParser.Result.Mark l s)
+ SimpleParser.Result: instance (GHC.Classes.Eq l, GHC.Classes.Eq s, GHC.Classes.Eq (SimpleParser.Stream.Token s), GHC.Classes.Eq (SimpleParser.Stream.Chunk s), GHC.Classes.Eq e) => GHC.Classes.Eq (SimpleParser.Result.ParseError l s e)
+ SimpleParser.Result: instance (GHC.Classes.Eq l, GHC.Classes.Eq s, GHC.Classes.Eq (SimpleParser.Stream.Token s), GHC.Classes.Eq (SimpleParser.Stream.Chunk s), GHC.Classes.Eq e, GHC.Classes.Eq a) => GHC.Classes.Eq (SimpleParser.Result.ParseResult l s e a)
+ SimpleParser.Result: instance (GHC.Classes.Eq s, GHC.Classes.Eq a) => GHC.Classes.Eq (SimpleParser.Result.ParseSuccess s a)
+ SimpleParser.Result: instance (GHC.Classes.Eq token, GHC.Classes.Eq chunk) => GHC.Classes.Eq (SimpleParser.Result.RawError chunk token)
+ SimpleParser.Result: instance (GHC.Show.Show (SimpleParser.Stream.Token s), GHC.Show.Show (SimpleParser.Stream.Chunk s)) => GHC.Show.Show (SimpleParser.Result.StreamError s)
+ SimpleParser.Result: instance (GHC.Show.Show (SimpleParser.Stream.Token s), GHC.Show.Show (SimpleParser.Stream.Chunk s), GHC.Show.Show e) => GHC.Show.Show (SimpleParser.Result.CompoundError s e)
+ SimpleParser.Result: instance (GHC.Show.Show l, GHC.Show.Show s) => GHC.Show.Show (SimpleParser.Result.Mark l s)
+ SimpleParser.Result: instance (GHC.Show.Show l, GHC.Show.Show s, GHC.Show.Show (SimpleParser.Stream.Token s), GHC.Show.Show (SimpleParser.Stream.Chunk s), GHC.Show.Show e) => GHC.Show.Show (SimpleParser.Result.ParseError l s e)
+ SimpleParser.Result: instance (GHC.Show.Show l, GHC.Show.Show s, GHC.Show.Show (SimpleParser.Stream.Token s), GHC.Show.Show (SimpleParser.Stream.Chunk s), GHC.Show.Show e, GHC.Show.Show a) => GHC.Show.Show (SimpleParser.Result.ParseResult l s e a)
+ SimpleParser.Result: instance (GHC.Show.Show s, GHC.Show.Show a) => GHC.Show.Show (SimpleParser.Result.ParseSuccess s a)
+ SimpleParser.Result: instance (GHC.Show.Show token, GHC.Show.Show chunk) => GHC.Show.Show (SimpleParser.Result.RawError chunk token)
+ SimpleParser.Result: instance Data.Foldable.Foldable (SimpleParser.Result.CompoundError s)
+ SimpleParser.Result: instance Data.Foldable.Foldable (SimpleParser.Result.ParseResult l s e)
+ SimpleParser.Result: instance Data.Foldable.Foldable (SimpleParser.Result.ParseSuccess s)
+ SimpleParser.Result: instance Data.Traversable.Traversable (SimpleParser.Result.CompoundError s)
+ SimpleParser.Result: instance Data.Traversable.Traversable (SimpleParser.Result.ParseResult l s e)
+ SimpleParser.Result: instance Data.Traversable.Traversable (SimpleParser.Result.ParseSuccess s)
+ SimpleParser.Result: instance GHC.Base.Functor (SimpleParser.Result.CompoundError s)
+ SimpleParser.Result: instance GHC.Base.Functor (SimpleParser.Result.ParseResult l s e)
+ SimpleParser.Result: instance GHC.Base.Functor (SimpleParser.Result.ParseSuccess s)
+ SimpleParser.Result: markParseError :: Mark l s -> ParseError l s e -> ParseError l s e
+ SimpleParser.Result: newtype StreamError s
+ SimpleParser.Result: parseErrorEnclosingLabels :: ParseError l s e -> Seq l
+ SimpleParser.Result: parseErrorNarrowestSpan :: Stream s => ParseError l s e -> (Maybe l, Span (Pos s))
+ SimpleParser.Result: parseErrorResume :: ParseError l s e -> s
+ SimpleParser.Result: unmarkParseError :: ParseError l s e -> ParseError l s e
+ SimpleParser.Stack: Stack :: Seq a -> Stack a
+ SimpleParser.Stack: [unStack] :: Stack a -> Seq a
+ SimpleParser.Stack: bottomStack :: Stack a -> Maybe a
+ SimpleParser.Stack: emptyStack :: Stack a
+ SimpleParser.Stack: instance Data.Foldable.Foldable SimpleParser.Stack.Stack
+ SimpleParser.Stack: instance Data.Traversable.Traversable SimpleParser.Stack.Stack
+ SimpleParser.Stack: instance GHC.Base.Functor SimpleParser.Stack.Stack
+ SimpleParser.Stack: instance GHC.Classes.Eq a => GHC.Classes.Eq (SimpleParser.Stack.Stack a)
+ SimpleParser.Stack: instance GHC.Show.Show a => GHC.Show.Show (SimpleParser.Stack.Stack a)
+ SimpleParser.Stack: newtype Stack a
+ SimpleParser.Stack: pushStack :: a -> Stack a -> Stack a
+ SimpleParser.Stack: topStack :: Stack a -> Maybe a
+ SimpleParser.Stream: Col :: Int -> Col
+ SimpleParser.Stream: Line :: Int -> Line
+ SimpleParser.Stream: LinePos :: !Offset -> !Line -> !Col -> LinePos
+ SimpleParser.Stream: LinePosStream :: !LinePos -> !s -> LinePosStream s
+ SimpleParser.Stream: Offset :: Int -> Offset
+ SimpleParser.Stream: Span :: !p -> !p -> Span p
+ SimpleParser.Stream: [lpCol] :: LinePos -> !Col
+ SimpleParser.Stream: [lpLine] :: LinePos -> !Line
+ SimpleParser.Stream: [lpOffset] :: LinePos -> !Offset
+ SimpleParser.Stream: [lpsLinePos] :: LinePosStream s -> !LinePos
+ SimpleParser.Stream: [lpsState] :: LinePosStream s -> !s
+ SimpleParser.Stream: [spanEnd] :: Span p -> !p
+ SimpleParser.Stream: [spanStart] :: Span p -> !p
+ SimpleParser.Stream: [unCol] :: Col -> Int
+ SimpleParser.Stream: [unLine] :: Line -> Int
+ SimpleParser.Stream: [unOffset] :: Offset -> Int
+ SimpleParser.Stream: data LinePos
+ SimpleParser.Stream: data LinePosStream s
+ SimpleParser.Stream: data Span p
+ SimpleParser.Stream: instance (SimpleParser.Stream.Stream s, SimpleParser.Stream.Token s GHC.Types.~ GHC.Types.Char) => SimpleParser.Stream.Stream (SimpleParser.Stream.LinePosStream s)
+ SimpleParser.Stream: instance Data.Foldable.Foldable SimpleParser.Stream.LinePosStream
+ SimpleParser.Stream: instance Data.Traversable.Traversable SimpleParser.Stream.LinePosStream
+ SimpleParser.Stream: instance GHC.Base.Functor SimpleParser.Stream.LinePosStream
+ SimpleParser.Stream: instance GHC.Classes.Eq SimpleParser.Stream.Col
+ SimpleParser.Stream: instance GHC.Classes.Eq SimpleParser.Stream.Line
+ SimpleParser.Stream: instance GHC.Classes.Eq SimpleParser.Stream.LinePos
+ SimpleParser.Stream: instance GHC.Classes.Eq SimpleParser.Stream.Offset
+ SimpleParser.Stream: instance GHC.Classes.Eq p => GHC.Classes.Eq (SimpleParser.Stream.Span p)
+ SimpleParser.Stream: instance GHC.Classes.Eq s => GHC.Classes.Eq (SimpleParser.Stream.LinePosStream s)
+ SimpleParser.Stream: instance GHC.Classes.Ord SimpleParser.Stream.Col
+ SimpleParser.Stream: instance GHC.Classes.Ord SimpleParser.Stream.Line
+ SimpleParser.Stream: instance GHC.Classes.Ord SimpleParser.Stream.LinePos
+ SimpleParser.Stream: instance GHC.Classes.Ord SimpleParser.Stream.Offset
+ SimpleParser.Stream: instance GHC.Classes.Ord p => GHC.Classes.Ord (SimpleParser.Stream.Span p)
+ SimpleParser.Stream: instance GHC.Enum.Enum SimpleParser.Stream.Col
+ SimpleParser.Stream: instance GHC.Enum.Enum SimpleParser.Stream.Line
+ SimpleParser.Stream: instance GHC.Enum.Enum SimpleParser.Stream.Offset
+ SimpleParser.Stream: instance GHC.Num.Num SimpleParser.Stream.Col
+ SimpleParser.Stream: instance GHC.Num.Num SimpleParser.Stream.Line
+ SimpleParser.Stream: instance GHC.Num.Num SimpleParser.Stream.Offset
+ SimpleParser.Stream: instance GHC.Real.Integral SimpleParser.Stream.Col
+ SimpleParser.Stream: instance GHC.Real.Integral SimpleParser.Stream.Line
+ SimpleParser.Stream: instance GHC.Real.Integral SimpleParser.Stream.Offset
+ SimpleParser.Stream: instance GHC.Real.Real SimpleParser.Stream.Col
+ SimpleParser.Stream: instance GHC.Real.Real SimpleParser.Stream.Line
+ SimpleParser.Stream: instance GHC.Real.Real SimpleParser.Stream.Offset
+ SimpleParser.Stream: instance GHC.Show.Show SimpleParser.Stream.Col
+ SimpleParser.Stream: instance GHC.Show.Show SimpleParser.Stream.Line
+ SimpleParser.Stream: instance GHC.Show.Show SimpleParser.Stream.LinePos
+ SimpleParser.Stream: instance GHC.Show.Show SimpleParser.Stream.Offset
+ SimpleParser.Stream: instance GHC.Show.Show p => GHC.Show.Show (SimpleParser.Stream.Span p)
+ SimpleParser.Stream: instance GHC.Show.Show s => GHC.Show.Show (SimpleParser.Stream.LinePosStream s)
+ SimpleParser.Stream: newLinePosStream :: s -> LinePosStream s
+ SimpleParser.Stream: newtype Col
+ SimpleParser.Stream: newtype Line
+ SimpleParser.Stream: newtype Offset
+ SimpleParser.Stream: streamViewPos :: Stream s => s -> Pos s
+ SimpleParser.Stream: type TextualStream s = (Stream s, Token s ~ Char, TextualChunked (Chunk s))
- SimpleParser.Examples.Json: JsonArray :: ![a] -> JsonF a
+ SimpleParser.Examples.Json: JsonArray :: !Seq a -> JsonF a
- SimpleParser.Examples.Json: JsonObject :: ![(String, a)] -> JsonF a
+ SimpleParser.Examples.Json: JsonObject :: !Seq (Text, a) -> JsonF a
- SimpleParser.Examples.Json: JsonString :: !String -> JsonF a
+ SimpleParser.Examples.Json: JsonString :: !Text -> JsonF a
- SimpleParser.Input: anyChunk :: (Stream s, Monad m) => Int -> ParserT e s m (Chunk s)
+ SimpleParser.Input: anyChunk :: (Stream s, Monad m) => Int -> ParserT l s e m (Chunk s)
- SimpleParser.Input: anyToken :: (Stream s, Monad m) => ParserT e s m (Token s)
+ SimpleParser.Input: anyToken :: (Stream s, Monad m) => ParserT l s e m (Token s)
- SimpleParser.Input: dropChunk :: (Stream s, Monad m) => Int -> ParserT e s m (Maybe Int)
+ SimpleParser.Input: dropChunk :: (Stream s, Monad m) => Int -> ParserT l s e m (Maybe Int)
- SimpleParser.Input: dropTokensWhile :: (Stream s, Monad m) => (Token s -> Bool) -> ParserT e s m Int
+ SimpleParser.Input: dropTokensWhile :: (Stream s, Monad m) => (Token s -> Bool) -> ParserT l s e m Int
- SimpleParser.Input: dropTokensWhile1 :: (Stream s, Monad m) => (Token s -> Bool) -> ParserT e s m Int
+ SimpleParser.Input: dropTokensWhile1 :: (Stream s, Monad m) => Maybe l -> (Token s -> Bool) -> ParserT l s e m Int
- SimpleParser.Input: foldTokensWhile :: (Stream s, Monad m) => (Token s -> x -> (Bool, x)) -> (x -> x) -> x -> ParserT e s m x
+ SimpleParser.Input: foldTokensWhile :: (Stream s, Monad m) => (Token s -> x -> (Bool, x)) -> x -> ParserT l s e m x
- SimpleParser.Input: isEnd :: (Stream s, Monad m) => ParserT e s m Bool
+ SimpleParser.Input: isEnd :: (Stream s, Monad m) => ParserT l s e m Bool
- SimpleParser.Input: matchChunk :: (Stream s, Monad m, Eq (Chunk s)) => Chunk s -> ParserT e s m (Chunk s)
+ SimpleParser.Input: matchChunk :: (Stream s, Monad m, Eq (Chunk s)) => Chunk s -> ParserT l s e m (Chunk s)
- SimpleParser.Input: matchEnd :: (Stream s, Monad m) => ParserT e s m ()
+ SimpleParser.Input: matchEnd :: (Stream s, Monad m) => ParserT l s e m ()
- SimpleParser.Input: matchToken :: (Stream s, Monad m, Eq (Token s)) => Token s -> ParserT e s m (Token s)
+ SimpleParser.Input: matchToken :: (Stream s, Monad m, Eq (Token s)) => Token s -> ParserT l s e m (Token s)
- SimpleParser.Input: peekChunk :: (Stream s, Monad m) => Int -> ParserT e s m (Maybe (Chunk s))
+ SimpleParser.Input: peekChunk :: (Stream s, Monad m) => Int -> ParserT l s e m (Maybe (Chunk s))
- SimpleParser.Input: peekToken :: (Stream s, Monad m) => ParserT e s m (Maybe (Token s))
+ SimpleParser.Input: peekToken :: (Stream s, Monad m) => ParserT l s e m (Maybe (Token s))
- SimpleParser.Input: popChunk :: (Stream s, Monad m) => Int -> ParserT e s m (Maybe (Chunk s))
+ SimpleParser.Input: popChunk :: (Stream s, Monad m) => Int -> ParserT l s e m (Maybe (Chunk s))
- SimpleParser.Input: popToken :: (Stream s, Monad m) => ParserT e s m (Maybe (Token s))
+ SimpleParser.Input: popToken :: (Stream s, Monad m) => ParserT l s e m (Maybe (Token s))
- SimpleParser.Input: satisfyToken :: (Stream s, Monad m) => (Token s -> Bool) -> ParserT e s m (Token s)
+ SimpleParser.Input: satisfyToken :: (Stream s, Monad m) => Maybe l -> (Token s -> Bool) -> ParserT l s e m (Token s)
- SimpleParser.Input: takeTokensWhile :: (Stream s, Monad m) => (Token s -> Bool) -> ParserT e s m (Chunk s)
+ SimpleParser.Input: takeTokensWhile :: (Stream s, Monad m) => (Token s -> Bool) -> ParserT l s e m (Chunk s)
- SimpleParser.Input: takeTokensWhile1 :: (Stream s, Monad m) => (Token s -> Bool) -> ParserT e s m (Chunk s)
+ SimpleParser.Input: takeTokensWhile1 :: (Stream s, Monad m) => Maybe l -> (Token s -> Bool) -> ParserT l s e m (Chunk s)
- SimpleParser.Parser: ParserT :: (s -> ListT m (ParseResult e s a)) -> ParserT e s m a
+ SimpleParser.Parser: ParserT :: (s -> m (Maybe (ParseResult l s e a))) -> ParserT l s e m a
- SimpleParser.Parser: [runParserT] :: ParserT e s m a -> s -> ListT m (ParseResult e s a)
+ SimpleParser.Parser: [runParserT] :: ParserT l s e m a -> s -> m (Maybe (ParseResult l s e a))
- SimpleParser.Parser: defaultParser :: Monad m => a -> ParserT e s m a -> ParserT e s m a
+ SimpleParser.Parser: defaultParser :: Monad m => a -> ParserT l s e m a -> ParserT l s e m a
- SimpleParser.Parser: greedyPlusParser :: Monad m => ParserT e s m a -> ParserT e s m [a]
+ SimpleParser.Parser: greedyPlusParser :: (Chunked seq elem, Monad m) => ParserT l s e m elem -> ParserT l s e m seq
- SimpleParser.Parser: greedyPlusParser_ :: Monad m => ParserT e s m a -> ParserT e s m ()
+ SimpleParser.Parser: greedyPlusParser_ :: Monad m => ParserT l s e m a -> ParserT l s e m ()
- SimpleParser.Parser: greedyStarParser :: Monad m => ParserT e s m a -> ParserT e s m [a]
+ SimpleParser.Parser: greedyStarParser :: (Chunked seq elem, Monad m) => ParserT l s e m elem -> ParserT l s e m seq
- SimpleParser.Parser: greedyStarParser_ :: Monad m => ParserT e s m a -> ParserT e s m ()
+ SimpleParser.Parser: greedyStarParser_ :: Monad m => ParserT l s e m a -> ParserT l s e m ()
- SimpleParser.Parser: lookAheadParser :: Monad m => ParserT e s m a -> ParserT e s m a
+ SimpleParser.Parser: lookAheadParser :: Monad m => Maybe l -> ParserT l s e m a -> ParserT l s e m a
- SimpleParser.Parser: newtype ParserT e s m a
+ SimpleParser.Parser: newtype ParserT l s e m a
- SimpleParser.Parser: optionalParser :: Monad m => ParserT e s m a -> ParserT e s m (Maybe a)
+ SimpleParser.Parser: optionalParser :: Monad m => ParserT l s e m a -> ParserT l s e m (Maybe a)
- SimpleParser.Parser: runParser :: Parser e s a -> s -> [ParseResult e s a]
+ SimpleParser.Parser: runParser :: Parser l s e a -> s -> Maybe (ParseResult l s e a)
- SimpleParser.Parser: silenceParser :: Monad m => ParserT e s m a -> ParserT e s m a
+ SimpleParser.Parser: silenceParser :: Monad m => ParserT l s e m a -> ParserT l s e m a
- SimpleParser.Parser: type Parser e s a = ParserT e s Identity a
+ SimpleParser.Parser: type Parser l s e a = ParserT l s e Identity a
- SimpleParser.Result: ParseError :: !e -> ParseValue e a
+ SimpleParser.Result: ParseError :: !MarkStack l s -> !s -> !CompoundError s e -> ParseError l s e
- SimpleParser.Result: ParseSuccess :: !a -> ParseValue e a
+ SimpleParser.Result: ParseSuccess :: !s -> !a -> ParseSuccess s a
- SimpleParser.Result: data ParseResult e s a
+ SimpleParser.Result: data ParseResult l s e a
- SimpleParser.Stream: OffsetStream :: !Int -> !s -> OffsetStream s
+ SimpleParser.Stream: OffsetStream :: !Offset -> !s -> OffsetStream s
- SimpleParser.Stream: [osOffset] :: OffsetStream s -> !Int
+ SimpleParser.Stream: [osOffset] :: OffsetStream s -> !Offset
- SimpleParser.Stream: type family Token s :: Type;
+ SimpleParser.Stream: type family Pos s :: Type;
Files
- README.md +5/−1
- simple-parser.cabal +16/−4
- src/SimpleParser.hs +11/−1
- src/SimpleParser/Chunked.hs +84/−0
- src/SimpleParser/Common.hs +199/−0
- src/SimpleParser/Examples/Json.hs +72/−95
- src/SimpleParser/Examples/Sexp.hs +115/−0
- src/SimpleParser/Explain.hs +135/−0
- src/SimpleParser/Input.hs +85/−52
- src/SimpleParser/Interactive.hs +27/−0
- src/SimpleParser/Parser.hs +269/−170
- src/SimpleParser/Result.hs +100/−23
- src/SimpleParser/Stack.hs +38/−0
- src/SimpleParser/Stream.hs +96/−66
- test/Main.hs +345/−363
README.md view
@@ -9,4 +9,8 @@ To lists of pairs Of things and strings. -In this case, we subsitute `ListT` for the list and add some error handling. We also swap out strings for any kind of input (streaming or not).+In this case, our lists are are little fancier.++## License++This project is BSD-licenced. Some gnarly functions to parse numbers and such have been adapted from Megaparsec, which is also [BSD-licensed](https://github.com/mrkkrp/megaparsec/blob/master/LICENSE.md).
simple-parser.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 05dbeeb0f7613ae6d38c54b0654472b0b1d48dc401b57a41cae0dc16554fb065+-- hash: 1d43d6b4332cdf22af4c6c459935317446659f1e7a3e9e1ff035e8447b877d50 name: simple-parser-version: 0.2.2+version: 0.3.0 synopsis: Simple parser combinators description: Please see the README on GitHub at <https://github.com/ejconlon/simple-parser#readme> category: Parsing@@ -29,16 +29,22 @@ library exposed-modules: SimpleParser+ SimpleParser.Chunked+ SimpleParser.Common SimpleParser.Examples.Json+ SimpleParser.Examples.Sexp+ SimpleParser.Explain SimpleParser.Input+ SimpleParser.Interactive SimpleParser.Parser SimpleParser.Result+ SimpleParser.Stack SimpleParser.Stream other-modules: Paths_simple_parser hs-source-dirs: src- default-extensions: BangPatterns DeriveFunctor DeriveFoldable DeriveTraversable DerivingStrategies FlexibleContexts FlexibleInstances FunctionalDependencies GeneralizedNewtypeDeriving KindSignatures MultiParamTypeClasses TypeFamilies+ default-extensions: BangPatterns ConstraintKinds DeriveFunctor DeriveFoldable DeriveTraversable DerivingStrategies FlexibleContexts FlexibleInstances FunctionalDependencies GeneralizedNewtypeDeriving KindSignatures MultiParamTypeClasses TypeFamilies ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -fno-warn-unused-top-binds build-depends: base >=4.12 && <5@@ -46,7 +52,10 @@ , list-t >=1.0 && <1.1 , mmorph >=1.1 && <1.2 , mtl >=2.2 && <2.3+ , nonempty-containers >=0.3 && <0.4+ , scientific >=0.3 && <0.4 , text >=1.2 && <1.3+ , text-builder >=0.6 && <0.7 default-language: Haskell2010 test-suite simple-parser-test@@ -56,7 +65,7 @@ Paths_simple_parser hs-source-dirs: test- default-extensions: BangPatterns DeriveFunctor DeriveFoldable DeriveTraversable DerivingStrategies FlexibleContexts FlexibleInstances FunctionalDependencies GeneralizedNewtypeDeriving KindSignatures MultiParamTypeClasses TypeFamilies+ default-extensions: BangPatterns ConstraintKinds DeriveFunctor DeriveFoldable DeriveTraversable DerivingStrategies FlexibleContexts FlexibleInstances FunctionalDependencies GeneralizedNewtypeDeriving KindSignatures MultiParamTypeClasses TypeFamilies ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -fno-warn-unused-top-binds -threaded -rtsopts -with-rtsopts=-N build-depends: base >=4.12 && <5@@ -64,9 +73,12 @@ , list-t >=1.0 && <1.1 , mmorph >=1.1 && <1.2 , mtl >=2.2 && <2.3+ , nonempty-containers >=0.3 && <0.4+ , scientific >=0.3 && <0.4 , simple-parser , tasty , tasty-hunit , tasty-th , text >=1.2 && <1.3+ , text-builder >=0.6 && <0.7 default-language: Haskell2010
src/SimpleParser.hs view
@@ -4,13 +4,23 @@ -- "SimpleParser.Parser" for the core transformer, "SimpleParser.Stream" for the source abstraction, -- or "SimpleParser.Input" for useful combinators. module SimpleParser- ( module SimpleParser.Input+ ( module SimpleParser.Chunked+ , module SimpleParser.Common+ , module SimpleParser.Explain+ , module SimpleParser.Input+ , module SimpleParser.Interactive , module SimpleParser.Parser , module SimpleParser.Result+ , module SimpleParser.Stack , module SimpleParser.Stream ) where +import SimpleParser.Chunked+import SimpleParser.Common+import SimpleParser.Explain import SimpleParser.Input+import SimpleParser.Interactive import SimpleParser.Parser import SimpleParser.Result+import SimpleParser.Stack import SimpleParser.Stream
+ src/SimpleParser/Chunked.hs view
@@ -0,0 +1,84 @@+module SimpleParser.Chunked+ ( Chunked (..)+ , TextualChunked (..)+ ) where++import Data.Foldable (toList)+import Data.List (uncons)+import Data.Sequence (Seq (..))+import qualified Data.Sequence as Seq+import Data.Text (Text)+import qualified Data.Text as T+import Text.Builder (Builder)+import qualified Text.Builder as TB++-- | 'Chunked' captures the basic relationship between tokens and chunks of them.+-- Basically, these things behave like lists, sequences, text, etc.+class Monoid chunk => Chunked chunk token | chunk -> token where+ consChunk :: token -> chunk -> chunk+ unconsChunk :: chunk -> Maybe (token, chunk)+ tokenToChunk :: token -> chunk+ tokensToChunk :: [token] -> chunk+ chunkToTokens :: chunk -> [token]+ chunkLength :: chunk -> Int+ chunkEmpty :: chunk -> Bool++ -- | Some datatypes (like 'Seq') may admit a "better" implementation+ -- for building a chunk in reverse.+ revTokensToChunk :: [token] -> chunk+ revTokensToChunk = tokensToChunk . reverse++-- | Captures textual streams.+class Chunked chunk Char => TextualChunked chunk where+ buildChunk :: chunk -> Builder+ packChunk :: chunk -> Text+ packChunk = TB.run . buildChunk+ unpackChunk :: Text -> chunk++-- TODO(ejconlon) Add instances for Strict BS, Lazy BS, and Lazy Text++instance Chunked [a] a where+ consChunk = (:)+ unconsChunk = uncons+ tokenToChunk a = [a]+ tokensToChunk = id+ chunkToTokens = id+ chunkLength = length+ chunkEmpty = null++instance (a ~ Char) => TextualChunked [a] where+ buildChunk = TB.string+ packChunk = T.pack+ unpackChunk = T.unpack++instance Chunked (Seq a) a where+ consChunk = (:<|)+ unconsChunk s =+ case s of+ Empty -> Nothing+ a :<| b -> Just (a, b)+ tokenToChunk = Seq.singleton+ tokensToChunk = Seq.fromList+ chunkToTokens = toList+ chunkLength = Seq.length+ chunkEmpty = Seq.null+ revTokensToChunk = foldr (flip (:|>)) Empty++instance (a ~ Char) => TextualChunked (Seq a) where+ buildChunk = TB.string . toList+ packChunk = T.pack . toList+ unpackChunk = Seq.fromList . T.unpack++instance Chunked Text Char where+ consChunk = T.cons+ unconsChunk = T.uncons+ tokenToChunk = T.singleton+ tokensToChunk = T.pack+ chunkToTokens = T.unpack+ chunkLength = T.length+ chunkEmpty = T.null++instance TextualChunked Text where+ buildChunk = TB.text+ packChunk = id+ unpackChunk = id
+ src/SimpleParser/Common.hs view
@@ -0,0 +1,199 @@+-- | Common parsers.+-- See <https://hackage.haskell.org/package/megaparsec-9.0.1/docs/Text-Megaparsec-Char-Lexer.html Text.Megaparsec.Char.Lexer>.+module SimpleParser.Common+ ( TextLabel (..)+ , EmbedTextLabel (..)+ , CompoundTextLabel (..)+ , sepByParser+ , betweenParser+ , lexemeParser+ , newlineParser+ , spaceParser+ , hspaceParser+ , spaceParser1+ , hspaceParser1+ , decimalParser+ , signedNumStartPred+ , scientificParser+ , signedParser+ , escapedStringParser+ , spanParser+ , getStreamPos+ ) where++import Control.Monad (void)+import Control.Monad.State (get, gets)+import Data.Char (digitToInt, isDigit, isSpace)+import Data.List (foldl')+import Data.Scientific (Scientific)+import qualified Data.Scientific as Sci+import SimpleParser.Chunked (Chunked (..))+import SimpleParser.Input (dropTokensWhile, dropTokensWhile1, foldTokensWhile, matchToken, takeTokensWhile1)+import SimpleParser.Parser (ParserT, defaultParser, greedyStarParser, optionalParser, orParser)+import SimpleParser.Stream (Span (..), Stream (..))++-- | Enumeration of common labels in textual parsing.+data TextLabel =+ TextLabelSpace+ | TextLabelHSpace+ | TextLabelDigit+ deriving (Eq, Show)++class EmbedTextLabel l where+ embedTextLabel :: TextLabel -> l++instance EmbedTextLabel TextLabel where+ embedTextLabel = id++-- | Union of text and custom labels+data CompoundTextLabel l =+ CompoundTextLabelText !TextLabel+ | CompoundTextLabelCustom !l+ deriving (Eq, Show, Functor, Foldable, Traversable)++instance EmbedTextLabel (CompoundTextLabel l) where+ embedTextLabel = CompoundTextLabelText++-- | Yields the maximal list of separated items. May return an empty list.+sepByParser :: (Chunked seq elem, Monad m) =>+ -- | How to parse item+ ParserT l s e m elem ->+ -- | How to parse separator+ ParserT l s e m () ->+ ParserT l s e m seq+sepByParser thing sep = do+ ma <- optionalParser thing+ case ma of+ Nothing -> pure mempty+ Just a -> do+ as <- greedyStarParser (sep *> thing)+ pure (consChunk a as)++-- | Parses between start and end markers.+betweenParser :: Monad m =>+ -- | How to parse start+ ParserT l s e m () ->+ -- | How to parse end+ ParserT l s e m () ->+ -- | How to parse inside+ ParserT l s e m a ->+ ParserT l s e m a+betweenParser start end thing = do+ start+ a <- thing+ end+ pure a++-- | A wrapper for lexemes (equivalent to Megaparsec's 'lexeme').+lexemeParser :: Monad m =>+ -- | How to consume white space after lexeme+ ParserT l s e m () ->+ -- | How to parse actual lexeme+ ParserT l s e m a ->+ ParserT l s e m a+lexemeParser spc p = p <* spc++-- | Consumes a newline character.+newlineParser :: (Stream s, Token s ~ Char, Monad m) => ParserT l s e m ()+newlineParser = void (matchToken '\n')++-- | Consumes 0 or more space characters.+spaceParser :: (Stream s, Token s ~ Char, Monad m) => ParserT l s e m ()+spaceParser = void (dropTokensWhile isSpace)++isHSpace :: Char -> Bool+isHSpace c = isSpace c && c /= '\n' && c /= '\r'++-- | Consumes 0 or more non-line-break space characters+hspaceParser :: (Stream s, Token s ~ Char, Monad m) => ParserT l s e m ()+hspaceParser = void (dropTokensWhile isHSpace)++-- | Consumes 1 or more space characters.+spaceParser1 :: (EmbedTextLabel l, Stream s, Token s ~ Char, Monad m) => ParserT l s e m ()+spaceParser1 = void (dropTokensWhile1 (Just (embedTextLabel TextLabelSpace)) isSpace)++-- | Consumes 1 or more non-line-break space characters+hspaceParser1 :: (EmbedTextLabel l, Stream s, Token s ~ Char, Monad m) => ParserT l s e m ()+hspaceParser1 = void (dropTokensWhile1 (Just (embedTextLabel TextLabelHSpace)) isHSpace)++-- | Parses an integer in decimal representation (equivalent to Megaparsec's 'decimal').+decimalParser :: (EmbedTextLabel l, Stream s, Token s ~ Char, Monad m, Num a) => ParserT l s e m a+decimalParser = fmap mkNum (takeTokensWhile1 (Just (embedTextLabel TextLabelDigit)) isDigit) where+ mkNum = foldl' step 0 . chunkToTokens+ step a c = a * 10 + fromIntegral (digitToInt c)++data SP = SP !Integer !Int++dotDecimalParser :: (EmbedTextLabel l, Stream s, Token s ~ Char, Monad m) => Integer -> ParserT l s e m SP+dotDecimalParser c' = do+ void (matchToken '.')+ let mkNum = foldl' step (SP c' 0) . chunkToTokens+ step (SP a e') c = SP (a * 10 + fromIntegral (digitToInt c)) (e' - 1)+ fmap mkNum (takeTokensWhile1 (Just (embedTextLabel TextLabelDigit)) isDigit)++exponentParser :: (EmbedTextLabel l, Stream s, Token s ~ Char, Monad m) => Int -> ParserT l s e m Int+exponentParser e' = do+ void (orParser (matchToken 'e') (matchToken 'E'))+ fmap (+ e') (signedParser (pure ()) decimalParser)++-- | Predicate for satisfying the start of signed numbers+signedNumStartPred :: Char -> Bool+signedNumStartPred c = isDigit c || c == '+' || c == '-'++-- | Parses a floating point value as a 'Scientific' number (equivalent to Megaparsec's 'scientific').+scientificParser :: (EmbedTextLabel l, Stream s, Token s ~ Char, Monad m) => ParserT l s e m Scientific+scientificParser = do+ c' <- decimalParser+ SP c e' <- defaultParser (SP c' 0) (dotDecimalParser c')+ e <- defaultParser e' (exponentParser e')+ pure (Sci.scientific c e)++-- | Parses an optional sign character followed by a number and yields a correctly-signed+-- number (equivalend to Megaparsec's 'signed').+signedParser :: (Stream s, Token s ~ Char, Monad m, Num a) =>+ -- | How to consume white space after the sign+ ParserT l s e m () ->+ -- | How to parse the number itself+ ParserT l s e m a ->+ -- | Parser for signed numbers+ ParserT l s e m a+signedParser spc p = defaultParser id (lexemeParser spc sign) <*> p where+ sign = orParser (id <$ matchToken '+') (negate <$ matchToken '-')++data Pair = Pair ![Char] !Bool++-- | Given a quote charcter (like a single or double quote), yields the contents of the+-- string bounded by those quotes. The contents may contain backslash-escaped quotes.+-- Returns nothing if outside quotes are missing or the stream ends before unquote.+escapedStringParser :: (Stream s, Token s ~ Char, Monad m) => Char -> ParserT l s e m (Chunk s)+escapedStringParser quoteChar =+ let quoteParser = void (matchToken quoteChar)+ accParser = foldTokensWhile go (Pair [] False)+ innerParser = fmap (\(Pair acc _) -> revTokensToChunk acc) accParser+ escChar = '\\'+ go c (Pair acc esc)+ | c == escChar =+ if esc+ then (True, Pair (escChar:acc) False) -- Was escaped escape, append one+ else (True, Pair acc True) -- Skip appending this esc+ | c == quoteChar =+ if esc+ then (True, Pair (c:acc) False) -- Escaped quote+ else (False, Pair acc False) -- End of quote+ | otherwise =+ if esc+ then (True, Pair (c:escChar:acc) False) -- Was a non-quote esc, append both+ else (True, Pair (c:acc) False) -- Just consume char+ in betweenParser quoteParser quoteParser innerParser++-- | Adds span information to parsed values.+spanParser :: (Stream s, Monad m) => (Span (Pos s) -> a -> b) -> ParserT l s e m a -> ParserT l s e m b+spanParser f p = do+ start <- get+ val <- p+ end <- get+ pure (f (Span (streamViewPos start) (streamViewPos end)) val)++-- | Gets the current stream position+getStreamPos :: (Stream s, Monad m) => ParserT l s e m (Pos s)+getStreamPos = gets streamViewPos
src/SimpleParser/Examples/Json.hs view
@@ -3,131 +3,108 @@ module SimpleParser.Examples.Json ( Json (..) , JsonF (..)- , parseJson+ , JsonParserC+ , JsonParserM+ , jsonParser+ , recJsonParser ) where -import Control.Applicative (empty) import Control.Monad (void)-import Data.Char (isSpace)+import Data.Foldable (asum)+import Data.Scientific (Scientific)+import Data.Sequence (Seq) import Data.Text (Text) import Data.Void (Void)-import SimpleParser+import SimpleParser.Chunked (TextualChunked (..))+import SimpleParser.Common (TextLabel, betweenParser, escapedStringParser, lexemeParser, scientificParser, sepByParser,+ signedNumStartPred, spaceParser)+import SimpleParser.Input (matchChunk, matchToken, satisfyToken)+import SimpleParser.Parser (Parser, commitParser, onEmptyParser, orParser)+import SimpleParser.Stream (Stream (..), TextualStream) --- JSON without numbers... data JsonF a =- JsonObject ![(String, a)]- | JsonArray ![a]- | JsonString !String+ JsonObject !(Seq (Text, a))+ | JsonArray !(Seq a)+ | JsonString !Text | JsonBool !Bool+ | JsonNum !Scientific | JsonNull deriving (Eq, Show, Functor, Foldable, Traversable) newtype Json = Json { unJson :: JsonF Json } deriving (Eq, Show) -type JsonParser a = Parser Void Text a--parseJson :: Text -> [Json]-parseJson str = do- ParseResult v _ <- runParser (jsonParser <* matchEnd) str- case v of- ParseSuccess a -> pure a+type JsonParserC s = (TextualStream s, Eq (Chunk s)) -jsonSpace :: JsonParser ()-jsonSpace = greedyStarParser_ (void (satisfyToken isSpace))+type JsonParserM s a = Parser TextLabel s Void a -jsonLexeme :: JsonParser () -> JsonParser a -> JsonParser a-jsonLexeme spaceAfter thing = do- a <- thing- spaceAfter- pure a+jsonParser :: JsonParserC s => JsonParserM s Json+jsonParser = let p = fmap Json (recJsonParser p) in p -jsonBetween :: JsonParser () -> JsonParser () -> JsonParser a -> JsonParser a-jsonBetween start end thing = do- start- a <- thing- end- pure a+isBoolStartPred :: Char -> Bool+isBoolStartPred c = c == 't' || c == 'f' -jsonSepBy :: JsonParser a -> JsonParser () -> JsonParser [a]-jsonSepBy thing sep = go [] where- optThing = optionalParser thing- optSep = optionalParser sep- go !acc = do- ma <- optThing- case ma of- Nothing -> if null acc then pure [] else empty- Just a -> do- let newAcc = a:acc- ms <- optSep- case ms of- Nothing -> pure (reverse newAcc)- Just () -> go newAcc+recJsonParser :: JsonParserC s => JsonParserM s a -> JsonParserM s (JsonF a)+recJsonParser root = onEmptyParser (asum opts) (fail "failed to parse json document") where+ pairP = objectPairP root+ opts =+ [ commitParser openBraceP (objectP pairP)+ , commitParser openBracketP (arrayP root)+ , commitParser openQuoteP stringP+ , commitParser (void (satisfyToken Nothing signedNumStartPred)) numP+ , commitParser (void (satisfyToken Nothing isBoolStartPred)) boolP+ , commitParser (void (matchToken 'n')) nullP+ ] -jsonCharLexeme :: Char -> JsonParser ()-jsonCharLexeme c = void (jsonLexeme jsonSpace (matchToken c))+spaceP :: JsonParserC s => JsonParserM s ()+spaceP = spaceParser -jsonWordLexeme :: Text -> JsonParser ()-jsonWordLexeme cs = void (jsonLexeme jsonSpace (matchChunk cs))+tokL :: JsonParserC s => Char -> JsonParserM s ()+tokL c = lexemeParser spaceP (void (matchToken c)) -openBrace, closeBrace, comma, colon, openBracket, closeBracket, closeQuote :: JsonParser ()-openBrace = jsonCharLexeme '{'-closeBrace = jsonCharLexeme '}'-comma = jsonCharLexeme ','-colon = jsonCharLexeme ':'-openBracket = jsonCharLexeme '['-closeBracket = jsonCharLexeme ']'-closeQuote = jsonCharLexeme '"'+chunkL :: JsonParserC s => Text -> JsonParserM s ()+chunkL cs = lexemeParser spaceP (void (matchChunk (unpackChunk cs))) -openQuote :: JsonParser ()-openQuote = void (matchToken '"')+openBraceP, closeBraceP, commaP, colonP, openBracketP, closeBracketP, closeQuoteP :: JsonParserC s => JsonParserM s ()+openBraceP = tokL '{'+closeBraceP = tokL '}'+commaP = tokL ','+colonP = tokL ':'+openBracketP = tokL '['+closeBracketP = tokL ']'+closeQuoteP = tokL '"' -nullTok, trueTok, falseTok :: JsonParser ()-nullTok = jsonWordLexeme "null"-trueTok = jsonWordLexeme "true"-falseTok = jsonWordLexeme "false"+openQuoteP :: JsonParserC s => JsonParserM s ()+openQuoteP = void (matchToken '"') -nonQuoteChar :: JsonParser Char-nonQuoteChar = satisfyToken (/= '"')+nullTokP, trueTokP, falseTokP :: JsonParserC s => JsonParserM s ()+nullTokP = chunkL "null"+trueTokP = chunkL "true"+falseTokP = chunkL "false" -nonQuoteString :: JsonParser String-nonQuoteString = greedyStarParser nonQuoteChar+rawStringP :: JsonParserC s => JsonParserM s Text+rawStringP = fmap packChunk (escapedStringParser '"') -rawStringParser :: JsonParser String-rawStringParser = jsonBetween openQuote closeQuote nonQuoteString+stringP :: JsonParserC s => JsonParserM s (JsonF a)+stringP = fmap JsonString rawStringP --- TODO(ejconlon) This does not handle escape codes. Use `foldTokensWhile` for that...-stringParser :: JsonParser (JsonF a)-stringParser = fmap JsonString rawStringParser+nullP :: JsonParserC s => JsonParserM s (JsonF a)+nullP = JsonNull <$ nullTokP -nullParser :: JsonParser (JsonF a)-nullParser = JsonNull <$ nullTok+boolP :: JsonParserC s => JsonParserM s (JsonF a)+boolP = orParser (JsonBool True <$ trueTokP) (JsonBool False <$ falseTokP) -boolParser :: JsonParser (JsonF a)-boolParser = isolateParser (branchParser [JsonBool True <$ trueTok, JsonBool False <$ falseTok])+numP :: JsonParserC s => JsonParserM s (JsonF a)+numP = fmap JsonNum scientificParser -objectPairParser :: JsonParser a -> JsonParser (String, a)-objectPairParser root = do- name <- rawStringParser- colon+objectPairP :: JsonParserC s => JsonParserM s a -> JsonParserM s (Text, a)+objectPairP root = do+ name <- rawStringP+ colonP value <- root pure (name, value) -objectParser :: JsonParser (String, a) -> JsonParser (JsonF a)-objectParser pairParser = jsonBetween openBrace closeBrace (fmap JsonObject (jsonSepBy pairParser comma))--arrayParser :: JsonParser a -> JsonParser (JsonF a)-arrayParser root = jsonBetween openBracket closeBracket (fmap JsonArray (jsonSepBy root comma))--rootParser :: JsonParser a -> JsonParser (JsonF a)-rootParser root = isolateParser (branchParser opts) where- pairParser = objectPairParser root- opts =- [ objectParser pairParser- , arrayParser root- , stringParser- , boolParser- , nullParser- ]+objectP :: JsonParserC s => JsonParserM s (Text, a) -> JsonParserM s (JsonF a)+objectP pairP = betweenParser openBraceP closeBraceP (fmap JsonObject (sepByParser pairP commaP)) -jsonParser :: JsonParser Json-jsonParser = let p = fmap Json (rootParser p) in p+arrayP :: JsonParserC s => JsonParserM s a -> JsonParserM s (JsonF a)+arrayP root = betweenParser openBracketP closeBracketP (fmap JsonArray (sepByParser root commaP))
+ src/SimpleParser/Examples/Sexp.hs view
@@ -0,0 +1,115 @@+{-# LANGUAGE OverloadedStrings #-}++module SimpleParser.Examples.Sexp+ ( Sexp (..)+ , SexpF (..)+ , Atom (..)+ , SexpLabel (..)+ , SexpParserC+ , SexpParserM+ , sexpParser+ , recSexpParser+ ) where++import Control.Monad (void)+import Data.Char (isDigit, isSpace)+import Data.Foldable (asum)+import Data.Scientific (Scientific)+import Data.Sequence (Seq)+import Data.Text (Text)+import Data.Void (Void)+import SimpleParser.Chunked (Chunked (..), packChunk)+import SimpleParser.Common (EmbedTextLabel (..), TextLabel, betweenParser, decimalParser, escapedStringParser,+ lexemeParser, scientificParser, sepByParser, signedNumStartPred, signedParser, spaceParser)+import SimpleParser.Explain (ExplainLabel (..))+import SimpleParser.Input (matchToken, satisfyToken, takeTokensWhile)+import SimpleParser.Parser (Parser, commitParser, onEmptyParser, orParser)+import SimpleParser.Stream (TextualStream)++data Atom =+ AtomIdent !Text+ | AtomString !Text+ | AtomInt !Integer+ | AtomFloat !Scientific+ deriving (Eq, Show)++data SexpF a =+ SexpAtom !Atom+ | SexpList !(Seq a)+ deriving (Eq, Show, Functor, Foldable, Traversable)++data SexpLabel =+ SexpLabelIdentStart+ | SexpLabelEmbedText !TextLabel+ deriving (Eq, Show)++instance ExplainLabel SexpLabel where+ explainLabel sl =+ case sl of+ SexpLabelIdentStart -> "start of identifier"+ SexpLabelEmbedText tl -> explainLabel tl++instance EmbedTextLabel SexpLabel where+ embedTextLabel = SexpLabelEmbedText++newtype Sexp = Sexp { unSexp :: SexpF Sexp }+ deriving (Eq, Show)++type SexpParserC s = TextualStream s++type SexpParserM s a = Parser SexpLabel s Void a++sexpParser :: SexpParserC s => SexpParserM s Sexp+sexpParser = let p = fmap Sexp (recSexpParser p) in p++recSexpParser :: SexpParserC s => SexpParserM s a -> SexpParserM s (SexpF a)+recSexpParser root = onEmptyParser (orParser lp ap) (fail "failed to parse sexp document") where+ lp = commitParser openParenP (fmap SexpList (listP root))+ ap = fmap SexpAtom atomP++nonDelimPred :: Char -> Bool+nonDelimPred c = c /= '(' && c /= ')' && not (isSpace c)++identStartPred :: Char -> Bool+identStartPred c = not (isDigit c) && identContPred c++identContPred :: Char -> Bool+identContPred c = c /= '"' && nonDelimPred c++stringP :: SexpParserC s => SexpParserM s Text+stringP = fmap packChunk (escapedStringParser '"')++identifierP :: SexpParserC s => SexpParserM s Text+identifierP = do+ x <- satisfyToken (Just SexpLabelIdentStart) identStartPred+ xs <- takeTokensWhile identContPred+ pure (packChunk (consChunk x xs))++spaceP :: SexpParserC s => SexpParserM s ()+spaceP = spaceParser++lexP :: SexpParserC s => SexpParserM s a -> SexpParserM s a+lexP = lexemeParser spaceP++openParenP :: SexpParserC s => SexpParserM s ()+openParenP = lexP (void (matchToken '('))++closeParenP :: SexpParserC s => SexpParserM s ()+closeParenP = lexP (void (matchToken ')'))++intP :: SexpParserC s => SexpParserM s Integer+intP = signedParser (pure ()) decimalParser++floatP :: SexpParserC s => SexpParserM s Scientific+floatP = signedParser (pure ()) scientificParser++atomP :: SexpParserC s => SexpParserM s Atom+atomP = lexP $ asum+ [ commitParser (void (matchToken '"')) (fmap AtomString stringP)+ , commitParser (void (satisfyToken Nothing signedNumStartPred)) (fmap AtomInt intP)+ , commitParser (void (satisfyToken Nothing signedNumStartPred)) (fmap AtomFloat floatP)+ , commitParser (void (satisfyToken Nothing identStartPred)) (fmap AtomIdent identifierP)+ ]++listP :: SexpParserC s => SexpParserM s a -> SexpParserM s (Seq a)+listP root = lexP (betweenParser openParenP closeParenP (sepByParser root spaceP))
+ src/SimpleParser/Explain.hs view
@@ -0,0 +1,135 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE UndecidableInstances #-}++module SimpleParser.Explain where++import Control.Monad (join)+import Data.Foldable (toList)+import Data.Sequence (Seq)+import qualified Data.Sequence as Seq+import Data.Text (Text)+import Data.Void (Void, absurd)+import SimpleParser.Chunked (TextualChunked (..))+import SimpleParser.Common (CompoundTextLabel (..), TextLabel (..))+import SimpleParser.Result (CompoundError (..), ParseError (..), RawError (..), StreamError (..),+ parseErrorEnclosingLabels, parseErrorNarrowestSpan)+import SimpleParser.Stream (LinePos (..), Span (..), Stream (..), TextualStream)+import Text.Builder (Builder)+import qualified Text.Builder as TB++class ExplainLabel l where+ explainLabel :: l -> Builder++ explainLabelText :: l -> Text+ explainLabelText = TB.run . explainLabel++instance ExplainLabel Void where+ explainLabel = absurd++instance ExplainLabel TextLabel where+ explainLabel l =+ case l of+ TextLabelSpace -> "space"+ TextLabelHSpace -> "non-line-breaking space"+ TextLabelDigit -> "digit"++instance ExplainLabel l => ExplainLabel (CompoundTextLabel l) where+ explainLabel c =+ case c of+ CompoundTextLabelText l -> explainLabel l+ CompoundTextLabelCustom l -> explainLabel l++data ErrorExplanation = ErrorExplanation+ { eeReason :: !Builder+ , eeExpected :: !(Maybe Builder)+ , eeActual :: !(Maybe Builder)+ }++class ExplainError e where+ explainError :: e -> ErrorExplanation++instance ExplainError Void where+ explainError = absurd++endMsg :: Builder+endMsg = "end of stream"++tokB :: Char -> Builder+tokB t = "token '" <> TB.char t <> "'"++mayTokB :: Maybe Char -> Builder+mayTokB = maybe endMsg tokB++chunkB :: TextualChunked chunk => chunk -> Builder+chunkB k = "chunk \"" <> buildChunk k <> "\""++mayChunkB :: TextualChunked chunk => Maybe chunk -> Builder+mayChunkB = maybe endMsg chunkB++instance (Token s ~ Char, TextualChunked (Chunk s)) => ExplainError (StreamError s) where+ explainError (StreamError re) =+ case re of+ RawErrorMatchEnd actTok ->+ ErrorExplanation "failed to match end of stream" (Just endMsg) (Just (tokB actTok))+ RawErrorAnyToken ->+ ErrorExplanation "failed to match any token" (Just "any token") (Just endMsg)+ RawErrorAnyChunk ->+ ErrorExplanation "failed to match any chunk" (Just "any chunk") (Just endMsg)+ RawErrorSatisfyToken mayActTok ->+ ErrorExplanation "failed to satisfy token predicate" Nothing (Just (mayTokB mayActTok))+ RawErrorMatchToken expTok mayActTok ->+ ErrorExplanation "failed to match token" (Just (tokB expTok)) (Just (mayTokB mayActTok))+ RawErrorMatchChunk expChunk mayActChunk ->+ ErrorExplanation "failed to match chunk" (Just (chunkB expChunk)) (Just (mayChunkB mayActChunk))+ RawErrorTakeTokensWhile1 mayActTok ->+ ErrorExplanation "failed to take 1 or more tokens" Nothing (Just (mayTokB mayActTok))+ RawErrorDropTokensWhile1 mayActTok ->+ ErrorExplanation "failed to drop 1 or more tokens" Nothing (Just (mayTokB mayActTok))++instance (Token s ~ Char, TextualChunked (Chunk s), ExplainError e) => ExplainError (CompoundError s e) where+ explainError ce =+ case ce of+ CompoundErrorStream se -> explainError se+ CompoundErrorFail msg -> ErrorExplanation (TB.text msg) Nothing Nothing+ CompoundErrorCustom e -> explainError e++type Explainable l s e = (TextualStream s, ExplainLabel l, ExplainError e)++data ParseErrorExplanation p = ParseErrorExplanation+ { peeSpan :: !(Span p)+ , peeContext :: !(Seq Builder)+ , peeDetails :: !(Maybe Builder)+ , peeErrExp :: !ErrorExplanation+ }++explainParseError :: Explainable l s e => ParseError l s e -> ParseErrorExplanation (Pos s)+explainParseError pe =+ let (mayLab, sp) = parseErrorNarrowestSpan pe+ context = fmap explainLabel (parseErrorEnclosingLabels pe)+ mayDetails = fmap explainLabel mayLab+ errExp = explainError (peError pe)+ in ParseErrorExplanation sp context mayDetails errExp++buildSpan :: Span LinePos -> Builder+buildSpan (Span (LinePos _ sl sc) (LinePos _ el ec)) =+ TB.decimal (succ sl) <> ":" <> TB.decimal (succ sc) <> "-" <> TB.decimal (succ el) <> ":" <> TB.decimal (succ ec)++buildErrorExplanation :: Maybe Builder -> ErrorExplanation -> [Builder]+buildErrorExplanation mayDetails (ErrorExplanation reason mayExpected mayActual) = join+ [ ["[Reason ] " <> reason]+ , maybe [] (\de -> ["[Details ] " <> de]) mayDetails+ , maybe [] (\ex -> ["[Expected] " <> ex]) mayExpected+ , maybe [] (\ac -> ["[Actual ] " <> ac]) mayActual+ ]++buildParseErrorExplanation :: ParseErrorExplanation LinePos -> Builder+buildParseErrorExplanation (ParseErrorExplanation sp context mayDetails errExp) =+ let hd = join+ [ ["[Pos ] " <> buildSpan sp]+ , ["[Context ] || " <> TB.intercalate " |> " context | not (Seq.null context)]+ ]+ tl = buildErrorExplanation mayDetails errExp+ in TB.intercalate "\n" (hd ++ tl)++buildAllParseErrorExplanations :: Foldable f => f (ParseErrorExplanation LinePos) -> Builder+buildAllParseErrorExplanations = TB.intercalate "\n\n" . fmap buildParseErrorExplanation . toList
src/SimpleParser/Input.hs view
@@ -1,6 +1,9 @@ -- | Useful combinators for 'ParserT' and 'Stream'.+-- Classified as SAFE or UNSAFE. SAFE always return a value. UNSAFE throw. module SimpleParser.Input- ( peekToken+ ( withToken+ , withChunk+ , peekToken , popToken , peekChunk , popChunk@@ -19,99 +22,129 @@ , matchChunk ) where -import Control.Applicative (empty) import Control.Monad.State (gets, state) import Data.Bifunctor (first) import Data.Maybe (isNothing)-import SimpleParser.Parser (ParserT)-import SimpleParser.Stream (Chunked (..), Stream (..))+import SimpleParser.Chunked (Chunked (..))+import SimpleParser.Parser (ParserT (..), markWithOptStateParser, markWithStateParser)+import SimpleParser.Result (CompoundError (..), ParseError (..), ParseResult (..), RawError (..), StreamError (..))+import SimpleParser.Stack (emptyStack)+import SimpleParser.Stream (Stream (..)) --- | Return the next token, if any, but don't consume it.-peekToken :: (Stream s, Monad m) => ParserT e s m (Maybe (Token s))+throwStreamError :: Monad m => RawError (Chunk s) (Token s) -> ParserT l s e m a+throwStreamError re = ParserT (\s -> pure (Just (ParseResultError (pure (ParseError emptyStack s (CompoundErrorStream (StreamError re)))))))++-- | Fetches the next token from the stream and runs the callback.+withToken :: (Stream s, Monad m) => Maybe l -> (Maybe (Token s) -> ParserT l s e m a) -> ParserT l s e m a+withToken ml = markWithOptStateParser ml streamTake1++-- | Fetches the next chunk from the stream and runs the callback.+withChunk :: (Stream s, Monad m) => Maybe l -> Int -> (Maybe (Chunk s) -> ParserT l s e m a) -> ParserT l s e m a+withChunk ml n = markWithOptStateParser ml (streamTakeN n)++-- | Return the next token, if any, but don't consume it. (SAFE)+peekToken :: (Stream s, Monad m) => ParserT l s e m (Maybe (Token s)) peekToken = gets (fmap fst . streamTake1) --- | Return the next token, if any, and consume it.-popToken :: (Stream s, Monad m) => ParserT e s m (Maybe (Token s))+-- | Return the next token, if any, and consume it. (SAFE)+popToken :: (Stream s, Monad m) => ParserT l s e m (Maybe (Token s)) popToken = state (\stream -> maybe (Nothing, stream) (first Just) (streamTake1 stream)) -- | Return the next chunk of the given size, if any, but don't consume it.--- May return a smaller chunk at end of stream, but never returns an empty chunk.-peekChunk :: (Stream s, Monad m) => Int -> ParserT e s m (Maybe (Chunk s))+-- May return a smaller chunk at end of stream, but never returns an empty chunk. (SAFE)+peekChunk :: (Stream s, Monad m) => Int -> ParserT l s e m (Maybe (Chunk s)) peekChunk n = gets (fmap fst . streamTakeN n) -- | Return the next chunk of the given size, if any, and consume it.--- May return a smaller chunk at end of stream, but never returns an empty chunk.-popChunk :: (Stream s, Monad m) => Int -> ParserT e s m (Maybe (Chunk s))+-- May return a smaller chunk at end of stream, but never returns an empty chunk. (SAFE)+popChunk :: (Stream s, Monad m) => Int -> ParserT l s e m (Maybe (Chunk s)) popChunk n = state (\stream -> maybe (Nothing, stream) (first Just) (streamTakeN n stream)) -- | Drop the next chunk of the given size, if any, and consume it.--- May return a smaller size at end of stream, but never returns size 0.-dropChunk :: (Stream s, Monad m) => Int -> ParserT e s m (Maybe Int)+-- May return a smaller size at end of stream, but never returns size 0. (SAFE)+dropChunk :: (Stream s, Monad m) => Int -> ParserT l s e m (Maybe Int) dropChunk n = state (\stream -> maybe (Nothing, stream) (first Just) (streamDropN n stream)) --- | Is this the end of the stream?-isEnd :: (Stream s, Monad m) => ParserT e s m Bool-isEnd = isNothing <$> peekToken+-- | Is this the end of the stream? (SAFE)+isEnd :: (Stream s, Monad m) => ParserT l s e m Bool+isEnd = fmap isNothing peekToken --- | Match the end of the stream or terminate the parser.-matchEnd :: (Stream s, Monad m) => ParserT e s m ()-matchEnd = peekToken >>= maybe (pure ()) (const empty)+-- | Match the end of the stream or terminate the parser. (UNSAFE)+matchEnd :: (Stream s, Monad m) => ParserT l s e m ()+matchEnd = withToken Nothing (maybe (pure ()) (throwStreamError . RawErrorMatchEnd)) --- | Return the next token or terminate the parser at end of stream.-anyToken :: (Stream s, Monad m) => ParserT e s m (Token s)-anyToken = popToken >>= maybe empty pure+-- | Return the next token or terminate the parser at end of stream. (UNSAFE)+anyToken :: (Stream s, Monad m) => ParserT l s e m (Token s)+anyToken = withToken Nothing (maybe (throwStreamError RawErrorAnyToken) pure) -- | Return the next chunk of the given size or terminate the parser at end of stream.--- May return a smaller chunk at end of stream, but never returns an empty chunk.-anyChunk :: (Stream s, Monad m) => Int -> ParserT e s m (Chunk s)-anyChunk n = popChunk n >>= maybe empty pure+-- May return a smaller chunk at end of stream, but never returns an empty chunk. (UNSAFE)+anyChunk :: (Stream s, Monad m) => Int -> ParserT l s e m (Chunk s)+anyChunk n = withChunk Nothing n (maybe (throwStreamError RawErrorAnyChunk) pure) --- | Match the next token with the given predicate or terminate the parser at predicate false or end of stream.-satisfyToken :: (Stream s, Monad m) => (Token s -> Bool) -> ParserT e s m (Token s)-satisfyToken p = do- m <- popToken- case m of- Just c | p c -> pure c- _ -> empty+-- | Match the next token with the given predicate or terminate the parser at predicate false or end of stream. (UNSAFE)+satisfyToken :: (Stream s, Monad m) => Maybe l -> (Token s -> Bool) -> ParserT l s e m (Token s)+satisfyToken ml pcate = withToken ml $ \mu ->+ case mu of+ Just u | pcate u -> pure u+ _ -> throwStreamError (RawErrorSatisfyToken mu) -- | Folds over a stream of tokens while the boolean value is true.--- Always succeeds, even at end of stream.-foldTokensWhile :: (Stream s, Monad m) => (Token s -> x -> (Bool, x)) -> (x -> x) -> x -> ParserT e s m x-foldTokensWhile f g = go where+-- Always succeeds, even at end of stream. Only consumes greediest match. (SAFE)+foldTokensWhile :: (Stream s, Monad m) => (Token s -> x -> (Bool, x)) -> x -> ParserT l s e m x+foldTokensWhile processNext = go where go !x = do m <- peekToken case m of- Nothing -> pure (g x)+ Nothing -> pure x Just c ->- let (ok, newX) = f c x+ let (ok, newX) = processNext c x in if ok then popToken *> go newX else pure x -- | Take tokens into a chunk while they satisfy the given predicate.--- Always succeeds, even at end of stream. May return an empty chunk.-takeTokensWhile :: (Stream s, Monad m) => (Token s -> Bool) -> ParserT e s m (Chunk s)+-- Always succeeds, even at end of stream. May return an empty chunk. Only yields greediest match. (SAFE)+takeTokensWhile :: (Stream s, Monad m) => (Token s -> Bool) -> ParserT l s e m (Chunk s) takeTokensWhile = state . streamTakeWhile -- | Take tokens into a chunk while they satisfy the given predicate. -- Only succeeds if 1 or more tokens are taken, so it never returns an empty chunk.-takeTokensWhile1 :: (Stream s, Monad m) => (Token s -> Bool) -> ParserT e s m (Chunk s)-takeTokensWhile1 pcate = takeTokensWhile pcate >>= \c -> if chunkEmpty c then empty else pure c+-- Also takes an optional label to describe the predicate. (UNSAFE)+takeTokensWhile1 :: (Stream s, Monad m) => Maybe l -> (Token s -> Bool) -> ParserT l s e m (Chunk s)+takeTokensWhile1 ml pcate = markWithStateParser ml (streamTakeWhile pcate) $ \j ->+ if chunkEmpty j+ then do+ mu <- peekToken+ throwStreamError (RawErrorTakeTokensWhile1 mu)+ else pure j -- | Drop tokens and return chunk size while they satisfy the given predicate.--- Always succeeds, even at end of stream. May return empty chunk size 0.-dropTokensWhile :: (Stream s, Monad m) => (Token s -> Bool) -> ParserT e s m Int+-- Always succeeds, even at end of stream. May return empty chunk size 0. Only drops greediest match. (SAFE)+dropTokensWhile :: (Stream s, Monad m) => (Token s -> Bool) -> ParserT l s e m Int dropTokensWhile = state . streamDropWhile -- | Drop tokens and return chunk size while they satisfy the given predicate. -- Only succeeds if 1 or more tokens are dropped.-dropTokensWhile1 :: (Stream s, Monad m) => (Token s -> Bool) -> ParserT e s m Int-dropTokensWhile1 pcate = dropTokensWhile pcate >>= \s -> if s == 0 then empty else pure s+-- Also takes an optional label to describe the predicate. (UNSAFE)+dropTokensWhile1 :: (Stream s, Monad m) => Maybe l -> (Token s -> Bool) -> ParserT l s e m Int+dropTokensWhile1 ml pcate = markWithStateParser ml (streamDropWhile pcate) $ \s ->+ if s == 0+ then do+ mu <- peekToken+ throwStreamError (RawErrorDropTokensWhile1 mu)+ else pure s --- | Match token with equality or terminate the parser at inequality or end of stream.-matchToken :: (Stream s, Monad m, Eq (Token s)) => Token s -> ParserT e s m (Token s)-matchToken = satisfyToken . (==)+-- | Match token with equality or terminate the parser at inequality or end of stream. (UNSAFE)+matchToken :: (Stream s, Monad m, Eq (Token s)) => Token s -> ParserT l s e m (Token s)+matchToken t = withToken Nothing $ \mu ->+ case mu of+ Just u | t == u -> pure u+ _ -> throwStreamError (RawErrorMatchToken t mu) --- | Match chunk with equality or terminate the parser at inequality or end of stream.-matchChunk :: (Stream s, Monad m, Eq (Chunk s)) => Chunk s -> ParserT e s m (Chunk s)-matchChunk k = popChunk (chunkLength k) >>= maybe empty (\j -> if k == j then pure j else empty)+-- | Match chunk with equality or terminate the parser at inequality or end of stream. (UNSAFE)+matchChunk :: (Stream s, Monad m, Eq (Chunk s)) => Chunk s -> ParserT l s e m (Chunk s)+matchChunk k = withChunk Nothing (chunkLength k) $ \mj ->+ case mj of+ Just j | k == j -> pure j+ _ -> throwStreamError (RawErrorMatchChunk k mj)
+ src/SimpleParser/Interactive.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE OverloadedStrings #-}++module SimpleParser.Interactive+ ( parseInteractive+ ) where++import Data.Foldable (toList)+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.IO as TIO+import SimpleParser.Explain (Explainable, buildAllParseErrorExplanations, explainParseError)+import SimpleParser.Input (matchEnd)+import SimpleParser.Parser (Parser, runParser)+import SimpleParser.Result (ParseResult (..), ParseSuccess (..))+import SimpleParser.Stream (LinePosStream, newLinePosStream)+import qualified Text.Builder as TB++parseInteractive :: (s ~ LinePosStream Text, Explainable l s e, Show a) => Parser l s e a -> String -> IO ()+parseInteractive parser input =+ case runParser (parser <* matchEnd) (newLinePosStream (T.pack input)) of+ Nothing ->+ putStrLn "No result."+ Just (ParseResultError es) ->+ let b = buildAllParseErrorExplanations (fmap explainParseError (toList es))+ in TIO.putStrLn (TB.run ("Errors:\n" <> b))+ Just (ParseResultSuccess (ParseSuccess _ a)) ->+ putStrLn "Success:" *> print a
src/SimpleParser/Parser.hs view
@@ -1,21 +1,34 @@+{-# LANGUAGE Rank2Types #-}+ -- | 'ParserT' is the core monad transformer for parsing. module SimpleParser.Parser ( ParserT (..) , Parser , runParser- , filterParser- , reflectParser- , branchParser- , suppressParser- , isolateParser- , defaultParser- , optionalParser- , silenceParser+ , pureParser+ , bindParser+ , failParser+ , liftParser+ , hoistParser+ , catchJustParser+ , throwParser+ , catchParser+ , emptyParser+ , orParser , greedyStarParser , greedyStarParser_ , greedyPlusParser , greedyPlusParser_+ , defaultParser+ , optionalParser+ , silenceParser , lookAheadParser+ , markParser+ , markWithStateParser+ , markWithOptStateParser+ , unmarkParser+ , commitParser+ , onEmptyParser ) where import Control.Applicative (Alternative (..), liftA2)@@ -25,205 +38,291 @@ import Control.Monad.Morph (MFunctor (..)) import Control.Monad.State (MonadState (..)) import Control.Monad.Trans (MonadTrans (..))-import Data.Foldable (toList)-import ListT (ListT (..))-import qualified ListT-import SimpleParser.Result (ParseResult (..), ParseValue (..))+import Data.Bifunctor (first)+import Data.Sequence (Seq (..))+import Data.Sequence.NonEmpty ((><|))+import qualified Data.Sequence.NonEmpty as NESeq+import Data.Text (Text)+import qualified Data.Text as T+import SimpleParser.Chunked (Chunked (..))+import SimpleParser.Result (CompoundError (..), Mark (..), ParseError (..), ParseResult (..), ParseSuccess (..),+ markParseError, parseErrorResume, unmarkParseError)+import SimpleParser.Stack (emptyStack) -- | A 'ParserT' is a state/error/list transformer useful for parsing. -- All MTL instances are for this transformer only. If, for example, your effect -- has its own 'MonadState' instance, you'll have to use 'lift get' instead of 'get'.-newtype ParserT e s m a = ParserT { runParserT :: s -> ListT m (ParseResult e s a) }+newtype ParserT l s e m a = ParserT { runParserT :: s -> m (Maybe (ParseResult l s e a)) } deriving (Functor) -- | Use 'Parser' if you have no need for other monadic effects.-type Parser e s a = ParserT e s Identity a+type Parser l s e a = ParserT l s e Identity a -instance Monad m => Applicative (ParserT e s m) where- pure a = ParserT (pure . ParseResult (ParseSuccess a))+-- | Runs a non-effectful parser from an inital state and collects all results.+runParser :: Parser l s e a -> s -> Maybe (ParseResult l s e a)+runParser parser s = runIdentity (runParserT parser s)++-- | Applicative pure+pureParser :: Monad m => a -> ParserT l s e m a+pureParser a = ParserT (\s -> pure (Just (ParseResultSuccess (ParseSuccess s a))))++instance Monad m => Applicative (ParserT l s e m) where+ pure = pureParser (<*>) = ap -instance Monad m => Monad (ParserT e s m) where+-- | Monadic bind+bindParser :: Monad m => ParserT l s e m a -> (a -> ParserT l s e m b) -> ParserT l s e m b+bindParser parser f = ParserT (runParserT parser >=> go) where+ go mres =+ case mres of+ Nothing -> pure Nothing+ Just res ->+ case res of+ ParseResultError errs -> pure (Just (ParseResultError errs))+ ParseResultSuccess (ParseSuccess t a) -> runParserT (f a) t++instance Monad m => Monad (ParserT l s e m) where return = pure- parser >>= f = ParserT (runParserT parser >=> go) where- go (ParseResult v t) =- case v of- ParseError e -> pure (ParseResult (ParseError e) t)- ParseSuccess a -> runParserT (f a) t+ (>>=) = bindParser -instance Monad m => Alternative (ParserT e s m) where- empty = ParserT (const empty)- first <|> second = ParserT (\s -> runParserT first s <|> runParserT second s)+-- | The empty parser+emptyParser :: Monad m => ParserT l s e m a+emptyParser = ParserT (const (pure Nothing)) -instance Monad m => MonadPlus (ParserT e s m) where+-- | Yields from the first parser of the two that returns a successfull result.+-- Otherwise will merge and yield all errors.+orParser :: Monad m => ParserT l s e m a -> ParserT l s e m a -> ParserT l s e m a+orParser one two = ParserT (\s -> runParserT one s >>= go1 s) where+ go1 s mres1 =+ case mres1 of+ Nothing -> runParserT two s >>= go2 empty+ Just res1 ->+ case res1 of+ ParseResultSuccess _ -> pure mres1+ ParseResultError es1 -> runParserT two s >>= go2 (NESeq.toSeq es1)++ go2 es1 mres2 =+ case mres2 of+ Nothing -> pure (fmap ParseResultError (NESeq.nonEmptySeq es1))+ Just res2 ->+ case res2 of+ ParseResultSuccess _ -> pure mres2+ ParseResultError es2 -> pure (Just (ParseResultError (es1 ><| es2)))++-- | Yields the LONGEST string of 0 or more successes of the given parser.+-- Failures will be silenced.+greedyStarParser :: (Chunked seq elem, Monad m) => ParserT l s e m elem -> ParserT l s e m seq+greedyStarParser parser = go [] where+ opt = optionalParser parser+ go !acc = do+ res <- opt+ case res of+ Nothing -> pure (revTokensToChunk acc)+ Just a -> go (consChunk a acc)++-- | Same as 'greedyStarParser' but discards the result.+greedyStarParser_ :: Monad m => ParserT l s e m a -> ParserT l s e m ()+greedyStarParser_ parser = go where+ opt = optionalParser parser+ go = do+ res <- opt+ case res of+ Nothing -> pure ()+ Just _ -> go++-- | Yields the LONGEST string of 1 or more successes of the given parser.+-- Failures in the tail will be silenced, but those in the head will be returned.+greedyPlusParser :: (Chunked seq elem, Monad m) => ParserT l s e m elem -> ParserT l s e m seq+greedyPlusParser parser = liftA2 consChunk parser (greedyStarParser parser)++-- | Same as 'greedyPlusParser' but discards the result.+greedyPlusParser_ :: Monad m => ParserT l s e m a -> ParserT l s e m ()+greedyPlusParser_ parser = parser *> greedyStarParser_ parser++instance Monad m => Alternative (ParserT l s e m) where+ empty = emptyParser+ (<|>) = orParser+ some = greedyPlusParser+ many = greedyStarParser++instance Monad m => MonadPlus (ParserT l s e m) where mzero = empty mplus = (<|>) -instance Monad m => MonadError e (ParserT e s m) where- throwError e = ParserT (pure . ParseResult (ParseError e))- -- TODO(ejconlon) Implement directly by unwrapping?- catchError parser handler = do- r <- reflectParser parser- case r of- ParseError e -> handler e- ParseSuccess a -> pure a+instance Monad m => MonadState s (ParserT l s e m) where+ get = ParserT (\s -> pure (Just (ParseResultSuccess (ParseSuccess s s))))+ put t = ParserT (\_ -> pure (Just (ParseResultSuccess (ParseSuccess t ()))))+ state f = ParserT (\s -> let (!a, !t) = f s in pure (Just (ParseResultSuccess (ParseSuccess t a)))) -instance Monad m => MonadState s (ParserT e s m) where- get = ParserT (\s -> pure (ParseResult (ParseSuccess s) s))- put t = ParserT (const (pure (ParseResult (ParseSuccess ()) t)))- state f = ParserT (\s -> let (a, t) = f s in pure (ParseResult (ParseSuccess a) t))+-- | Catch only a subset of custom errors. This preserves label information vs rethrowing.+catchJustParser :: Monad m => (e -> Maybe b) -> ParserT l s e m a -> (b -> ParserT l s e m a) -> ParserT l s e m a+catchJustParser filterer parser handler = ParserT (runParserT parser >=> go) where+ go mres =+ case mres of+ Nothing -> pure Nothing+ Just res ->+ case res of+ ParseResultSuccess _ ->+ -- Nothing to catch, yield existing success+ pure mres+ ParseResultError es ->+ -- Find first custom error to handle+ goSplit Empty (NESeq.toSeq es) -instance MonadTrans (ParserT e s) where- lift ma = ParserT (\s -> lift (fmap (\a -> ParseResult (ParseSuccess a) s) ma))+ goSplit beforeEs afterEs =+ case seqPartition extractCustomError afterEs of+ Nothing ->+ -- No next custom error, finally yield all other errors+ pure (maybe empty (pure . ParseResultError) (NESeq.nonEmptySeq (beforeEs <> afterEs)))+ Just sep ->+ -- Found custom error - handle it+ goHandle beforeEs sep -instance MFunctor (ParserT e s) where- hoist trans (ParserT f) = ParserT (hoist trans . f)+ goHandle beforeEs (SeqPartition nextBeforeEs targetE (s, e) afterEs) =+ case filterer e of+ Nothing ->+ -- Not handling error; - find next custom error+ goSplit (beforeEs <> (targetE :<| nextBeforeEs)) afterEs+ Just b -> do+ mres <- runParserT (handler b) s+ case mres of+ Nothing ->+ -- No results from handled error - find next custom error+ goSplit (beforeEs <> nextBeforeEs) afterEs+ Just res ->+ case res of+ ParseResultSuccess _ -> pure mres+ ParseResultError es ->+ -- Add to list of errors and find next custom error+ goSplit (beforeEs <> nextBeforeEs <> NESeq.toSeq es) afterEs --- | Runs a non-effectful parser from an inital state and collects all results.-runParser :: Parser e s a -> s -> [ParseResult e s a]-runParser m s = runIdentity (ListT.toList (runParserT m s))+-- | Throws a custom error+throwParser :: Monad m => e -> ParserT l s e m a+throwParser e = ParserT (\s -> pure (Just (ParseResultError (pure (ParseError emptyStack s (CompoundErrorCustom e)))))) --- | Filters parse results-filterParser :: Monad m => (a -> Bool) -> ParserT e s m a -> ParserT e s m a-filterParser f parser = ParserT (ListT . go . runParserT parser) where- go listt = do- m <- ListT.uncons listt- case m of- Nothing -> pure Nothing- Just (r@(ParseResult v _), rest) ->- case v of- ParseSuccess a | not (f a) -> go rest- _ -> pure (Just (r, ListT (go rest)))+-- | Catches a custom error+catchParser :: Monad m => ParserT l s e m a -> (e -> ParserT l s e m a) -> ParserT l s e m a+catchParser = catchJustParser Just --- | A kind of "catch" that returns all results, success and failure.-reflectParser :: Monad m => ParserT e s m a -> ParserT e s m (ParseValue e a)-reflectParser parser = ParserT (ListT . go . runParserT parser) where- go listt = do- m <- ListT.uncons listt- case m of- Nothing -> pure Nothing- Just (ParseResult v t, rest) ->- pure (Just (ParseResult (ParseSuccess v) t, ListT (go rest)))+instance Monad m => MonadError e (ParserT l s e m) where+ throwError = throwParser+ catchError = catchJustParser Just --- | Combines the results of many parsers.--- Equvalent to 'asum'.-branchParser :: (Foldable f, Monad m) => f (ParserT e s m a) -> ParserT e s m a-branchParser = start . toList where- start ps =- case ps of- [] -> empty- q:qs -> ParserT (\s -> ListT (run s q qs))- run s q qs = do- m <- ListT.uncons (runParserT q s)- case m of- Nothing ->- case qs of- [] -> pure Nothing- r:rs -> run s r rs- Just (a, rest) -> pure (Just (a, rest))+-- | A simple failing parser+failParser :: Monad m => Text -> ParserT l s e m a+failParser msg = ParserT (\s -> pure (Just (ParseResultError (pure (ParseError emptyStack s (CompoundErrorFail msg)))))) -gatherParser :: Monad m => Bool -> ParserT e s m a -> ParserT e s m a-gatherParser single parser = ParserT (ListT . go [] . runParserT parser) where- go !acc listt = do- m <- ListT.uncons listt- case m of- Nothing -> returnErr (reverse acc)- Just (r@(ParseResult v _), rest) ->- case v of- ParseError _ -> go (r:acc) rest- ParseSuccess _ ->- let t = if single then empty else ListT (filterOk rest)- in pure (Just (r, t))+instance Monad m => MonadFail (ParserT l s e m) where+ fail = failParser . T.pack - returnErr racc =- case racc of- [] -> pure Nothing- r:rs -> pure (Just (r, ListT (returnErr rs)))+liftParser :: Monad m => m a -> ParserT l s e m a+liftParser ma = ParserT (\s -> fmap (Just . ParseResultSuccess . ParseSuccess s) ma) - filterOk listt = do- m <- ListT.uncons listt- case m of- Nothing -> pure Nothing- Just (r@(ParseResult v _), rest) ->- let nextListt = filterOk rest- in case v of- ParseError _ -> nextListt- ParseSuccess _ -> pure (Just (r, ListT nextListt))+instance MonadTrans (ParserT l s e) where+ lift = liftParser --- | If the parse results in ANY successes, keep only those. Otherwise return all failures.--- This may block indefinitely as it awaits either the end of the parser or its first success.--- See 'isolateParser' if you want only one success.-suppressParser :: Monad m => ParserT e s m a -> ParserT e s m a-suppressParser = gatherParser False+hoistParser :: (forall x. m x -> n x) -> ParserT l s e m a -> ParserT l s e n a+hoistParser trans (ParserT f) = ParserT (trans . f) --- | If the parse results in ANY successes, keep only THE FIRST. Otherwise return all failures.--- This may block indefinitely as it awaits either the end of the parser or its first success.--- See 'suppressParser' if you want all successes.-isolateParser :: Monad m => ParserT e s m a -> ParserT e s m a-isolateParser = gatherParser True+instance MFunctor (ParserT l s e) where+ hoist = hoistParser --- | If the parser yields no results (success or failure), yield a given value.-defaultParser :: Monad m => a -> ParserT e s m a -> ParserT e s m a-defaultParser def parser = ParserT (\s -> ListT (go s (runParserT parser s))) where- go s listt = do- m <- ListT.uncons listt- case m of- Nothing -> pure (Just (ParseResult (ParseSuccess def) s, empty))- Just _ -> pure m+-- | If the parser does not succeed, yield the given value.+defaultParser :: Monad m => a -> ParserT l s e m a -> ParserT l s e m a+defaultParser val parser = orParser parser (pure val) --- | A parser that yields 'Nothing' if there are no results (success or failure),--- otherwise wrapping successes in 'Just'.-optionalParser :: Monad m => ParserT e s m a -> ParserT e s m (Maybe a)+-- | A parser that yields 'Nothing' if the parser does not succeed, otherwise+-- wraps success in 'Just'.+optionalParser :: Monad m => ParserT l s e m a -> ParserT l s e m (Maybe a) optionalParser parser = defaultParser Nothing (fmap Just parser) --- | Removes all failures from the parse results.--- Equivalent to 'catchError (const empty)'.-silenceParser :: Monad m => ParserT e s m a -> ParserT e s m a-silenceParser parser = ParserT (ListT . go . runParserT parser) where- go listt = do- m <- ListT.uncons listt- case m of- Nothing -> pure Nothing- Just (r@(ParseResult v _), rest) ->- let nextListt = go rest- in case v of- ParseError _ -> nextListt- ParseSuccess _ -> pure (Just (r, ListT nextListt))+-- | Removes all failures from the parse results. Catches more errors than 'catchError (const empty)'+-- because this includes stream errors, not just custom errors.+-- If you want more fine-grained control, use 'reflectParser' and map over the results.+silenceParser :: Monad m => ParserT l s e m a -> ParserT l s e m a+silenceParser parser = ParserT (fmap (>>= go) . runParserT parser) where+ go res =+ case res of+ ParseResultError _ -> Nothing+ ParseResultSuccess _ -> Just res --- | Yields the LONGEST string of 0 or more successes of the given parser (and passes through failures).-greedyStarParser :: Monad m => ParserT e s m a -> ParserT e s m [a]-greedyStarParser parser = go [] where- opt = optionalParser parser- go !acc = do- res <- opt+-- | Yield the results of the given parser, but rewind back to the starting state in ALL cases (success and error).+-- Note that these results may contain errors, so you may want to stifle them with 'silenceParser', for example.+lookAheadParser :: Monad m => Maybe l -> ParserT l s e m a -> ParserT l s e m a+lookAheadParser ml parser = ParserT (\s -> fmap (fmap (go s)) (runParserT parser s)) where+ go s res = case res of- Nothing -> pure (reverse acc)- Just a -> go (a:acc)+ ParseResultError es -> ParseResultError (fmap (markParseError (Mark ml s)) es)+ ParseResultSuccess (ParseSuccess _ a) -> ParseResultSuccess (ParseSuccess s a) --- | Same as 'greedyStarParser' but discards the result.-greedyStarParser_ :: Monad m => ParserT e s m a -> ParserT e s m ()-greedyStarParser_ parser = go where- opt = optionalParser parser- go = do- res <- opt+-- | Yield the results of the given parser, but rewind back to the starting state on error ONLY.+markParser :: Monad m => Maybe l -> ParserT l s e m a -> ParserT l s e m a+markParser ml parser = ParserT (\s -> fmap (fmap (go s)) (runParserT parser s)) where+ go s res = case res of- Nothing -> pure ()- Just _ -> go+ ParseResultError es -> ParseResultError (fmap (markParseError (Mark ml s)) es)+ ParseResultSuccess _ -> res --- | Yields the LONGEST string of 1 or more successes of the given parser (and passes through failures).-greedyPlusParser :: Monad m => ParserT e s m a -> ParserT e s m [a]-greedyPlusParser parser = liftA2 (:) parser (greedyStarParser parser)+-- | Like 'markParser' but allows you to mutate state. See 'withToken' and 'withChunk'.+markWithStateParser :: Monad m => Maybe l -> (s -> (b, s)) -> (b -> ParserT l s e m a) -> ParserT l s e m a+markWithStateParser ml g f = markParser ml (state g >>= f) --- | Same as 'greedyPlusParser' but discards the result.-greedyPlusParser_ :: Monad m => ParserT e s m a -> ParserT e s m ()-greedyPlusParser_ parser = parser *> greedyStarParser_ parser+-- | Like 'markParser' but allows you to mutate state. See 'withToken' and 'withChunk'.+markWithOptStateParser :: Monad m => Maybe l -> (s -> Maybe (b, s)) -> (Maybe b -> ParserT l s e m a) -> ParserT l s e m a+markWithOptStateParser ml g = markWithStateParser ml (\s -> maybe (Nothing, s) (first Just) (g s)) --- | Yield the results of the given parser, but rewind back to the starting state.--- Note that these results may contain errors, so you may want to stifle them with 'silenceParser', for example.-lookAheadParser :: Monad m => ParserT e s m a -> ParserT e s m a-lookAheadParser parser = do+-- | Clear marks from parse errors. You can mark immediately after to widen the narrowest+-- marked span to the range you want to report.+unmarkParser :: Monad m => ParserT l s e m a -> ParserT l s e m a+unmarkParser parser = ParserT (fmap (fmap go) . runParserT parser) where+ go res =+ case res of+ ParseResultError es -> ParseResultError (fmap unmarkParseError es)+ ParseResultSuccess _ -> res++-- | If the first parser succeeds in the given state, yield results from the second parser in the given+-- state. This is likely the look-ahead you want. Use the first parser to check a prefix of input,+-- and use the second to consume that input.+commitParser :: Monad m => ParserT l s e m () -> ParserT l s e m a -> ParserT l s e m a+commitParser checker parser = do s <- get- flip catchError (\e -> put s *> throwError e) $ do- v <- parser- put s- pure v+ o <- optionalParser checker+ case o of+ Nothing -> empty+ Just _ -> put s *> parser++-- | If the first parser yields NO results (success or failure), yield from the second.+-- Note that this is different from 'orParser' in that it does not try the second if there+-- are errors in the first. You might use this on the outside of a complex parser with+-- a fallback to 'fail' to indicate that there are no matches.+onEmptyParser :: Parser l s e a -> Parser l s e a -> Parser l s e a+onEmptyParser parser fallback = ParserT (\s -> runParserT parser s >>= go s) where+ go s mres =+ case mres of+ Nothing -> runParserT fallback s+ Just _ -> pure mres++-- Private utility functions++data SeqPartition a b = SeqPartition+ { spBefore :: !(Seq a)+ , spKey :: !a+ , spValue :: !b+ , spAfter :: !(Seq a)+ } deriving (Eq, Show)++seqPartition :: (a -> Maybe b) -> Seq a -> Maybe (SeqPartition a b)+seqPartition f = go Empty where+ go before after =+ case after of+ Empty -> Nothing+ (x :<| xs) ->+ case f x of+ Nothing -> go (before :|> x) xs+ Just y -> Just (SeqPartition before x y xs)++extractCustomError :: ParseError l s e -> Maybe (s, e)+extractCustomError pe@(ParseError _ _ ce) =+ case ce of+ CompoundErrorCustom e -> Just (parseErrorResume pe, e)+ _ -> Nothing
src/SimpleParser/Result.hs view
@@ -1,31 +1,108 @@+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE UndecidableInstances #-}+ module SimpleParser.Result- ( ParseResult (..)- , ParseValue (..)- , parseSuccessResult- , parseErrorResult- , parseValue+ ( RawError (..)+ , StreamError (..)+ , CompoundError (..)+ , Mark (..)+ , ParseError (..)+ , parseErrorResume+ , markParseError+ , unmarkParseError+ , parseErrorEnclosingLabels+ , parseErrorNarrowestSpan+ , ParseSuccess (..)+ , ParseResult (..) ) where --- | Strict 'Either' for parse results.-data ParseValue e a =- ParseError !e- | ParseSuccess !a- deriving (Eq, Show, Functor, Foldable, Traversable)+import Data.Sequence (Seq (..))+import qualified Data.Sequence as Seq+import Data.Sequence.NonEmpty (NESeq)+import Data.Text (Text)+import SimpleParser.Stack (Stack (..), bottomStack, emptyStack, pushStack, topStack)+import SimpleParser.Stream (Span (..), Stream (..)) -parseValue :: (e -> r) -> (a -> r) -> ParseValue e a -> r-parseValue onError onSuccess value =- case value of- ParseError e -> onError e- ParseSuccess a -> onSuccess a+data RawError chunk token =+ RawErrorMatchEnd !token+ | RawErrorAnyToken+ | RawErrorAnyChunk+ | RawErrorSatisfyToken !(Maybe token)+ | RawErrorMatchToken !token !(Maybe token)+ | RawErrorMatchChunk !chunk !(Maybe chunk)+ | RawErrorTakeTokensWhile1 !(Maybe token)+ | RawErrorDropTokensWhile1 !(Maybe token)+ deriving (Eq, Show) --- | Strict pair of parse result and state at the time it was yielded.-data ParseResult e s a = ParseResult- { prValue :: !(ParseValue e a)- , prState :: !s+-- | 'RawStreamError' specialized to 'Stream' types - newtyped to allow GHC+-- to derive eq/show in the absense of type families.+newtype StreamError s = StreamError+ { unStreamError :: RawError (Chunk s) (Token s)+ }++deriving instance (Eq (Token s), Eq (Chunk s)) => Eq (StreamError s)+deriving instance (Show (Token s), Show (Chunk s)) => Show (StreamError s)++data CompoundError s e =+ CompoundErrorStream !(StreamError s)+ | CompoundErrorFail !Text+ | CompoundErrorCustom !e+ deriving (Functor, Foldable, Traversable)++deriving instance (Eq (Token s), Eq (Chunk s), Eq e) => Eq (CompoundError s e)+deriving instance (Show (Token s), Show (Chunk s), Show e) => Show (CompoundError s e)++data Mark l s = Mark+ { markLabel :: !(Maybe l)+ , markState :: !s+ } deriving (Eq, Show)++type MarkStack l s = Stack (Mark l s)++data ParseError l s e = ParseError+ { peMarkStack :: !(MarkStack l s)+ , peEndState :: !s+ , peError :: !(CompoundError s e)+ }++-- | Returns the resumption point of the 'ParseError'.+-- If it has been marked, we use that, otherwise we assume it starts at the exact error point.+parseErrorResume :: ParseError l s e -> s+parseErrorResume pe = maybe (peEndState pe) markState (topStack (peMarkStack pe))++-- | Updates a 'ParseError' with a resumption point.+markParseError :: Mark l s -> ParseError l s e -> ParseError l s e+markParseError s pe = pe { peMarkStack = pushStack s (peMarkStack pe) }++-- | Clears marks from a 'ParseError'.+unmarkParseError :: ParseError l s e -> ParseError l s e+unmarkParseError pe = pe { peMarkStack = emptyStack }++-- | Returns the narrowest span+parseErrorNarrowestSpan :: Stream s => ParseError l s e -> (Maybe l, Span (Pos s))+parseErrorNarrowestSpan pe = (ml, Span startPos endPos) where+ endPos = streamViewPos (peEndState pe)+ (ml, startPos) = maybe (Nothing, endPos) (\(Mark mx s) -> (mx, streamViewPos s)) (bottomStack (peMarkStack pe))++-- | Returns labels enclosing the narrowest span, from coarsest to finest+parseErrorEnclosingLabels :: ParseError l s e -> Seq l+parseErrorEnclosingLabels pe =+ case unStack (peMarkStack pe) of+ Empty -> Empty+ _ :<| s -> s >>= \(Mark ml _) -> maybe Seq.empty Seq.singleton ml++deriving instance (Eq l, Eq s, Eq (Token s), Eq (Chunk s), Eq e) => Eq (ParseError l s e)+deriving instance (Show l, Show s, Show (Token s), Show (Chunk s), Show e) => Show (ParseError l s e)++data ParseSuccess s a = ParseSuccess+ { psEndState :: !s+ , psValue :: !a } deriving (Eq, Show, Functor, Foldable, Traversable) -parseSuccessResult :: a -> s -> ParseResult e s a-parseSuccessResult = ParseResult . ParseSuccess+data ParseResult l s e a =+ ParseResultError !(NESeq (ParseError l s e))+ | ParseResultSuccess !(ParseSuccess s a)+ deriving (Functor, Foldable, Traversable) -parseErrorResult :: e -> s -> ParseResult e s a-parseErrorResult = ParseResult . ParseError+deriving instance (Eq l, Eq s, Eq (Token s), Eq (Chunk s), Eq e, Eq a) => Eq (ParseResult l s e a)+deriving instance (Show l, Show s, Show (Token s), Show (Chunk s), Show e, Show a) => Show (ParseResult l s e a)
+ src/SimpleParser/Stack.hs view
@@ -0,0 +1,38 @@+module SimpleParser.Stack+ ( Stack (..)+ , emptyStack+ , pushStack+ , topStack+ , bottomStack+ ) where++import Data.Sequence (Seq (..))++-- | A stack supporting O(1) push, top, and bottom.+-- Behind the newtype, a "push" onto the stack is implemented as "snoc", therefore+-- fold/traverse goes from bottom of stack (most generic label) to top (most specific label).+newtype Stack a = Stack+ { unStack :: Seq a+ } deriving (Eq, Show, Functor, Foldable, Traversable)++-- | Easy constructor for the empty stack+emptyStack :: Stack a+emptyStack = Stack Empty++-- | Pushes a an element onto a 'Stack'+pushStack :: a -> Stack a -> Stack a+pushStack a = Stack . (:|> a) . unStack++-- | Returns the top element of the stack (most recently pushed).+topStack :: Stack a -> Maybe a+topStack (Stack s) =+ case s of+ Empty -> Nothing+ _ :|> a -> Just a++-- | Returns the bottom element of the stack (least recently pushed).+bottomStack :: Stack a -> Maybe a+bottomStack (Stack s) =+ case s of+ Empty -> Nothing+ a :<| _ -> Just a
src/SimpleParser/Stream.hs view
@@ -1,72 +1,39 @@ -- | This reworks 'Text.Megaparsec.Stream' to split interfaces. -- See <https://hackage.haskell.org/package/megaparsec-9.0.1/docs/Text-Megaparsec-Stream.html Text.Megaparsec.Stream>. module SimpleParser.Stream- ( Chunked (..)- , Stream (..)+ ( Stream (..)+ , TextualStream , defaultStreamDropN , defaultStreamDropWhile+ , Offset (..) , OffsetStream (..) , newOffsetStream+ , Line (..)+ , Col (..)+ , LinePos (..)+ , LinePosStream (..)+ , newLinePosStream+ , Span (..) ) where import Data.Bifunctor (first, second)-import Data.Foldable (toList) import Data.Kind (Type)-import Data.List (uncons)+import Data.List (foldl') import Data.Sequence (Seq (..)) import qualified Data.Sequence as Seq import Data.Text (Text) import qualified Data.Text as T---- | 'Chunked' captures the basic relationship between tokens and chunks of them.--- Basically, these things behave like lists, sequences, text, etc.-class Monoid chunk => Chunked chunk token | chunk -> token where- consChunk :: token -> chunk -> chunk- unconsChunk :: chunk -> Maybe (token, chunk)- tokenToChunk :: token -> chunk- tokensToChunk :: [token] -> chunk- chunkToTokens :: chunk -> [token]- chunkLength :: chunk -> Int- chunkEmpty :: chunk -> Bool---- TODO(ejconlon) Add instances for Strict BS, Lazy BS, and Lazy Text--instance Chunked [a] a where- consChunk = (:)- unconsChunk = uncons- tokenToChunk a = [a]- tokensToChunk = id- chunkToTokens = id- chunkLength = length- chunkEmpty = null--instance Chunked (Seq a) a where- consChunk = (:<|)- unconsChunk s =- case s of- Empty -> Nothing- a :<| b -> Just (a, b)- tokenToChunk = Seq.singleton- tokensToChunk = Seq.fromList- chunkToTokens = toList- chunkLength = Seq.length- chunkEmpty = Seq.null--instance Chunked Text Char where- consChunk = T.cons- unconsChunk = T.uncons- tokenToChunk = T.singleton- tokensToChunk = T.pack- chunkToTokens = T.unpack- chunkLength = T.length- chunkEmpty = T.null+import SimpleParser.Chunked (Chunked (..), TextualChunked (..)) -- | 'Stream' lets us peel off tokens and chunks for parsing -- with explicit state passing. class Chunked (Chunk s) (Token s) => Stream s where type family Chunk s :: Type type family Token s :: Type+ type family Pos s :: Type + streamViewPos :: s -> Pos s+ streamTake1 :: s -> Maybe (Token s, s) streamTakeN :: Int -> s -> Maybe (Chunk s, s) streamTakeWhile :: (Token s -> Bool) -> s -> (Chunk s, s)@@ -77,22 +44,21 @@ streamDropWhile :: (Token s -> Bool) -> s -> (Int, s) streamDropWhile = defaultStreamDropWhile --- TODO(ejconlon) Specialize drops- defaultStreamDropN :: Stream s => Int -> s -> Maybe (Int, s) defaultStreamDropN n = fmap (first chunkLength) . streamTakeN n defaultStreamDropWhile :: Stream s => (Token s -> Bool) -> s -> (Int, s) defaultStreamDropWhile pcate = first chunkLength . streamTakeWhile pcate +type TextualStream s = (Stream s, Token s ~ Char, TextualChunked (Chunk s))+ instance Stream [a] where type instance Chunk [a] = [a] type instance Token [a] = a+ type instance Pos [a] = () - streamTake1 l =- case l of- [] -> Nothing- t:ts -> Just (t, ts)+ streamViewPos = const ()+ streamTake1 = unconsChunk streamTakeN n s | n <= 0 = Just ([], s) | null s = Nothing@@ -102,21 +68,24 @@ instance Stream (Seq a) where type instance Chunk (Seq a) = Seq a type instance Token (Seq a) = a+ type instance Pos (Seq a) = () - streamTake1 s =- case s of- Empty -> Nothing- t :<| ts -> Just (t, ts)+ streamViewPos = const ()+ streamTake1 = unconsChunk streamTakeN n s | n <= 0 = Just (Seq.empty, s) | Seq.null s = Nothing | otherwise = Just (Seq.splitAt n s) streamTakeWhile = Seq.spanl + -- TODO(ejconlon) Specialize drops+ instance Stream Text where type instance Chunk Text = Text type instance Token Text = Char+ type instance Pos Text = () + streamViewPos = const () streamTake1 = T.uncons streamTakeN n s | n <= 0 = Just (T.empty, s)@@ -124,26 +93,87 @@ | otherwise = Just (T.splitAt n s) streamTakeWhile = T.span +newtype Offset = Offset { unOffset :: Int }+ deriving newtype (Eq, Show, Ord, Enum, Num, Real, Integral)++-- | Stream wrapper that maintains an offset position. data OffsetStream s = OffsetStream- { osOffset :: !Int+ { osOffset :: !Offset , osState :: !s } deriving (Eq, Show, Functor, Foldable, Traversable) instance Stream s => Stream (OffsetStream s) where type instance Chunk (OffsetStream s) = Chunk s type instance Token (OffsetStream s) = Token s+ type instance Pos (OffsetStream s) = Offset + streamViewPos (OffsetStream o _) = o streamTake1 (OffsetStream o s) = fmap (second (OffsetStream (succ o))) (streamTake1 s)- streamTakeN n (OffsetStream o s) = fmap go (streamTakeN n s) where- go (a, b) = (a, OffsetStream (o + chunkLength a) b)- streamTakeWhile pcate (OffsetStream o s) =+ streamTakeN n (OffsetStream (Offset x) s) = fmap go (streamTakeN n s) where+ go (a, b) = (a, OffsetStream (Offset (x + chunkLength a)) b)+ streamTakeWhile pcate (OffsetStream (Offset x) s) = let (a, b) = streamTakeWhile pcate s- in (a, OffsetStream (o + chunkLength a) b)- streamDropN n (OffsetStream o s) = fmap go (streamDropN n s) where- go (m, b) = (m, OffsetStream (o + m) b)- streamDropWhile pcate (OffsetStream o s) =+ in (a, OffsetStream (Offset (x + chunkLength a)) b)+ streamDropN n (OffsetStream (Offset x) s) = fmap go (streamDropN n s) where+ go (m, b) = (m, OffsetStream (Offset (x + m)) b)+ streamDropWhile pcate (OffsetStream (Offset x) s) = let (m, b) = streamDropWhile pcate s- in (m, OffsetStream (o + m) b)+ in (m, OffsetStream (Offset (x + m)) b) newOffsetStream :: s -> OffsetStream s newOffsetStream = OffsetStream 0++newtype Line = Line { unLine :: Int }+ deriving newtype (Eq, Show, Ord, Enum, Num, Real, Integral)++newtype Col = Col { unCol :: Int }+ deriving newtype (Eq, Show, Ord, Enum, Num, Real, Integral)++-- | A 0-based line/col position in a character-based stream.+data LinePos = LinePos+ { lpOffset :: !Offset+ , lpLine :: !Line+ , lpCol :: !Col+ } deriving (Eq, Show, Ord)++-- | The canonical initial position.+initLinePos :: LinePos+initLinePos = LinePos 0 0 0++incrLinePosToken :: LinePos -> Char -> LinePos+incrLinePosToken (LinePos o l c) z+ | z == '\n' = LinePos (succ o) (succ l) 0+ | otherwise = LinePos (succ o) l (succ c)++incrLinePosChunk :: LinePos -> [Char] -> LinePos+incrLinePosChunk = foldl' incrLinePosToken++-- | Stream wrapper that maintains a line/col position.+data LinePosStream s = LinePosStream+ { lpsLinePos :: !LinePos+ , lpsState :: !s+ } deriving (Eq, Show, Functor, Foldable, Traversable)++instance (Stream s, Token s ~ Char) => Stream (LinePosStream s) where+ type instance Chunk (LinePosStream s) = Chunk s+ type instance Token (LinePosStream s) = Token s+ type instance Pos (LinePosStream s) = LinePos++ streamViewPos (LinePosStream p _) = p+ streamTake1 (LinePosStream p s) = fmap (\(a, b) -> (a, LinePosStream (incrLinePosToken p a) b)) (streamTake1 s)+ streamTakeN n (LinePosStream p s) = fmap go (streamTakeN n s) where+ go (a, b) = (a, LinePosStream (incrLinePosChunk p (chunkToTokens a)) b)+ streamTakeWhile pcate (LinePosStream p s) =+ let (a, b) = streamTakeWhile pcate s+ in (a, LinePosStream (incrLinePosChunk p (chunkToTokens a)) b)++ -- Drops can't be specialized because we need to examine each character for newlines.++newLinePosStream :: s -> LinePosStream s+newLinePosStream = LinePosStream initLinePos++-- | A range between two positions.+data Span p = Span+ { spanStart :: !p+ , spanEnd :: !p+ } deriving (Eq, Show, Ord)
test/Main.hs view
@@ -3,606 +3,588 @@ module Main (main) where -import Control.Applicative (empty)-import Control.Monad.Except (catchError, throwError)+import Control.Monad (void) import Data.Foldable (asum)-import Data.Functor (($>))+import qualified Data.Sequence as Seq+import qualified Data.Sequence.NonEmpty as NESeq+import Data.String (IsString) import Data.Text (Text)+import qualified Data.Text as T import SimpleParser-import SimpleParser.Examples.Json (Json (..), JsonF (..), parseJson)+import SimpleParser.Examples.Json (Json (..), JsonF (..), jsonParser) import Test.Tasty (TestName, TestTree, testGroup)-import Test.Tasty.HUnit (Assertion, testCase, (@?=))+import Test.Tasty.HUnit (testCase, (@?=)) import Test.Tasty.TH (defaultMainGenerator) -newtype Error = Error { unError :: String } deriving (Eq, Show)+newtype Label = Label { unLabel :: String } deriving (Eq, Show, IsString) -type TestParser a = Parser Error (OffsetStream Text) a+newtype Error = Error { unError :: String } deriving (Eq, Show, IsString) -type TestResult a = ParseResult Error (OffsetStream Text) a+type TestState = OffsetStream Text -data InputOutput a = InputOutput !Text ![TestResult a]+type TestParser a = Parser Label TestState Error a -runParserCase :: (Show a, Eq a) => TestParser a -> InputOutput a -> Assertion-runParserCase parser (InputOutput input expected) = do- let actual = runParser parser (newOffsetStream input)- actual @?= expected+type TestResult a = ParseResult Label TestState Error a -testParserCase :: (Show a, Eq a) => TestName -> TestParser a -> InputOutput a -> TestTree-testParserCase name parser inOut = testCase name (runParserCase parser inOut)+type TestRawError = RawError Text Char -testParserTrees :: (Show a, Eq a) => TestParser a -> [(TestName, InputOutput a)] -> [TestTree]-testParserTrees parser = fmap (\(n, io) -> testParserCase n parser io)+type TestParseError = ParseError Label TestState Error +data ParserCase a = ParserCase !TestName !(TestParser a) !Text !(Maybe (TestResult a))++fwd :: Int -> TestState -> TestState+fwd n (OffsetStream (Offset i) t) =+ let m = min n (T.length t)+ in OffsetStream (Offset (i + m)) (T.drop m t)++sucRes :: TestState -> a -> Maybe (TestResult a)+sucRes st = Just . ParseResultSuccess . ParseSuccess st++errRes :: [TestParseError] -> Maybe (TestResult a)+errRes es = Just (ParseResultError (NESeq.unsafeFromSeq (Seq.fromList es)))++custErr :: TestState -> Error -> TestParseError+custErr endSt = ParseError emptyStack endSt . CompoundErrorCustom++stmErr :: TestState -> TestRawError -> TestParseError+stmErr endSt = ParseError emptyStack endSt . CompoundErrorStream . StreamError++failErr :: TestState -> Text -> TestParseError+failErr endSt = ParseError emptyStack endSt . CompoundErrorFail++markWith :: TestState -> TestParseError -> TestParseError+markWith s = markParseError (Mark Nothing s)++anyTokErr :: TestState -> TestParseError+anyTokErr s = markWith s (stmErr s RawErrorAnyToken)++anyChunkErr :: TestState -> TestParseError+anyChunkErr s = markWith s (stmErr s RawErrorAnyChunk)++matchTokErr :: TestState -> Char -> Maybe Char -> TestParseError+matchTokErr s x my = markWith s (stmErr (fwd 1 s) (RawErrorMatchToken x my))++matchChunkErr :: TestState -> Text -> Maybe Text -> TestParseError+matchChunkErr s x my = markWith s (stmErr (fwd (T.length x) s) (RawErrorMatchChunk x my))++matchEndErr :: TestState -> Char -> TestParseError+matchEndErr s x = markWith s (stmErr (fwd 1 s) (RawErrorMatchEnd x))++takeTokErr :: TestState -> Int -> Maybe Char -> TestParseError+takeTokErr s n my = markWith s (stmErr (fwd n s) (RawErrorTakeTokensWhile1 my))++dropTokErr :: TestState -> Int -> Maybe Char -> TestParseError+dropTokErr s n my = markWith s (stmErr (fwd n s) (RawErrorDropTokensWhile1 my))++testParserCase :: (Show a, Eq a) => ParserCase a -> TestTree+testParserCase (ParserCase name parser input expected) = testCase name $ do+ let actual = runParser parser (newOffsetStream input)+ actual @?= expected+ test_empty :: [TestTree] test_empty =- let parser = empty :: TestParser Int+ let parser = emptyParser :: TestParser Int cases =- [ ("empty", InputOutput "" [])- , ("non-empty", InputOutput "hi" [])+ [ ParserCase "empty" parser "" Nothing+ , ParserCase "non-empty" parser "hi" Nothing ]- in testParserTrees parser cases+ in fmap testParserCase cases test_pure :: [TestTree] test_pure = let parser = pure (1 :: Int) cases =- [ ("empty", InputOutput "" [parseSuccessResult 1 (OffsetStream 0 "")])- , ("non-empty", InputOutput "hi" [parseSuccessResult 1 (OffsetStream 0 "hi")])+ [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") 1)+ , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 0 "hi") 1) ]- in testParserTrees parser cases+ in fmap testParserCase cases +test_fail :: [TestTree]+test_fail =+ let parser = fail "i give up" :: TestParser Int+ cases =+ [ ParserCase "empty" parser "" (errRes [(failErr (OffsetStream 0 "") "i give up")])+ , ParserCase "non-empty" parser "hi" (errRes [(failErr (OffsetStream 0 "hi") "i give up")])+ ]+ in fmap testParserCase cases+ test_peek_token :: [TestTree] test_peek_token = let parser = peekToken cases =- [ ("empty", InputOutput "" [parseSuccessResult Nothing (OffsetStream 0 "")])- , ("match", InputOutput "hi" [parseSuccessResult (Just 'h') (OffsetStream 0 "hi")])+ [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") Nothing)+ , ParserCase "match" parser "hi" (sucRes (OffsetStream 0 "hi") (Just 'h')) ]- in testParserTrees parser cases+ in fmap testParserCase cases test_pop_token :: [TestTree] test_pop_token = let parser = popToken cases =- [ ("empty", InputOutput "" [parseSuccessResult Nothing (OffsetStream 0 "")])- , ("match", InputOutput "hi" [parseSuccessResult (Just 'h') (OffsetStream 1 "i")])+ [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") Nothing)+ , ParserCase "match" parser "hi" (sucRes (OffsetStream 1 "i") (Just 'h')) ]- in testParserTrees parser cases+ in fmap testParserCase cases test_peek_chunk :: [TestTree] test_peek_chunk = let parser = peekChunk 2 cases =- [ ("len 0", InputOutput "" [parseSuccessResult Nothing (OffsetStream 0 "")])- , ("len 1", InputOutput "h" [parseSuccessResult (Just "h") (OffsetStream 0 "h")])- , ("len 2", InputOutput "hi" [parseSuccessResult (Just "hi") (OffsetStream 0 "hi")])- , ("len 3", InputOutput "hii" [parseSuccessResult (Just "hi") (OffsetStream 0 "hii")])+ [ ParserCase "len 0" parser "" (sucRes (OffsetStream 0 "") Nothing)+ , ParserCase "len 1" parser "h" (sucRes (OffsetStream 0 "h") (Just "h"))+ , ParserCase "len 2" parser "hi" (sucRes (OffsetStream 0 "hi") (Just "hi"))+ , ParserCase "len 3" parser "hii" (sucRes (OffsetStream 0 "hii") (Just "hi")) ]- in testParserTrees parser cases+ in fmap testParserCase cases test_pop_chunk :: [TestTree] test_pop_chunk = let parser = popChunk 2 cases =- [ ("len 0", InputOutput "" [parseSuccessResult Nothing (OffsetStream 0 "")])- , ("len 1", InputOutput "h" [parseSuccessResult (Just "h") (OffsetStream 1 "")])- , ("len 2", InputOutput "hi" [parseSuccessResult (Just "hi") (OffsetStream 2 "")])- , ("len 3", InputOutput "hii" [parseSuccessResult (Just "hi") (OffsetStream 2 "i")])+ [ ParserCase "len 0" parser "" (sucRes (OffsetStream 0 "") Nothing)+ , ParserCase "len 1" parser "h" (sucRes (OffsetStream 1 "") (Just "h"))+ , ParserCase "len 2" parser "hi" (sucRes (OffsetStream 2 "") (Just "hi"))+ , ParserCase "len 3" parser "hii" (sucRes (OffsetStream 2 "i") (Just "hi")) ]- in testParserTrees parser cases+ in fmap testParserCase cases test_drop_chunk :: [TestTree] test_drop_chunk = let parser = dropChunk 2 cases =- [ ("len 0", InputOutput "" [parseSuccessResult Nothing (OffsetStream 0 "")])- , ("len 1", InputOutput "h" [parseSuccessResult (Just 1) (OffsetStream 1 "")])- , ("len 2", InputOutput "hi" [parseSuccessResult (Just 2) (OffsetStream 2 "")])- , ("len 3", InputOutput "hii" [parseSuccessResult (Just 2) (OffsetStream 2 "i")])+ [ ParserCase "len 0" parser "" (sucRes (OffsetStream 0 "") Nothing)+ , ParserCase "len 1" parser "h" (sucRes (OffsetStream 1 "") (Just 1))+ , ParserCase "len 2" parser "hi" (sucRes (OffsetStream 2 "") (Just 2))+ , ParserCase "len 3" parser "hii" (sucRes (OffsetStream 2 "i") (Just 2)) ]- in testParserTrees parser cases+ in fmap testParserCase cases test_is_end :: [TestTree] test_is_end = let parser = isEnd cases =- [ ("empty", InputOutput "" [parseSuccessResult True (OffsetStream 0 "")])- , ("non-empty", InputOutput "hi" [parseSuccessResult False (OffsetStream 0 "hi")])+ [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") True)+ , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 0 "hi") False) ]- in testParserTrees parser cases+ in fmap testParserCase cases test_match_end :: [TestTree] test_match_end = let parser = matchEnd cases =- [ ("empty", InputOutput "" [parseSuccessResult () (OffsetStream 0 "")])- , ("non-empty", InputOutput "hi" [])+ [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") ())+ , ParserCase "non-empty" parser "hi" (errRes [matchEndErr (OffsetStream 0 "hi") 'h']) ]- in testParserTrees parser cases+ in fmap testParserCase cases test_any_token :: [TestTree] test_any_token = let parser = anyToken cases =- [ ("empty", InputOutput "" [])- , ("non-empty", InputOutput "hi" [parseSuccessResult 'h' (OffsetStream 1 "i")])+ [ ParserCase "empty" parser "" (errRes [anyTokErr (OffsetStream 0 "")])+ , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 1 "i") 'h') ]- in testParserTrees parser cases+ in fmap testParserCase cases test_any_chunk :: [TestTree] test_any_chunk = let parser = anyChunk 2 :: TestParser Text cases =- [ ("len 0", InputOutput "" [])- , ("len 1", InputOutput "h" [parseSuccessResult "h" (OffsetStream 1 "")])- , ("len 2", InputOutput "hi" [parseSuccessResult "hi" (OffsetStream 2 "")])- , ("len 3", InputOutput "hii" [parseSuccessResult "hi" (OffsetStream 2 "i")])+ [ ParserCase "len 0" parser "" (errRes [anyChunkErr (OffsetStream 0 "")])+ , ParserCase "len 1" parser "h" (sucRes (OffsetStream 1 "") "h")+ , ParserCase "len 2" parser "hi" (sucRes (OffsetStream 2 "") "hi")+ , ParserCase "len 3" parser "hii" (sucRes (OffsetStream 2 "i") "hi") ]- in testParserTrees parser cases+ in fmap testParserCase cases test_match_token :: [TestTree] test_match_token = let parser = matchToken 'h' cases =- [ ("empty", InputOutput "" [])- , ("non-empty", InputOutput "hi" [parseSuccessResult 'h' (OffsetStream 1 "i")])- , ("non-match", InputOutput "bye" [])+ [ ParserCase "empty" parser "" (errRes [matchTokErr (OffsetStream 0 "") 'h' Nothing])+ , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 1 "i") 'h')+ , ParserCase "non-match" parser "bye" (errRes [matchTokErr (OffsetStream 0 "bye") 'h' (Just 'b')]) ]- in testParserTrees parser cases+ in fmap testParserCase cases test_match_chunk :: [TestTree] test_match_chunk = let parser = matchChunk "hi" cases =- [ ("empty", InputOutput "" [])- , ("non-empty", InputOutput "hi" [parseSuccessResult "hi" (OffsetStream 2 "")])- , ("prefix", InputOutput "hiya" [parseSuccessResult "hi" (OffsetStream 2 "ya")])- , ("partial", InputOutput "hey" [])- , ("non-match", InputOutput "bye" [])+ [ ParserCase "empty" parser "" (errRes [matchChunkErr (OffsetStream 0 "") "hi" Nothing])+ , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 2 "") "hi")+ , ParserCase "prefix" parser "hiya" (sucRes (OffsetStream 2 "ya") "hi")+ , ParserCase "partial" parser "hey" (errRes [matchChunkErr (OffsetStream 0 "hey") "hi" (Just "he")])+ , ParserCase "non-match" parser "bye" (errRes [matchChunkErr (OffsetStream 0 "bye") "hi" (Just "by")])+ , ParserCase "short" parser "h" (errRes [matchChunkErr (OffsetStream 0 "h") "hi" (Just "h")]) ]- in testParserTrees parser cases+ in fmap testParserCase cases test_greedy_star :: [TestTree] test_greedy_star =- let parser = greedyStarParser (matchToken 'h')+ let parser = greedyStarParser (matchToken 'h') :: TestParser String cases =- [ ("empty", InputOutput "" [parseSuccessResult "" (OffsetStream 0 "")])- , ("non-empty", InputOutput "hi" [parseSuccessResult "h" (OffsetStream 1 "i")])- , ("repeat", InputOutput "hhi" [parseSuccessResult "hh" (OffsetStream 2 "i")])- , ("full", InputOutput "hhh" [parseSuccessResult "hhh" (OffsetStream 3 "")])- , ("non-match", InputOutput "bye" [parseSuccessResult "" (OffsetStream 0 "bye")])+ [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") "")+ , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 1 "i") "h")+ , ParserCase "repeat" parser "hhi" (sucRes (OffsetStream 2 "i") "hh")+ , ParserCase "full" parser "hhh" (sucRes (OffsetStream 3 "") "hhh")+ , ParserCase "non-match" parser "bye" (sucRes (OffsetStream 0 "bye") "") ]- in testParserTrees parser cases+ in fmap testParserCase cases test_greedy_star_unit :: [TestTree] test_greedy_star_unit = let parser = greedyStarParser_ (matchToken 'h') cases =- [ ("empty", InputOutput "" [parseSuccessResult () (OffsetStream 0 "")])- , ("non-empty", InputOutput "hi" [parseSuccessResult () (OffsetStream 1 "i")])- , ("repeat", InputOutput "hhi" [parseSuccessResult () (OffsetStream 2 "i")])- , ("full", InputOutput "hhh" [parseSuccessResult () (OffsetStream 3 "")])- , ("non-match", InputOutput "bye" [parseSuccessResult () (OffsetStream 0 "bye")])+ [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") ())+ , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 1 "i") ())+ , ParserCase "repeat" parser "hhi" (sucRes (OffsetStream 2 "i") ())+ , ParserCase "full" parser "hhh" (sucRes (OffsetStream 3 "") ())+ , ParserCase "non-match" parser "bye" (sucRes (OffsetStream 0 "bye") ()) ]- in testParserTrees parser cases+ in fmap testParserCase cases test_greedy_plus :: [TestTree] test_greedy_plus =- let parser = greedyPlusParser (matchToken 'h')+ let parser = greedyPlusParser (matchToken 'h') :: TestParser String cases =- [ ("empty", InputOutput "" [])- , ("non-empty", InputOutput "hi" [parseSuccessResult "h" (OffsetStream 1 "i")])- , ("repeat", InputOutput "hhi" [parseSuccessResult "hh" (OffsetStream 2 "i")])- , ("full", InputOutput "hhh" [parseSuccessResult "hhh" (OffsetStream 3 "")])- , ("non-match", InputOutput "bye" [])+ [ ParserCase "empty" parser "" (errRes [matchTokErr (OffsetStream 0 "") 'h' Nothing])+ , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 1 "i") "h")+ , ParserCase "repeat" parser "hhi" (sucRes (OffsetStream 2 "i") "hh")+ , ParserCase "full" parser "hhh" (sucRes (OffsetStream 3 "") "hhh")+ , ParserCase "non-match" parser "bye" (errRes [matchTokErr (OffsetStream 0 "bye") 'h' (Just 'b')]) ]- in testParserTrees parser cases+ in fmap testParserCase cases test_greedy_plus_unit :: [TestTree] test_greedy_plus_unit = let parser = greedyPlusParser_ (matchToken 'h') cases =- [ ("empty", InputOutput "" [])- , ("non-empty", InputOutput "hi" [parseSuccessResult () (OffsetStream 1 "i")])- , ("repeat", InputOutput "hhi" [parseSuccessResult () (OffsetStream 2 "i")])- , ("full", InputOutput "hhh" [parseSuccessResult () (OffsetStream 3 "")])- , ("non-match", InputOutput "bye" [])- ]- in testParserTrees parser cases--test_branch :: [TestTree]-test_branch =- let parser = branchParser [matchToken 'h', matchToken 'x']- cases =- [ ("empty", InputOutput "" [])- , ("first", InputOutput "hi" [parseSuccessResult 'h' (OffsetStream 1 "i")])- , ("second", InputOutput "xi" [parseSuccessResult 'x' (OffsetStream 1 "i")])- , ("non-match", InputOutput "bye" [])- ]- in testParserTrees parser cases--test_branch_first :: [TestTree]-test_branch_first =- let parser = branchParser [anyToken $> 'h', matchToken 'x']- cases =- [ ("empty", InputOutput "" [])- , ("first", InputOutput "hi" [parseSuccessResult 'h' (OffsetStream 1 "i")])- , ("second", InputOutput "xi" [parseSuccessResult 'h' (OffsetStream 1 "i")])- ]- in testParserTrees parser cases--test_branch_second :: [TestTree]-test_branch_second =- let parser = branchParser [empty, anyToken $> 'x']- cases =- [ ("empty", InputOutput "" [])- , ("first", InputOutput "hi" [parseSuccessResult 'x' (OffsetStream 1 "i")])- , ("second", InputOutput "xi" [parseSuccessResult 'x' (OffsetStream 1 "i")])- ]- in testParserTrees parser cases--test_combine :: [TestTree]-test_combine =- let parser = asum [matchToken 'h', matchToken 'x']- cases =- [ ("empty", InputOutput "" [])- , ("first", InputOutput "hi" [parseSuccessResult 'h' (OffsetStream 1 "i")])- , ("second", InputOutput "xi" [parseSuccessResult 'x' (OffsetStream 1 "i")])- , ("non-match", InputOutput "bye" [])+ [ ParserCase "empty" parser "" (errRes [matchTokErr (OffsetStream 0 "") 'h' Nothing])+ , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 1 "i") ())+ , ParserCase "repeat" parser "hhi" (sucRes (OffsetStream 2 "i") ())+ , ParserCase "full" parser "hhh" (sucRes (OffsetStream 3 "") ())+ , ParserCase "non-match" parser "bye" (errRes [matchTokErr (OffsetStream 0 "bye") 'h' (Just 'b')]) ]- in testParserTrees parser cases+ in fmap testParserCase cases -test_combine_first :: [TestTree]-test_combine_first =- let state = OffsetStream 1 "i"- parser = asum [anyToken $> 'h', matchToken 'x']+test_or :: [TestTree]+test_or =+ let parser = orParser (matchToken 'h') (matchToken 'x') cases =- [ ("empty", InputOutput "" [])- , ("first", InputOutput "hi" [parseSuccessResult 'h' state])- , ("second", InputOutput "xi" [parseSuccessResult 'h' state, parseSuccessResult 'x' state])+ [ ParserCase "empty" parser "" $ errRes+ [ matchTokErr (OffsetStream 0 "") 'h' Nothing+ , matchTokErr (OffsetStream 0 "") 'x' Nothing+ ]+ , ParserCase "first" parser "hi" (sucRes (OffsetStream 1 "i") 'h')+ , ParserCase "second" parser "xi" (sucRes (OffsetStream 1 "i") 'x')+ , ParserCase "non-match" parser "bye" $ errRes+ [ matchTokErr (OffsetStream 0 "bye") 'h' (Just 'b')+ , matchTokErr (OffsetStream 0 "bye") 'x' (Just 'b')+ ] ]- in testParserTrees parser cases+ in fmap testParserCase cases -test_combine_second :: [TestTree]-test_combine_second =+test_asum :: [TestTree]+test_asum = let state = OffsetStream 1 "i"- parser = asum [empty, anyToken $> 'x']+ parser = asum [matchToken 'h', 'y' <$ anyToken, matchToken 'x'] cases =- [ ("empty", InputOutput "" [])- , ("first", InputOutput "hi" [parseSuccessResult 'x' state])- , ("second", InputOutput "xi" [parseSuccessResult 'x' state])+ [ ParserCase "empty" parser "" $ errRes+ [ matchTokErr (OffsetStream 0 "") 'h' Nothing+ , anyTokErr (OffsetStream 0 "")+ , matchTokErr (OffsetStream 0 "") 'x' Nothing+ ]+ , ParserCase "first" parser "hi" (sucRes state 'h')+ , ParserCase "middle" parser "zi" (sucRes state 'y')+ , ParserCase "last" parser "xi" (sucRes state 'y') ]- in testParserTrees parser cases+ in fmap testParserCase cases -test_with_default_empty :: [TestTree]-test_with_default_empty =- let parser = defaultParser 'z' empty+test_default_empty :: [TestTree]+test_default_empty =+ let parser = defaultParser 'z' emptyParser cases =- [ ("empty", InputOutput "" [parseSuccessResult 'z' (OffsetStream 0 "")])- , ("non-empty", InputOutput "hi" [parseSuccessResult 'z' (OffsetStream 0 "hi")])+ [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") 'z')+ , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 0 "hi") 'z') ]- in testParserTrees parser cases+ in fmap testParserCase cases -test_with_default :: [TestTree]-test_with_default =+test_default :: [TestTree]+test_default = let parser = defaultParser 'z' (matchToken 'h') cases =- [ ("empty", InputOutput "" [parseSuccessResult 'z' (OffsetStream 0 "")])- , ("match", InputOutput "hi" [parseSuccessResult 'h' (OffsetStream 1 "i")])- , ("non-match", InputOutput "bye" [parseSuccessResult 'z' (OffsetStream 0 "bye")])+ [ ParserCase "non-match empty" parser "" (sucRes (OffsetStream 0 "") 'z')+ , ParserCase "match" parser "hi" (sucRes (OffsetStream 1 "i") 'h')+ , ParserCase "non-match" parser "bye" (sucRes (OffsetStream 0 "bye") 'z') ]- in testParserTrees parser cases+ in fmap testParserCase cases -test_bind_multi_pre :: [TestTree]-test_bind_multi_pre =+test_bind_1 :: [TestTree]+test_bind_1 = let state = OffsetStream 1 "i"- parser = asum [anyToken $> 'h', matchToken 'x'] >>= \c -> pure [c, c]+ parser = matchToken 'x' >>= \c -> pure [c, c] cases =- [ ("empty", InputOutput "" [])- , ("first", InputOutput "hi" [parseSuccessResult "hh" state])- , ("second", InputOutput "xi" [parseSuccessResult "hh" state, parseSuccessResult "xx" state])+ [ ParserCase "empty" parser "" (errRes [matchTokErr (OffsetStream 0 "") 'x' Nothing])+ , ParserCase "first" parser "hi" (errRes [matchTokErr (OffsetStream 0 "hi") 'x' (Just 'h')])+ , ParserCase "second" parser "xi" (sucRes state "xx") ]- in testParserTrees parser cases+ in fmap testParserCase cases -test_bind_multi_post :: [TestTree]-test_bind_multi_post =- let state1 = OffsetStream 1 "i"- state2 = OffsetStream 2 ""- parser = anyToken >>= \x -> asum [pure x, matchToken 'i']+test_bind_2 :: [TestTree]+test_bind_2 =+ let state = OffsetStream 1 "i"+ parser = anyToken >>= \x -> if x == 'x' then pure 'y' else emptyParser cases =- [ ("empty", InputOutput "" [])- , ("first", InputOutput "hi" [parseSuccessResult 'h' state1, parseSuccessResult 'i' state2])- , ("second", InputOutput "xi" [parseSuccessResult 'x' state1, parseSuccessResult 'i' state2])+ [ ParserCase "empty" parser "" (errRes [anyTokErr (OffsetStream 0 "")])+ , ParserCase "first" parser "hi" Nothing+ , ParserCase "second" parser "xi" (sucRes state 'y') ]- in testParserTrees parser cases+ in fmap testParserCase cases test_throw :: [TestTree] test_throw = let err = Error "boo"- parser = throwError err :: TestParser Int+ parser = throwParser err :: TestParser Int cases =- [ ("empty", InputOutput "" [parseErrorResult err (OffsetStream 0 "")])- , ("non-empty", InputOutput "hi" [parseErrorResult err (OffsetStream 0 "hi")])+ [ ParserCase "empty" parser "" (errRes [custErr (OffsetStream 0 "") err])+ , ParserCase "non-empty" parser "hi" (errRes [custErr (OffsetStream 0 "hi") err]) ]- in testParserTrees parser cases+ in fmap testParserCase cases test_consume_throw :: [TestTree] test_consume_throw = let err = Error "boo"- parser = anyToken *> throwError err :: TestParser Int+ parser = anyToken *> throwParser err :: TestParser Int cases =- [ ("empty", InputOutput "" [])- , ("non-empty", InputOutput "hi" [parseErrorResult err (OffsetStream 1 "i")])+ [ ParserCase "empty" parser "" (errRes [anyTokErr (OffsetStream 0 "")])+ , ParserCase "non-empty" parser "hi" (errRes [custErr (OffsetStream 1 "i") err]) ]- in testParserTrees parser cases+ in fmap testParserCase cases -test_with_default_throw :: [TestTree]-test_with_default_throw =+test_default_throw :: [TestTree]+test_default_throw = let err = Error "boo"- parser = defaultParser 'z' (throwError err)+ parser = defaultParser 'z' (throwParser err) cases =- [ ("empty", InputOutput "" [parseErrorResult err (OffsetStream 0 "")])- , ("non-empty", InputOutput "hi" [parseErrorResult err (OffsetStream 0 "hi")])+ [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") 'z')+ , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 0 "hi") 'z') ]- in testParserTrees parser cases+ in fmap testParserCase cases -test_with_default_consume_throw :: [TestTree]-test_with_default_consume_throw =+test_default_consume_throw :: [TestTree]+test_default_consume_throw = let err = Error "boo"- parser = defaultParser 'z' (anyToken *> throwError err)+ parser = defaultParser 'z' (anyToken *> throwParser err) cases =- [ ("empty", InputOutput "" [parseSuccessResult 'z' (OffsetStream 0 "")])- , ("non-empty", InputOutput "hi" [parseErrorResult err (OffsetStream 1 "i")])+ [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") 'z')+ , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 0 "hi") 'z') ]- in testParserTrees parser cases+ in fmap testParserCase cases test_throw_mixed :: [TestTree] test_throw_mixed = let state = OffsetStream 0 "hi" err = Error "boo"- parser = asum [throwError err, pure 1 :: TestParser Int]+ parser = orParser (throwParser err) (pure 1) :: TestParser Int cases =- [ ("non-empty", InputOutput "hi" [parseErrorResult err state, parseSuccessResult 1 state])+ [ ParserCase "non-empty" parser "hi" (sucRes state 1) ]- in testParserTrees parser cases+ in fmap testParserCase cases +test_throw_mixed_flip :: [TestTree]+test_throw_mixed_flip =+ let state = OffsetStream 0 "hi"+ err = Error "boo"+ parser = orParser (pure 1) (throwParser err) :: TestParser Int+ cases =+ [ ParserCase "non-empty" parser "hi" (sucRes state 1)+ ]+ in fmap testParserCase cases+ test_catch :: [TestTree] test_catch = let state = OffsetStream 0 "hi" err = Error "boo"- parser = catchError (asum [throwError err, pure 1]) (\(Error m) -> pure (if m == "boo" then 2 else 3)) :: TestParser Int+ parser = catchParser (throwParser err) (\(Error m) -> pure (if m == "boo" then 2 else 3)) :: TestParser Int cases =- [ ("non-empty", InputOutput "hi" [parseSuccessResult 2 state, parseSuccessResult 1 state])+ [ ParserCase "non-empty" parser "hi" (sucRes state 2) ]- in testParserTrees parser cases+ in fmap testParserCase cases test_catch_recur :: [TestTree] test_catch_recur = let state = OffsetStream 0 "hi" err1 = Error "boo" err2 = Error "two"- parser = catchError (throwError err1) (const (throwError err2)) :: TestParser Int- cases =- [ ("non-empty", InputOutput "hi" [parseErrorResult err2 state])- ]- in testParserTrees parser cases--test_suppress_success :: [TestTree]-test_suppress_success =- let state = OffsetStream 0 "hi"- parser = suppressParser (asum [pure 1, pure 2]) :: TestParser Int- cases =- [ ("non-empty", InputOutput "hi" [parseSuccessResult 1 state, parseSuccessResult 2 state])- ]- in testParserTrees parser cases--test_suppress_fail_first :: [TestTree]-test_suppress_fail_first =- let err = Error "boo"- parser = suppressParser (asum [throwError err, pure 2]) :: TestParser Int- cases =- [ ("non-empty", InputOutput "hi" [parseSuccessResult 2 (OffsetStream 0 "hi")])- ]- in testParserTrees parser cases--test_suppress_fail_second :: [TestTree]-test_suppress_fail_second =- let err = Error "boo"- parser = suppressParser (asum [pure 1, throwError err]) :: TestParser Int- cases =- [ ("non-empty", InputOutput "hi" [parseSuccessResult 1 (OffsetStream 0 "hi")])- ]- in testParserTrees parser cases--test_suppress_fail_both :: [TestTree]-test_suppress_fail_both =- let state = OffsetStream 0 "hi"- err1 = Error "boo1"- err2 = Error "boo2"- parser = suppressParser (asum [throwError err1, throwError err2]) :: TestParser Int- cases =- [ ("non-empty", InputOutput "hi" [parseErrorResult err1 state, parseErrorResult err2 state])- ]- in testParserTrees parser cases--test_isolate_success :: [TestTree]-test_isolate_success =- let state = OffsetStream 0 "hi"- parser = isolateParser (asum [pure 1, pure 2]) :: TestParser Int- cases =- [ ("non-empty", InputOutput "hi" [parseSuccessResult 1 state])- ]- in testParserTrees parser cases--test_isolate_fail_first :: [TestTree]-test_isolate_fail_first =- let err = Error "boo"- parser = isolateParser (asum [throwError err, pure 2]) :: TestParser Int- cases =- [ ("non-empty", InputOutput "hi" [parseSuccessResult 2 (OffsetStream 0 "hi")])- ]- in testParserTrees parser cases--test_isolate_fail_second :: [TestTree]-test_isolate_fail_second =- let err = Error "boo"- parser = isolateParser (asum [pure 1, throwError err]) :: TestParser Int- cases =- [ ("non-empty", InputOutput "hi" [parseSuccessResult 1 (OffsetStream 0 "hi")])- ]- in testParserTrees parser cases--test_isolate_fail_both :: [TestTree]-test_isolate_fail_both =- let state = OffsetStream 0 "hi"- err1 = Error "boo1"- err2 = Error "boo2"- parser = isolateParser (asum [throwError err1, throwError err2]) :: TestParser Int+ parser = catchParser (throwParser err1) (const (throwParser err2)) :: TestParser Int cases =- [ ("non-empty", InputOutput "hi" [parseErrorResult err1 state, parseErrorResult err2 state])+ [ ParserCase "non-empty" parser "hi" (errRes [custErr state err2]) ]- in testParserTrees parser cases+ in fmap testParserCase cases test_silence_success :: [TestTree] test_silence_success = let state = OffsetStream 0 "hi"- parser = silenceParser (asum [pure 1, pure 2]) :: TestParser Int+ parser = silenceParser (pure 1) :: TestParser Int cases =- [ ("non-empty", InputOutput "hi" [parseSuccessResult 1 state, parseSuccessResult 2 state])+ [ ParserCase "non-empty" parser "hi" (sucRes state 1) ]- in testParserTrees parser cases+ in fmap testParserCase cases -test_silence_fail_first :: [TestTree]-test_silence_fail_first =+test_silence_fail :: [TestTree]+test_silence_fail = let err = Error "boo"- parser = silenceParser (asum [throwError err, pure 2]) :: TestParser Int+ parser = silenceParser (throwParser err) :: TestParser Int cases =- [ ("non-empty", InputOutput "hi" [parseSuccessResult 2 (OffsetStream 0 "hi")])+ [ ParserCase "non-empty" parser "hi" Nothing ]- in testParserTrees parser cases+ in fmap testParserCase cases -test_silence_fail_second :: [TestTree]-test_silence_fail_second =- let err = Error "boo"- parser = silenceParser (asum [pure 1, throwError err]) :: TestParser Int+test_silence_empty :: [TestTree]+test_silence_empty =+ let parser = silenceParser emptyParser :: TestParser Int cases =- [ ("non-empty", InputOutput "hi" [parseSuccessResult 1 (OffsetStream 0 "hi")])+ [ ParserCase "non-empty" parser "hi" Nothing ]- in testParserTrees parser cases+ in fmap testParserCase cases -test_silence_fail_both :: [TestTree]-test_silence_fail_both =- let err1 = Error "boo1"- err2 = Error "boo2"- parser = silenceParser (asum [throwError err1, throwError err2]) :: TestParser Int+test_look_ahead_pure :: [TestTree]+test_look_ahead_pure =+ let parser = lookAheadParser Nothing (pure 1) :: TestParser Int cases =- [ ("non-empty", InputOutput "hi" [])+ [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") 1)+ , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 0 "hi") 1) ]- in testParserTrees parser cases+ in fmap testParserCase cases test_look_ahead_success :: [TestTree] test_look_ahead_success =- let parser = lookAheadParser anyToken+ let parser = lookAheadParser Nothing anyToken cases =- [ ("empty", InputOutput "" [])- , ("non-empty", InputOutput "hi" [parseSuccessResult 'h' (OffsetStream 0 "hi")])+ [ ParserCase "non-match empty" parser "" (errRes [markWith (OffsetStream 0 "") (anyTokErr (OffsetStream 0 ""))])+ , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 0 "hi") 'h') ]- in testParserTrees parser cases+ in fmap testParserCase cases test_look_ahead_failure :: [TestTree] test_look_ahead_failure = let err = Error "boo"- parser = lookAheadParser (anyToken *> throwError err) :: TestParser Char+ parser = lookAheadParser Nothing (anyToken *> throwParser err) :: TestParser Char cases =- [ ("empty", InputOutput "" [])- , ("non-empty", InputOutput "hi" [parseErrorResult err (OffsetStream 0 "hi")])+ [ ParserCase "non-match empty" parser "" (errRes [markWith (OffsetStream 0 "") (anyTokErr (OffsetStream 0 ""))])+ , ParserCase "non-empty" parser "hi" (errRes [markWith (OffsetStream 0 "hi") (custErr (OffsetStream 1 "i") err)]) ]- in testParserTrees parser cases+ in fmap testParserCase cases +test_commit :: [TestTree]+test_commit =+ let parser = commitParser (void (matchToken 'h')) (matchChunk "hi") :: TestParser Text+ cases =+ [ ParserCase "non-match empty" parser "" Nothing+ , ParserCase "non-match non-empty" parser "ho" (errRes [matchChunkErr (OffsetStream 0 "ho") "hi" (Just "ho")])+ , ParserCase "match" parser "hi" (sucRes (OffsetStream 2 "") "hi")+ ]+ in fmap testParserCase cases+ test_take_while :: [TestTree] test_take_while = let parser = takeTokensWhile (=='h') :: TestParser Text cases =- [ ("empty", InputOutput "" [parseSuccessResult "" (OffsetStream 0 "")])- , ("non-match", InputOutput "i" [parseSuccessResult "" (OffsetStream 0 "i")])- , ("match", InputOutput "hi" [parseSuccessResult "h" (OffsetStream 1 "i")])- , ("match 2", InputOutput "hhi" [parseSuccessResult "hh" (OffsetStream 2 "i")])- , ("match end", InputOutput "hh" [parseSuccessResult "hh" (OffsetStream 2 "")])+ [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") "")+ , ParserCase "non-match" parser "i" (sucRes (OffsetStream 0 "i") "")+ , ParserCase "match" parser "hi" (sucRes (OffsetStream 1 "i") "h")+ , ParserCase "match 2" parser "hhi" (sucRes (OffsetStream 2 "i") "hh")+ , ParserCase "match end" parser "hh" (sucRes (OffsetStream 2 "") "hh") ]- in testParserTrees parser cases+ in fmap testParserCase cases test_take_while_1 :: [TestTree] test_take_while_1 =- let parser = takeTokensWhile1 (=='h') :: TestParser Text+ let parser = takeTokensWhile1 Nothing (=='h') :: TestParser Text cases =- [ ("empty", InputOutput "" [])- , ("non-match", InputOutput "i" [])- , ("match", InputOutput "hi" [parseSuccessResult "h" (OffsetStream 1 "i")])- , ("match 2", InputOutput "hhi" [parseSuccessResult "hh" (OffsetStream 2 "i")])- , ("match end", InputOutput "hh" [parseSuccessResult "hh" (OffsetStream 2 "")])+ [ ParserCase "empty" parser "" (errRes [takeTokErr(OffsetStream 0 "") 0 Nothing])+ , ParserCase "non-match" parser "i" (errRes [takeTokErr (OffsetStream 0 "i") 0 (Just 'i')])+ , ParserCase "match" parser "hi" (sucRes (OffsetStream 1 "i") "h")+ , ParserCase "match 2" parser "hhi" (sucRes (OffsetStream 2 "i") "hh")+ , ParserCase "match end" parser "hh" (sucRes (OffsetStream 2 "") "hh") ]- in testParserTrees parser cases+ in fmap testParserCase cases test_drop_while :: [TestTree] test_drop_while = let parser = dropTokensWhile (=='h') :: TestParser Int cases =- [ ("empty", InputOutput "" [parseSuccessResult 0 (OffsetStream 0 "")])- , ("non-match", InputOutput "i" [parseSuccessResult 0 (OffsetStream 0 "i")])- , ("match", InputOutput "hi" [parseSuccessResult 1 (OffsetStream 1 "i")])- , ("match 2", InputOutput "hhi" [parseSuccessResult 2 (OffsetStream 2 "i")])- , ("match end", InputOutput "hh" [parseSuccessResult 2 (OffsetStream 2 "")])+ [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") 0)+ , ParserCase "non-match" parser "i" (sucRes (OffsetStream 0 "i") 0)+ , ParserCase "match" parser "hi" (sucRes (OffsetStream 1 "i") 1)+ , ParserCase "match 2" parser "hhi" (sucRes (OffsetStream 2 "i") 2)+ , ParserCase "match end" parser "hh" (sucRes (OffsetStream 2 "") 2) ]- in testParserTrees parser cases+ in fmap testParserCase cases test_drop_while_1 :: [TestTree] test_drop_while_1 =- let parser = dropTokensWhile1 (=='h') :: TestParser Int+ let parser = dropTokensWhile1 Nothing (=='h') :: TestParser Int cases =- [ ("empty", InputOutput "" [])- , ("non-match", InputOutput "i" [])- , ("match", InputOutput "hi" [parseSuccessResult 1 (OffsetStream 1 "i")])- , ("match 2", InputOutput "hhi" [parseSuccessResult 2 (OffsetStream 2 "i")])- , ("match end", InputOutput "hh" [parseSuccessResult 2 (OffsetStream 2 "")])+ [ ParserCase "empty" parser "" (errRes [dropTokErr (OffsetStream 0 "") 0 Nothing])+ , ParserCase "non-match" parser "i" (errRes [dropTokErr (OffsetStream 0 "i") 0 (Just 'i')])+ , ParserCase "match" parser "hi" (sucRes (OffsetStream 1 "i") 1)+ , ParserCase "match 2" parser "hhi" (sucRes (OffsetStream 2 "i") 2)+ , ParserCase "match end" parser "hh" (sucRes (OffsetStream 2 "") 2) ]- in testParserTrees parser cases+ in fmap testParserCase cases -testJsonCase :: TestName -> Text -> [Json] -> TestTree+type JsonResult = Maybe Json++testJsonCase :: TestName -> Text -> JsonResult -> TestTree testJsonCase name str expected = testCase ("json " <> name) $ do let actual = parseJson str actual @?= expected -testJsonTrees :: [(TestName, Text, [Json])] -> [TestTree]+testJsonTrees :: [(TestName, Text, JsonResult)] -> [TestTree] testJsonTrees = fmap (\(n, s, e) -> testJsonCase n s e) +parseJson :: Text -> JsonResult+parseJson str =+ let p = jsonParser <* matchEnd+ in case runParser p str of+ Just (ParseResultSuccess (ParseSuccess _ a)) -> Just a+ _ -> Nothing+ test_json :: [TestTree] test_json = let nullVal = Json JsonNull trueVal = Json (JsonBool True) falseVal = Json (JsonBool False)- arrVal = Json . JsonArray+ arrVal = Json . JsonArray . Seq.fromList strVal = Json . JsonString- objVal = Json . JsonObject+ objVal = Json . JsonObject . Seq.fromList+ numVal = Json . JsonNum cases =- [ ("empty", "", [])- , ("bad", "bad", [])- , ("null", "null", [nullVal])- , ("true", "true", [trueVal])- , ("false", "false", [falseVal])- , ("arr0", "[]", [arrVal []])- , ("arr1", "[null]", [arrVal [nullVal]])- , ("arr2", "[null, false]", [arrVal [nullVal, falseVal]])- , ("arr3", "[null, false, true]", [arrVal [nullVal, falseVal, trueVal]])- , ("arrx", "[null,]", [])- , ("str0", "\"\"", [strVal ""])- , ("str1", "\"x\"", [strVal "x"])- , ("str2", "\"xy\"", [strVal "xy"])- , ("str3", "\"xyz\"", [strVal "xyz"])- -- TODO(ejconlon) Refine parser to make this pass- -- , ("str4", "\"xy\\\"z\"", [strVal "xy\"z"])- , ("obj0", "{}", [objVal []])- , ("obj1", "{\"x\": true}", [objVal [("x", trueVal)]])- , ("obj2", "{\"x\": true, \"y\": false}", [objVal [("x", trueVal), ("y", falseVal)]])+ [ ("empty", "", Nothing)+ , ("bad", "bad", Nothing)+ , ("null", "null", Just nullVal)+ , ("true", "true", Just trueVal)+ , ("false", "false", Just falseVal)+ , ("arr0", "[]", Just (arrVal []))+ , ("arr1", "[null]", Just (arrVal [nullVal]))+ , ("arr2", "[null, false]", Just (arrVal [nullVal, falseVal]))+ , ("arr3", "[null, false, true]", Just (arrVal [nullVal, falseVal, trueVal]))+ , ("arrx", "[null,]", Nothing)+ , ("str0", "\"\"", Just (strVal ""))+ , ("str1", "\"x\"", Just (strVal "x"))+ , ("str2", "\"xy\"", Just (strVal "xy"))+ , ("str3", "\"xyz\"", Just (strVal "xyz"))+ , ("str4", "\"xy\\\"z\"", Just (strVal "xy\"z"))+ , ("obj0", "{}", Just (objVal []))+ , ("obj1", "{\"x\": true}", Just (objVal [("x", trueVal)]))+ , ("obj2", "{\"x\": true, \"y\": false}", Just (objVal [("x", trueVal), ("y", falseVal)]))+ , ("num0", "0", Just (numVal (read "0")))+ , ("num1", "123", Just (numVal (read "123")))+ , ("num2", "123.45", Just (numVal (read "123.45")))+ , ("num3", "1e100", Just (numVal (read "1e100")))+ , ("num4", "{\"x\": 1e100, \"y\": 123.45}", Just (objVal [("x", numVal (read "1e100")), ("y", numVal (read "123.45"))])) ] in testJsonTrees cases