Skip to content

Commit fda0dc8

Browse files
committed
New combinator: withRecovery
1 parent cf6c741 commit fda0dc8

File tree

3 files changed

+59
-1
lines changed

3 files changed

+59
-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

+36
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,41 @@ 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
210+
-- | on the `ParseError` to get
211+
-- | a recovery parser. Then the input stream will be backtracked to where the
212+
-- | main parser began, and the recovery parser will run.
213+
-- |
214+
-- | The recovery parser should typically consume input until it is safe to
215+
-- | resume normal parsing and then return some data describing the parse
216+
-- | failure and recovery.
217+
-- |
218+
-- | If the recovery parser fails, the original `ParseError` from the main parser
219+
-- | will be returned. There is no way to see the error from the recovery parser.
220+
withRecovery
221+
:: forall s m a
222+
. (ParseError -> ParserT s m a)
223+
-> ParserT s m a
224+
-> ParserT s m a
225+
withRecovery recover (ParserT k1) = ParserT
226+
( mkFn5 \state1 more lift throw done ->
227+
runFn5 k1 state1 more lift
228+
( mkFn2 \state2 err ->
229+
let
230+
(ParserT k2) = recover err
231+
in
232+
runFn5 k2 state1 more lift
233+
--throw
234+
-- https://hackage.haskell.org/package/megaparsec-9.3.0/docs/Text-Megaparsec.html#v:withRecovery
235+
-- “if recovery fails, the original error message is reported as
236+
-- if without withRecovery. In no way can the recovering parser r
237+
-- influence error messages.”
238+
(mkFn2 \_ _ -> runFn2 throw state2 err)
239+
done
240+
)
241+
done
242+
)
243+
208244
-- | Match the phrase `p` as many times as possible.
209245
-- |
210246
-- | 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)