-
Notifications
You must be signed in to change notification settings - Fork 51
Use ADTs for ParseError, PState and intermediate results #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e94a5e6
1482aad
196309b
9e0d291
fc4ee44
3823559
3ee5346
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,13 @@ | ||
module Text.Parsing.Parser where | ||
module Text.Parsing.Parser ( | ||
unParserT | ||
, runParserT | ||
, ParserT (..) | ||
, runParser | ||
, Parser () | ||
, consume | ||
, fail | ||
, parseFailed | ||
) where | ||
|
||
import Prelude | ||
|
||
|
@@ -13,74 +22,76 @@ import Data.Tuple (Tuple(..)) | |
import Text.Parsing.Parser.Pos (Position, initialPos) | ||
|
||
-- | A parsing error, consisting of a message and position information. | ||
data ParseError = ParseError | ||
{ message :: String | ||
, position :: Position | ||
} | ||
data ParseError = ParseError String Position | ||
|
||
instance showParseError :: Show ParseError where | ||
show (ParseError msg) = "ParseError { message: " <> msg.message <> ", position: " <> show msg.position <> " }" | ||
derive instance eqParseError :: Eq ParseError | ||
|
||
-- | The result of a single parse | ||
data Result s a = Result s -- the new input | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, let's hide the constructor. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is also |
||
(Either ParseError a) -- the result | ||
Boolean -- consumed? | ||
Position -- the new position | ||
|
||
instance eqParseError :: Eq ParseError where | ||
eq (ParseError {message : m1, position : p1}) (ParseError {message : m2, position : p2}) = m1 == m2 && p1 == p2 | ||
instance showParseError :: Show ParseError where | ||
show (ParseError msg pos) = "ParseError " <> show msg <> " " <> show pos | ||
|
||
-- | `PState` contains the remaining input and current position. | ||
data PState s = PState | ||
{ input :: s | ||
, position :: Position | ||
} | ||
data PState s = PState s Position | ||
|
||
-- | The Parser monad transformer. | ||
-- | | ||
-- | The first type argument is the stream type. Typically, this is either `String`, or some sort of token stream. | ||
newtype ParserT s m a = ParserT (PState s -> m { input :: s, result :: Either ParseError a, consumed :: Boolean, position :: Position }) | ||
newtype ParserT s m a = ParserT (PState s -> m (Result s a)) | ||
|
||
-- | Apply a parser by providing an initial state. | ||
unParserT :: forall m s a. ParserT s m a -> PState s -> m { input :: s, result :: Either ParseError a, consumed :: Boolean, position :: Position } | ||
unParserT :: forall m s a. ParserT s m a -> PState s -> m (Result s a) | ||
unParserT (ParserT p) = p | ||
|
||
-- | Apply a parser, keeping only the parsed result. | ||
runParserT :: forall m s a. Monad m => PState s -> ParserT s m a -> m (Either ParseError a) | ||
runParserT s p = do | ||
o <- unParserT p s | ||
pure o.result | ||
Result _ result _ _ <- unParserT p s | ||
pure result | ||
|
||
-- | The `Parser` monad is a synonym for the parser monad transformer applied to the `Identity` monad. | ||
type Parser s a = ParserT s Identity a | ||
|
||
-- | Apply a parser, keeping only the parsed result. | ||
runParser :: forall s a. s -> Parser s a -> Either ParseError a | ||
runParser s = runIdentity <<< runParserT (PState { input: s, position: initialPos }) | ||
runParser s = runIdentity <<< runParserT (PState s initialPos) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The expression |
||
|
||
instance functorParserT :: (Functor m) => Functor (ParserT s m) where | ||
map f p = ParserT $ \s -> f' <$> unParserT p s | ||
map f p = ParserT \s -> f' <$> unParserT p s | ||
where | ||
f' o = { input: o.input, result: f <$> o.result, consumed: o.consumed, position: o.position } | ||
f' (Result input result consumed pos) = Result input (f <$> result) consumed pos | ||
|
||
instance applyParserT :: Monad m => Apply (ParserT s m) where | ||
apply = ap | ||
|
||
instance applicativeParserT :: Monad m => Applicative (ParserT s m) where | ||
pure a = ParserT $ \(PState { input: s, position: pos }) -> pure { input: s, result: Right a, consumed: false, position: pos } | ||
pure a = ParserT \(PState s pos) -> do | ||
pure (Result s (Right a) false pos) | ||
|
||
instance altParserT :: Monad m => Alt (ParserT s m) where | ||
alt p1 p2 = ParserT $ \s -> unParserT p1 s >>= \o -> | ||
case o.result of | ||
Left _ | not o.consumed -> unParserT p2 s | ||
_ -> pure o | ||
alt p1 p2 = ParserT \s -> do | ||
(o@(Result input result consumed pos)) <- unParserT p1 s | ||
case result of | ||
Left _ | not consumed -> unParserT p2 s | ||
otherwise -> pure o | ||
|
||
instance plusParserT :: Monad m => Plus (ParserT s m) where | ||
empty = fail "No alternative" | ||
|
||
instance alternativeParserT :: Monad m => Alternative (ParserT s m) | ||
|
||
instance bindParserT :: Monad m => Bind (ParserT s m) where | ||
bind p f = ParserT $ \s -> unParserT p s >>= \o -> | ||
case o.result of | ||
Left err -> pure { input: o.input, result: Left err, consumed: o.consumed, position: o.position } | ||
Right a -> updateConsumedFlag o.consumed <$> unParserT (f a) (PState { input: o.input, position: o.position }) | ||
where | ||
updateConsumedFlag c o = { input: o.input, consumed: c || o.consumed, result: o.result, position: o.position } | ||
bind p f = ParserT \s -> do | ||
(Result input result consumed pos) <- unParserT p s | ||
case result of | ||
Left err -> pure (Result input (Left err) consumed pos) | ||
Right a -> do | ||
(Result input' result' consumed' pos') <- unParserT (f a) (PState input pos) | ||
pure (Result input' result' (consumed || consumed') pos') | ||
|
||
instance monadParserT :: Monad m => Monad (ParserT s m) | ||
|
||
|
@@ -89,27 +100,27 @@ instance monadZeroParserT :: Monad m => MonadZero (ParserT s m) | |
instance monadPlusParserT :: Monad m => MonadPlus (ParserT s m) | ||
|
||
instance monadTransParserT :: MonadTrans (ParserT s) where | ||
lift m = ParserT $ \(PState { input: s, position: pos }) -> (\a -> { input: s, consumed: false, result: Right a, position: pos }) <$> m | ||
lift m = ParserT \(PState s pos) -> (\a -> Result s (Right a) false pos) <$> m | ||
|
||
instance monadStateParserT :: Monad m => MonadState s (ParserT s m) where | ||
state f = ParserT $ \(PState { input: s, position: pos }) -> | ||
state f = ParserT \(PState s pos) -> | ||
pure $ case f s of | ||
Tuple a s' -> { input: s', consumed: false, result: Right a, position: pos } | ||
Tuple a s' -> Result s' (Right a) false pos | ||
|
||
instance lazyParserT :: Lazy (ParserT s m a) where | ||
defer f = ParserT $ \s -> unParserT (f unit) s | ||
defer f = ParserT \s -> unParserT (f unit) s | ||
|
||
-- | Set the consumed flag. | ||
consume :: forall s m. Monad m => ParserT s m Unit | ||
consume = ParserT $ \(PState { input: s, position: pos }) -> pure { consumed: true, input: s, result: Right unit, position: pos } | ||
consume = ParserT \(PState s pos) -> pure (Result s (Right unit) true pos) | ||
|
||
-- | Fail with a message. | ||
fail :: forall m s a. Monad m => String -> ParserT s m a | ||
fail message = ParserT $ \(PState { input: s, position: pos }) -> pure $ parseFailed s pos message | ||
fail message = ParserT \(PState s pos) -> pure $ parseFailed s pos message | ||
|
||
-- | Creates a failed parser state for the remaining input `s` and current position | ||
-- | with an error message. | ||
-- | | ||
-- | Most of the time, `fail` should be used instead. | ||
parseFailed :: forall s a. s -> Position -> String -> { input :: s, result :: Either ParseError a, consumed :: Boolean, position :: Position } | ||
parseFailed s pos message = { input: s, consumed: false, result: Left (ParseError { message: message, position: pos }), position: pos } | ||
parseFailed :: forall s a. s -> Position -> String -> Result s a | ||
parseFailed s pos message = Result s (Left (ParseError message pos)) false pos |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's hide the constructor for this in the exports list, and provide a smart constructor. We might want to add additional information here later, without breaking the API.