diff --git a/examples/resilient.py b/examples/resilient.py new file mode 100644 index 0000000..961e6b3 --- /dev/null +++ b/examples/resilient.py @@ -0,0 +1,156 @@ +# A grammar based on +# https://matklad.github.io/2023/05/21/resilient-ll-parsing-tutorial.html +from parser import * + + +@rule +def File(): + # TODO: Make lists easier + return _functions + + +@rule +def _functions(): + return Function | (_functions + Function) + + +@rule +def Function(): + return FN + NAME + ParamList + opt(ARROW + TypeExpr) + Block + + +@rule +def ParamList(): + return LPAREN + opt(_parameters) + RPAREN + + +@rule +def _parameters(): + # NOTE: The ungrammar in the reference does not talk about commas required between parameters + # so this massages it to make them required. Commas are in the list not the param, which + # is more awkward for processing but not terminally so. + return (Param + opt(COMMA)) | (Param + COMMA + _parameters) + + +@rule +def Param(): + return NAME + COLON + TypeExpr + + +@rule +def TypeExpr(): + return NAME + + +@rule +def Block(): + return LCURLY + opt(_statements) + RCURLY + + +@rule +def _statements(): + return Stmt | _statements + Stmt + + +@rule +def Stmt(): + return StmtExpr | StmtLet | StmtReturn + + +@rule +def StmtExpr(): + return Expr + SEMICOLON + + +@rule +def StmtLet(): + return LET + NAME + EQUAL + Expr + SEMICOLON + + +@rule +def StmtReturn(): + return RETURN + Expr + SEMICOLON + + +@rule(error_name="expression") +def Expr(): + return ExprLiteral | ExprName | ExprParen | ExprBinary | ExprCall + + +@rule +def ExprLiteral(): + return INT | TRUE | FALSE + + +@rule +def ExprName(): + return NAME + + +@rule +def ExprParen(): + return LPAREN + Expr + RPAREN + + +@rule +def ExprBinary(): + return Expr + (PLUS | MINUS | STAR | SLASH) + Expr + + +@rule +def ExprCall(): + return Expr + ArgList + + +@rule +def ArgList(): + return LPAREN + opt(_arg_star) + RPAREN + + +@rule +def _arg_star(): + # Again, a deviation from the original. See _parameters. + return (Expr + opt(COMMA)) | (Expr + COMMA + _arg_star) + + +BLANKS = Terminal("BLANKS", Re.set(" ", "\t", "\r", "\n").plus()) + +TRUE = Terminal("TRUE", "true") +FALSE = Terminal("FALSE", "false") +INT = Terminal("INT", Re.set(("0", "9")).plus()) +FN = Terminal("FN", "fn") +ARROW = Terminal("ARROW", "->") +COMMA = Terminal("COMMA", ",") +LPAREN = Terminal("LPAREN", "(") +RPAREN = Terminal("RPAREN", ")") +LCURLY = Terminal("LCURLY", "{") +RCURLY = Terminal("RCURLY", "}") +COLON = Terminal("COLON", ":") +SEMICOLON = Terminal("SEMICOLON", ";") +LET = Terminal("LET", "let") +EQUAL = Terminal("EQUAL", "=") +RETURN = Terminal("RETURN", "return") +PLUS = Terminal("PLUS", "+") +MINUS = Terminal("MINUS", "-") +STAR = Terminal("STAR", "*") +SLASH = Terminal("SLASH", "/") + +NAME = Terminal( + "NAME", + Re.seq( + Re.set(("a", "z"), ("A", "Z"), "_"), + Re.set(("a", "z"), ("A", "Z"), ("0", "9"), "_").star(), + ), +) + +LGrammar = Grammar( + name="L", + start=File, + trivia=[BLANKS], + # Need a little bit of disambiguation for the symbol involved. + precedence=[ + (Assoc.LEFT, [PLUS, MINUS]), + (Assoc.LEFT, [STAR, SLASH]), + (Assoc.LEFT, [LPAREN]), + ], +)