import ast from collections import Iterable, Sequence, MutableSequence from functools import reduce from pathlib import Path from os import chdir from os.path import normpath, abspath import astunparse from mochi import GE_PYTHON_34, IS_PYPY from mochi.parser import Symbol, Keyword, parse, lex, get_temp_name from .utils import issequence_except_str from .constants import * from .exceptions import MochiSyntaxError, DuplicatedDefError from .global_env import global_env if GE_PYTHON_34: expr_and_stmt_ast = (ast.Expr, ast.If, ast.For, ast.FunctionDef, ast.Assign, ast.Delete, ast.Try, ast.Raise, ast.With, ast.While, ast.Break, ast.Return, ast.Continue, ast.ClassDef, ast.Import, ast.ImportFrom, ast.Pass, ast.Assert) else: expr_and_stmt_ast = (ast.Expr, ast.If, ast.For, ast.FunctionDef, ast.Assign, ast.Delete, ast.TryFinally, ast.TryExcept, ast.Raise, ast.With, ast.While, ast.Break, ast.Return, ast.Continue, ast.ClassDef, ast.Import, ast.ImportFrom, ast.Pass) syntax_table = {} #-- double builtins :( def chunks(l, n): for i in range(0, len(l), n): yield l[i:i + n] def tuple_it(obj): return tuple(map(tuple_it, obj)) if isinstance(obj, MutableSequence) else obj #--- def syntax(*names): def _syntax(func): for name in names: syntax_table[name] = func if name not in global_env.keys(): global_env[name] = 'syntax_' + name return func return _syntax required_filenames_stack = [set()] def lexical_scope_for_require(func): def _lexical_scope(*args, **kwargs): try: required_filenames_stack.append(set()) return func(*args, **kwargs) finally: required_filenames_stack.pop() return _lexical_scope def lexical_scope(quasi=False): def _lexical_scope(func): def __lexical_scope(*args, **kwargs): try: scope = Scope(quasi) if quasi: binding_name_set_stack[-1].add_child(scope) binding_name_set_stack.append(scope) return func(*args, **kwargs) finally: if binding_name_set_stack[-1] is scope: binding_name_set_stack.pop() return __lexical_scope return _lexical_scope def flatten_list(lis): i = 0 while i < len(lis): while isinstance(lis[i], Iterable): if not lis[i]: lis.pop(i) i -= 1 break else: lis[i:i + 1] = lis[i] i += 1 return lis def add_binding_name(binding_name, file_name): return binding_name_set_stack[-1].add_binding_name(binding_name, file_name) class Translator(object): def __init__(self, macro_table={}, filename='<string>'): self.hidden_vars = [] self.macro_table = macro_table self.filename = filename def translate_block(self, mochi_block, filename='<string>', show_tokens=False): """Translate sexpressions into a Python AST. """ sexps = parse(lex(mochi_block, debug=show_tokens), filename) body = [] for sexp in sexps: if isinstance(sexp, MutableSequence): sexp = tuple_it(sexp) if sexp is COMMENT: continue pre, value = self.translate(sexp) body.extend([self.enclose(exp, True) for exp in pre]) body.append(self.enclose(value, True)) return ast.Module(body=body) def translate_file(self, filename, show_tokens=False): """Translate a Mochi file into a Python AST. """ self.filename = filename with open(filename, 'r') as fobj: mochi_block = fobj.read() # TODO: Why this change of directory is needed? #chdir(normpath(str(Path(filename).parent))) return self.translate_block(mochi_block, filename=filename, show_tokens=show_tokens) def translate_loaded_file(self, filename, show_tokens=False): body = [] self.filename = filename with open(filename, 'r') as f: sexps = parse(lex(f.read(), debug=show_tokens), filename) for sexp in sexps: if isinstance(sexp, MutableSequence): sexp = tuple_it(sexp) if sexp is COMMENT: continue pre, value = self.translate(sexp) body.extend(pre) body.append(value) return body def translate_required_file(self, filename): required_filenames = required_filenames_stack[-1] if filename in required_filenames: return self.translate_ref(NONE_SYM)[1], else: required_filenames.add(filename) return self.translate_loaded_file(filename) def translate_loaded_files(self, file_paths): body = [] for file_path in file_paths: if not isinstance(file_path, str): raise MochiSyntaxError(file_path, self.filename) body.extend(self.translate_loaded_file(abspath(file_path))) return body, self.translate_ref(NONE_SYM)[1] def translate_required_files(self, file_paths): body = [] for file_path in file_paths: if not isinstance(file_path, str): raise MochiSyntaxError(file_path, self.filename) body.extend(self.translate_required_file(abspath(file_path))) return body, self.translate_ref(NONE_SYM)[1] def translate_sexp_to_interact(self, sexp): pre, value = self.translate(sexp, False) body = [self.enclose(exp, True) for exp in pre] write_value = ast.Call(func=ast.Name(id='write', ctx=ast.Load(), lineno=1, col_offset=0), ctx=ast.Load(), args=[value], keywords=[], starargs=None, kwargs=None, lineno=1, col_offset=0) body.append(self.enclose(write_value, True)) return ast.Module(body=body) def translate_sexp(self, sexp): # TODO value pre, value = self.translate(sexp, False) body = [self.enclose(exp, True) for exp in pre] return ast.Module(body=body) def is_self_evaluating(self, exp): return isinstance(exp, (int, float, complex, str, bool, type(None), Keyword)) def translate_self_evaluating(self, exp): if isinstance(exp, (int, float, complex)): if hasattr(exp, 'value'): return EMPTY, ast.Num(exp.value, lineno=exp.lineno, col_offset=0) else: return EMPTY, ast.Num(exp, lineno=0, col_offset=0) expType = type(exp) if expType is str: return EMPTY, ast.Str(exp, lineno=0, col_offset=0) if expType is Keyword: #raise SyntaxError('keyword!', self.filename) modast = ast.parse( 'Keyword("{name}", {lineno})'.format(name=exp.name, lineno=exp.lineno)) return EMPTY, modast.body[0].value def _translate_items(self, items): return flatten_list([self.translate(item, False) for item in items]) def is_variable(self, exp): return type(exp) is Symbol def is_get_attrs(self, exp): return type(exp) is Symbol and len(exp.name.split('.')) > 1 def translate_ref(self, exp): return EMPTY, ast.Name(id=exp.name, ctx=ast.Load(), lineno=exp.lineno, col_offset=0) def translate_atom(self, exp): if self.is_self_evaluating(exp): return self.translate_self_evaluating(exp) elif self.is_get_attrs(exp): parts = exp.name.split('.') parts[0] = Symbol(parts[0]) return self.translate( reduce(lambda a, b: (Symbol('getattr'), a, b), parts), False) elif self.is_variable(exp): return self.translate_ref(exp) @syntax('_val') def translate_def(self, exp): if len(exp) != 3: raise MochiSyntaxError(exp, self.filename) return self.translate_assign(exp) def create_assign_target(self, symbol): return ast.Name(id=symbol.name, ctx=ast.Store(), lineno=symbol.lineno, col_offset=0) def create_assign_targets(self, seq): elts = [] for index, item in enumerate(seq): if isinstance(item, Symbol): # TODO errorcheck index + 1 != lastIndex if item == VARG: elts.append( ast.Starred( value=self.create_assign_target( seq[index + 1]), ctx=ast.Store(), lineno=0, # TODO col_offset=0)) break elts.append(self.create_assign_target(item)) elif issequence_except_str(item): if len(item) == 0: raise MochiSyntaxError("can't bind to ()", self.filename) #if item[0] == 'getattr': # attr_exp = self.translate_getattr(item)[1] # attr_exp.ctx = ast.Store() # elts.append(attr_exp) #else: elts.append(self.create_assign_targets(item)) else: raise MochiSyntaxError(item, self.filename) return ast.Tuple(elts=elts, ctx=ast.Store(), lineno=0, # TODO col_offset=0) #@syntax('set') def translate_assign(self, exp, visible=True): if len(exp) != 3: raise MochiSyntaxError(exp, self.filename) left = exp[1] left_type = type(left) if left_type is Symbol: targets = [self.create_assign_target(left)] ref_symbol = left if not visible: self.hidden_vars.append(ref_symbol.name) elif issequence_except_str(left): targets = [self.create_assign_targets(left)] ref_symbol = NONE_SYM else: raise MochiSyntaxError(exp, self.filename) pre = [] right_value_builder, right_value = self.translate(exp[2], False) if type(right_value) is ast.Expr: right_value = right_value.value assign = ast.Assign(targets=targets, value=right_value, lineno=right_value.lineno, col_offset=0) pre.extend(right_value_builder) pre.append(assign) _, ref = self.translate_ref(ref_symbol) return pre, ref #@syntax('del') #def translate_del(self, exp): # return (ast.Delete(targets=[ast.Name(id=exp[1].name, # lineno=exp[1].lineno, # col_offset=exp[1].col_offset, # ctx=ast.Del())], # lineno=exp[0].lineno, # col_offset=exp[0].col_offset),), self.translate_ref(NONE_SYM)[1] @syntax('yield') def translate_yield(self, exp): if len(exp) != 2: raise MochiSyntaxError(exp, self.filename) pre, value = self.translate(exp[1], False) if type(value) is ast.Expr: value = value.value yield_node = ast.Yield(value=value, lineno=exp[0].lineno, col_offset=0) return pre, yield_node @syntax('yield_from') def translate_yield_from(self, exp): if len(exp) != 2: raise MochiSyntaxError(exp, self.filename) pre, value = self.translate(exp[1], False) if type(value) is ast.Expr: value = value.value yield_from_node = ast.YieldFrom(value=value, lineno=exp[0].lineno, col_offset=0) return pre, yield_from_node def _translate_args(self, args): return [ast.arg(arg=arg.name, annotation=None, lineno=arg.lineno, col_offset=0) for arg in args] def _translate_sequence(self, exps, enclose=True): seq = [] for exp in exps: pre, value = self.translate(exp, enclose) seq.extend(pre) seq.append(value) return seq @syntax('do') def translate_do(self, exp, enclose=False): seq = self._translate_sequence(exp[1:], enclose) return seq[:-1], seq[-1] @syntax('if') def translate_if(self, exp): if not len(exp) >= 3: raise MochiSyntaxError(exp, self.filename) temp_var_symbol = get_temp_name() return self._translate_if(exp, temp_var_symbol) def _translate_if(self, exp, temp_var_symbol): if_ast = None cur_if_ast = None pre = [] for i, v in enumerate(exp): if i == 0: pass elif (i % 2) == 0: pre_test, test = self.translate(exp[i - 1], False) pre.extend(pre_test) if if_ast is None: if issequence_except_str(v) and v[0] == Symbol('return'): body, ref = self.translate_return(v) if_ast = ast.If(test=test, body=body, orelse=[], lineno=exp[0].lineno, col_offset=0) else: body, ref = self.translate_assign(('dummy', temp_var_symbol, v), visible=False) if_ast = ast.If(test=test, body=body, orelse=[], lineno=exp[0].lineno, col_offset=0) cur_if_ast = if_ast else: else_if = [Symbol('if'), exp[i - 1], v] else_body, ref = self._translate_if(else_if, temp_var_symbol) cur_if_ast.orelse = else_body cur_if_ast = else_body[0] elif i == (len(exp) - 1): if issequence_except_str(v) and v[0] == Symbol('return'): else_body, ref = self.translate_return(v) cur_if_ast.orelse = else_body else: else_body, ref = self.translate_assign( ('dummy', temp_var_symbol, v), visible=False) cur_if_ast.orelse = else_body if len(if_ast.orelse) == 0: else_body, ref = self.translate_assign( ('dummy', temp_var_symbol, EMPTY_SYM), visible=False) if_ast.orelse = else_body pre.append(if_ast) return pre, ref @syntax('+', '-', '*', '/', '%', '**', '<<', '>>', '|', '^^', '&&', '//') def translate_bin_op(self, exp): if not len(exp) >= 2: raise MochiSyntaxError(exp, self.filename) if len(exp) == 2: return self.translate(exp[1], False) op_symbol = exp[0] op_name = op_symbol.name op = op_ast_map[op_name] if len(exp) == 3: left, right = exp[1], exp[2] left_pre, left_value = self.translate(left, False) right_pre, right_value = self.translate(right, False) pre = left_pre + right_pre return pre, ast.BinOp(op=op, left=left_value, right=right_value, lineno=op_symbol.lineno, col_offset=0) else: rest, right = exp[0:-1], exp[-1] rest_pre, rest_value = self.translate(rest, False) right_pre, right_value = self.translate(right, False) pre = rest_pre + right_pre return pre, ast.BinOp(op=op, left=rest_value, right=right_value, lineno=op_symbol.lineno, col_offset=0) @syntax('and', 'or') def translate_bool_op(self, exp): if not len(exp) >= 3: raise MochiSyntaxError(exp, self.filename) op_symbol = exp[0] op_name = op_symbol.name op = op_ast_map[op_name] pre = [] values = [] for value in exp[1:]: pre_value, value_value = self.translate(value, False) pre += pre_value values.append(value_value) return pre, ast.BoolOp(op=op, values=values, lineno=op_symbol.lineno, col_offset=0) @syntax('==', '!=', '<', '<=', '>', '>=', 'is', 'is_not', 'in', 'not_in') def translate_compare(self, exp): if len(exp) < 3: raise MochiSyntaxError(exp, self.filename) op_symbol = exp[0] op_name = op_symbol.name left, rights = exp[1], exp[2:] ops = [op_ast_map[op_name]] * len(rights) pre, left_value = self.translate(left, False) right_values = [] for right in rights: right_pre, right_value = self.translate(right, False) pre = pre + right_pre right_values.append(right_value) return pre, ast.Compare(ops=ops, left=left_value, comparators=right_values, lineno=op_symbol.lineno, col_offset=0) def make_return(self, exp): pre, value = self.translate(exp, False) if type(value) is ast.Expr: value = value.value ret = ast.Return(value=value, lineno=0, # exp.lineno, col_offset=0) return pre, ret @syntax('for', 'each') def translate_foreach(self, exp): if not (len(exp) >= 4): raise MochiSyntaxError(exp, self.filename) target_exp = exp[1] if isinstance(target_exp, Symbol): target = self.create_assign_target(target_exp) elif issequence_except_str(target_exp): target = self.create_assign_targets(target_exp) else: raise MochiSyntaxError(exp, self.filename) pre = [] iter_pre, iter_value = self.translate(exp[2], False) pre.extend(iter_pre) body = self._translate_sequence(exp[3:]) pre.append(ast.For(target=target, iter=iter_value, body=body, orelse=[], lineno=exp[0].lineno, col_offset=0)) _, ref = self.translate_ref(NONE_SYM) return pre, ref @syntax('while') def translate_while(self, exp): if not (len(exp) >= 3): raise SyntaxError(exp, self.filename) test_exp = exp[1] body_exps = exp[2:] pre = [] test_pre, test_value = self.translate(test_exp, False) pre.extend(test_pre) body = self._translate_sequence(body_exps) pre.append(ast.While(test=test_value, body=body, orelse=[], lineno=exp[0].lineno, col_offset=0)) _, ref = self.translate_ref(NONE_SYM) return pre, ref @syntax('break') def translate_break(self, exp): if len(exp) > 1: raise MochiSyntaxError(exp, self.filename) return (), ast.Break(lineno=exp[0].lineno, col_offset=0) @syntax('continue') def translate_continue(self, exp): if len(exp) > 1: raise MochiSyntaxError(exp, self.filename) return (), ast.Continue(lineno=exp[0].lineno, col_offset=0) def _translate_get_index(self, exp): pre = [] target_pre, target_value = self.translate(exp[1], False) pre.extend(target_pre) index_pre, index_value = self.translate(exp[2], False) pre.extend(index_pre) return pre, ast.Subscript(value=target_value, slice=ast.Index(value=index_value), ctx=ast.Load(), lineno=exp[0].lineno, col_offset=0) def _translate_get_slice(self, lineno, target, start=None, stop=None, step=None): pre = [] target_pre, target_value = self.translate(target, False) pre.extend(target_pre) start_pre, start_value = self.translate(start, False) if start is not None else ((), None) pre.extend(start_pre) stop_pre, stop_value = self.translate(stop, False) if stop is not None else ((), None) pre.extend(stop_pre) step_pre, step_value = self.translate(step, False) if step is not None else ((), None) pre.extend(step_pre) return pre, ast.Subscript(value=target_value, slice=ast.Slice(lower=start_value, upper=stop_value, step=step_value), ctx=ast.Load(), lineno=lineno, col_offset=0) @syntax('get') def translate_get(self, exp): exp_length = len(exp) if exp_length == 3: return self._translate_get_index(exp) elif exp_length == 4: return self._translate_get_slice(exp[0].lineno, exp[1], exp[2], exp[3]) elif exp_length == 5: return self._translate_get_slice(exp[0].lineno, exp[1], exp[2], exp[3], exp[4]) else: raise MochiSyntaxError(exp, self.filename) @syntax('car', 'first') def translate_car(self, exp): if len(exp) != 2: raise MochiSyntaxError(exp, self.filename) return self._translate_get_index((GET, exp[1], 0)) @syntax('cdr', 'rest') def translate_cdr(self, exp): if len(exp) != 2: raise MochiSyntaxError(exp, self.filename) return self._translate_get_slice(exp[0].lineno, exp[1], 1) @syntax('last') def translate_last(self, exp): if len(exp) != 2: raise MochiSyntaxError(exp, self.filename) return self._translate_get_index((GET, exp[1], -1)) @syntax('cadr') def translate_cadr(self, exp): if len(exp) != 2: raise MochiSyntaxError(exp, self.filename) return self._translate_get_index((GET, exp[1], 1)) @syntax('getattr') def translate_getattr(self, exp): pre, value = self.translate(exp[1], False) return pre, ast.Attribute(value=value, attr=exp[2], lineno=exp[0].lineno, col_offset=exp[0].col_offset, ctx=ast.Load()) @syntax('with_decorator') def translate_with_decorator(self, exp): if not (len(exp) >= 3): raise MochiSyntaxError(exp, self.filename) return self.translate_defun(exp[-1], decorator_exps=exp[1:-1]) class DuplicatedBindingChecker(ast.NodeTransformer): def __init__(self, filename): self.filename = filename @lexical_scope() def visit_ClassDef(self, node): add_binding_name(node.name, self.filename) return self.generic_visit(node) @lexical_scope(quasi=True) def visit_If(self, node): self.visit(node.test) for body_item in node.body: self.visit(body_item) binding_name_set_stack.pop() for orelse_item in node.orelse: if not isinstance(orelse_item, ast.If): new_scope = Scope(quasi=True) binding_name_set_stack[-1].add_child(new_scope) binding_name_set_stack.append(new_scope) self.visit(orelse_item) binding_name_set_stack.pop() else: self.visit(orelse_item) return node def create_init_assign(self, node): targets = [] arg_names = [arg if isinstance(arg, str) else arg.arg for arg in node.args.args] if node.args.vararg is not None: arg_names.append(node.args.vararg if isinstance(node.args.vararg, str) else node.args.vararg.arg) if node.args.kwarg is not None: arg_names.append(node.args.kwarg if isinstance(node.args.kwarg, str) else node.args.kwarg.arg) for binding_name in node.binding_name_set: if binding_name == node.name: continue if binding_name in arg_names: continue targets.append(ast.Name(id=binding_name, ctx=ast.Store(), lineno=0, col_offset=0)) if len(targets) > 0: if hasattr(ast, 'NameConstant'): right_value = ast.NameConstant(value=None) else: right_value = ast.Name(id=NONE_SYM.name, ctx=ast.Load(), lineno=0, col_offset=0) return [ast.Assign(targets=targets, value=right_value, lineno=0, col_offset=0)] else: return [] @lexical_scope() def visit_FunctionDef(self, node): if hasattr(node, 'is_visited') and node.is_visited: return node add_binding_name(node.name, self.filename) for arg in node.args.args: add_binding_name(arg if isinstance(arg, str) else arg.arg, self.filename) if node.args.vararg is not None: add_binding_name(node.args.vararg if isinstance(node.args.vararg, str) else node.args.vararg.arg, self.filename) if node.args.kwarg is not None: add_binding_name(node.args.kwarg if isinstance(node.args.kwarg) else node.args.kwarg.arg, self.filename) for stmt in node.body: self.visit(stmt) binding_name_set_stack[-1].merge_children() node.binding_name_set = binding_name_set_stack[-1] node.body = self.create_init_assign(node) + node.body node.is_visited = True return node def visit_Assign(self, node): if not (hasattr(node, 're_assign') and node.re_assign): for target in node.targets: add_binding_name(target.id, self.filename) return node def visit_ExceptHandler(self, node): if hasattr(node, 'name'): add_binding_name(node.name, self.filename) return node def _check_duplicated_binding(self, func_ast): checker = self.DuplicatedBindingChecker(self.filename) return checker.visit(func_ast) class SelfTailRecursiveCallTransformer(ast.NodeTransformer): optimized = False def __init__(self, target_func): self.target_func = target_func def visit_FunctionDef(self, node): if node is not self.target_func: return node new_body = [] for stmt in node.body: new_stmt = self.__class__(self.target_func).visit(stmt) if isinstance(new_stmt, Sequence): new_body.extend(new_stmt) else: new_body.append(new_stmt) node.body = new_body ast.fix_missing_locations(node) return node def visit_Return(self, node): if isinstance(node.value, ast.Call) and isinstance(node.value.func, ast.Name): if node.value.func.id == self.target_func.name: self.__class__.optimized = True tmp_result = [] result = [] default_values = [] + self.target_func.args.defaults for arg in self.target_func.args.args: arg_name = ast.Name(id=arg.arg, ctx=ast.Store(), lineno=0, col_offset=0) try: arg_value = node.value.args.pop(0) except IndexError: arg_value = default_values.pop(0) tmp_arg_sym = get_temp_name() tmp_arg_name = ast.Name(id=tmp_arg_sym.name, ctx=ast.Store(), lineno=0, col_offset=0) arg_value_assign = ast.Assign(targets=[tmp_arg_name], value=arg_value, lineno=0, col_offset=0) tmp_result.append(arg_value_assign) arg_value_ref = translator.translate_ref(tmp_arg_sym)[1] arg_re_assign = ast.Assign(targets=[arg_name], value=arg_value_ref, lineno=0, col_offset=0) arg_re_assign.re_assign = True result.append(arg_re_assign) if self.target_func.args.vararg is not None: vararg = self.target_func.args.vararg arg_name = ast.Name(id=vararg.arg if GE_PYTHON_34 else vararg, ctx=ast.Store(), lineno=0, col_offset=0) arg_value = ast.Tuple(elts=node.value.args, lineno=0, col_offset=0, ctx=ast.Load()) arg_re_assign = ast.Assign(targets=[arg_name], value=arg_value, lineno=0, col_offset=0) arg_re_assign.re_assign = True result.append(arg_re_assign) result.append(ast.Continue(lineno=0, col_offset=0)) return tmp_result + result return node def _tail_recursion_optimize(self, func_ast): transformer = self.SelfTailRecursiveCallTransformer(func_ast) optimized_func_ast = transformer.visit(func_ast) if self.SelfTailRecursiveCallTransformer.optimized: if IS_PYPY: optimized_func_ast.body = [ast.While(test=ast.Name(id='True', ctx=ast.Load(), lineno=0, col_offset=0), body=optimized_func_ast.body + [ast.Break(lineno=0, col_offset=0)], orelse=[], lineno=0, col_offset=0)] else: optimized_func_ast.body = [ast.While(test=ast.Num(n=1, lineno=0, col_offset=0), body=optimized_func_ast.body + [ast.Break(lineno=0, col_offset=0)], orelse=[], lineno=0, col_offset=0)] self.SelfTailRecursiveCallTransformer.optimized = False return optimized_func_ast else: return func_ast def _tail_to_return_s(self, exps): def except_tail_to_return(except_exp): if issequence_except_str(except_exp) and isinstance(except_exp[0], Symbol)\ and except_exp[0].name == 'except': return (except_exp[0],) + self._tail_to_return_s(except_exp[1:]) return except_exp exp = exps[-1] if not issequence_except_str(exp): exp = (Symbol('return'), exp) elif not isinstance(exp[0], Symbol): exp = (Symbol('return'), exp) elif exp[0].name in {'val', '_val', 'return', 'yield', 'yield_from', 'pass', 'raise'}: pass elif exp[0].name == 'if': if_exp = exp if len(if_exp) == 3: if_sym, test_cls, true_cls = if_exp exp = (if_sym, test_cls, self._tail_to_return_s([true_cls])[0]) elif len(if_exp) == 4: if_sym, test_cls, true_cls, else_cls = if_exp exp = (if_sym, test_cls, self._tail_to_return_s([true_cls])[0], self._tail_to_return_s([else_cls])[0]) else: new_if_exp = list(if_exp) i = 2 exp_len = len(if_exp) while i < exp_len: new_if_exp[i] = self._tail_to_return_s([if_exp[i]])[0] i += 2 if exp_len % 2 == 0: new_if_exp[-1] = self._tail_to_return_s([if_exp[-1]])[0] exp = tuple(new_if_exp) elif exp[0].name in {'do', 'with', 'finally', 'except'}: exp = (exp[0], ) + self._tail_to_return_s(exp[1:]) elif exp[0].name == 'try': exp = (exp[0], ) + self._tail_to_return_s(tuple(filter(lambda item: not (issequence_except_str(item) and isinstance(item[0], Symbol) and item[0].name in {'except', 'finally'}), exp[1:]))) + \ tuple(map(except_tail_to_return, filter(lambda item: issequence_except_str(item) and isinstance(item[0], Symbol) and item[0].name in {'except', 'finally'}, exp[1:]))) elif exp[0].name == 'match': new_exp = list(exp) i = 3 exp_len = len(exp) while i < exp_len: new_exp[i] = self._tail_to_return_s([exp[i]])[0] i += 2 exp = tuple(new_exp) else: exp = (Symbol('return'), exp) return tuple(exps[:-1]) + (exp,) @syntax('return') def translate_return(self, exp): if len(exp) != 2: raise MochiSyntaxError(exp, self.filename) pre, value = self.translate(exp[1], False) if type(value) is ast.Expr: value = value.value return pre + [ast.Return(value=value, lineno=exp[0].lineno, col_offset=0)], self.translate(NONE_SYM)[1] def _translate_handler(self, exp): type_expr, var, *body = exp return ast.ExceptHandler(type=self.translate(type_expr, False)[1], name=var.name, body=self._translate_sequence(body, True), lineno=var.lineno, col_offset=0) def _translate_handlers(self, exps): handlers = [] for exp in exps: handlers.append(self._translate_handler(exp)) return handlers @syntax('try') def translate_try(self, exp): if len(exp) < 2: raise MochiSyntaxError(exp, self.filename) body_exp = [] handler_exps = [] orelse_exp = [] final_body_exp = [] for expr in exp: if issequence_except_str(expr) and len(expr) > 1 and isinstance(expr[0], Symbol): expr_name = expr[0].name if expr_name == 'finally': final_body_exp = expr[1:] continue elif expr_name == 'orelse': orelse_exp = expr[1:] continue elif expr_name == 'except': handler_exps.append(expr[1:]) continue body_exp.append(expr) body = self._translate_sequence(body_exp, True) handlers = self._translate_handlers(handler_exps) orelse = self._translate_sequence(orelse_exp, True) final_body = self._translate_sequence(final_body_exp, True) if GE_PYTHON_34: return (ast.Try(body=body, handlers=handlers, orelse=orelse, finalbody=final_body, lineno=exp[0].lineno, col_offset=0),), self.translate(EMPTY_SYM, False)[1] else: if len(handlers) == 0: return (ast.TryFinally(body=body, finalbody=final_body, lineno=exp[0].lineno, col_offset=0),), self.translate(EMPTY_SYM, False)[1] else: return (ast.TryFinally(body=[ast.TryExcept(body=body, handlers=handlers, orelse=orelse, lineno=exp[0].lineno, col_offset=0)], finalbody=final_body, lineno=exp[0].lineno, col_offset=0),), self.translate(EMPTY_SYM, False)[1] @syntax('with') def translate_with(self, exp): if len(exp) < 3: raise MochiSyntaxError(exp, self.filename) if GE_PYTHON_34: return self.translate_with_34(exp) else: return self.translate_with_old(exp) def translate_with_34(self, exp): keyword_with, items, *body = exp pre = [] items_py = [] for item in items: item_pre, item_value = self.translate(item[0], False) pre.extend(item_pre) var = item[1] items_py.append(ast.withitem(context_expr=item_value, optional_vars=ast.Name(id=var.name, ctx=ast.Store(), lineno=var.lineno, col_offset=0), lineno=var.lineno, col_offset=0)) body_py = self._translate_sequence(body, True) pre.append(ast.With(items=items_py, body=body_py, lineno=keyword_with.lineno, col_offset=0)) return pre, self.translate(NONE_SYM, False)[1] def translate_with_old(self, exp): keyword_with, items, *body = exp pre = [] first_with_py = None with_py = None for item in items: item_pre, item_value = self.translate(item[0], False) pre.extend(item_pre) var = item[1] if with_py is None: with_py = ast.With(context_expr=item_value, optional_vars=ast.Name(id=var.name, ctx=ast.Store(), lineno=var.lineno, col_offset=0), lineno=var.lineno, col_offset=0) first_with_py = with_py else: inner_with_py = ast.With(context_expr=item_value, optional_vars=ast.Name(id=var.name, ctx=ast.Store(), lineno=var.lineno, col_offset=0), lineno=var.lineno, col_offset=0) with_py.body = [inner_with_py] with_py = inner_with_py with_py.body = self._translate_sequence(body, True) pre.append(first_with_py) return pre, self.translate(NONE_SYM, False)[1] @syntax('raise') def translate_raise(self, exp): if len(exp) != 2: raise MochiSyntaxError(exp, self.filename) return (ast.Raise(exc=self.translate(exp[1], False)[1], cause=None, lineno=exp[0].lineno, col_offset=0),), self.translate(EMPTY_SYM, False)[1] @syntax('def') @lexical_scope_for_require def translate_defun(self, exp, decorator_exps=None, visible=True): if not (len(exp) >= 4 and type(exp[1]) is Symbol): raise MochiSyntaxError(exp, self.filename) id = exp[1].name arg = exp[2] vararg_exp = None varkwarg_exp = None if not visible: self.hidden_vars.append(id) if type(arg) is Symbol: arg_exps = [] vararg_exp = arg else: vararg_count = arg.count(VARG) varkwarg_count = arg.count(VKWARG) if vararg_count > 1 or varkwarg_count > 1: raise MochiSyntaxError(exp, self.filename) elif vararg_count and varkwarg_count: VARGIndex = arg.index(VARG) VKWARGIndex = arg.index(VKWARG) if (VARGIndex > VKWARGIndex): raise MochiSyntaxError(exp, self.filename) arg_exps = arg[:VARGIndex] vararg_exp = arg[VARGIndex + 1] varkwarg_exp = arg[VKWARGIndex + 1:] if len(varkwarg_exp) == 1: varkwarg_exp = varkwarg_exp[0] else: raise MochiSyntaxError(exp, self.filename) elif vararg_count: VARGIndex = arg.index(VARG) arg_exps = arg[:VARGIndex] vararg_exp = arg[VARGIndex + 1:] if len(vararg_exp) == 1: vararg_exp = vararg_exp[0] else: raise MochiSyntaxError(exp, self.filename) elif varkwarg_count: VKWARGIndex = arg.index(VKWARG) arg_exps = arg[:VKWARGIndex] varkwarg_exp = arg[VKWARGIndex + 1:] if len(varkwarg_exp) == 1: varkwarg_exp = varkwarg_exp[0] else: raise MochiSyntaxError(exp, self.filename) else: arg_exps = arg args = self._translate_args(arg_exps) if vararg_exp is None: vararg = None else: vararg = ast.arg(arg=vararg_exp.name, annotation=None) if GE_PYTHON_34 else vararg_exp.name if varkwarg_exp is None: varkwarg = None else: varkwarg = ast.arg(arg=varkwarg_exp.name, annotation=None) if GE_PYTHON_34 else varkwarg_exp.name # 末尾のS式をreturnに変換する。 # 末尾がif式やdo式の場合はその中まで変換する。 exp = tuple(exp[:3]) + self._tail_to_return_s(exp[3:]) # TODO 最後が変数参照だと戻り値がNoneになる。 body = self._translate_sequence(exp[3:]) if varkwarg is not None: node = ast.parse('{0} = pmap({0})'.format(varkwarg.arg if GE_PYTHON_34 else varkwarg)) body = node.body + body pre = [] decorator_list = [] if decorator_exps is not None: for decoratorExp in decorator_exps: pre_deco, value_deco = self.translate(decoratorExp, False) pre.extend(pre_deco) decorator_list.append(value_deco) defn = ast.FunctionDef(name=id, args=ast.arguments(args=args, vararg=vararg, varargannotation=None, kwonlyargs=[], kwarg=varkwarg, kwargannotation=None, defaults=[], kw_defaults=[], lineno=exp[1].lineno, col_offset=0), body=body, decorator_list=decorator_list, returns=None, lineno=exp[1].lineno, col_offset=0) defn = self._tail_recursion_optimize(self._check_duplicated_binding(defn)) pre.append(defn) _, ref = self.translate_ref(exp[1]) return pre, ref def _generate_new_method_def(self, cls_name, members_exp): member_names = tuple(map(lambda member_exp: member_exp if isinstance(member_exp, Symbol) else member_exp[0], members_exp)) check_type_sexpr = () for member_exp in members_exp: if issequence_except_str(member_exp): member_name_exp, member_type_exp = member_exp check_type_sexpr += ((Symbol('if'), (Symbol('not'), (Symbol('isinstance'), member_name_exp, member_type_exp)), (Symbol('raise'), (Symbol('TypeError'), member_name_exp.name + ' is not an instance of ' + str(member_type_exp)))),) return (Symbol('def'), Symbol('__new__'), (Symbol('cls'),) + member_names, (Symbol('val'), Symbol('super_cls'), (Symbol('super'), Symbol('cls'), Symbol('cls')))) + \ check_type_sexpr + ((Symbol('super_cls.__new__'), Symbol('cls')) + member_names,) @syntax('vector') def translate_vector(self, exp): if not (len(exp) == 3 and isinstance(exp[1], Symbol)): raise MochiSyntaxError(exp, self.filename) _, vector_name, item_type = exp pre = [] item_type_pre, item_type_value = self.translate(item_type, False) pre.extend(item_type_pre) lineno = vector_name.lineno col_offset = 0 vector_def = ast.ClassDef(name=vector_name.name, bases=[ast.Name(id='CheckedPVector', ctx=ast.Load(), lineno=lineno, col_offset=col_offset)], keywords=[], starargs=None, kwargs=None, body=[ast.Assign(targets=[ast.Name(id='__type__', ctx=ast.Store(), lineno=lineno, col_offset=col_offset)], value=item_type_value, lineno=lineno, col_offset=col_offset)], decorator_list=[], lineno=lineno, col_offset=col_offset) pre.append(vector_def) _, def_ref = self.translate_ref(vector_name) return pre, def_ref @syntax('assert') def translate_assert(self, exp): if len(exp) != 2: raise MochiSyntaxError(exp, self.filename) assert_symbol, value_exp = exp pre, value = self.translate(value_exp, False) pre.append(ast.Assert(test=value, msg=None, lineno=assert_symbol.lineno, col_offset=assert_symbol.col_offset)) return pre, self.translate(NONE_SYM, False)[1] @syntax('record') def translate_record(self, exp): exp_length = len(exp) if not (exp_length > 2 and isinstance(exp[1], Symbol)): raise MochiSyntaxError(exp, self.filename) if not ((isinstance(exp[2], Symbol) and issequence_except_str(exp[3])) or issequence_except_str(exp[2])): raise MochiSyntaxError(exp, self.filename) record_name = exp[1] # Symbol parent_record = exp[2] if isinstance(exp[2], Symbol) else RECORD_SYM # Symbol members_exp = exp[3] if isinstance(exp[2], Symbol) else exp[2] # Sequence body_exps = exp[4:] if isinstance(exp[2], Symbol) else exp[3:] # Sequence lineno = record_name.lineno col_offset = record_name.col_offset pre = [] members = [] body = [ast.Assign(targets=[ast.Name(id='__slots__', lineno=record_name.lineno, col_offset=record_name.col_offset, ctx=ast.Store())], value=ast.Tuple(elts=[], lineno=record_name.lineno, col_offset=record_name.col_offset, ctx=ast.Load()), lineno=record_name.lineno, col_offset=record_name.col_offset)] for member_exp in members_exp: if issequence_except_str(member_exp): if len(member_exp) != 2 and not isinstance(member_exp[0], Symbol): raise MochiSyntaxError(member_exp, self.filename) member_name_exp, member_type_exp = member_exp member_pre, member = self.translate(member_name_exp.name, False) pre.extend(member_pre) members.append(member) else: member_pre, member = self.translate(member_exp.name, False) pre.extend(member_pre) members.append(member) new_method_def = self._generate_new_method_def(record_name, members_exp) if new_method_def is not None: body_exps = (new_method_def,) + body_exps for body_exp in body_exps: if isinstance(body_exp, (tuple, list)) and body_exp[0] == DEF: defun_pre, _ = self.translate_defun(body_exp) pre.extend(defun_pre[:-1]) body.append(defun_pre[-1]) else: body_pre, body_py_exp = self.translate(body_exp) pre.extend(body_pre) body.append(body_py_exp) if parent_record is not RECORD_SYM: record_def = ast.ClassDef(name=record_name.name, bases=[ast.Call(func=ast.Name(id='immutable', lineno=lineno, col_offset=col_offset, ctx=ast.Load()), args=[ ast.Tuple(elts=members, lineno=parent_record.lineno, col_offset=parent_record.col_offset, ctx=ast.Load()), ast.Str(s=record_name.name, lineno=lineno, col_offset=col_offset)], keywords=[], starargs=None, kwargs=None, lineno=parent_record.lineno, col_offset=parent_record.col_offset), ast.Name(id=parent_record.name, lineno=parent_record.lineno, col_offset=parent_record.col_offset, ctx=ast.Load())], keywords=[], starargs=None, kwargs=None, lineno=lineno, col_offset=col_offset, body=body, decorator_list=[]) else: record_def = ast.ClassDef(name=record_name.name, bases=[ast.Call(func=ast.Name(id='immutable', lineno=lineno, col_offset=col_offset, ctx=ast.Load()), args=[ ast.Tuple(elts=members, lineno=parent_record.lineno, col_offset=parent_record.col_offset, ctx=ast.Load()), ast.Str(s=record_name.name, lineno=lineno, col_offset=col_offset)], keywords=[], starargs=None, kwargs=None, lineno=parent_record.lineno, col_offset=parent_record.col_offset)], keywords=[], starargs=None, kwargs=None, lineno=lineno, col_offset=col_offset, body=body, decorator_list=[]) pre.append(record_def) _, def_ref = self.translate_ref(record_name) return pre, def_ref @syntax('class') def translate_class(self, exp): exp_length = len(exp) if not (exp_length > 2 and isinstance(exp[1], Symbol)): raise MochiSyntaxError(exp, self.filename) if not ((isinstance(exp[2], Symbol) and issequence_except_str(exp[3])) or issequence_except_str(exp[2])): raise MochiSyntaxError(exp, self.filename) class_name = exp[1] # Symbol parent_class = exp[2] if isinstance(exp[2], Symbol) else OBJECT_SYM # Symbol members_exp = exp[3] if isinstance(exp[2], Symbol) else exp[2] # Sequence body_exps = exp[4:] if isinstance(exp[2], Symbol) else exp[3:] # Sequence lineno = class_name.lineno col_offset = class_name.col_offset pre = [] members = [] for member_exp in members_exp: member_pre, member = self.translate(member_exp.name, False) pre.extend(member_pre) members.append(member) body = [ast.Assign(targets=[ast.Name(id='__slots__', lineno=lineno, col_offset=col_offset, ctx=ast.Store())], value=ast.Tuple(elts=members, lineno=lineno, col_offset=col_offset, ctx=ast.Load()), lineno=class_name.lineno, col_offset=class_name.col_offset)] # check_duplicated_binding_name_outer(class_name, self.filename) # TODO for body_exp in body_exps: if isinstance(body_exp, (tuple, list)) and body_exp[0] == DEF: defun_pre, _ = self.translate_defun(body_exp) pre.extend(defun_pre[:-1]) body.append(defun_pre[-1]) else: body_pre, body_py_exp = self.translate(body_exp) pre.extend(body_pre) body.append(body_py_exp) class_def = ast.ClassDef(name=class_name.name, bases=[ast.Name(id=parent_class.name, lineno=parent_class.lineno, col_offset=parent_class.col_offset, ctx=ast.Load())], keywords=[], starargs=None, kwargs=None, lineno=lineno, col_offset=col_offset, body=body, decorator_list=[]) pre.append(class_def) _, def_ref = self.translate_ref(class_name) return pre, def_ref @syntax('import') def translate_import(self, exp): if len(exp) < 2: raise MochiSyntaxError(exp, self.filename) names = [ast.alias(name=import_sym.name, asname=None, lineno=import_sym.lineno, col_offset=import_sym.col_offset) for import_sym in exp[1:]] return (ast.Import(names=names, lineno=exp[0].lineno, col_offset=exp[0].col_offset),), self.translate_ref(NONE_SYM)[1] @syntax('from_import') def translate_from(self, exp): if len(exp) < 3: raise MochiSyntaxError(exp, self.filename) names = [ast.alias(name=import_sym.name, asname=None, lineno=import_sym.lineno, col_offset=import_sym.col_offset) for import_sym_names in exp[2:] for import_sym in import_sym_names] return (ast.ImportFrom(module=exp[1].name, names=names, level=0, lineno=exp[0].lineno, col_offset=exp[0].col_offset),), self.translate_ref(NONE_SYM)[1] def is_macro(self, exp): if not issequence_except_str(exp): return False if not isinstance(exp[0], Symbol): return False return translator.has_macro(exp[0].name) def has_macro(self, name): return name in self.macro_table def add_macro(self, name, macro_func): self.macro_table[name] = macro_func def expand_macro(self, name, args): if name not in global_env: code = compile( ast.Module(body=[self.macro_table[name]]), self.filename, 'exec', optimize=2) exec(code, global_env) macro_func = global_env[name] keyword_params = {} newargs = [] for arg in args: # if type(arg) is Keyword: # keyword_params = arg # continue newargs.append(arg) args = newargs return tuple_it(macro_func(*args, **keyword_params)) @syntax('mac') def translate_def_macro(self, exp): macro_func_ast_seq, ref = self.translate_defun(exp) self.add_macro(exp[1].name, macro_func_ast_seq[0]) return macro_func_ast_seq, ref def translate_apply(self, exp): pre = [] callable_pre, callable_value = self.translate(exp[0], False) pre.extend(callable_pre) args = [] keyword_args = [] keyword_arg_exps = [] arg_exps = exp[1:] for i, argexp in enumerate(arg_exps): if type(argexp) is Keyword: keyword_arg_exps = arg_exps[i:] arg_exps = arg_exps[:i] break for argexp in arg_exps: arg_pre, arg_value = self.translate(argexp, False) pre.extend(arg_pre) args.append(arg_value) for argKey, argExp in chunks(keyword_arg_exps, 2): if type(argKey) is not Keyword: raise MochiSyntaxError(argKey, self.filename) arg_pre, arg_value = self.translate(argExp, False) pre.extend(arg_pre) keyword_args.append(ast.keyword(arg=argKey.name, value=arg_value)) value = ast.Call(func=callable_value, args=args, keywords=keyword_args, starargs=None, kwargs=None, lineno=callable_value.lineno, col_offset=0) return pre, value @syntax('fn') def translate_fn(self, exp): if not (len(exp) >= 3): raise MochiSyntaxError(exp, self.filename) name_symbol = get_temp_name() defexp = [] defexp.append('dummy') defexp.append(name_symbol) defexp.extend(exp[1:]) return self.translate_defun(defexp, visible=False) @syntax('make_tuple') @syntax('make_pvector') def translate_make_pvector(self, exp): make_tuple_symbol, *items = exp pre = [] elts = [] lineno = make_tuple_symbol.lineno col_offset = make_tuple_symbol.col_offset for item in items: item_pre, item_value = self.translate(item, False) pre.extend(item_pre) elts.append(item_value) tuple_ast = ast.Tuple(elts=elts, ctx=ast.Load(), lineno=lineno, col_offset=col_offset) return pre, ast.Call(func=ast.Name(id='pvector', ctx=ast.Load(), lineno=lineno, col_offset=col_offset), args=[tuple_ast], keywords=[], starargs=None, kwargs=None, lineno=lineno, col_offset=col_offset) @syntax('make_list') def translate_make_list(self, exp): make_list_symbol, *items = exp pre = [] elts = [] lineno = make_list_symbol.lineno col_offset = make_list_symbol.col_offset for item in items: item_pre, item_value = self.translate(item, False) pre.extend(item_pre) elts.append(item_value) return pre, ast.List(elts=elts, ctx=ast.Load(), lineno=lineno, col_offset=col_offset) @syntax('quote') def translate_quote(self, exp): if len(exp) != 2: raise MochiSyntaxError(exp, self.filename) quote_symbol, value = exp if issequence_except_str(value): pre = [] elts = [] lineno = quote_symbol.lineno col_offset = quote_symbol.col_offset for item in value: item_pre, item_value = self.translate(item, False, True) pre.extend(item_pre) elts.append(item_value) return pre, ast.Tuple(elts=elts, ctx=ast.Load(), lineno=lineno, col_offset=col_offset) elif isinstance(value, Symbol): lineno = value.lineno col_offset = value.col_offset return (EMPTY, ast.Call(func=ast.Name(id='Symbol', ctx=ast.Load(), lineno=lineno, col_offset=col_offset), args=[ast.Str(s=value.name, lineno=lineno, col_offset=col_offset)], keywords=[], starargs=None, kwargs=None, lineno=lineno, col_offset=col_offset)) else: return self.translate_atom(value) def call_unquote_splicing(self, list_ast): lineno = list_ast.lineno col_offset = list_ast.col_offset return ast.Call(func=ast.Name(id='unquote_splice', ctx=ast.Load(), lineno=lineno, col_offset=col_offset), args=[list_ast], keywords=[], starargs=None, kwargs=None, lineno=lineno, col_offset=col_offset) @syntax('quasiquote') def translate_quasi_quote(self, exp): if len(exp) != 2: raise MochiSyntaxError(exp, self.filename) quote_symbol, value = exp if issequence_except_str(value): if len(value) >= 1 and type(value[0]) is Symbol: if value[0].name == 'unquote': return self.translate(value[1], False) elif value[0].name == 'unquote_splicing': value1_pre, value1_body = self.translate(value[1], False) return (tuple(value1_pre), [self.translate((Symbol('quote'), Symbol('_mochi_unquote_splicing')), False), value1_body]) lineno = quote_symbol.lineno col_offset = quote_symbol.col_offset pre = [] elts = [] for item in value: item_pre, item_value = self.translate(item, True, False, True) pre.extend(item_pre) elts.append(item_value) return (pre, self.call_unquote_splicing(ast.Tuple(elts=flatten_list(elts), ctx=ast.Load(), lineno=lineno, col_offset=col_offset))) elif isinstance(value, Symbol): lineno = value.lineno col_offset = value.col_offset return (EMPTY, ast.Call(func=ast.Name(id='Symbol', ctx=ast.Load(), lineno=lineno, col_offset=col_offset), args=[ast.Str(s=value.name, lineno=lineno, col_offset=col_offset)], keywords=[], starargs=None, kwargs=None, lineno=lineno, col_offset=col_offset)) else: return self.translate_atom(value) def enclose(self, py_ast, flag): if isinstance(py_ast, expr_and_stmt_ast): return py_ast if issequence_except_str(py_ast): ast_list = [] for item in py_ast: if isinstance(item, expr_and_stmt_ast): ast_list.append(item) elif issequence_except_str(item): # ((pre_foo ...), value_bar) => ((enclose(pre_foo) ...), value_bar) newitem = [] for itemitem in item: if isinstance(itemitem, expr_and_stmt_ast): newitem.append(itemitem) else: newitem.append( ast.Expr(value=itemitem, lineno=itemitem.lineno, col_offset=0)) ast_list.append(newitem) else: if flag: ast_list.append(ast.Expr(value=item, lineno=item.lineno, col_offset=0)) else: ast_list.append(item) return ast_list else: if flag and (not isinstance(py_ast, expr_and_stmt_ast)): return ast.Expr(value=py_ast, lineno=py_ast.lineno, col_offset=py_ast.col_offset) else: return py_ast def translate(self, exp, enclose=True, quoted=False, quasi_quoted=False): if quoted: quoted_exp = [Symbol('quote'), exp] return self.translate_quote(quoted_exp) if quasi_quoted: quoted_exp = [Symbol('quote'), exp] return self.translate_quasi_quote(quoted_exp) if not issequence_except_str(exp): return self.enclose(self.translate_atom(exp), enclose) if type(exp[0]) is Symbol: if exp[0].name == 'load': tl = Translator(self.macro_table) return tl.translate_loaded_files(exp[1:]) elif exp[0].name == 'require': tl = Translator(self.macro_table) return tl.translate_required_files(exp[1:]) elif exp[0].name in self.macro_table: return self.translate(self.expand_macro(exp[0].name, exp[1:]), enclose) elif exp[0].name in syntax_table: return self.enclose(syntax_table[exp[0].name](self, exp), enclose) return self.enclose(self.translate_apply(exp), enclose) class Scope(dict): def __init__(self, quasi=False): self.quasi = quasi self.children = [] def add_child(self, child): self.children.append(child) def merge_children(self): merge_map = {} for child in self.children: merge_map.update(child) for binding_name in merge_map.keys(): self.add_binding_name(binding_name, merge_map[binding_name]) def add_binding_name(self, binding_name, file_name): # TODO _gs if not binding_name.startswith('_gs') and binding_name in self: raise DuplicatedDefError(binding_name, file_name) self[binding_name] = file_name return binding_name # 'translator' and 'bindingNameSetStack' is a global variable. translator = Translator() global_scope = Scope() for name in global_env.keys(): global_scope.add_binding_name(name, "<builtin>") binding_name_set_stack = [global_scope] INIT_CODE = """ from mochi.core import init init() """ def ast2py(ast, mochi_env='', add_init=False): """Translate Python AST to Python code. :param ast: Python AST :param env: Mochi environment such monkey patch :param add_init: Add Mochi intialization or not :return: Python source code """ if mochi_env: env_ast = translator.translate_block(mochi_env) ast.body = env_ast.body + ast.body source = astunparse.unparse(ast) if add_init: source = INIT_CODE + source return source