|
- #
- # This is a toy example that compiles Python directly to bytecode, without generating an AST.
- # It currently only works for very very simple Python code.
- #
- # It requires the 'bytecode' library. You can get it using
- #
- # $ pip install bytecode
- #
-
- from lark import Lark, Transformer, v_args
- from lark.indenter import Indenter
-
- from bytecode import Instr, Bytecode
-
- class PythonIndenter(Indenter):
- NL_type = '_NEWLINE'
- OPEN_PAREN_types = ['LPAR', 'LSQB', 'LBRACE']
- CLOSE_PAREN_types = ['RPAR', 'RSQB', 'RBRACE']
- INDENT_type = '_INDENT'
- DEDENT_type = '_DEDENT'
- tab_len = 8
-
-
- @v_args(inline=True)
- class Compile(Transformer):
- def number(self, n):
- return [Instr('LOAD_CONST', int(n))]
- def string(self, s):
- return [Instr('LOAD_CONST', s[1:-1])]
- def var(self, n):
- return [Instr('LOAD_NAME', n)]
-
- def arith_expr(self, a, op, b):
- # TODO support chain arithmetic
- assert op == '+'
- return a + b + [Instr('BINARY_ADD')]
-
- def arguments(self, args):
- return args
-
- def funccall(self, name, args):
- return name + args + [Instr('CALL_FUNCTION', 1)]
-
- @v_args(inline=False)
- def file_input(self, stmts):
- return sum(stmts, []) + [Instr("RETURN_VALUE")]
-
- def expr_stmt(self, lval, rval):
- # TODO more complicated than that
- name ,= lval
- assert name.name == 'LOAD_NAME' # XXX avoid with another layer of abstraction
- return rval + [Instr("STORE_NAME", name.arg)]
-
- def __default__(self, *args):
- assert False, args
-
-
- python_parser3 = Lark.open('python3.lark', rel_to=__file__, start='file_input',
- parser='lalr', postlex=PythonIndenter(),
- transformer=Compile(), propagate_positions=False)
-
- def compile_python(s):
- insts = python_parser3.parse(s+"\n")
- return Bytecode(insts).to_code()
-
- code = compile_python("""
- a = 3
- b = 5
- print("Hello World!")
- print(a+(b+2))
- print((a+b)+2)
- """)
- exec(code)
- # -- Output --
- # Hello World!
- # 10
- # 10
|