simple-parser-0.10.0: src/SimpleParser/LookAhead.hs
{-# LANGUAGE UndecidableInstances #-}
module SimpleParser.LookAhead
( MatchCase (..)
, PureMatchCase
, MatchBlock (..)
, PureMatchBlock
, lookAheadMatch
, consumeMatch
, MatchPos (..)
, LookAheadTestResult (..)
, lookAheadTest
, pureLookAheadTest
, lookAheadSimple
) where
import Control.Monad.Identity (Identity (runIdentity))
import Data.Sequence (Seq (..))
import Data.Sequence.NonEmpty (NESeq)
import qualified Data.Sequence.NonEmpty as NESeq
import SimpleParser.Parser (ParserT (..), lookAheadParser, markParser)
import SimpleParser.Result (ParseResult (..), ParseSuccess (..))
data MatchCase l s e m a b = MatchCase
{ matchCaseLabel :: !(Maybe l)
, matchCaseChoose :: !(a -> Bool)
, matchCaseHandle :: !(ParserT l s e m b)
}
type PureMatchCase l s e a b = MatchCase l s e Identity a b
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 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 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
-- | Same as 'lookAheadMatch' but consumes the selector instead of looking ahead.
-- Cases will not have to re-parse the selected portion.
consumeMatch :: Monad m => MatchBlock l s e m a b -> ParserT l s e m b
consumeMatch (MatchBlock sel dc mcs) = 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
, matchPosLabel :: !(Maybe l)
} deriving stock (Eq, Show)
data LookAheadTestResult 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 b -> s -> m (LookAheadTestResult l)
lookAheadTest (MatchBlock sel _ mcs) = go1 where
go1 s = do
mres <- runParserT sel s
case mres of
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 b -> s -> LookAheadTestResult l
pureLookAheadTest mb = runIdentity . lookAheadTest mb
-- | 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]