Skip to content

Commit 3db0cd8

Browse files
authored
Merge pull request #559 from axic/yup-parser-pkeyword-backtracking
Fix parser `pKeyword` backtracking in Yul
2 parents 3b9b3c6 + 874b5a4 commit 3db0cd8

2 files changed

Lines changed: 40 additions & 5 deletions

File tree

src/Language/Yul/Parser.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ commaSep :: Parser a -> Parser [a]
4646
commaSep p = p `sepBy` symbol ","
4747

4848
pKeyword :: String -> Parser String
49-
pKeyword w = lexeme (string w <* notFollowedBy identChar)
49+
pKeyword w = try $ lexeme (string w <* notFollowedBy identChar)
5050

5151
pMeta :: Parser String
5252
pMeta =
@@ -70,8 +70,8 @@ yulLiteral =
7070
*> choice
7171
[ YulNumber <$> integer,
7272
YulString <$> stringLiteral,
73-
YulTrue <$ try (pKeyword "true"),
74-
YulFalse <$ try (pKeyword "false")
73+
YulTrue <$ pKeyword "true",
74+
YulFalse <$ pKeyword "false"
7575
]
7676

7777
yulStmt :: Parser YulStmt
@@ -106,7 +106,7 @@ yulCase = do
106106

107107
yulFun :: Parser YulStmt
108108
yulFun = do
109-
_ <- try (pKeyword "function")
109+
_ <- pKeyword "function"
110110
name <- pName
111111
args <- parens (commaSep pName)
112112
rets <- optional (symbol "->" *> commaSep pName)

test/YulParserTests.hs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ yulParserTests =
3131
"Yul parser"
3232
[ functionKeywordTests,
3333
boolLiteralTests,
34-
switchTests
34+
switchTests,
35+
statementKeywordTests
3536
]
3637

3738
-- Bug 1: yulFun matched the `function` keyword with `symbol "function"`, which
@@ -70,6 +71,40 @@ boolLiteralTests =
7071
parsesAs yulExp "false" (YLit YulFalse)
7172
]
7273

74+
-- Bug 4: pKeyword ran `string` (consuming input) before the
75+
-- `notFollowedBy identChar` boundary check, and was not wrapped in `try`. In a
76+
-- `choice`, a keyword parser that consumed a prefix and then failed the
77+
-- boundary check aborted the whole choice instead of falling through to a
78+
-- later alternative -- so a statement whose leading identifier merely starts
79+
-- with a keyword (e.g. `format` vs `for`) failed to parse. Every
80+
-- statement-level keyword is affected, including the merged
81+
-- break/continue/leave.
82+
statementKeywordTests :: TestTree
83+
statementKeywordTests =
84+
testGroup
85+
"statement keyword boundary"
86+
( [ testCase (kw ++ "-prefixed identifier parses as a call statement") $
87+
parsesAs yulStmt (kw ++ "ish") (YExp (yIdent (kw ++ "ish")))
88+
| kw <- ["let", "if", "for", "switch", "case", "default", "break", "continue", "leave"]
89+
]
90+
++
91+
-- The canonical case from the bug report: `format` must not be read as a
92+
-- `for` loop.
93+
[ testCase "`format` parses as an identifier, not a for loop" $
94+
parsesAs yulStmt "format" (YExp (yIdent "format")),
95+
-- Regression guards: the bare keyword statements still parse.
96+
testCase "bare break parses" $ parsesAs yulStmt "break" YBreak,
97+
testCase "bare continue parses" $ parsesAs yulStmt "continue" YContinue,
98+
testCase "bare leave parses" $ parsesAs yulStmt "leave" YLeave,
99+
testCase "genuine let still parses" $
100+
parsesAs yulStmt "let x" (YLet [Name "x"] Nothing),
101+
testCase "genuine if still parses" $
102+
parsesAs yulStmt "if cond { }" (YIf (yIdent "cond") []),
103+
testCase "genuine for still parses" $
104+
parsesAs yulStmt "for { } cond { } { }" (YFor [] (yIdent "cond") [] [])
105+
]
106+
)
107+
73108
-- Bug 3: YSwitch used `many yulCase`, so a switch with zero `case` clauses
74109
-- parsed successfully. Yul requires at least one case.
75110
switchTests :: TestTree

0 commit comments

Comments
 (0)