diff --git a/examples/lens.py b/examples/lens.py new file mode 100644 index 0000000..db5ae61 --- /dev/null +++ b/examples/lens.py @@ -0,0 +1,233 @@ +from parser import * + + +@rule +def expression(): + return alt( + TRUE | FALSE | NUMBER | NAME, + LPAREN + expression + RPAREN, + binary_expression, + object_expression, + with_expression, + function_expression, + invoke_expression, + member_expression, + statement_block_expression, + ) + + +@rule +def binary_expression(): + return alt( + expression + PLUS + expression, + expression + STAR + expression, + expression + MINUS + expression, + expression + SLASH + expression, + expression + LESSER + expression, + expression + GREATER + expression, + expression + LESSER_EQUAL + expression, + expression + GREATER_EQUAL + expression, + expression + EQUAL_EQUAL + expression, + expression + AND + expression, + expression + OR + expression, + ) + + +@rule +def object_expression(): + return LCURLY + zero_or_more(value_pair + opt(COMMA)) + RCURLY + + +@rule +def value_pair(): + return value_name + opt(EQUAL, expression) + + +@rule +def value_name(): + return NAME | LSQUARE + expression + RSQUARE + + +@rule +def with_expression(): + return WITH + object_expression + YIELD + expression + + +@rule +def function_expression(): + return FN + opt(param_list) + FAT_ARROW + expression + + +@rule +def param_list(): + return zero_or_more(NAME + COMMA) + NAME + opt(COMMA) + + +@rule +def invoke_expression(): + return seq( + expression, + LPAREN, + opt( + zero_or_more(expression + COMMA), + expression + opt(COMMA), + ), + RPAREN, + ) + + +@rule +def member_expression(): + return expression + DOT + NAME + + +@rule +def statement_block_expression(): + return DO + statement_list + YIELD + expression + + +@rule +def statement_list(): + return zero_or_more(_statement) + + +@rule +def _statement(): + return alt( + assignment_statement, + if_statement, + declaration_statement, + while_loop, + ) + + +@rule +def assignment_statement(): + return ( + NAME + + alt(COLON_EQUAL, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL) + + expression + + SEMICOLON + ) + + +@rule +def if_statement(): + return IF + expression + THEN + statement_list + END + + +@rule +def declaration_statement(): + return VAR + NAME + COLON_EQUAL + expression + SEMICOLON + + +@rule +def while_loop(): + return WHILE + expression + DO + statement_list + END + + +BLANKS = Terminal("BLANKS", Re.set(" ", "\t", "\r", "\n").plus()) +COMMENT = Terminal("COMMENT", Re.seq(Re.literal("//"), Re.set("\n").invert().star())) + +TRUE = Terminal("TRUE", "true") +FALSE = Terminal("FALSE", "false") + +NUMBER = Terminal( + "NUMBER", + Re.seq( + Re.set(("0", "9")).plus(), + Re.seq( + Re.literal("."), + Re.set(("0", "9")).plus(), + ).question(), + Re.seq( + Re.set("e", "E"), + Re.set("+", "-").question(), + Re.set(("0", "9")).plus(), + ).question(), + ), + highlight=highlight.constant.numeric, +) + +FN = Terminal("FN", "fn") +ARROW = Terminal("ARROW", "->") +FAT_ARROW = Terminal("FAT_ARROW", "=>") + +COMMA = Terminal("COMMA", ",") +LPAREN = Terminal("LPAREN", "(") +RPAREN = Terminal("RPAREN", ")") +LCURLY = Terminal("LCURLY", "{") +RCURLY = Terminal("RCURLY", "}") +LSQUARE = Terminal("LSQUARE", "[") +RSQUARE = Terminal("RSQUARE", "]") +COLON = Terminal("COLON", ":") +SEMICOLON = Terminal("SEMICOLON", ";") +LET = Terminal("LET", "let") +EQUAL = Terminal("EQUAL", "=") +RETURN = Terminal("RETURN", "return") +PLUS = Terminal("PLUS", "+") +PLUS_EQUAL = Terminal("PLUS_EQUAL", "+=") +MINUS = Terminal("MINUS", "-") +MINUS_EQUAL = Terminal("MINUS_EQUAL", "-=") +STAR = Terminal("STAR", "*") +STAR_EQUAL = Terminal("STAR_EQUAL", "*=") +SLASH = Terminal("SLASH", "/") +SLASH_EQUAL = Terminal("SLASH_EQUAL", "/=") +DOT = Terminal("DOT", ".") + +# Design: A different assignment operator to make sure we +# don't get confused. +COLON_EQUAL = Terminal("COLON_EQUAL", ":=") + +GREATER = Terminal("GREATER", ">") +GREATER_EQUAL = Terminal("GREATER_EQUAL", ">=") +LESSER = Terminal("LESSER", "<") +LESSER_EQUAL = Terminal("LESSER_EQUAL", "<=") +EQUAL_EQUAL = Terminal("EQUAL_EQUAL", "==") +AND = Terminal("AND", "and") +OR = Terminal("OR", "or") +NOT = Terminal("NOT", "not") + +# I like `DO -> YIELD` instead of `BEGIN -> YIELD` +# A `YIELD` at the end instead of a generic "last expression" +# setup because I want to be explicit that blocks yield values +# and you don't get the wrong one by accident! +DO = Terminal("DO", "do") +IF = Terminal("IF", "if") +THEN = Terminal("THEN", "then") +END = Terminal("END", "end") +WHILE = Terminal("WHILE", "while") + +# I just like explicit variable declaration, otherwise +# sometimes you don't mean to make a new variable but +# make a typeo and do. :( +# +# This also allows you to control shadowing. +VAR = Terminal("VAR", "var") + + +WITH = Terminal("WITH", "with") +YIELD = Terminal("YIELD", "yield") + +NAME = Terminal( + "NAME", + Re.seq( + Re.set(("a", "z"), ("A", "Z"), "_"), + Re.set(("a", "z"), ("A", "Z"), ("0", "9"), "_").star(), + ), +) + +LensGrammar = Grammar( + name="Lens", + start=expression, + trivia=[BLANKS, COMMENT], + precedence=[ + (Assoc.LEFT, [OR]), + (Assoc.LEFT, [AND]), + (Assoc.LEFT, [EQUAL_EQUAL, LESSER_EQUAL, LESSER, GREATER_EQUAL, GREATER]), + (Assoc.LEFT, [PLUS, MINUS]), + (Assoc.LEFT, [STAR, SLASH]), + (Assoc.LEFT, [LPAREN]), + (Assoc.LEFT, [DOT]), + ], +)