Skip to content

Commit a7ca658

Browse files
Correctly parse ado without any statements in it (#46)
* Correctly parse ado without any statements in it * Tests for ado/in, with empty cases and recovery * Inline tweaked layout helper with comments explaining why * Update src/PureScript/CST/Parser.purs --------- Co-authored-by: Nathan Faubion <[email protected]>
1 parent 5afff30 commit a7ca658

File tree

5 files changed

+89
-11
lines changed

5 files changed

+89
-11
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ slowest parse times along with the mean parse time for the set.
6767
npm run parse-package-set
6868
```
6969

70-
You can also benchmark a single file:
70+
You can also benchmark or parse a single file:
7171

7272
```sh
7373
npm run bench-file MyModule.purs
74+
npm run parse-file -- MyModule.purs --tokens
7475
```

bench/ParseFile.purs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import PureScript.CST.Types (SourceToken)
2525

2626
main :: Effect Unit
2727
main = launchAff_ do
28-
args <- Array.drop 2 <$> liftEffect Process.argv
28+
args <- Array.drop 1 <$> liftEffect Process.argv
2929
let printTokens = (elem "--tokens" || elem "-t") args
3030
case Array.head args of
3131
Just fileName -> do

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"scripts": {
44
"parse-package-set": "spago -x parse-package-set/parse-package-set.dhall run",
55
"bench-file": "spago -x bench/bench.dhall build && node --expose-gc --input-type=\"module\" -e \"import { main } from './output/BenchFile/index.js';main()\"",
6+
"parse-file": "spago -x bench/bench.dhall build && node --input-type=\"module\" -e \"import { main } from './output/ParseFile/index.js';main()\" --",
67
"format": "purs-tidy format-in-place src test bench parse-package-set",
78
"check": "purs-tidy check src test bench parse-package-set"
89
},

src/PureScript/CST/Parser.purs

+14-9
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,6 @@ layoutNonEmpty valueParser = ado
9595
tail <- many (tokLayoutSep *> valueParser) <* tokLayoutEnd
9696
in NonEmptyArray.cons' head tail
9797

98-
layout :: forall a. Parser a -> Parser (Array a)
99-
layout valueParser =
100-
tokLayoutStart *> values <* tokLayoutEnd
101-
where
102-
values = (go =<< valueParser) <|> pure []
103-
tail = many (tokLayoutSep *> valueParser)
104-
go head = Array.cons head <$> tail
105-
10698
parseModule :: Parser (Recovered Module)
10799
parseModule = do
108100
header <- parseModuleHeader
@@ -673,7 +665,20 @@ parseDo = do
673665
parseAdo :: Parser (Recovered Expr)
674666
parseAdo = do
675667
keyword <- tokQualifiedKeyword "ado"
676-
statements <- layout (recoverDoStatement parseDoStatement)
668+
-- A possibly-empty version of `layoutNonEmpty` to handle empty `ado in`
669+
statements <- do
670+
let
671+
-- `recoverDoStatement` recovers too much if it is immediately
672+
-- confronted with `TokLayoutEnd`, since that is associated with a
673+
-- `layoutStack` _of the parent_ as opposed to the stuff we actually
674+
-- want to recover, which we would correctly guess if we saw a statement
675+
-- or two inside the block
676+
valueParser = recoverDoStatement parseDoStatement
677+
nonEmptyCase =
678+
Array.cons <$> valueParser <*> many (tokLayoutSep *> valueParser)
679+
_ <- tokLayoutStart
680+
-- So we explicitly handle `TokLayoutEnd` ahead of time:
681+
[] <$ tokLayoutEnd <|> nonEmptyCase <* tokLayoutEnd
677682
in_ <- tokKeyword "in"
678683
result <- parseExpr
679684
pure $ ExprAdo { keyword, statements, in: in_, result }

test/Main.purs

+71
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,77 @@ main = do
7777
_ ->
7878
false
7979

80+
assertParse "Recovered ado statements"
81+
"""
82+
ado
83+
foo <- bar
84+
a b c +
85+
foo
86+
in 5
87+
"""
88+
case _ of
89+
ParseSucceededWithErrors (ExprAdo { statements }) _
90+
| [ DoBind _ _ _
91+
, DoError _
92+
, DoDiscard _
93+
] <- statements ->
94+
true
95+
_ ->
96+
false
97+
98+
assertParse "Recovered ado last statement"
99+
"""
100+
ado
101+
foo <- bar
102+
a b c +
103+
in 5
104+
"""
105+
case _ of
106+
ParseSucceededWithErrors (ExprAdo { statements }) _
107+
| [ DoBind _ _ _
108+
, DoError _
109+
] <- statements ->
110+
true
111+
_ ->
112+
false
113+
114+
assertParse "Recovered ado first statement"
115+
"""
116+
ado
117+
a b c +
118+
foo <- bar
119+
in 5
120+
"""
121+
case _ of
122+
ParseSucceededWithErrors (ExprAdo { statements }) _
123+
| [ DoError _
124+
, DoBind _ _ _
125+
] <- statements ->
126+
true
127+
_ ->
128+
false
129+
130+
assertParse "Empty ado in"
131+
"""
132+
ado in 1
133+
"""
134+
case _ of
135+
(ParseSucceeded _ :: RecoveredParserResult Expr) ->
136+
true
137+
_ ->
138+
false
139+
140+
assertParse "Empty ado \\n in"
141+
"""
142+
ado
143+
in 1
144+
"""
145+
case _ of
146+
(ParseSucceeded _ :: RecoveredParserResult Expr) ->
147+
true
148+
_ ->
149+
false
150+
80151
assertParse "Recovered let bindings"
81152
"""
82153
let

0 commit comments

Comments
 (0)