@@ -0,0 +1,105 @@ | |||||
""" | |||||
This example demonstrates how to transform a parse-tree into an AST using `lark.ast_utils`. | |||||
This example only works with Python 3. | |||||
""" | |||||
import sys | |||||
from typing import List | |||||
from dataclasses import dataclass | |||||
from lark import Lark, ast_utils, Transformer, v_args | |||||
this_module = sys.modules[__name__] | |||||
# | |||||
# Define AST | |||||
# | |||||
class _Ast(ast_utils.Ast): | |||||
pass | |||||
class _Statement(_Ast): | |||||
pass | |||||
@dataclass | |||||
class Value(_Ast): | |||||
value: object | |||||
@dataclass | |||||
class Name(_Ast): | |||||
name: str | |||||
@dataclass | |||||
class CodeBlock(_Ast, ast_utils.AsList): | |||||
statements: List[_Statement] | |||||
@dataclass | |||||
class If(_Statement): | |||||
cond: Value | |||||
then: CodeBlock | |||||
@dataclass | |||||
class SetVar(_Statement): | |||||
name: str | |||||
value: Value | |||||
@dataclass | |||||
class Print(_Statement): | |||||
value: Value | |||||
class ToAst(Transformer): | |||||
def STRING(self, s): | |||||
# Remove quotation marks | |||||
return s[1:-1] | |||||
def DEC_NUMBER(self, n): | |||||
return int(n) | |||||
@v_args(inline=True) | |||||
def start(self, x): | |||||
return x | |||||
# | |||||
# Define Parser | |||||
# | |||||
parser = Lark(""" | |||||
start: code_block | |||||
code_block: statement+ | |||||
?statement: if | set_var | print | |||||
if: "if" value "{" code_block "}" | |||||
set_var: NAME "=" value ";" | |||||
print: "print" value ";" | |||||
value: name | STRING | DEC_NUMBER | |||||
name: NAME | |||||
%import python (NAME, STRING, DEC_NUMBER) | |||||
%import common.WS | |||||
%ignore WS | |||||
""", | |||||
parser="lalr", | |||||
) | |||||
transformer = ast_utils.create_transformer(this_module, ToAst()) | |||||
def parse(text): | |||||
return transformer.transform(parser.parse(text)) | |||||
# | |||||
# Test | |||||
# | |||||
if __name__ == '__main__': | |||||
print(parse(""" | |||||
a = 1; | |||||
if a { | |||||
print "a is 1"; | |||||
a = 2; | |||||
} | |||||
""")) |
@@ -0,0 +1,51 @@ | |||||
""" | |||||
Module of utilities for transforming a lark.Tree into a custom Abstract Syntax Tree | |||||
""" | |||||
import inspect, re | |||||
from lark import Transformer, v_args | |||||
class Ast(object): | |||||
"""Abstract class | |||||
Subclasses will be collected by `create_transformer()` | |||||
""" | |||||
pass | |||||
class AsList(object): | |||||
"""Abstract class | |||||
Subclasses will be instanciated with the parse results as a single list, instead of as arguments. | |||||
""" | |||||
def camel_to_snake(name): | |||||
return re.sub(r'(?<!^)(?=[A-Z])', '_', name).lower() | |||||
def _call(func, _data, children, _meta): | |||||
return func(*children) | |||||
inline = v_args(wrapper=_call) | |||||
def create_transformer(ast_module, transformer=None): | |||||
"""Collects `Ast` subclasses from the given module, and creates a Lark transformer that builds the AST. | |||||
For each class, we create a corresponding rule in the transformer, with a matching name. | |||||
CamelCase names will be converted into snake_case. Example: "CodeBlock" -> "code_block". | |||||
Parameters: | |||||
ast_module - A Python module containing all the subclasses of `ast_utils.Ast` | |||||
Classes starting with an underscore (`_`) will be skipped. | |||||
transformer (Optional[Transformer]) - An initial transformer. Its attributes may be overwritten. | |||||
""" | |||||
t = transformer or Transformer() | |||||
for name, obj in inspect.getmembers(ast_module): | |||||
if not name.startswith('_') and inspect.isclass(obj): | |||||
if issubclass(obj, Ast): | |||||
if not issubclass(obj, AsList): | |||||
obj = inline(obj).__get__(t) | |||||
setattr(t, camel_to_snake(name), obj) | |||||
return t |