module Text.ParserCombinators.Poly.StateLazy ( -- * The Parser datatype Parser(P) -- datatype, instance of: Functor, Monad, PolyParse , Result(..) -- internal to the parser monad , runParser -- :: Parser s t a -> s -> [t] -> (Either String a, s, [t]) -- ** basic parsers , next -- :: Parser s t t , eof -- :: Parser s t () , satisfy -- :: (t->Bool) -> Parser s t t , manyFinally -- :: Parser s t a -> Parser s t z -> Parser s t [a] -- ** State-handling , stUpdate -- :: (s->s) -> Parser s t () , stQuery -- :: (s->a) -> Parser s t a , stGet -- :: Parser s t s -- ** re-parsing , reparse -- :: [t] -> Parser s t () -- * Re-export all more general combinators , module Text.ParserCombinators.Poly.Base ) where import Text.ParserCombinators.Poly.Base hiding (manyFinally) #if __GLASGOW_HASKELL__ import Control.Exception hiding (bracket) throwE :: String -> a throwE msg = throw (ErrorCall msg) #else throwE :: String -> a throwE msg = error msg #endif -- | This @Parser@ datatype is a fairly generic parsing monad with error -- reporting. It can be used for arbitrary token types, not just -- String input. (If you require a running state, use module PolyState -- instead) newtype Parser s t a = P (s -> [t] -> Result [t] s a) -- | A return type like Either, that distinguishes not only between -- right and wrong answers, but also has gradations of wrongness. -- This should only be used for writing very primitive -- parsers - really it is an internal detail of the library. data Result z s a = Success z s a | Failure z s String | Committed (Result z s a) instance Functor (Result z s) where fmap f (Success z s a) = Success z s (f a) fmap f (Failure z s e) = Failure z s e fmap f (Committed r) = Committed (fmap f r) -- | Apply a parser to an input token sequence. runParser :: Parser s t a -> s -> [t] -> (a, s, [t]) runParser (P p) = \s -> fromResult . p s where fromResult :: Result z s a -> (a, s, z) fromResult (Success z s a) = (a, s, z) fromResult (Failure z s e) = throwE e fromResult (Committed r) = fromResult r instance Functor (Parser s t) where fmap f (P p) = P (\s -> fmap f . p s) instance Monad (Parser s t) where return x = P (\s ts-> Success ts s x) fail e = P (\s ts-> Failure ts s e) (P f) >>= g = P (\s-> continue . f s) where continue (Success ts s x) = let (P g') = g x in g' s ts continue (Committed (Committed r)) = continue (Committed r) continue (Committed r) = Committed (continue r) continue (Failure ts s e) = Failure ts s e instance PolyParse (Parser s t) where commit (P p) = P (\s-> Committed . p s) (P p) `adjustErr` f = P (\s-> adjust . p s) where adjust (Failure z s e) = Failure z s (f e) adjust (Committed r) = Committed (adjust r) adjust good = good (P p) `onFail` (P q) = P (\s ts-> continue s ts $ p s ts) where continue s ts (Failure _ _ _) = q s ts -- continue _ _ (Committed r) = r -- no, remain Committed continue _ _ r = r oneOf' = accum [] where accum errs [] = fail ("failed to parse any of the possible choices:\n" ++indent 2 (concatMap showErr (reverse errs))) accum errs ((e,P p):ps) = P (\s ts-> case p s ts of Failure _ _ err -> let (P p) = accum ((e,err):errs) ps in p s ts r@(Success z _ a) -> r r@(Committed _) -> r ) showErr (name,err) = name++":\n"++indent 2 err -- Apply a parsed function to a parsed value. This version -- is strict in the result of the function parser, but -- lazy in the result of the argument parser. (Argument laziness is -- the distinctive feature over other implementations.) (P pf) `apply` px = P (\s-> continue . pf s) where continue (Success z s f) = let (x,s',z') = runParser px s z in Success z' s' (f x) continue (Failure z s e) = Failure z s e continue (Committed r) = Committed (continue r) manyFinally :: Parser s t a -> Parser s t z -> Parser s t [a] {- manyFinally pp@(P p) pt@(P t) = P (\s ts -> item s ts (p s ts)) where item _ _ (Success ts s x) = success ts s x item s ts (Failure _ _ e) = terminate (t s ts) item s ts (Committed r) = Committed (within r) success ts s x = let (tail,s',ts') = runParser (manyFinally pp pt) s ts in Success ts' s' (x:tail) terminate (Success ts s _) = Success ts s [] terminate (Failure ts s e) = Failure ts s e terminate (Committed r) = Committed (terminate r) within (Success ts s x) = success ts s x within (Failure ts s e) = Failure ts s e within (Committed r) = within r -} manyFinally p z = (do x <- p; return (x:) `apply` manyFinally p z) `onFail` (do z; return []) `onFail` oneOf' [ ("item in sequence", (do p; return [])) , ("sequence terminator", (do z; return [])) ] ------------------------------------------------------------------------ next :: Parser s t t next = P (\s ts-> case ts of [] -> Failure [] s "Ran out of input (EOF)" (t:ts') -> Success ts' s t ) eof :: Parser s t () eof = P (\s ts-> case ts of [] -> Success [] s () (t:ts') -> Failure ts s "Expected end of input (eof)" ) satisfy :: (t->Bool) -> Parser s t t satisfy pred = do { x <- next ; if pred x then return x else fail "Parse.satisfy: failed" } ------------------------------------------------------------------------ -- State handling -- | Update the internal state. stUpdate :: (s->s) -> Parser s t () stUpdate f = P (\s ts-> Success ts (f s) ()) -- | Query the internal state. stQuery :: (s->a) -> Parser s t a stQuery f = P (\s ts-> Success ts s (f s)) -- | Deliver the entire internal state. stGet :: Parser s t s stGet = P (\s ts-> Success ts s s) ------------------------------------------------------------------------ -- | Push some tokens back onto the front of the input stream and reparse. -- This is useful e.g. for recursively expanding macros. When the -- user-parser recognises a macro use, it can lookup the macro -- expansion from the parse state, lex it, and then stuff the -- lexed expansion back down into the parser. reparse :: [t] -> Parser s t () reparse ts = P (\s inp-> Success (ts++inp) s ()) ------------------------------------------------------------------------