Skip to content

Commit d3be1b0

Browse files
Improve default error (#57)
* Breaking change: runParser prints error message and position on failure * Change ParseError to a record type with error message and position * Remove 'continuation' comments in docs * Provide contrast in runParser's docs to unParser docs
1 parent aff7f2b commit d3be1b0

File tree

2 files changed

+27
-25
lines changed

2 files changed

+27
-25
lines changed

src/Text/Parsing/StringParser.purs

+21-19
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Control.MonadPlus (class MonadPlus, class MonadZero, class Alternative)
99
import Control.Monad.Rec.Class (class MonadRec, tailRecM, Step(..))
1010
import Control.Plus (class Plus, class Alt)
1111
import Control.Lazy (class Lazy)
12-
import Data.Bifunctor (bimap, lmap)
12+
import Data.Bifunctor (lmap)
1313
import Data.Either (Either(..))
1414

1515
-- | A position in an input string.
@@ -26,26 +26,28 @@ type Pos = Int
2626
type PosString = { str :: String, pos :: Pos }
2727

2828
-- | The type of parsing errors.
29-
newtype ParseError = ParseError String
30-
31-
instance showParseError :: Show ParseError where
32-
show (ParseError msg) = msg
33-
34-
derive instance eqParseError :: Eq ParseError
35-
36-
derive instance ordParseError :: Ord ParseError
37-
38-
-- | A parser is represented as a function which takes a pair of
39-
-- | continuations for failure and success.
40-
newtype Parser a = Parser (PosString -> Either { pos :: Pos, error :: ParseError } { result :: a, suffix :: PosString })
41-
42-
-- | Run a parser by providing success and failure continuations.
43-
unParser :: forall a. Parser a -> PosString -> Either { pos :: Pos, error :: ParseError } { result :: a, suffix :: PosString }
29+
type ParseError = { error :: String, pos :: Pos }
30+
31+
-- | A parser is represented as a function that, when successful, returns
32+
-- | a result and the position where the parse finished or, when it fails,
33+
-- | a ParserError with more information on where and why it failed.
34+
-- | See also `printParserError`.
35+
newtype Parser a = Parser (PosString -> Either ParseError { result :: a, suffix :: PosString })
36+
37+
-- | Run a parser, allowing the caller to define where to start within the
38+
-- | input `String` and what to do with the unchanged output of the Parser.
39+
-- | See `runparser` for more typical usages.
40+
unParser :: forall a. Parser a -> PosString -> Either ParseError { result :: a, suffix :: PosString }
4441
unParser (Parser p) = p
4542

46-
-- | Run a parser for an input string, returning either an error or a result.
43+
-- | Run a parser for an input string. See also `printParserError`
44+
-- | and `unParser` for more flexible usages.
4745
runParser :: forall a. Parser a -> String -> Either ParseError a
48-
runParser (Parser p) s = bimap _.error _.result (p { str: s, pos: 0 })
46+
runParser (Parser p) s = map _.result (p { str: s, pos: 0 })
47+
48+
-- | Prints a ParseError's the error message and the position of the error.
49+
printParserError :: ParseError -> String
50+
printParserError rec = rec.error <> "; pos = " <> show rec.pos
4951

5052
instance functorParser :: Functor Parser where
5153
map f (Parser p) = Parser (map (\{ result, suffix } -> { result: f result, suffix }) <<< p)
@@ -93,7 +95,7 @@ instance lazyParser :: Lazy (Parser a) where
9395

9496
-- | Fail with the specified message.
9597
fail :: forall a. String -> Parser a
96-
fail msg = Parser \{ pos } -> Left { pos, error: ParseError msg }
98+
fail error = Parser \{ pos } -> Left { pos, error }
9799

98100
-- | In case of error, the default behavior is to backtrack if no input was consumed.
99101
-- |

src/Text/Parsing/StringParser/CodeUnits.purs

+6-6
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ import Data.String.CodeUnits as SCU
3737
import Data.String.Pattern (Pattern(..))
3838
import Data.String.Regex as Regex
3939
import Data.String.Regex.Flags (noFlags)
40-
import Text.Parsing.StringParser (Parser(..), ParseError(..), try, fail)
40+
import Text.Parsing.StringParser (Parser(..), try, fail)
4141
import Text.Parsing.StringParser.Combinators (many, (<?>))
4242

4343
-- | Match the end of the file.
4444
eof :: Parser Unit
4545
eof = Parser \s ->
4646
case s of
47-
{ str, pos } | pos < SCU.length str -> Left { pos, error: ParseError "Expected EOF" }
47+
{ str, pos } | pos < SCU.length str -> Left { pos, error: "Expected EOF" }
4848
_ -> Right { result: unit, suffix: s }
4949

5050
-- | Match any character. This is limited by `Char` to any code points
@@ -54,14 +54,14 @@ anyChar :: Parser Char
5454
anyChar = Parser \{ str, pos } ->
5555
case charAt pos str of
5656
Just chr -> Right { result: chr, suffix: { str, pos: pos + 1 } }
57-
Nothing -> Left { pos, error: ParseError "Unexpected EOF" }
57+
Nothing -> Left { pos, error: "Unexpected EOF" }
5858

5959
-- | Match any code point, including those above `0xFFFF`
6060
anyCodePoint :: Parser SCP.CodePoint
6161
anyCodePoint = Parser \rec@{ str, pos } ->
6262
case SCP.codePointAt 0 (SCU.drop pos str) of
6363
Just cp -> Right { result: cp, suffix: { str, pos: pos + SCU.length (SCP.singleton cp) } }
64-
Nothing -> Left { pos, error: ParseError "Unexpected EOF" }
64+
Nothing -> Left { pos, error: "Unexpected EOF" }
6565

6666
-- | Match any digit.
6767
anyDigit :: Parser Char
@@ -76,7 +76,7 @@ string :: String -> Parser String
7676
string nt = Parser \s ->
7777
case s of
7878
{ str, pos } | SCU.indexOf' (Pattern nt) pos str == Just pos -> Right { result: nt, suffix: { str, pos: pos + SCU.length nt } }
79-
{ pos } -> Left { pos, error: ParseError ("Expected '" <> nt <> "'.") }
79+
{ pos } -> Left { pos, error: "Expected '" <> nt <> "'." }
8080

8181
-- | Match a character satisfying the given predicate.
8282
satisfy :: (Char -> Boolean) -> Parser Char
@@ -158,4 +158,4 @@ regex pat =
158158
Just (Just matched) ->
159159
Right { result: matched, suffix: { str, pos: pos + SCU.length matched } }
160160
_ ->
161-
Left { pos, error: ParseError "no match" }
161+
Left { pos, error: "no match" }

0 commit comments

Comments
 (0)