-- Author: Malcolm Wallace -- | In a strict language, where creating the entire input list of tokens -- in one shot may be infeasible, we can use a lazy "callback" kind of -- architecture instead. The lexer returns a single token at a time, -- together with a continuation. -- -- This module defines a Parser type (capable of use with the Poly -- combinators), specialised to the callback-lexer style of input stream. module Text.ParserCombinators.Poly.Lex ( -- * The Parser datatype LexReturn(..) , Parser(P) , Result(..) , runParser -- ** Basic parsers , next , eof , satisfy , onFail -- ** Re-parsing , reparse -- * Re-export all more general combinators , module Text.ParserCombinators.Poly.Base , module Control.Applicative ) where import Text.ParserCombinators.Poly.Base import Text.ParserCombinators.Poly.Result import Control.Applicative -- | In a strict language, where creating the entire input list of tokens -- in one shot may be infeasible, we can use a lazy "callback" kind of -- architecture instead. The lexer returns a single token at a time, -- together with a continuation. The @next@ parser is responsible for -- pulling on the token stream, applying the continuation where necessary. data LexReturn t = LexReturn t String (String->LexReturn t) | LexFinish -- | This @Parser@ datatype is a specialised parsing monad with error -- reporting. This version is specialised to pre-lexed String input, -- where the lexer has been written to yield a @LexReturn@. newtype Parser t a = P (LexReturn t -> Result (LexReturn t) a) -- | Apply a parser to an input token sequence. runParser :: Parser t a -> LexReturn t -> (Either String a, String) runParser (P p) = (\ (a,b)->(a,stripLex b)) . resultToEither . p where stripLex LexFinish = "" stripLex (LexReturn _ s _) = s instance Functor (Parser t) where fmap f (P p) = P (fmap f . p) instance Monad (Parser t) where return x = P (\ts-> Success ts x) fail e = P (\ts-> Failure ts e) (P f) >>= g = P (continue . f) where continue (Success ts x) = let (P g') = g x in g' ts continue (Committed (Committed r)) = continue (Committed r) continue (Committed r) = Committed (continue r) continue (Failure ts e) = Failure ts e instance Commitment (Parser t) where commit (P p) = P (Committed . p) (P p) `adjustErr` f = P (adjust . p) where adjust (Failure z e) = Failure z (f e) adjust (Committed r) = Committed (adjust r) adjust good = good oneOf' = accum [] where accum errs [] = fail ("failed to parse any of the possible choices:\n" ++(indent 2 . unlines . map showErr . reverse $ errs)) accum errs ((e,P p):ps) = P (\ts-> case p ts of Failure _ err -> let (P p') = accum ((e,err):errs) ps in p' ts r@(Success _ _) -> r r@(Committed _) -> r ) showErr (name,err) = name ++ "\n" ++ indent 2 err infixl 6 `onFail` -- not sure about precedence 6? -- | @p `onFail` q@ means parse p, unless p fails, in which case -- parse q instead. -- Can be chained together to give multiple attempts to parse something. -- (Note that q could itself be a failing parser, e.g. to change the error -- message from that defined in p to something different.) -- However, a severe failure in p cannot be ignored. (P p) `onFail` (P q) = P (\ts-> continue ts $ p ts) where continue ts (Failure _ _) = q ts -- continue _ (Committed r) = r -- no, remain Committed continue _ r = r instance Applicative (Parser t) where pure f = return f pf <*> px = do { f <- pf; x <- px; return (f x) } #if defined(GLASGOW_HASKELL) && GLASGOW_HASKELL > 610 p <* q = p `discard` q #endif instance Alternative (Parser t) where empty = fail "no parse" p <|> q = p `onFail` q instance PolyParse (Parser t) ------------------------------------------------------------------------ -- | Simply return the next token in the input tokenstream. next :: Parser t t next = P (\ts-> case ts of LexFinish -> Failure ts "Ran out of input (EOF)" LexReturn t s k -> Success (k s) t) -- | Succeed if the end of file/input has been reached, fail otherwise. eof :: Parser t () eof = P (\ts -> case ts of LexFinish -> Success ts () LexReturn _ _ _ -> Failure ts "Expected end of input (EOF)" ) -- | Return the next token if it satisfies the given predicate. satisfy :: (t -> Bool) -> Parser t t satisfy f = do { x <- next ; if f x then return x else fail "Parse.satisfy: failed" } ------------------------------------------------------------------------ -- | 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 t () reparse ts = P (\inp-> Success (ts `prefix` inp) ()) where (t:ts) `prefix` k = LexReturn t "" (const (ts `prefix` k)) [] `prefix` k = k ------------------------------------------------------------------------