packages feed

simple-parser 0.6.0 → 0.7.0

raw patch · 12 files changed

+345/−148 lines, 12 filesdep +errataPVP ok

version bump matches the API change (PVP)

Dependencies added: errata

API changes (from Hackage documentation)

- SimpleParser.Examples.Sexp: AtomFloat :: !Scientific -> Atom
- SimpleParser.LookAhead: DefaultCase :: !Maybe l -> !Seq (MatchMiss l s e) -> ParserT l s e m a -> DefaultCase l s e m a
- SimpleParser.LookAhead: [defaultCaseHandle] :: DefaultCase l s e m a -> !Seq (MatchMiss l s e) -> ParserT l s e m a
- SimpleParser.LookAhead: [defaultCaseLabel] :: DefaultCase l s e m a -> !Maybe l
- SimpleParser.LookAhead: [matchCaseBody] :: MatchCase l s e m a -> !ParserT l s e m a
- SimpleParser.LookAhead: [matchCaseGuard] :: MatchCase l s e m a -> !ParserT l s e m ()
- SimpleParser.LookAhead: data DefaultCase l s e m a
- SimpleParser.LookAhead: 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.LookAhead.MatchMiss l s e)
- SimpleParser.LookAhead: 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.LookAhead.MatchMiss l s e)
- SimpleParser.LookAhead: lookAheadChunk :: (Stream s, Monad m, Eq (Chunk s)) => [(Chunk s, ParserT l s e m a)] -> ParserT l s e m a -> ParserT l s e m a
- SimpleParser.LookAhead: type PureDefaultCase l s e a = DefaultCase l s e Identity a
+ SimpleParser.Common: SignNeg :: Sign
+ SimpleParser.Common: SignPos :: Sign
+ SimpleParser.Common: applySign :: Num a => Maybe Sign -> a -> a
+ SimpleParser.Common: data Sign
+ SimpleParser.Common: instance GHC.Classes.Eq SimpleParser.Common.Sign
+ SimpleParser.Common: instance GHC.Show.Show SimpleParser.Common.Sign
+ SimpleParser.Common: numParser :: (EmbedTextLabel l, Stream s, Token s ~ Char, Monad m) => ParserT l s e m (Either Integer Scientific)
+ SimpleParser.Common: signParser :: (Stream s, Token s ~ Char, Monad m) => ParserT l s e m (Maybe Sign)
+ SimpleParser.Errata: errataParseError :: LinePosExplainable l s e => Style -> FilePath -> ParseError l s e -> Block
+ SimpleParser.Errata: errataParseResult :: LinePosExplainable l s e => Style -> FilePath -> ParseResult l s e a -> [Block]
+ SimpleParser.Errata: type LinePosExplainable l s e = (Explainable l s e, Pos s ~ LinePos)
+ SimpleParser.Examples.Sexp: AtomSci :: !Scientific -> Atom
+ SimpleParser.Examples.Sexp: SexpLabelCustom :: !Text -> SexpLabel
+ SimpleParser.Interactive: ErrorStyleErrata :: ErrorStyle
+ SimpleParser.Interactive: ErrorStyleExplain :: ErrorStyle
+ SimpleParser.Interactive: data ErrorStyle
+ SimpleParser.Interactive: instance GHC.Classes.Eq SimpleParser.Interactive.ErrorStyle
+ SimpleParser.Interactive: instance GHC.Show.Show SimpleParser.Interactive.ErrorStyle
+ SimpleParser.Interactive: parseInteractiveStyle :: (s ~ LinePosStream Text, Explainable l s e, Show a) => ErrorStyle -> Parser l s e a -> String -> IO ()
+ SimpleParser.LookAhead: LookAheadTestEmpty :: LookAheadTestResult l
+ SimpleParser.LookAhead: [matchBlockSelect] :: MatchBlock l s e m a b -> !ParserT l s e m a
+ SimpleParser.LookAhead: [matchCaseChoose] :: MatchCase l s e m a b -> !a -> Bool
+ SimpleParser.LookAhead: [matchCaseHandle] :: MatchCase l s e m a b -> !ParserT l s e m b
+ SimpleParser.LookAhead: lookAheadSimple :: (Monad m, Eq a) => ParserT l s e m a -> ParserT l s e m b -> [(a, ParserT l s e m b)] -> ParserT l s e m b
+ SimpleParser.Result: coerceStreamError :: (Chunk s ~ Chunk t, Token s ~ Token t) => StreamError s -> StreamError t
+ SimpleParser.Result: parseErrorLabels :: ParseError l s e -> Seq l
+ SimpleParser.Stack: bottomUpStack :: (a -> Maybe b) -> Stack a -> Seq b
- SimpleParser.LookAhead: LookAheadTestDefault :: !Maybe l -> LookAheadTestResult l
+ SimpleParser.LookAhead: LookAheadTestDefault :: LookAheadTestResult l
- SimpleParser.LookAhead: MatchBlock :: !DefaultCase l s e m a -> ![MatchCase l s e m a] -> MatchBlock l s e m a
+ SimpleParser.LookAhead: MatchBlock :: !ParserT l s e m a -> !ParserT l s e m b -> ![MatchCase l s e m a b] -> MatchBlock l s e m a b
- SimpleParser.LookAhead: MatchCase :: !Maybe l -> !ParserT l s e m () -> !ParserT l s e m a -> MatchCase l s e m a
+ SimpleParser.LookAhead: MatchCase :: !Maybe l -> !a -> Bool -> !ParserT l s e m b -> MatchCase l s e m a b
- SimpleParser.LookAhead: [matchBlockDefault] :: MatchBlock l s e m a -> !DefaultCase l s e m a
+ SimpleParser.LookAhead: [matchBlockDefault] :: MatchBlock l s e m a b -> !ParserT l s e m b
- SimpleParser.LookAhead: [matchBlockElems] :: MatchBlock l s e m a -> ![MatchCase l s e m a]
+ SimpleParser.LookAhead: [matchBlockElems] :: MatchBlock l s e m a b -> ![MatchCase l s e m a b]
- SimpleParser.LookAhead: [matchCaseLabel] :: MatchCase l s e m a -> !Maybe l
+ SimpleParser.LookAhead: [matchCaseLabel] :: MatchCase l s e m a b -> !Maybe l
- SimpleParser.LookAhead: data MatchBlock l s e m a
+ SimpleParser.LookAhead: data MatchBlock l s e m a b
- SimpleParser.LookAhead: data MatchCase l s e m a
+ SimpleParser.LookAhead: data MatchCase l s e m a b
- SimpleParser.LookAhead: lookAheadMatch :: Monad m => MatchBlock l s e m a -> ParserT l s e m a
+ SimpleParser.LookAhead: lookAheadMatch :: Monad m => MatchBlock l s e m a b -> ParserT l s e m b
- SimpleParser.LookAhead: lookAheadTest :: Monad m => MatchBlock l s e m a -> s -> m (LookAheadTestResult l)
+ SimpleParser.LookAhead: lookAheadTest :: Monad m => MatchBlock l s e m a b -> s -> m (LookAheadTestResult l)
- SimpleParser.LookAhead: pureLookAheadTest :: PureMatchBlock l s e a -> s -> LookAheadTestResult l
+ SimpleParser.LookAhead: pureLookAheadTest :: PureMatchBlock l s e a b -> s -> LookAheadTestResult l
- SimpleParser.LookAhead: type PureMatchBlock l s e a = MatchBlock l s e Identity a
+ SimpleParser.LookAhead: type PureMatchBlock l s e a b = MatchBlock l s e Identity a b
- SimpleParser.LookAhead: type PureMatchCase l s e a = MatchCase l s e Identity a
+ SimpleParser.LookAhead: type PureMatchCase l s e a b = MatchCase l s e Identity a b
- SimpleParser.Parser: lookAheadParser :: Monad m => Maybe l -> ParserT l s e m a -> ParserT l s e m a
+ SimpleParser.Parser: lookAheadParser :: Monad m => ParserT l s e m a -> ParserT l s e m a

Files

simple-parser.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 4839c5f528bb2f3a48f2fbbcee91749ab73716791dc72efa365b56fefc75e6e6+-- hash: 7341b05daadfbc05dcc6e23a6a38993f95e78a160b7942b60b7d39fc93331b6e  name:           simple-parser-version:        0.6.0+version:        0.7.0 synopsis:       Simple parser combinators description:    Please see the README on GitHub at <https://github.com/ejconlon/simple-parser#readme> category:       Parsing@@ -31,6 +31,7 @@       SimpleParser       SimpleParser.Chunked       SimpleParser.Common+      SimpleParser.Errata       SimpleParser.Examples.Json       SimpleParser.Examples.Sexp       SimpleParser.Explain@@ -59,11 +60,13 @@       KindSignatures       MultiParamTypeClasses       StandaloneDeriving+      TupleSections       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     , containers ==0.6.*+    , errata ==0.3.*     , list-t ==1.0.*     , mmorph ==1.1.*     , mtl ==2.2.*@@ -94,11 +97,13 @@       KindSignatures       MultiParamTypeClasses       StandaloneDeriving+      TupleSections       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     , containers ==0.6.*+    , errata ==0.3.*     , list-t ==1.0.*     , mmorph ==1.1.*     , mtl ==2.2.*
src/SimpleParser/Common.hs view
@@ -15,6 +15,10 @@   , decimalParser   , signedNumStartPred   , scientificParser+  , numParser+  , Sign (..)+  , signParser+  , applySign   , signedParser   , escapedStringParser   , spanParser@@ -24,11 +28,13 @@ import Control.Monad (void) import Control.Monad.State (get, gets) import Data.Char (digitToInt, isDigit, isSpace)+import Data.Functor (($>)) 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.Input (dropTokensWhile, dropTokensWhile1, foldTokensWhile, matchToken, peekToken, popToken,+                           takeTokensWhile1) import SimpleParser.Parser (ParserT, defaultParser, greedyStarParser, optionalParser, orParser) import SimpleParser.Stream (PosStream (..), Span (..), Stream (..)) @@ -148,6 +154,38 @@   e <- defaultParser e' (exponentParser e')   pure (Sci.scientific c e) +-- | Parses a number as a literal integer or a 'Scientific' number.+-- Though 'Scientific' can represent integers, this allows you to distinugish integer literals from scientific literals+-- since that information is lost after parsing.+numParser :: (EmbedTextLabel l, Stream s, Token s ~ Char, Monad m) => ParserT l s e m (Either Integer Scientific)+numParser = do+  c' <- decimalParser+  (SP c e', b1) <- defaultParser (SP c' 0, False) (fmap (,True) (dotDecimalParser c'))+  (e, b2) <- defaultParser (e', False) (fmap (,True) (exponentParser e'))+  -- If there is no decimal or exponent, return this as an integer+  -- Otherwise return as scientific, which may be float or exponentiated integer+  if not b1 && not b2+    then pure (Left c')+    else pure (Right (Sci.scientific c e))++data Sign = SignPos | SignNeg deriving (Eq, Show)++-- | Consumes an optional + or - representing the sign of a number.+signParser :: (Stream s, Token s ~ Char, Monad m) => ParserT l s e m (Maybe Sign)+signParser = do+  mc <- peekToken+  case mc of+    Just '+' -> popToken $> Just SignPos+    Just '-' -> popToken $> Just SignNeg+    _ -> pure Nothing++-- | Optionally negate the number according to the sign (treating 'Nothing' as positive sign).+applySign :: Num a => Maybe Sign -> a -> a+applySign ms n =+  case ms of+    Just SignNeg -> negate n+    _ -> n+ -- | 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) =>@@ -157,8 +195,10 @@   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 '-')+signedParser spc p = do+  ms <- signParser+  spc+  fmap (applySign ms) p  data Pair = Pair ![Char] !Bool 
+ src/SimpleParser/Errata.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE OverloadedStrings #-}++module SimpleParser.Errata+  ( LinePosExplainable+  , errataParseError+  , errataParseResult+  ) where++import Control.Monad (join)+import Data.Foldable (toList)+import Data.Sequence (Seq (..))+import qualified Data.Text as T+import Errata (Block, Style, blockMerged')+import SimpleParser.Explain (ErrorExplanation (..), Explainable, ParseErrorExplanation (..), explainParseError)+import SimpleParser.Result (ParseError, ParseResult (..))+import SimpleParser.Stream (Col (..), Line (..), LinePos (..), Pos, Span (..))++type LinePosExplainable l s e = (Explainable l s e, Pos s ~ LinePos)++errataExplanation :: Style -> FilePath -> ParseErrorExplanation LinePos -> Block+errataExplanation style fp (ParseErrorExplanation sp context mayDetails  (ErrorExplanation reason mayExpected mayActual)) =+  let Span (LinePos _ (Line startLine) (Col startCol)) (LinePos _ (Line endLine) (Col endCol)) = sp+      mayLabel = Just reason+      mayHeader =+       case context of+         Empty -> Nothing+         _ -> Just (T.intercalate " > " (toList context))+      start = (startLine + 1, startCol + 1, mayLabel)+      end = (endLine + 1, endCol + 1, Nothing)+      mayBody =+        case (mayDetails, mayExpected, mayActual) of+          (Nothing, Nothing, Nothing) -> Nothing+          _ -> Just $ T.unlines $ join+            [ maybe [] (\de -> ["[Details ] " <> de]) mayDetails+            , maybe [] (\ex -> ["[Expected] " <> ex]) mayExpected+            , maybe [] (\ac -> ["[Actual  ] " <> ac]) mayActual+            ]+  in blockMerged' style fp mayHeader start end mayLabel mayBody++errataParseError :: LinePosExplainable l s e => Style -> FilePath -> ParseError l s e -> Block+errataParseError style fp pe =+  let pee = explainParseError pe+  in errataExplanation style fp pee++errataParseResult :: LinePosExplainable l s e => Style -> FilePath -> ParseResult l s e a -> [Block]+errataParseResult style fp pr =+  case pr of+    ParseResultError errs -> fmap (errataParseError style fp) (toList errs)+    ParseResultSuccess _ -> []
src/SimpleParser/Examples/Json.hs view
@@ -14,10 +14,9 @@ import Data.Sequence (Seq) import Data.Text (Text) import Data.Void (Void)-import SimpleParser (DefaultCase (..), MatchBlock (..), MatchCase (..), Parser, PureMatchBlock, Stream (..), TextLabel,-                     TextualChunked (..), TextualStream, betweenParser, escapedStringParser, lexemeParser,-                     lookAheadMatch, matchChunk, matchToken, orParser, satisfyToken, scientificParser, sepByParser,-                     signedNumStartPred, spaceParser)+import SimpleParser (MatchBlock (..), MatchCase (..), Parser, Stream (..), TextLabel, TextualChunked (..),+                     TextualStream, anyToken, betweenParser, escapedStringParser, lexemeParser, lookAheadMatch,+                     matchChunk, matchToken, orParser, scientificParser, sepByParser, signedNumStartPred, spaceParser)  data JsonF a =     JsonObject !(Seq (Text, a))@@ -32,8 +31,6 @@  type JsonParserC s = (TextualStream s, Eq (Chunk s)) -type JsonParserB s a = PureMatchBlock TextLabel s Void a- type JsonParserM s a = Parser TextLabel s Void a  jsonParser :: JsonParserC s => JsonParserM s Json@@ -42,27 +39,28 @@ isBoolStartPred :: Char -> Bool isBoolStartPred c = c == 't' || c == 'f' -recJsonB :: JsonParserC s => JsonParserM s a -> JsonParserB s (JsonF a)-recJsonB root = MatchBlock (DefaultCase Nothing (const (fail "failed to parse json document")))-  [ MatchCase Nothing openBraceP (objectP (objectPairP root))-  , MatchCase Nothing openBracketP (arrayP root)-  , MatchCase Nothing openQuoteP stringP-  , MatchCase Nothing (void (satisfyToken Nothing signedNumStartPred)) numP-  , MatchCase Nothing (void (satisfyToken Nothing isBoolStartPred)) boolP-  , MatchCase Nothing (void (matchToken 'n')) nullP-  ]- recJsonParser :: JsonParserC s => JsonParserM s a -> JsonParserM s (JsonF a)-recJsonParser root = lookAheadMatch (recJsonB root)+recJsonParser root = lexP (lookAheadMatch block) where+  block = MatchBlock anyToken (fail "failed to parse json document")+    [ MatchCase Nothing (== '{') (objectP (objectPairP root))+    , MatchCase Nothing (== '[') (arrayP root)+    , MatchCase Nothing (== '"') stringP+    , MatchCase Nothing signedNumStartPred numP+    , MatchCase Nothing isBoolStartPred boolP+    , MatchCase Nothing (== 'n') nullP+    ]  spaceP :: JsonParserC s => JsonParserM s () spaceP = spaceParser +lexP :: JsonParserC s => JsonParserM s a -> JsonParserM s a+lexP = lexemeParser spaceP+ tokL :: JsonParserC s => Char -> JsonParserM s ()-tokL c = lexemeParser spaceP (void (matchToken c))+tokL c = lexP (void (matchToken c))  chunkL :: JsonParserC s => Text -> JsonParserM s ()-chunkL cs = lexemeParser spaceP (void (matchChunk (unpackChunk cs)))+chunkL cs = lexP (void (matchChunk (unpackChunk cs)))  openBraceP, closeBraceP, commaP, colonP, openBracketP, closeBracketP, closeQuoteP :: JsonParserC s => JsonParserM s () openBraceP = tokL '{'
src/SimpleParser/Examples/Sexp.hs view
@@ -11,23 +11,24 @@   , recSexpParser   ) where +import Control.Applicative (empty) import Control.Monad (void) import Data.Char (isDigit, isSpace) import Data.Scientific (Scientific) import Data.Sequence (Seq) import Data.Text (Text)+import qualified Data.Text as T import Data.Void (Void)-import SimpleParser (Chunked (..), DefaultCase (..), EmbedTextLabel (..), ExplainLabel (..), MatchBlock (..),-                     MatchCase (..), Parser, PureMatchBlock, TextLabel, TextualStream, betweenParser, commitParser,-                     decimalParser, escapedStringParser, lexemeParser, lookAheadMatch, matchToken, onEmptyParser,-                     orParser, packChunk, satisfyToken, scientificParser, sepByParser, signedNumStartPred, signedParser,-                     spaceParser, takeTokensWhile)+import SimpleParser (Chunked (..), EmbedTextLabel (..), ExplainLabel (..), MatchBlock (..), MatchCase (..), Parser,+                     TextLabel, TextualStream, anyToken, applySign, betweenParser, escapedStringParser, lexemeParser,+                     lookAheadMatch, matchToken, numParser, packChunk, popChunk, satisfyToken, sepByParser, signParser,+                     signedNumStartPred, spaceParser, takeTokensWhile)  data Atom =     AtomIdent !Text   | AtomString !Text   | AtomInt !Integer-  | AtomFloat !Scientific+  | AtomSci !Scientific   deriving (Eq, Show)  data SexpF a =@@ -38,6 +39,7 @@ data SexpLabel =     SexpLabelIdentStart   | SexpLabelEmbedText !TextLabel+  | SexpLabelCustom !Text   deriving (Eq, Show)  instance ExplainLabel SexpLabel where@@ -45,6 +47,7 @@     case sl of       SexpLabelIdentStart -> "start of identifier"       SexpLabelEmbedText tl -> explainLabel tl+      _ -> undefined  instance EmbedTextLabel SexpLabel where   embedTextLabel = SexpLabelEmbedText@@ -54,17 +57,16 @@  type SexpParserC s = TextualStream s -type SexpParserB s a = PureMatchBlock SexpLabel s Void a- 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+recSexpParser root = lookAheadMatch block where+  block = MatchBlock anyToken (fmap SexpAtom atomP)+    [ MatchCase Nothing (== '(') (fmap SexpList (listP root))+    ]  nonDelimPred :: Char -> Bool nonDelimPred c = c /= '(' && c /= ')' && not (isSpace c)@@ -96,22 +98,42 @@ closeParenP :: SexpParserC s => SexpParserM s () closeParenP = lexP (void (matchToken ')')) -intP :: SexpParserC s => SexpParserM s Integer-intP = signedParser (pure ()) decimalParser+numAtomP :: SexpParserC s => SexpParserM s Atom+numAtomP = do+  ms <- signParser+  n <- numParser+  case n of+    Left i -> pure (AtomInt (applySign ms i))+    Right s -> pure (AtomSci (applySign ms s)) -floatP :: SexpParserC s => SexpParserM s Scientific-floatP = signedParser (pure ()) scientificParser+chunk1 :: SexpParserC s => SexpParserM s Text+chunk1 = do+  mc <- popChunk 2+  case mc of+    Just c | not (chunkEmpty c) -> pure (packChunk c)+    _ -> empty -atomB :: SexpParserC s => SexpParserB s Atom-atomB = MatchBlock (DefaultCase Nothing (const (fail "failed to parse sexp atom")))-  [ MatchCase Nothing (void (matchToken '"')) (fmap AtomString stringP)-  , MatchCase Nothing (void (satisfyToken Nothing signedNumStartPred)) (fmap AtomInt intP)-  , MatchCase Nothing (void (satisfyToken Nothing signedNumStartPred)) (fmap AtomFloat floatP)-  , MatchCase Nothing (void (satisfyToken Nothing identStartPred)) (fmap AtomIdent identifierP)-  ]+unaryIdentPred :: Char -> Text -> Bool+unaryIdentPred u t0 =+  case T.uncons t0 of+    Just (c0, t1) | u == c0 ->+      case T.uncons t1 of+        Just (c1, _) -> not (isDigit c1)+        Nothing -> True+    _ -> False +identAtomP :: SexpParserC s => SexpParserM s Atom+identAtomP = fmap AtomIdent identifierP+ atomP :: SexpParserC s => SexpParserM s Atom-atomP = lexP (lookAheadMatch atomB)+atomP = lexP (lookAheadMatch block) where+  block = MatchBlock chunk1 (fail "failed to parse sexp atom")+    [ MatchCase Nothing ((== '"') . T.head) (fmap AtomString stringP)+    , MatchCase Nothing (unaryIdentPred '+') identAtomP+    , MatchCase Nothing (unaryIdentPred '-') identAtomP+    , MatchCase Nothing (signedNumStartPred . T.head) numAtomP+    , MatchCase Nothing (identStartPred . T.head) identAtomP+    ]  listP :: SexpParserC s => SexpParserM s a -> SexpParserM s (Seq a) listP root = lexP (betweenParser openParenP closeParenP (sepByParser root spaceP))
src/SimpleParser/Explain.hs view
@@ -140,8 +140,8 @@ buildParseErrorExplanation :: ParseErrorExplanation LinePos -> Builder buildParseErrorExplanation (ParseErrorExplanation sp context mayDetails errExp) =   let hd = join-        [ ["[Pos       ] " <> buildSpan sp]-        , ["[Context   ] || " <> TB.intercalate " |> " (fmap TB.text (toList context)) | not (Seq.null context)]+        [ ["[Pos     ] " <> buildSpan sp]+        , ["[Context ] " <> TB.intercalate " > " (fmap TB.text (toList context)) | not (Seq.null context)]         ]       tl = buildErrorExplanation (fmap TB.text mayDetails) errExp   in TB.intercalate "\n" (hd ++ tl)
src/SimpleParser/Interactive.hs view
@@ -1,13 +1,18 @@ {-# LANGUAGE OverloadedStrings #-}  module SimpleParser.Interactive-  ( parseInteractive+  ( ErrorStyle (..)+  , parseInteractiveStyle+  , parseInteractive   ) where  import Data.Foldable (toList) import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.IO as TIO+import qualified Data.Text.Lazy.IO as TLIO+import Errata (Errata (..), fancyStyle, prettyErrors)+import SimpleParser.Errata (errataParseError) import SimpleParser.Explain (Explainable, buildAllParseErrorExplanations, explainParseError) import SimpleParser.Input (matchEnd) import SimpleParser.Parser (Parser, runParser)@@ -15,13 +20,28 @@ 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 =+data ErrorStyle =+    ErrorStyleErrata+  | ErrorStyleExplain+  deriving stock (Eq, Show)++parseInteractiveStyle :: (s ~ LinePosStream Text, Explainable l s e, Show a) => ErrorStyle -> Parser l s e a -> String -> IO ()+parseInteractiveStyle errStyle 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))+      case errStyle of+        ErrorStyleErrata ->+          let blocks = fmap (errataParseError fancyStyle "<interactive>") (toList es)+              errata = Errata Nothing blocks Nothing+              pretty = prettyErrors input [errata]+          in TLIO.putStrLn pretty+        ErrorStyleExplain ->+          let b = buildAllParseErrorExplanations (fmap explainParseError (toList es))+          in TIO.putStrLn (TB.run ("Errors:\n" <> b))     Just (ParseResultSuccess (ParseSuccess _ a)) ->       putStrLn "Success:" *> print a++parseInteractive :: (s ~ LinePosStream Text, Explainable l s e, Show a) => Parser l s e a -> String -> IO ()+parseInteractive = parseInteractiveStyle ErrorStyleErrata
src/SimpleParser/LookAhead.hs view
@@ -3,8 +3,6 @@ module SimpleParser.LookAhead   ( MatchCase (..)   , PureMatchCase-  , DefaultCase (..)-  , PureDefaultCase   , MatchBlock (..)   , PureMatchBlock   , lookAheadMatch@@ -12,59 +10,40 @@   , LookAheadTestResult (..)   , lookAheadTest   , pureLookAheadTest-  , lookAheadChunk+  , lookAheadSimple   ) where -import Control.Monad (void) import Control.Monad.Identity (Identity (runIdentity)) import Data.Sequence (Seq (..)) import Data.Sequence.NonEmpty (NESeq) import qualified Data.Sequence.NonEmpty as NESeq-import SimpleParser.Input (matchChunk)-import SimpleParser.Parser (ParserT (..), markParser)-import SimpleParser.Result (ParseError, ParseResult (..))-import SimpleParser.Stream (Stream (..))+import SimpleParser.Parser (ParserT (..), lookAheadParser, markParser)+import SimpleParser.Result (ParseResult (..), ParseSuccess (..)) -data MatchCase l s e m a = MatchCase+data MatchCase l s e m a b = MatchCase   { matchCaseLabel :: !(Maybe l)-  , matchCaseGuard :: !(ParserT l s e m ())-  , matchCaseBody :: !(ParserT l s e m a)-  }--type PureMatchCase l s e a = MatchCase l s e Identity a--data MatchMiss l s e = MatchMiss-  { matchMissLabel :: !(Maybe l)-  , matchMissErrors :: !(Maybe (NESeq (ParseError l s e)))-  }--deriving instance (Eq l, Eq s, Eq (Token s), Eq (Chunk s), Eq e) => Eq (MatchMiss l s e)-deriving instance (Show l, Show s, Show (Token s), Show (Chunk s), Show e) => Show (MatchMiss l s e)--data DefaultCase l s e m a = DefaultCase-  { defaultCaseLabel :: !(Maybe l)-  , defaultCaseHandle :: !(Seq (MatchMiss l s e) -> ParserT l s e m a)+  , matchCaseChoose :: !(a -> Bool)+  , matchCaseHandle :: !(ParserT l s e m b)   } -type PureDefaultCase l s e a = DefaultCase l s e Identity a+type PureMatchCase l s e a b = MatchCase l s e Identity a b -data MatchBlock l s e m a = MatchBlock-  { matchBlockDefault :: !(DefaultCase l s e m a)-  , matchBlockElems :: ![MatchCase l s e m a]+data MatchBlock l s e m a b = MatchBlock+  { matchBlockSelect :: !(ParserT l s e m a)+  , matchBlockDefault :: !(ParserT l s e m b)+  , matchBlockElems :: ![MatchCase l s e m a b]   } -type PureMatchBlock l s e a = MatchBlock l s e Identity a+type PureMatchBlock l s e a b = MatchBlock l s e Identity a b  -- | Parse with look-ahead for each case and follow the first that matches (or follow the default if none do).-lookAheadMatch :: Monad m => MatchBlock l s e m a -> ParserT l s e m a-lookAheadMatch (MatchBlock (DefaultCase dcl dch) mcs) = ParserT (go Empty mcs) where-  go !macc [] s = runParserT (markParser dcl (dch macc)) s-  go !macc ((MatchCase mcl mcg mcb):mcs') s = do-    mres <- runParserT mcg s-    case mres of-      Nothing -> go (macc :|> MatchMiss mcl Nothing) mcs' s-      Just (ParseResultError es) -> go (macc :|> MatchMiss mcl (Just es)) mcs' s-      Just (ParseResultSuccess _) -> runParserT (markParser mcl mcb) s+lookAheadMatch :: Monad m => MatchBlock l s e m a b -> ParserT l s e m b+lookAheadMatch (MatchBlock sel dc mcs) = lookAheadParser sel >>= go mcs where+  go [] _ = dc+  go ((MatchCase mcl mcg mch):mcs') val =+    if mcg val+      then markParser mcl mch+      else go mcs' val  data MatchPos l = MatchPos   { matchPosIndex :: !Int@@ -72,26 +51,29 @@   } deriving stock (Eq, Show)  data LookAheadTestResult l =-    LookAheadTestDefault !(Maybe l)+    LookAheadTestEmpty+  | LookAheadTestDefault   | LookAheadTestMatches !(NESeq (MatchPos l))   deriving stock (Eq, Show)  -- | Test which branches match the look-ahead. Useful to assert that your parser makes exclusive choices.-lookAheadTest :: Monad m => MatchBlock l s e m a -> s -> m (LookAheadTestResult l)-lookAheadTest (MatchBlock (DefaultCase dcl _) mcs) = go Empty 0 mcs where-  go !acc _ [] _ =-    case NESeq.nonEmptySeq acc of-      Nothing -> pure (LookAheadTestDefault dcl)-      Just ms -> pure (LookAheadTestMatches ms)-  go !acc !i ((MatchCase mcl mcg _):mcs') s = do-    mres <- runParserT mcg s+lookAheadTest :: Monad m => MatchBlock l s e m a b -> s -> m (LookAheadTestResult l)+lookAheadTest (MatchBlock sel _ mcs) = go1 where+  go1 s = do+    mres <- runParserT sel s     case mres of-      Just (ParseResultSuccess _) -> go (acc :|> MatchPos i mcl) (i + 1) mcs' s-      _ -> go acc (i + 1) mcs' s+      Just (ParseResultSuccess (ParseSuccess _ val)) -> pure (go2 Empty 0 mcs val)+      _ -> pure LookAheadTestEmpty+  go2 !acc _ [] _ = maybe LookAheadTestDefault LookAheadTestMatches (NESeq.nonEmptySeq acc)+  go2 !acc !i ((MatchCase mcl mcg _):mcs') val =+    if mcg val+      then go2 (acc :|> MatchPos i mcl) (i + 1) mcs' val+      else go2 acc (i + 1) mcs' val -pureLookAheadTest :: PureMatchBlock l s e a -> s -> LookAheadTestResult l+pureLookAheadTest :: PureMatchBlock l s e a b -> s -> LookAheadTestResult l pureLookAheadTest mb = runIdentity . lookAheadTest mb --- | Simple look-ahead that matches by chunk.-lookAheadChunk :: (Stream s, Monad m, Eq (Chunk s)) => [(Chunk s, ParserT l s e m a)] -> ParserT l s e m a -> ParserT l s e m a-lookAheadChunk ps d = lookAheadMatch (MatchBlock (DefaultCase Nothing (const d)) (fmap (\(c, p) -> MatchCase Nothing (void (matchChunk c)) p) ps))+-- | Simple look-ahead that selects a parser based on first equal prefix.+lookAheadSimple :: (Monad m, Eq a) => ParserT l s e m a -> ParserT l s e m b -> [(a, ParserT l s e m b)] -> ParserT l s e m b+lookAheadSimple sel dc pairs = lookAheadMatch (MatchBlock sel dc mcs) where+  mcs = [MatchCase Nothing (== x) p | (x, p) <- pairs]
src/SimpleParser/Parser.hs view
@@ -242,30 +242,31 @@ -- | Run the parser speculatively and return results. Does not advance state or throw errors. reflectParser :: Monad m => ParserT l s e m a -> ParserT l s e m (Maybe (ParseResult l s e a)) reflectParser parser = ParserT go where-  go s0 = do-    mres <- runParserT parser s0-    pure (Just (ParseResultSuccess (ParseSuccess s0 mres)))+  go s = do+    mres <- runParserT parser s+    pure (Just (ParseResultSuccess (ParseSuccess s mres)))  -- | 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+silenceParser parser = ParserT (fmap go . runParserT parser) where+  go mres =+    case mres of+      Just (ParseResultSuccess _) -> mres+      _ -> Nothing --- | Yield the results of the given parser, but rewind back to the starting state in ALL cases (success and error).+-- | 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 => 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+lookAheadParser :: Monad m => ParserT l s e m a -> ParserT l s e m a+lookAheadParser parser = ParserT (\s -> fmap (fmap (go s)) (runParserT parser s)) where   go s res =     case res of-      ParseResultError es -> ParseResultError (fmap (markParseError (Mark ml s)) es)+      ParseResultError es -> ParseResultError es       ParseResultSuccess (ParseSuccess _ a) -> ParseResultSuccess (ParseSuccess s a) --- | Yield the results of the given parser, but rewind back to the starting state on error ONLY.+-- | Push the label and current state onto the parse error mark stack.+-- Useful to delimit named sub-spans for error reporting. 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 =@@ -290,9 +291,9 @@       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.+-- | If the first parser succeeds in the initial state, yield results from the second parser in the initial+-- state. This is likely the look-ahead you want, since errors from the first parser are completely ignored.+-- 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
src/SimpleParser/Result.hs view
@@ -3,10 +3,12 @@ module SimpleParser.Result   ( RawError (..)   , StreamError (..)+  , coerceStreamError   , CompoundError (..)   , Mark (..)   , ParseError (..)   , parseErrorResume+  , parseErrorLabels   , markParseError   , unmarkParseError   , parseErrorEnclosingLabels@@ -20,7 +22,7 @@ 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.Stack (Stack (..), bottomStack, bottomUpStack, emptyStack, pushStack, topStack) import SimpleParser.Stream (PosStream (..), Span (..), Stream (..))  data RawError chunk token =@@ -43,6 +45,9 @@ deriving instance (Eq (Token s), Eq (Chunk s)) => Eq (StreamError s) deriving instance (Show (Token s), Show (Chunk s)) => Show (StreamError s) +coerceStreamError :: (Chunk s ~ Chunk t, Token s ~ Token t) => StreamError s -> StreamError t+coerceStreamError = StreamError . unStreamError+ data CompoundError s e =     CompoundErrorStream !(StreamError s)   | CompoundErrorFail !Text@@ -59,6 +64,10 @@  type MarkStack l s = Stack (Mark l s) +-- | Returns a sequence of labels from most general to most specific.+markStackLabels :: MarkStack l s -> Seq l+markStackLabels = bottomUpStack markLabel+ data ParseError l s e = ParseError   { peMarkStack :: !(MarkStack l s)   , peEndState :: !s@@ -70,6 +79,10 @@ parseErrorResume :: ParseError l s e -> s parseErrorResume pe = maybe (peEndState pe) markState (topStack (peMarkStack pe)) +-- | Returns the sequence of ALL labels from coarsest to finest.+parseErrorLabels :: ParseError l s e -> Seq l+parseErrorLabels = markStackLabels . peMarkStack+ -- | 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) }@@ -85,6 +98,7 @@   (ml, startPos) = maybe (Nothing, endPos) (\(Mark mx s) -> (mx, streamViewPos s)) (bottomStack (peMarkStack pe))  -- | Returns labels enclosing the narrowest span, from coarsest to finest+-- Does NOT include the label for the narrowest span (if any). parseErrorEnclosingLabels :: ParseError l s e -> Seq l parseErrorEnclosingLabels pe =   case unStack (peMarkStack pe) of
src/SimpleParser/Stack.hs view
@@ -4,6 +4,7 @@   , pushStack   , topStack   , bottomStack+  , bottomUpStack   ) where  import Data.Sequence (Seq (..))@@ -36,3 +37,13 @@   case s of     Empty -> Nothing     a :<| _ -> Just a++-- | Selects elements from the bottom of the stack to the top.+bottomUpStack :: (a -> Maybe b) -> Stack a -> Seq b+bottomUpStack f = go Empty . unStack where+  go !acc s =+    case s of+      Empty -> acc+      a :<| s' ->+        let acc' = maybe acc (acc :|>) (f a)+        in go acc' s'
test/Main.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE NegativeLiterals #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} @@ -6,6 +7,7 @@ import Control.Monad (void) import Data.Foldable (asum) import Data.Functor (($>))+import Data.Sequence (Seq (..)) import qualified Data.Sequence as Seq import qualified Data.Sequence.NonEmpty as NESeq import Data.String (IsString)@@ -13,6 +15,7 @@ import qualified Data.Text as T import SimpleParser import SimpleParser.Examples.Json (Json (..), JsonF (..), jsonParser)+import SimpleParser.Examples.Sexp (Atom (..), Sexp (..), SexpF (..), sexpParser) import Test.Tasty (TestName, TestTree, testGroup) import Test.Tasty.HUnit (testCase, (@?=)) import Test.Tasty.TH (defaultMainGenerator)@@ -23,7 +26,7 @@  type TestState = OffsetStream Text -type TestBlock a = PureMatchBlock Label TestState Error a+type TestBlock a b = PureMatchBlock Label TestState Error a b  type TestParser a = Parser Label TestState Error a @@ -35,7 +38,7 @@  data ParserCase a = ParserCase !TestName !(TestParser a) !Text !(Maybe (TestResult a)) -data ExamineCase a = ExamineCase !TestName !(TestBlock a) !Text !(LookAheadTestResult Label)+data ExamineCase a b = ExamineCase !TestName !(TestBlock a b) !Text !(LookAheadTestResult Label)  fwd :: Int -> TestState -> TestState fwd n (OffsetStream (Offset i) t) =@@ -86,7 +89,7 @@   let actual = runParser parser (newOffsetStream input)   actual @?= expected -testExamineCase :: ExamineCase a -> TestTree+testExamineCase :: ExamineCase a b -> TestTree testExamineCase (ExamineCase name block input expected) = testCase name $ do   let actual = pureLookAheadTest block (newOffsetStream input)   actual @?= expected@@ -461,7 +464,7 @@  test_look_ahead_pure :: [TestTree] test_look_ahead_pure =-  let parser = lookAheadParser Nothing (pure 1) :: TestParser Int+  let parser = lookAheadParser (pure 1) :: TestParser Int       cases =         [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") 1)         , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 0 "hi") 1)@@ -470,9 +473,9 @@  test_look_ahead_success :: [TestTree] test_look_ahead_success =-  let parser = lookAheadParser Nothing anyToken+  let parser = lookAheadParser anyToken       cases =-        [ ParserCase "non-match empty" parser "" (errRes [markWith (OffsetStream 0 "") (anyTokErr (OffsetStream 0 ""))])+        [ ParserCase "non-match empty" parser "" (errRes [anyTokErr (OffsetStream 0 "")])         , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 0 "hi") 'h')         ]   in fmap testParserCase cases@@ -480,10 +483,10 @@ test_look_ahead_failure :: [TestTree] test_look_ahead_failure =   let err = Error "boo"-      parser = lookAheadParser Nothing (anyToken *> throwParser err) :: TestParser Char+      parser = lookAheadParser (anyToken *> throwParser err) :: TestParser Char       cases =-        [ 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)])+        [ ParserCase "non-match empty" parser "" (errRes [anyTokErr (OffsetStream 0 "")])+        , ParserCase "non-empty" parser "hi" (errRes [custErr (OffsetStream 1 "i") err])         ]   in fmap testParserCase cases @@ -545,19 +548,19 @@         ]   in fmap testParserCase cases -simpleBlock :: TestBlock Text-simpleBlock = MatchBlock (DefaultCase (Just (Label "default")) (\misses -> pure ("n misses: " <> T.pack (show (Seq.length misses)))))-  [ MatchCase (Just (Label "match x")) (void (matchToken 'x')) (anyToken $> "found x - consuming")-  , MatchCase (Just (Label "match x dupe")) (void (matchToken 'x')) (pure "dupe x - leaving")-  , MatchCase (Just (Label "match y")) (void (matchToken 'y')) (pure "found y - leaving")+simpleBlock :: TestBlock Char Text+simpleBlock = MatchBlock anyToken (pure "default")+  [ MatchCase (Just (Label "match x")) (== 'x') (anyToken $> "found x - consuming")+  , MatchCase (Just (Label "match x dupe")) (== 'x') (pure "dupe x - leaving")+  , MatchCase (Just (Label "match y")) (== 'y') (pure "found y - leaving")   ]  test_look_ahead_match :: [TestTree] test_look_ahead_match =   let parser = lookAheadMatch simpleBlock       cases =-        [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") "n misses: 3")-        , ParserCase "non-match" parser "wz" (sucRes (OffsetStream 0 "wz") "n misses: 3")+        [ ParserCase "empty" parser "" (errRes [anyTokErr (OffsetStream 0 "")])+        , ParserCase "non-match" parser "wz" (sucRes (OffsetStream 0 "wz") "default")         , ParserCase "match x" parser "xz" (sucRes (OffsetStream 1 "z") "found x - consuming")         , ParserCase "match y" parser "yz" (sucRes (OffsetStream 0 "yz") "found y - leaving")         ]@@ -568,8 +571,8 @@   let xpositions = [MatchPos 0 (Just (Label "match x")), MatchPos 1 (Just (Label "match x dupe"))]       ypositions = [MatchPos 2 (Just (Label "match y"))]       cases =-        [ ExamineCase "empty" simpleBlock "" (LookAheadTestDefault (Just (Label "default")))-        , ExamineCase "non-match" simpleBlock "wz" (LookAheadTestDefault (Just (Label "default")))+        [ ExamineCase "empty" simpleBlock "" LookAheadTestEmpty+        , ExamineCase "non-match" simpleBlock "wz" LookAheadTestDefault         , ExamineCase "match x" simpleBlock "xz" (LookAheadTestMatches (NESeq.unsafeFromSeq (Seq.fromList xpositions)))         , ExamineCase "match y" simpleBlock "yz" (LookAheadTestMatches (NESeq.unsafeFromSeq (Seq.fromList ypositions)))         ]@@ -620,13 +623,65 @@         , ("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")))+        , ("num0", "0", Just (numVal 0))+        , ("num1", "123", Just (numVal 123))+        , ("num2", "123.45", Just (numVal 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"))]))+        , ("num4", "{\"x\": 1e100, \"y\": 123.45}", Just (objVal [("x", numVal (read "1e100")), ("y", numVal 123.45)]))         ]   in testJsonTrees cases++type SexpResult = Maybe Sexp++testSexpCase :: TestName -> Text -> SexpResult -> TestTree+testSexpCase name str expected = testCase ("sexp " <> name) $ do+  let actual = parseSexp str+  actual @?= expected++testSexpTrees :: [(TestName, Text, SexpResult)] -> [TestTree]+testSexpTrees = fmap (\(n, s, e) -> testSexpCase n s e)++parseSexp :: Text -> SexpResult+parseSexp str =+  let p = sexpParser <* matchEnd+  in case runParser p str of+    Just (ParseResultSuccess (ParseSuccess _ a)) -> Just a+    _ -> Nothing++test_sexp :: [TestTree]+test_sexp =+  let numSexp = Sexp (SexpAtom (AtomInt 1))+      sciExpSexp = Sexp (SexpAtom (AtomSci 1))+      identSexp = Sexp (SexpAtom (AtomIdent "abc"))+      stringSexp = Sexp (SexpAtom (AtomString "xyz"))+      sciSexp = Sexp (SexpAtom (AtomSci 3.14))+      emptyList = Sexp (SexpList Empty)+      singletonList = Sexp (SexpList (Seq.singleton numSexp))+      pairList = Sexp (SexpList (Seq.fromList [numSexp, numSexp]))+      cases =+        [ ("empty", "", Nothing)+        , ("empty list", "()", Just emptyList)+        , ("singleton list", "(1)", Just singletonList)+        , ("singleton empty list", "(())", Just (Sexp (SexpList (Seq.fromList [emptyList]))))+        , ("singleton nested list", "((1))", Just (Sexp (SexpList (Seq.fromList [singletonList]))))+        , ("num", "1", Just numSexp)+        , ("num pos", "+1", Just numSexp)+        , ("num neg", "-1", Just (Sexp (SexpAtom (AtomInt -1))))+        , ("ident", "abc", Just identSexp)+        , ("string", "\"xyz\"", Just stringSexp)+        , ("sci", "3.14", Just sciSexp)+        , ("sci pos", "+3.14", Just sciSexp)+        , ("sci neg", "-3.14", Just (Sexp (SexpAtom (AtomSci -3.14))))+        , ("sci exp", "1e0", Just sciExpSexp)+        , ("sci pos exp", "+1e0", Just sciExpSexp)+        , ("sci dec exp", "1.0", Just sciExpSexp)+        , ("sci dec exp 2", "1.0e0", Just sciExpSexp)+        , ("plus", "+", Just (Sexp (SexpAtom (AtomIdent "+"))))+        , ("minus", "-", Just (Sexp (SexpAtom (AtomIdent "-"))))+        , ("multi list", "(1 abc \"xyz\" 3.14)", Just (Sexp (SexpList (Seq.fromList [numSexp, identSexp, stringSexp, sciSexp]))))+        , ("pair nested list", "((1 1) (1 1))", Just (Sexp (SexpList (Seq.fromList [pairList, pairList]))))+        ]+  in testSexpTrees cases  main :: IO () main = $(defaultMainGenerator)