Skip to content

Commit be0bf1a

Browse files
committed
Merge pull request purescript-contrib#16 from stefankoegel/line-and-column-numbers
Add line and column numbers to `ParseError`
2 parents c06eab2 + 7a61c03 commit be0bf1a

File tree

7 files changed

+189
-67
lines changed

7 files changed

+189
-67
lines changed

docs/Module.md

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22

33
## Module Text.Parsing.Parser
44

5-
6-
75
#### `ParseError`
86

97
``` purescript
108
data ParseError
11-
= ParseError { message :: String }
9+
= ParseError { position :: Position, message :: String }
1210
```
1311

1412

@@ -26,25 +24,34 @@ instance showParseError :: Show ParseError
2624
```
2725

2826

27+
#### `PState`
28+
29+
``` purescript
30+
data PState s
31+
= PState { position :: Position, input :: s }
32+
```
33+
34+
`PState` contains the remaining input and current position.
35+
2936
#### `ParserT`
3037

3138
``` purescript
3239
newtype ParserT s m a
33-
= ParserT (s -> m { consumed :: Boolean, result :: Either ParseError a, input :: s })
40+
= ParserT (PState s -> m { position :: Position, consumed :: Boolean, result :: Either ParseError a, input :: s })
3441
```
3542

3643

3744
#### `unParserT`
3845

3946
``` purescript
40-
unParserT :: forall m s a. ParserT s m a -> s -> m { consumed :: Boolean, result :: Either ParseError a, input :: s }
47+
unParserT :: forall m s a. ParserT s m a -> PState s -> m { position :: Position, consumed :: Boolean, result :: Either ParseError a, input :: s }
4148
```
4249

4350

4451
#### `runParserT`
4552

4653
``` purescript
47-
runParserT :: forall m s a. (Monad m) => s -> ParserT s m a -> m (Either ParseError a)
54+
runParserT :: forall m s a. (Monad m) => PState s -> ParserT s m a -> m (Either ParseError a)
4855
```
4956

5057

@@ -160,10 +167,18 @@ fail :: forall m s a. (Monad m) => String -> ParserT s m a
160167
```
161168

162169

170+
#### `parseFailed`
163171

164-
## Module Text.Parsing.Parser.Combinators
172+
``` purescript
173+
parseFailed :: forall s a. s -> Position -> String -> { position :: Position, consumed :: Boolean, result :: Either ParseError a, input :: s }
174+
```
175+
176+
Creates a failed parser state for the remaining input `s` and current position
177+
with an error message.
178+
Most of the time, `fail` should be used instead.
165179

166180

181+
## Module Text.Parsing.Parser.Combinators
167182

168183
#### `(<?>)`
169184

@@ -343,8 +358,6 @@ many1Till :: forall s a m e. (Monad m) => ParserT s m a -> ParserT s m e -> Pars
343358

344359
## Module Text.Parsing.Parser.Expr
345360

346-
347-
348361
#### `Assoc`
349362

350363
``` purescript
@@ -436,9 +449,51 @@ buildExprParser :: forall m s a. (Monad m) => OperatorTable m s a -> ParserT s m
436449

437450

438451

439-
## Module Text.Parsing.Parser.String
452+
## Module Text.Parsing.Parser.Pos
440453

454+
#### `Position`
441455

456+
``` purescript
457+
data Position
458+
= Position { column :: Number, line :: Number }
459+
```
460+
461+
`Position` represents the position of the parser in the input.
462+
- `line` is the current line in the input
463+
- `column` is the column of the next character in the current line that will be parsed
464+
465+
#### `showPosition`
466+
467+
``` purescript
468+
instance showPosition :: Show Position
469+
```
470+
471+
472+
#### `eqPosition`
473+
474+
``` purescript
475+
instance eqPosition :: Eq Position
476+
```
477+
478+
479+
#### `initialPos`
480+
481+
``` purescript
482+
initialPos :: Position
483+
```
484+
485+
The `Position` before any input has been parsed.
486+
487+
#### `updatePosString`
488+
489+
``` purescript
490+
updatePosString :: Position -> String -> Position
491+
```
492+
493+
Updates a `Position` by adding the columns and lines in `String`.
494+
495+
496+
## Module Text.Parsing.Parser.String
442497

443498
#### `eof`
444499

@@ -499,26 +554,24 @@ noneOf :: forall s m a. (Monad m) => [String] -> ParserT String m String
499554

500555
## Module Text.Parsing.Parser.Token
501556

502-
503-
504557
#### `token`
505558

506559
``` purescript
507-
token :: forall m a. (Monad m) => ParserT [a] m a
560+
token :: forall m a. (Monad m) => (a -> Position) -> ParserT [a] m a
508561
```
509562

510563

511564
#### `when`
512565

513566
``` purescript
514-
when :: forall m a. (Monad m) => (a -> Boolean) -> ParserT [a] m a
567+
when :: forall m a. (Monad m) => (a -> Position) -> (a -> Boolean) -> ParserT [a] m a
515568
```
516569

517570

518571
#### `match`
519572

520573
``` purescript
521-
match :: forall a m. (Monad m, Eq a) => a -> ParserT [a] m a
574+
match :: forall a m. (Monad m, Eq a) => (a -> Position) -> a -> ParserT [a] m a
522575
```
523576

524577

examples/Test.purs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Data.Maybe
77

88
import Control.Alt
99
import Control.Alternative
10+
import Control.Apply ((*>))
1011
import Control.Monad.Eff
1112
import Control.Lazy
1213

@@ -17,6 +18,7 @@ import Text.Parsing.Parser.Combinators
1718
import Text.Parsing.Parser.Expr
1819
import Text.Parsing.Parser.String
1920
import Text.Parsing.Parser.Token
21+
import Text.Parsing.Parser.Pos
2022

2123
parens :: forall m a. (Monad m) => ParserT String m a -> ParserT String m a
2224
parens = between (string "(") (string ")")
@@ -31,6 +33,15 @@ parseTest p input = case runParser input p of
3133
Left (ParseError err) -> print err.message
3234
Right result -> print result
3335

36+
parseErrorTestPosition :: forall s a eff. (Show a) => Parser s a -> s -> Position -> Eff (trace :: Trace | eff) Unit
37+
parseErrorTestPosition p input expected = case runParser input p of
38+
Right _ -> print "error: ParseError expected!"
39+
Left (ParseError { position: pos }) -> case expected == pos of
40+
true -> print $ "ok, as expected: " ++ show pos
41+
false -> print $ "error: got " ++ show pos ++ " instead of " ++ show expected
42+
43+
44+
3445
opTest :: Parser String String
3546
opTest = chainl char (do string "+"
3647
return (++)) ""
@@ -91,21 +102,27 @@ main = do
91102
parseTest exprTest "1*2+3/4-5"
92103
parseTest manySatisfyTest "ab?"
93104

105+
let tokpos = const initialPos
94106
print "should be A"
95-
parseTest token [A, B]
107+
parseTest (token tokpos) [A, B]
96108
print "should be B"
97-
parseTest token [B, A]
109+
parseTest (token tokpos) [B, A]
98110

99111
print "should be A"
100-
parseTest (when isA) [A, B]
112+
parseTest (when tokpos isA) [A, B]
101113
print "should fail"
102-
parseTest (when isA) [B, B]
114+
parseTest (when tokpos isA) [B, B]
103115

104116
print "should be A"
105-
parseTest (match A) [A]
117+
parseTest (match tokpos A) [A]
106118
print "should be B"
107-
parseTest (match B) [B]
119+
parseTest (match tokpos B) [B]
108120
print "should be A"
109-
parseTest (match A) [A, B]
121+
parseTest (match tokpos A) [A, B]
110122
print "should fail"
111-
parseTest (match B) [A, B]
123+
parseTest (match tokpos B) [A, B]
124+
125+
parseErrorTestPosition (string "abc") "bcd" (Position { column: 1, line: 1 })
126+
parseErrorTestPosition (string "abc" *> eof) "abcdefg" (Position { column: 4, line: 1 })
127+
parseErrorTestPosition (string "a\nb\nc\n" *> eof) "a\nb\nc\nd\n" (Position { column: 1, line: 4 })
128+
parseErrorTestPosition (string "\ta" *> eof) "\tab" (Position { column: 10, line: 1 })

src/Text/Parsing/Parser.purs

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,42 +19,51 @@ import Control.Monad.Error.Trans
1919
import Control.MonadPlus
2020
import Control.Plus
2121

22+
import Text.Parsing.Parser.Pos
23+
2224
data ParseError = ParseError
2325
{ message :: String
26+
, position :: Position
2427
}
2528

2629
instance errorParseError :: Error ParseError where
27-
noMsg = ParseError { message: "" }
28-
strMsg msg = ParseError { message: msg }
30+
noMsg = ParseError { message: "", position: initialPos }
31+
strMsg msg = ParseError { message: msg, position: initialPos }
2932

3033
instance showParseError :: Show ParseError where
31-
show (ParseError msg) = "ParseError { message: " ++ msg.message ++ " }"
34+
show (ParseError msg) = "ParseError { message: " ++ msg.message ++ ", position: " ++ show msg.position ++ " }"
35+
36+
-- | `PState` contains the remaining input and current position.
37+
data PState s = PState
38+
{ input :: s
39+
, position :: Position
40+
}
3241

33-
newtype ParserT s m a = ParserT (s -> m { input :: s, result :: Either ParseError a, consumed :: Boolean })
42+
newtype ParserT s m a = ParserT (PState s -> m { input :: s, result :: Either ParseError a, consumed :: Boolean, position :: Position })
3443

35-
unParserT :: forall m s a. ParserT s m a -> s -> m { input :: s, result :: Either ParseError a, consumed :: Boolean }
44+
unParserT :: forall m s a. ParserT s m a -> PState s -> m { input :: s, result :: Either ParseError a, consumed :: Boolean, position :: Position }
3645
unParserT (ParserT p) = p
3746

38-
runParserT :: forall m s a. (Monad m) => s -> ParserT s m a -> m (Either ParseError a)
47+
runParserT :: forall m s a. (Monad m) => PState s -> ParserT s m a -> m (Either ParseError a)
3948
runParserT s p = do
4049
o <- unParserT p s
4150
return o.result
4251

4352
type Parser s a = ParserT s Identity a
4453

4554
runParser :: forall s a. s -> Parser s a -> Either ParseError a
46-
runParser s = runIdentity <<< runParserT s
55+
runParser s = runIdentity <<< runParserT (PState { input: s, position: initialPos })
4756

4857
instance functorParserT :: (Functor m) => Functor (ParserT s m) where
4958
(<$>) f p = ParserT $ \s -> f' <$> unParserT p s
5059
where
51-
f' o = { input: o.input, result: f <$> o.result, consumed: o.consumed }
60+
f' o = { input: o.input, result: f <$> o.result, consumed: o.consumed, position: o.position }
5261

5362
instance applyParserT :: (Monad m) => Apply (ParserT s m) where
5463
(<*>) = ap
5564

5665
instance applicativeParserT :: (Monad m) => Applicative (ParserT s m) where
57-
pure a = ParserT $ \s -> pure { input: s, result: Right a, consumed: false }
66+
pure a = ParserT $ \(PState { input: s, position: pos }) -> pure { input: s, result: Right a, consumed: false, position: pos }
5867

5968
instance altParserT :: (Monad m) => Alt (ParserT s m) where
6069
(<|>) p1 p2 = ParserT $ \s -> unParserT p1 s >>= \o ->
@@ -70,29 +79,35 @@ instance alternativeParserT :: (Monad m) => Alternative (ParserT s m)
7079
instance bindParserT :: (Monad m) => Bind (ParserT s m) where
7180
(>>=) p f = ParserT $ \s -> unParserT p s >>= \o ->
7281
case o.result of
73-
Left err -> return { input: o.input, result: Left err, consumed: o.consumed }
74-
Right a -> updateConsumedFlag o.consumed <$> unParserT (f a) o.input
82+
Left err -> return { input: o.input, result: Left err, consumed: o.consumed, position: o.position }
83+
Right a -> updateConsumedFlag o.consumed <$> unParserT (f a) (PState { input: o.input, position: o.position })
7584
where
76-
updateConsumedFlag c o = { input: o.input, consumed: c || o.consumed, result: o.result }
85+
updateConsumedFlag c o = { input: o.input, consumed: c || o.consumed, result: o.result, position: o.position }
7786

7887
instance monadParserT :: (Monad m) => Monad (ParserT s m)
7988

8089
instance monadPlusParserT :: (Monad m) => MonadPlus (ParserT s m)
8190

8291
instance monadTransParserT :: MonadTrans (ParserT s) where
83-
lift m = ParserT $ \s -> (\a -> { input: s, consumed: false, result: Right a }) <$> m
92+
lift m = ParserT $ \(PState { input: s, position: pos }) -> (\a -> { input: s, consumed: false, result: Right a, position: pos }) <$> m
8493

8594
instance monadStateParserT :: (Monad m) => MonadState s (ParserT s m) where
86-
state f = ParserT $ \s ->
95+
state f = ParserT $ \(PState { input: s, position: pos }) ->
8796
return $ case f s of
88-
Tuple a s' -> { input: s', consumed: false, result: Right a }
97+
Tuple a s' -> { input: s', consumed: false, result: Right a, position: pos }
8998

9099
instance lazy1ParserT :: Lazy1 (ParserT s m) where
91100
defer1 f = ParserT $ \s -> unParserT (f unit) s
92101

93102
consume :: forall s m. (Monad m) => ParserT s m Unit
94-
consume = ParserT $ \s -> return { consumed: true, input: s, result: Right unit }
103+
consume = ParserT $ \(PState { input: s, position: pos }) -> return { consumed: true, input: s, result: Right unit, position: pos }
95104

96105
fail :: forall m s a. (Monad m) => String -> ParserT s m a
97-
fail message = ParserT $ \s -> return { input: s, consumed: false, result: Left (ParseError { message: message }) }
106+
fail message = ParserT $ \(PState { input: s, position: pos }) -> return $ parseFailed s pos message
107+
108+
-- | Creates a failed parser state for the remaining input `s` and current position
109+
-- | with an error message.
110+
-- | Most of the time, `fail` should be used instead.
111+
parseFailed :: forall s a. s -> Position -> String -> { input :: s, result :: Either ParseError a, consumed :: Boolean, position :: Position }
112+
parseFailed s pos message = { input: s, consumed: false, result: Left (ParseError { message: message, position: pos }), position: pos }
98113

src/Text/Parsing/Parser/Combinators.purs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ optionMaybe :: forall m s a. (Functor m, Monad m) => ParserT s m a -> ParserT s
3838
optionMaybe p = option Nothing (Just <$> p)
3939

4040
try :: forall m s a. (Functor m) => ParserT s m a -> ParserT s m a
41-
try p = ParserT $ \s -> try' s <$> unParserT p s
41+
try p = ParserT $ \(PState { input: s, position: pos }) -> try' s pos <$> unParserT p (PState { input: s, position: pos })
4242
where
43-
try' s o@{ result = Left _ } = { input: s, result: o.result, consumed: false }
44-
try' _ o = o
43+
try' s pos o@{ result = Left _ } = { input: s, result: o.result, consumed: false, position: pos }
44+
try' _ _ o = o
4545

4646
sepBy :: forall m s a sep. (Monad m) => ParserT s m a -> ParserT s m sep -> ParserT s m [a]
4747
sepBy p sep = sepBy1 p sep <|> return []
@@ -117,9 +117,9 @@ skipMany1 p = do
117117
return unit
118118

119119
lookAhead :: forall s a m. (Monad m) => ParserT s m a -> ParserT s m a
120-
lookAhead (ParserT p) = ParserT \s -> do
121-
state <- p s
122-
return state{input = s, consumed = false}
120+
lookAhead (ParserT p) = ParserT \(PState { input: s, position: pos }) -> do
121+
state <- p (PState { input: s, position: pos })
122+
return state{input = s, consumed = false, position = pos}
123123

124124
notFollowedBy :: forall s a m. (Monad m) => ParserT s m a -> ParserT s m Unit
125125
notFollowedBy p = try $ (try p *> fail "Negated parser succeeded") <|> return unit

0 commit comments

Comments
 (0)