Skip to content

Commit 39e93f4

Browse files
committed
Add liftMaybe, liftEither, liftExceptT
#196
1 parent 2a49674 commit 39e93f4

File tree

4 files changed

+69
-4
lines changed

4 files changed

+69
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ New features:
1212

1313
- add `MonadAsk` and `MonadReader` instances (#208 by @bentongxyz)
1414
- Add `Parsing.String.parseErrorHuman` (#209 by @jamesdbrock)
15+
- Add `liftMaybe`, `liftEither`, `liftExceptT` (#212 by @jamesdbrock)
1516

1617
Other improvements:
1718

src/Parsing.purs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ module Parsing
2020
, fail
2121
, failWithPosition
2222
, region
23+
, liftMaybe
24+
, liftEither
25+
, liftExceptT
2326
, ParseState(..)
2427
, stateParserT
2528
, getParserT
@@ -33,6 +36,7 @@ import Control.Alt (class Alt)
3336
import Control.Apply (lift2)
3437
import Control.Lazy (class Lazy)
3538
import Control.Monad.Error.Class (class MonadError, class MonadThrow, catchError, throwError)
39+
import Control.Monad.Except (ExceptT, runExceptT)
3640
import Control.Monad.Reader.Class (class MonadAsk, class MonadReader, ask, local)
3741
import Control.Monad.Rec.Class (class MonadRec, Step(..), tailRecM)
3842
import Control.Monad.State.Class (class MonadState, state)
@@ -43,6 +47,7 @@ import Data.Function.Uncurried (Fn2, Fn5, mkFn2, mkFn3, mkFn5, runFn2, runFn3, r
4347
import Data.Generic.Rep (class Generic)
4448
import Data.Identity (Identity)
4549
import Data.Lazy as Lazy
50+
import Data.Maybe (Maybe(..))
4651
import Data.Newtype (unwrap)
4752
import Data.Show.Generic (genericShow)
4853
import Data.Tuple (Tuple(..), fst)
@@ -441,4 +446,58 @@ instance Ord Position where
441446
-- |
442447
-- | `{ index: 0, line: 1, column: 1 }`
443448
initialPos :: Position
444-
initialPos = Position { index: 0, line: 1, column: 1 }
449+
initialPos = Position { index: 0, line: 1, column: 1 }
450+
451+
-- | Lift a `Maybe a` computation into a `ParserT`, with a note for
452+
-- | the `ParseError` message in case of `Nothing`.
453+
-- |
454+
-- | Consumes no parsing input, does not change the parser state at all.
455+
-- | If the `Maybe` computation is `Nothing`, then this will `fail` in the
456+
-- | `ParserT` monad with the given error message `String` at the current input
457+
-- | `Position`.
458+
-- |
459+
-- | This is a “validation” function, for when we want to produce some
460+
-- | data from the parsing input or fail at the current
461+
-- | parsing position if that’s impossible.
462+
-- |
463+
-- | For example:
464+
-- |
465+
-- | ```
466+
-- | runParser "3" do
467+
-- | myenum :: MyEnum <- tryRethrow do
468+
-- | x <- intDecimal
469+
-- | liftMaybe (defer $ "Bad MyEnum " <> show x) $ toEnum x
470+
-- | pure myenum
471+
-- | ```
472+
liftMaybe :: forall s m a. Monad m => Lazy.Lazy String -> Maybe a -> ParserT s m a
473+
liftMaybe message f = case f of
474+
Nothing -> fail (Lazy.force message)
475+
Just x -> pure x
476+
477+
-- | Lift an `Either String a` computation into a `ParserT`.
478+
-- |
479+
-- | Consumes no parsing input, does not change the parser state at all.
480+
-- | If the `Either` computation is `Left String`, then this will `fail` in the
481+
-- | `ParserT` monad at the current input `Position`.
482+
-- |
483+
-- | This is a “validation” function, for when we want to produce some
484+
-- | data from the parsing input or fail at the current
485+
-- | parsing position if that’s impossible.
486+
liftEither :: forall s m a. Monad m => Either String a -> ParserT s m a
487+
liftEither f = case f of
488+
Left err -> fail err
489+
Right x -> pure x
490+
491+
-- | Lift an `ExceptT String m a` computation into a `ParserT`.
492+
-- |
493+
-- | Consumes no parsing input, does not change the parser state at all.
494+
-- | If the `ExceptT` computation is `Left String`, then this will `fail` in the
495+
-- | `ParserT` monad at the current input `Position`.
496+
-- |
497+
-- | This is a “validation” function, for when we want to produce some
498+
-- | data from the parsing input or fail at the current
499+
-- | parsing position if that’s impossible.
500+
liftExceptT :: forall s m a. (Monad m) => ExceptT String m a -> ParserT s m a
501+
liftExceptT f = lift (runExceptT f) >>= case _ of
502+
Left err -> fail err
503+
Right x -> pure x

src/Parsing/Combinators.purs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ optionMaybe p = option Nothing (Just <$> p)
158158
-- | >>> runParser "ac" ((char 'a' *> char 'b') <|> (char 'a' *> char 'c'))
159159
-- | Left (ParseError "Expected 'b'" (Position { line: 1, column: 2 }))
160160
-- | ```
161-
-- |
161+
-- | ---
162162
-- | ```
163163
-- | >>> runParser "ac" (try (char 'a' *> char 'b') <|> (char 'a' *> char 'c'))
164164
-- | Right 'c'
@@ -181,7 +181,7 @@ try (ParserT k1) = ParserT
181181
-- | >>> runParser "ac" (try (char 'a' *> char 'b'))
182182
-- | Left (ParseError "Expected 'b'" (Position { index: 1, line: 1, column: 2 }))
183183
-- | ```
184-
-- |
184+
-- | ---
185185
-- | ```
186186
-- | >>> runParser "ac" (tryRethrow (char 'a' *> char 'b'))
187187
-- | Left (ParseError "Expected 'b'" (Position { index: 0, line: 1, column: 1 }))

test/Main.purs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import Effect.Console (log, logShow)
3434
import Effect.Unsafe (unsafePerformEffect)
3535
import Node.Process (lookupEnv)
3636
import Parsing (ParseError(..), ParseState(..), Parser, ParserT, Position(..), consume, fail, getParserT, initialPos, parseErrorMessage, parseErrorPosition, position, region, runParser)
37-
import Parsing.Combinators (advance, between, chainl, chainl1, chainr, chainr1, choice, empty, endBy, endBy1, lookAhead, many, many1, many1Till, many1Till_, manyIndex, manyTill, manyTill_, notFollowedBy, optionMaybe, replicateA, sepBy, sepBy1, sepEndBy, sepEndBy1, skipMany, skipMany1, try, (<?>), (<??>), (<~?>))
37+
import Parsing.Combinators (advance, between, chainl, chainl1, chainr, chainr1, choice, empty, endBy, endBy1, lookAhead, many, many1, many1Till, many1Till_, manyIndex, manyTill, manyTill_, notFollowedBy, optionMaybe, replicateA, sepBy, sepBy1, sepEndBy, sepEndBy1, skipMany, skipMany1, try, tryRethrow, (<?>), (<??>), (<~?>))
3838
import Parsing.Combinators.Array as Combinators.Array
3939
import Parsing.Expr (Assoc(..), Operator(..), buildExprParser)
4040
import Parsing.Language (haskellDef, haskellStyle, javaStyle)
@@ -702,6 +702,11 @@ main = do
702702
, expected: Right false
703703
}
704704

705+
assertEqual' "tryRethrow 1"
706+
{ actual: runParser "abx" $ char 'a' *> tryRethrow (char 'b' *> char 'c')
707+
, expected: Left $ ParseError "Expected 'c'" (Position { index: 1, column: 2, line: 1 })
708+
}
709+
705710
log "\nTESTS number\n"
706711

707712
-- assert' "Number.fromString" $ Just infinity == Data.Number.fromString "Infinity"

0 commit comments

Comments
 (0)