Skip to content

Commit 34b10c1

Browse files
committed
New combinator: withRecovery
1 parent cf6c741 commit 34b10c1

File tree

3 files changed

+48
-1
lines changed

3 files changed

+48
-1
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Breaking changes:
1010

1111
New features:
1212

13+
- New combinator `withRecovery` (#224 by @jamesdbrock)
14+
1315
Other improvements:
1416

1517
## [v10.2.0](https://github.com/purescript-contrib/purescript-parsing/releases/tag/v10.2.0) - 2022-11-30

src/Parsing/Combinators.purs

+25
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ module Parsing.Combinators
4343
( try
4444
, tryRethrow
4545
, lookAhead
46+
, withRecovery
4647
, choice
4748
, between
4849
, notFollowedBy
@@ -205,6 +206,30 @@ lookAhead (ParserT k1) = ParserT
205206
(mkFn2 \_ res -> runFn2 done state1 res)
206207
)
207208

209+
-- | If the main parser fails, the recovery function will be called to get
210+
-- | a recovery parser. Then the input stream will be backtracked to where the
211+
-- | main parser began, and the recovery parser will run.
212+
-- |
213+
-- | To save the error for later examination, use a parser return type that
214+
-- | includes a possible `ParseError`. There is an example of this in the
215+
-- | test suite.
216+
withRecovery
217+
:: forall s m a
218+
. (ParseError -> ParserT s m a)
219+
-> ParserT s m a
220+
-> ParserT s m a
221+
withRecovery recover (ParserT k1) = ParserT
222+
( mkFn5 \state1 more lift throw done ->
223+
runFn5 k1 state1 more lift
224+
( mkFn2 \_ err ->
225+
let
226+
(ParserT k2) = recover err
227+
in
228+
runFn5 k2 state1 more lift throw done
229+
)
230+
done
231+
)
232+
208233
-- | Match the phrase `p` as many times as possible.
209234
-- |
210235
-- | If `p` never consumes input when it

test/Main.purs

+21-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Control.Monad.State (State, lift, modify, runState)
1313
import Data.Array (some, toUnfoldable)
1414
import Data.Array as Array
1515
import Data.Bifunctor (lmap, rmap)
16+
import Data.CodePoint.Unicode (isSpace)
1617
import Data.CodePoint.Unicode as CodePoint.Unicode
1718
import Data.Either (Either(..), either, fromLeft, hush)
1819
import Data.Foldable (oneOf)
@@ -36,7 +37,7 @@ import Effect.Console (log, logShow)
3637
import Effect.Unsafe (unsafePerformEffect)
3738
import Node.Process (lookupEnv)
3839
import Parsing (ParseError(..), ParseState(..), Parser, ParserT, Position(..), consume, fail, getParserT, initialPos, parseErrorMessage, parseErrorPosition, position, region, runParser)
39-
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, (<?>), (<??>), (<~?>))
40+
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, withRecovery, (<?>), (<??>), (<~?>))
4041
import Parsing.Combinators.Array as Combinators.Array
4142
import Parsing.Expr (Assoc(..), Operator(..), buildExprParser)
4243
import Parsing.Language (haskellDef, haskellStyle, javaStyle)
@@ -688,6 +689,25 @@ main = do
688689
parseErrorTestPosition (string "a\nb\nc\n" *> eof) "a\nb\nc\nd\n" (Position { index: 6, column: 1, line: 4 })
689690
parseErrorTestPosition (string "\ta" *> eof) "\tab" (Position { index: 2, column: 10, line: 1 })
690691

692+
assertEqual' "withRecovery1"
693+
{ actual: runParser " not-an-int here" do
694+
_ <- takeWhile isSpace
695+
withRecovery
696+
( \err -> do
697+
nonint <- takeWhile (not <<< isSpace)
698+
pure $ Left
699+
{ error: err
700+
, input: nonint
701+
}
702+
)
703+
(Right <$> intDecimal)
704+
, expected:
705+
Right $ Left
706+
{ error: ParseError "Expected Int" (Position { index: 2, column: 3, line: 1 })
707+
, input: "not-an-int"
708+
} :: Either ParseError (Either { error :: ParseError, input :: String } Int)
709+
}
710+
691711
assertEqual' "skipSpaces consumes if position advancement issue #200"
692712
{ actual: runParser " " do
693713
skipSpaces

0 commit comments

Comments
 (0)