packages feed

simple-parser 0.5.0 → 0.6.0

raw patch · 12 files changed

+225/−57 lines, 12 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ SimpleParser.LookAhead: DefaultCase :: !Maybe l -> !Seq (MatchMiss l s e) -> ParserT l s e m a -> DefaultCase l s e m a
+ SimpleParser.LookAhead: LookAheadTestDefault :: !Maybe l -> LookAheadTestResult l
+ SimpleParser.LookAhead: LookAheadTestMatches :: !NESeq (MatchPos l) -> 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: MatchCase :: !Maybe l -> !ParserT l s e m () -> !ParserT l s e m a -> MatchCase l s e m a
+ SimpleParser.LookAhead: MatchPos :: !Int -> !Maybe l -> MatchPos l
+ 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: [matchBlockDefault] :: MatchBlock l s e m a -> !DefaultCase l s e m a
+ SimpleParser.LookAhead: [matchBlockElems] :: MatchBlock l s e m a -> ![MatchCase l s e m a]
+ 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: [matchCaseLabel] :: MatchCase l s e m a -> !Maybe l
+ SimpleParser.LookAhead: [matchPosIndex] :: MatchPos l -> !Int
+ SimpleParser.LookAhead: [matchPosLabel] :: MatchPos l -> !Maybe l
+ SimpleParser.LookAhead: data DefaultCase l s e m a
+ SimpleParser.LookAhead: data LookAheadTestResult l
+ SimpleParser.LookAhead: data MatchBlock l s e m a
+ SimpleParser.LookAhead: data MatchCase l s e m a
+ SimpleParser.LookAhead: data MatchPos l
+ 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: instance GHC.Classes.Eq l => GHC.Classes.Eq (SimpleParser.LookAhead.LookAheadTestResult l)
+ SimpleParser.LookAhead: instance GHC.Classes.Eq l => GHC.Classes.Eq (SimpleParser.LookAhead.MatchPos l)
+ SimpleParser.LookAhead: instance GHC.Show.Show l => GHC.Show.Show (SimpleParser.LookAhead.LookAheadTestResult l)
+ SimpleParser.LookAhead: instance GHC.Show.Show l => GHC.Show.Show (SimpleParser.LookAhead.MatchPos l)
+ 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: lookAheadMatch :: Monad m => MatchBlock l s e m a -> ParserT l s e m a
+ SimpleParser.LookAhead: lookAheadTest :: Monad m => MatchBlock l s e m a -> s -> m (LookAheadTestResult l)
+ SimpleParser.LookAhead: pureLookAheadTest :: PureMatchBlock l s e a -> s -> LookAheadTestResult l
+ SimpleParser.LookAhead: type PureDefaultCase l s e a = DefaultCase l s e Identity a
+ SimpleParser.LookAhead: type PureMatchBlock l s e a = MatchBlock l s e Identity a
+ SimpleParser.LookAhead: type PureMatchCase l s e a = MatchCase l s e Identity a
+ SimpleParser.Parser: reflectParser :: Monad m => ParserT l s e m a -> ParserT l s e m (Maybe (ParseResult l s e a))
+ SimpleParser.Result: matchSoleParseError :: NESeq (ParseError l s e) -> Maybe (ParseError l s e)
+ SimpleParser.Stream: class Stream s => PosStream s where {
+ SimpleParser.Stream: instance (SimpleParser.Stream.Stream s, SimpleParser.Stream.Token s GHC.Types.~ GHC.Types.Char) => SimpleParser.Stream.PosStream (SimpleParser.Stream.LinePosStream s)
+ SimpleParser.Stream: instance SimpleParser.Stream.Stream s => SimpleParser.Stream.PosStream (SimpleParser.Stream.OffsetStream s)
- SimpleParser.Common: getStreamPos :: (Stream s, Monad m) => ParserT l s e m (Pos s)
+ SimpleParser.Common: getStreamPos :: (PosStream s, Monad m) => ParserT l s e m (Pos s)
- 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.Common: spanParser :: (PosStream s, Monad m) => (Span (Pos s) -> a -> b) -> ParserT l s e m a -> ParserT l s e m b
- SimpleParser.Explain: type Explainable l s e = (TextualStream s, ExplainLabel l, ExplainError e)
+ SimpleParser.Explain: type Explainable l s e = (TextualStream s, PosStream s, ExplainLabel l, ExplainError e)
- SimpleParser.Result: parseErrorNarrowestSpan :: Stream s => ParseError l s e -> (Maybe l, Span (Pos s))
+ SimpleParser.Result: parseErrorNarrowestSpan :: PosStream s => ParseError l s e -> (Maybe l, Span (Pos s))
- SimpleParser.Stream: streamViewPos :: Stream s => s -> Pos s
+ SimpleParser.Stream: streamViewPos :: PosStream s => s -> Pos s

Files

README.md view
@@ -13,4 +13,4 @@  ## 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).+This project is BSD-licensed. 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: 9f3603d415ba39bde869cc248fafc793d22852c365bfe638986552697a10c988+-- hash: 4839c5f528bb2f3a48f2fbbcee91749ab73716791dc72efa365b56fefc75e6e6  name:           simple-parser-version:        0.5.0+version:        0.6.0 synopsis:       Simple parser combinators description:    Please see the README on GitHub at <https://github.com/ejconlon/simple-parser#readme> category:       Parsing@@ -36,6 +36,7 @@       SimpleParser.Explain       SimpleParser.Input       SimpleParser.Interactive+      SimpleParser.LookAhead       SimpleParser.Parser       SimpleParser.Result       SimpleParser.Stack
src/SimpleParser.hs view
@@ -9,6 +9,7 @@   , module SimpleParser.Explain   , module SimpleParser.Input   , module SimpleParser.Interactive+  , module SimpleParser.LookAhead   , module SimpleParser.Parser   , module SimpleParser.Result   , module SimpleParser.Stack@@ -20,6 +21,7 @@ import SimpleParser.Explain import SimpleParser.Input import SimpleParser.Interactive+import SimpleParser.LookAhead import SimpleParser.Parser import SimpleParser.Result import SimpleParser.Stack
src/SimpleParser/Common.hs view
@@ -30,7 +30,7 @@ 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 (..))+import SimpleParser.Stream (PosStream (..), Span (..), Stream (..))  -- | Enumeration of common labels in textual parsing. data TextLabel =@@ -187,7 +187,7 @@   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 :: (PosStream 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@@ -195,5 +195,5 @@   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 :: (PosStream s, Monad m) => ParserT l s e m (Pos s) getStreamPos = gets streamViewPos
src/SimpleParser/Examples/Json.hs view
@@ -10,14 +10,14 @@   ) where  import Control.Monad (void)-import Data.Foldable (asum) import Data.Scientific (Scientific) import Data.Sequence (Seq) import Data.Text (Text) import Data.Void (Void)-import SimpleParser (Parser, Stream (..), TextLabel, TextualChunked (..), TextualStream, betweenParser, commitParser,-                     escapedStringParser, lexemeParser, matchChunk, matchToken, onEmptyParser, orParser, satisfyToken,-                     scientificParser, sepByParser, signedNumStartPred, spaceParser)+import SimpleParser (DefaultCase (..), MatchBlock (..), MatchCase (..), Parser, PureMatchBlock, Stream (..), TextLabel,+                     TextualChunked (..), TextualStream, betweenParser, escapedStringParser, lexemeParser,+                     lookAheadMatch, matchChunk, matchToken, orParser, satisfyToken, scientificParser, sepByParser,+                     signedNumStartPred, spaceParser)  data JsonF a =     JsonObject !(Seq (Text, a))@@ -32,6 +32,8 @@  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@@ -40,17 +42,18 @@ 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 = 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-    ]+recJsonParser root = lookAheadMatch (recJsonB root)  spaceP :: JsonParserC s => JsonParserM s () spaceP = spaceParser
src/SimpleParser/Examples/Sexp.hs view
@@ -13,15 +13,15 @@  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 (..), EmbedTextLabel (..), ExplainLabel (..), Parser, TextLabel, TextualStream,-                     betweenParser, commitParser, decimalParser, escapedStringParser, lexemeParser, matchToken,-                     onEmptyParser, orParser, packChunk, satisfyToken, scientificParser, sepByParser,-                     signedNumStartPred, signedParser, spaceParser, takeTokensWhile)+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)  data Atom =     AtomIdent !Text@@ -54,6 +54,8 @@  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@@ -100,13 +102,16 @@ 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)+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)   ]++atomP :: SexpParserC s => SexpParserM s Atom+atomP = lexP (lookAheadMatch atomB)  listP :: SexpParserC s => SexpParserM s a -> SexpParserM s (Seq a) listP root = lexP (betweenParser openParenP closeParenP (sepByParser root spaceP))
src/SimpleParser/Explain.hs view
@@ -22,7 +22,7 @@ import SimpleParser.Common (CompoundTextLabel (..), TextLabel (..)) import SimpleParser.Result (CompoundError (..), ParseError (..), RawError (..), StreamError (..),                             parseErrorEnclosingLabels, parseErrorNarrowestSpan)-import SimpleParser.Stream (LinePos (..), Span (..), Stream (..), TextualStream)+import SimpleParser.Stream (LinePos (..), PosStream (..), Span (..), Stream (..), TextualStream) import Text.Builder (Builder) import qualified Text.Builder as TB @@ -108,7 +108,7 @@       CompoundErrorFail msg -> ErrorExplanation msg Nothing Nothing       CompoundErrorCustom e -> explainError e -type Explainable l s e = (TextualStream s, ExplainLabel l, ExplainError e)+type Explainable l s e = (TextualStream s, PosStream s, ExplainLabel l, ExplainError e)  data ParseErrorExplanation p = ParseErrorExplanation   { peeSpan :: !(Span p)
+ src/SimpleParser/LookAhead.hs view
@@ -0,0 +1,97 @@+{-# LANGUAGE UndecidableInstances #-}++module SimpleParser.LookAhead+  ( MatchCase (..)+  , PureMatchCase+  , DefaultCase (..)+  , PureDefaultCase+  , MatchBlock (..)+  , PureMatchBlock+  , lookAheadMatch+  , MatchPos (..)+  , LookAheadTestResult (..)+  , lookAheadTest+  , pureLookAheadTest+  , lookAheadChunk+  ) 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 (..))++data MatchCase l s e m a = 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)+  }++type PureDefaultCase l s e a = DefaultCase l s e Identity a++data MatchBlock l s e m a = MatchBlock+  { matchBlockDefault :: !(DefaultCase l s e m a)+  , matchBlockElems :: ![MatchCase l s e m a]+  }++type PureMatchBlock l s e a = MatchBlock l s e Identity a++-- | 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++data MatchPos l = MatchPos+  { matchPosIndex :: !Int+  , matchPosLabel :: !(Maybe l)+  } deriving stock (Eq, Show)++data LookAheadTestResult l =+    LookAheadTestDefault !(Maybe l)+  | 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+    case mres of+      Just (ParseResultSuccess _) -> go (acc :|> MatchPos i mcl) (i + 1) mcs' s+      _ -> go acc (i + 1) mcs' s++pureLookAheadTest :: PureMatchBlock l s e a -> 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))
src/SimpleParser/Parser.hs view
@@ -21,6 +21,7 @@   , greedyPlusParser_   , defaultParser   , optionalParser+  , reflectParser   , silenceParser   , lookAheadParser   , markParser@@ -184,6 +185,8 @@           -- Not handling error;  - find next custom error           goSplit s0 (beforeEs <> (targetE :<| nextBeforeEs)) afterEs         Just b -> do+          -- NOTE(ejconlon) We resume parsing at the start state s0 (captured at catchError invocation)+          -- Is it reasonable to support parsing at the error state? (This is in the SeqPartition wildcard above)           mres <- runParserT (handler b) s0           case mres of             Nothing ->@@ -235,6 +238,13 @@ -- 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)++-- | 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)))  -- | Removes all failures from the parse results. Catches more errors than 'catchError (const empty)' -- because this includes stream errors, not just custom errors.
src/SimpleParser/Result.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE UndecidableInstances #-}  module SimpleParser.Result@@ -14,14 +13,15 @@   , parseErrorNarrowestSpan   , ParseSuccess (..)   , ParseResult (..)+  , matchSoleParseError   ) where  import Data.Sequence (Seq (..)) import qualified Data.Sequence as Seq-import Data.Sequence.NonEmpty (NESeq)+import Data.Sequence.NonEmpty (NESeq (..)) import Data.Text (Text) import SimpleParser.Stack (Stack (..), bottomStack, emptyStack, pushStack, topStack)-import SimpleParser.Stream (Span (..), Stream (..))+import SimpleParser.Stream (PosStream (..), Span (..), Stream (..))  data RawError chunk token =     RawErrorMatchEnd !token@@ -79,7 +79,7 @@ unmarkParseError pe = pe { peMarkStack = emptyStack }  -- | Returns the narrowest span-parseErrorNarrowestSpan :: Stream s => ParseError l s e -> (Maybe l, Span (Pos s))+parseErrorNarrowestSpan :: PosStream 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))@@ -106,3 +106,12 @@  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)++-- | If there is one parse error, return it, otherwise return nothing.+-- Errors can accumulate if you use unrestricted branching (with 'orParser' or 'Alternative' '<|>') or manual 'Parser' constructor application.+-- However, if you always branch with 'lookAheadMatch' then you will have singleton parse errors, and this will always return 'Just'.+matchSoleParseError :: NESeq (ParseError l s e) -> Maybe (ParseError l s e)+matchSoleParseError es =+  case es of+    e :<|| Empty -> Just e+    _ -> Nothing
src/SimpleParser/Stream.hs view
@@ -2,9 +2,10 @@ -- See <https://hackage.haskell.org/package/megaparsec-9.0.1/docs/Text-Megaparsec-Stream.html Text.Megaparsec.Stream>. module SimpleParser.Stream   ( Stream (..)-  , TextualStream   , defaultStreamDropN   , defaultStreamDropWhile+  , TextualStream+  , PosStream (..)   , Offset (..)   , OffsetStream (..)   , newOffsetStream@@ -25,15 +26,11 @@ import qualified Data.Text as T import SimpleParser.Chunked (Chunked (..), TextualChunked (..)) --- | 'Stream' lets us peel off tokens and chunks for parsing--- with explicit state passing.+-- | '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)@@ -55,9 +52,7 @@ instance Stream [a] where   type instance Chunk [a] = [a]   type instance Token [a] = a-  type instance Pos [a] = () -  streamViewPos = const ()   streamTake1 = unconsChunk   streamTakeN n s     | n <= 0 = Just ([], s)@@ -68,9 +63,7 @@ instance Stream (Seq a) where   type instance Chunk (Seq a) = Seq a   type instance Token (Seq a) = a-  type instance Pos (Seq a) = () -  streamViewPos = const ()   streamTake1 = unconsChunk   streamTakeN n s     | n <= 0 = Just (Seq.empty, s)@@ -83,9 +76,7 @@ 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)@@ -93,6 +84,12 @@     | otherwise = Just (T.splitAt n s)   streamTakeWhile = T.span +-- | 'PosStream' adds position tracking to a 'Stream'.+class Stream s => PosStream s where+  type family Pos s :: Type++  streamViewPos :: s -> Pos s+ newtype Offset = Offset { unOffset :: Int }   deriving newtype (Eq, Show, Ord, Enum, Num, Real, Integral) @@ -105,9 +102,6 @@ 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 (Offset x) s) = fmap go (streamTakeN n s) where     go (a, b) = (a, OffsetStream (Offset (x + chunkLength a)) b)@@ -120,6 +114,11 @@     let (m, b) = streamDropWhile pcate s     in (m, OffsetStream (Offset (x + m)) b) +instance Stream s => PosStream (OffsetStream s) where+  type instance Pos (OffsetStream s) = Offset++  streamViewPos (OffsetStream o _) = o+ newOffsetStream :: s -> OffsetStream s newOffsetStream = OffsetStream 0 @@ -157,9 +156,6 @@ 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)@@ -168,6 +164,11 @@     in (a, LinePosStream (incrLinePosChunk p (chunkToTokens a)) b)    -- Drops can't be specialized because we need to examine each character for newlines.++instance (Stream s, Token s ~ Char) => PosStream (LinePosStream s) where+  type instance Pos (LinePosStream s) = LinePos++  streamViewPos (LinePosStream p _) = p  newLinePosStream :: s -> LinePosStream s newLinePosStream = LinePosStream initLinePos
test/Main.hs view
@@ -5,6 +5,7 @@  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)@@ -22,6 +23,8 @@  type TestState = OffsetStream Text +type TestBlock a = PureMatchBlock Label TestState Error a+ type TestParser a = Parser Label TestState Error a  type TestResult a = ParseResult Label TestState Error a@@ -32,6 +35,8 @@  data ParserCase a = ParserCase !TestName !(TestParser a) !Text !(Maybe (TestResult a)) +data ExamineCase a = ExamineCase !TestName !(TestBlock a) !Text !(LookAheadTestResult Label)+ fwd :: Int -> TestState -> TestState fwd n (OffsetStream (Offset i) t) =   let m = min n (T.length t)@@ -81,6 +86,11 @@   let actual = runParser parser (newOffsetStream input)   actual @?= expected +testExamineCase :: ExamineCase a -> TestTree+testExamineCase (ExamineCase name block input expected) = testCase name $ do+  let actual = pureLookAheadTest block (newOffsetStream input)+  actual @?= expected+ test_empty :: [TestTree] test_empty =   let parser = emptyParser :: TestParser Int@@ -103,8 +113,8 @@ 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")])+        [ 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 @@ -534,6 +544,36 @@         , ParserCase "match end" parser "hh" (sucRes (OffsetStream 2 "") 2)         ]   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")+  ]++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 "match x" parser "xz" (sucRes (OffsetStream 1 "z") "found x - consuming")+        , ParserCase "match y" parser "yz" (sucRes (OffsetStream 0 "yz") "found y - leaving")+        ]+  in fmap testParserCase cases++test_look_ahead_examine :: [TestTree]+test_look_ahead_examine =+  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 "match x" simpleBlock "xz" (LookAheadTestMatches (NESeq.unsafeFromSeq (Seq.fromList xpositions)))+        , ExamineCase "match y" simpleBlock "yz" (LookAheadTestMatches (NESeq.unsafeFromSeq (Seq.fromList ypositions)))+        ]+  in fmap testExamineCase cases  type JsonResult = Maybe Json