def __init__(self, input_path = None, output_path = None, input_text = None, main_compiler = None, module_name = None, print_module_result = False): self.indent_level = 0 self.current_indent = "" self.print_module_result = print_module_result self.context_stack = ContextStack() self.is_main_compiler = not main_compiler self.main_compiler = main_compiler if input_path: (self.input_directory_path, self.input_file_path) = self.get_file_path(input_path, "__init__.py") if output_path != False: self.output_file_path = output_path or "%s.js" % self.input_file_path else: self.output_file_path = None self.python_path = [self.input_directory_path] + sys.path self.input_text = None elif input_text: self.input_file_path = None self.input_directory_path = os.path.abspath('.') self.python_path = sys.path self.output_file_path = output_path self.input_text = input_text else: raise CompileError("Can't run compiler without either specifying input_path or input_text in the arguments") if self.is_main_compiler: self.module_name = module_name or "__main__" self.modules = [] self.builtins = Builtins() else: if not module_name: try: module_name = os.path.relpath(self.input_file_path, self.main_compiler.input_directory_path) except ValueError: module_name = self.gensym("module").id self.module_name = self.re_py_ext.sub("", module_name) else: self.module_name = module_name # self.local_module_name = self.gensym(self.module_name, ast.Load()) if self.is_main_compiler: self.main_compiler = self
class Compiler: byte = 1 kilobyte = 1024 * byte megabyte = 1024 * kilobyte max_file_size = 100 * megabyte default_indent = " " function_ops = (ast.FloorDiv, ast.Pow, ast.Eq, ast.NotEq, ast.In, ast.NotIn,) re_py_ext = re.compile("(\.py)?$") local_module_name = ast.Name("$module", ast.Load()) def __init__(self, input_path = None, output_path = None, input_text = None, main_compiler = None, module_name = None, print_module_result = False): self.indent_level = 0 self.current_indent = "" self.print_module_result = print_module_result self.context_stack = ContextStack() self.is_main_compiler = not main_compiler self.main_compiler = main_compiler if input_path: (self.input_directory_path, self.input_file_path) = self.get_file_path(input_path, "__init__.py") if output_path != False: self.output_file_path = output_path or "%s.js" % self.input_file_path else: self.output_file_path = None self.python_path = [self.input_directory_path] + sys.path self.input_text = None elif input_text: self.input_file_path = None self.input_directory_path = os.path.abspath('.') self.python_path = sys.path self.output_file_path = output_path self.input_text = input_text else: raise CompileError("Can't run compiler without either specifying input_path or input_text in the arguments") if self.is_main_compiler: self.module_name = module_name or "__main__" self.modules = [] self.builtins = Builtins() else: if not module_name: try: module_name = os.path.relpath(self.input_file_path, self.main_compiler.input_directory_path) except ValueError: module_name = self.gensym("module").id self.module_name = self.re_py_ext.sub("", module_name) else: self.module_name = module_name # self.local_module_name = self.gensym(self.module_name, ast.Load()) if self.is_main_compiler: self.main_compiler = self _native_types = ("Array", "Object", "RegExp", "Date", "Function", "Arguments", "Number", "String", "window", "global",) def _is_native_type(self, _type): return _type in self._native_types def _is_statement_of(self, node): for item in reversed(self._nodes): try: body = item.body if isinstance(item, ast.If) and item.orelse: body = body + item.orelse except AttributeError: body = None if isinstance(body, list): for body_item in body: if node is body_item or (isinstance(body_item, ast.Expr) and node is body_item.value): return item def get_file_path(self, path, default_file_name): f = None d = None if path != None and os.path.exists(path): path = os.path.abspath(path) if os.path.isdir(path): f = os.path.join(path, os.path.basename(default_file_name)) d = path elif os.path.isfile(path): f = path d = os.path.dirname(path) return (d, f) def indent(self, modify = 0): if modify: self.indent_level += modify if self.indent_level < 0: raise CompileError("Indent level can't drop below zero. Current level is %i." % (self.indent_level)) self.current_indent = "".join([self.default_indent for x in range(self.indent_level)]) return self.current_indent re_not_word = re.compile(r"[^\w]") gensym_map = {} def gensym(self, name = "sym", ctx = ast.Load()): name = self.re_not_word.sub("_", name) i = None try: i = self.gensym_map[name] except KeyError: i = 0 self.gensym_map[name] = i id = '$%s%i' % (name, i) return ast.Name(id, ctx) def op_is_function(self, op): for node_class in self.function_ops: if isinstance(op, node_class): return True return False def import_me(self): to_call = ast.Name("__import__", ast.Load()) name = ast.Str(self.module_name) args = [name] call = ast.Call(to_call, args, None, None, None) return self.compile_node(call) def compile(self, bare = False, verbose = False): self.bare = bare self.verbose = verbose if self.input_file_path: input_f = open(self.input_file_path, 'r') python_code = input_f.read(self.max_file_size) else: python_code = self.input_text file_ast = ast.parse(python_code) if self.verbose: print_node(file_ast) self._nodes = [] self.compile_node(file_ast) if self.is_main_compiler: if not self.bare: if self.builtins.module_text: self.compile_Import(input_text = self.builtins.module_text, input_name = "builtins") import_stmt = self.import_me() code = self.modules + [import_stmt] else: code = self.modules compiled = ";\n\n".join(code) + ";\n" if not self.bare: compiled = "(function () {\n\n" \ "%s\n" \ "})();" % compiled if self.output_file_path: output_f = open(self.output_file_path, 'w+') output_f.write(compiled) output_f.close() else: return compiled jseval_name = "jseval" def jseval_to_JSCode(self, node): if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == self.jseval_name: arg = node.args[0] if isinstance(arg, ast.Str): return JSCode(arg.s) else: raise CompileError("%s can only be called with a single string argument." % self.jseval_name) return node def compile_node(self, node, *args, **kwargs): node = self.jseval_to_JSCode(node) if isinstance(node, list): f = self.compile_statement_list else: if isinstance(node, ast.AST): self._nodes.append(node) class_name = node.__class__.__name__ f = getattr(self, "compile_%s" % class_name) if not f: raise CompileError("No compiler for AST node %s" % class_name) return f(node, *args, **kwargs) def compile_node_list(self, nodes, joiner = ", ", trailing = ""): compiled = [self.compile_node(n) for n in nodes] result = joiner.join(filter(lambda x: x, compiled)) if trailing and not result.endswith(trailing): result += trailing return result def compile_statement_list(self, ls): return self.compile_node_list(ls, ";\n%s" % self.indent(), ";") def compile_Module(self, node): self.is_builtins = self.module_name == "builtins" body = node.body if self.print_module_result: try: last_body_item = body[-1] except IndexError: last_body_item = ast.Name("None", ast.Load()) print_fn = ast.Name("print", ast.Load()) last_body_item = ast.Call(print_fn, [last_body_item], None, None, None) body = body[:-1] + [last_body_item] if not self.bare and not self.is_builtins: module_name = ast.Str(self.module_name) args = ast.arguments([ast.arg(self.local_module_name.id, None)], None, None, None, None, None, None, None) func = ast.FunctionDef(name = '', args = args, body = body, decorator_list = [], returns = None) to_call = ast.Name("__registermodule__", ast.Load()) call = ast.Call(to_call, [module_name, func], None, None, None) result = self.compile_node(call) else: context = self.context_stack.new() result = self.compile_node(body) result = self.compile_statement_list([context.get_vars(True), JSCode(result)]) self.context_stack.pop() if self.is_builtins: self.main_compiler.modules = [result] + self.main_compiler.modules else: self.main_compiler.modules.append(result) def compile_Import(self, node = None, input_text = None, input_name = None, local = False): def do_import(name, input_path = None, input_text = None): compiler = Compiler(input_path = input_path, input_text = input_text, main_compiler = self.main_compiler, module_name = input_name) compiler.compile() import_call = compiler.import_me() ctx = StoreLocal() if local else ast.Store() asname = ast.Name(name, ctx) assign = ast.Assign([asname], JSCode(import_call)) return assign if node: names = node.names results = [] for alias in names: name = alias.name file_name = "%s.py" % os.path.join(self.input_directory_path, name) result = do_import(alias.asname or name, input_path = file_name) results.append(result) return self.compile_node_list(results) elif isinstance(input_text, str): if not input_name: raise CompileError("Can't compile input_text without input_name") assign = do_import(input_name, input_text = input_text) return self.compile_node(assign) else: raise CompileError("Can't import nothing") def compile_ImportFrom(self, node): module = node.module names = node.names module_name = ast.Name(module, ast.Load()) module_alias = ast.alias(module, None) imprt = ast.Import([module_alias]) imprt = JSCode(self.compile_node(imprt, local = True)) results = [imprt] for alias in names: name = alias.name asname = ast.Name(alias.asname or name, ast.Store()) attr = ast.Attribute(module_name, name, ast.Load()) assign = ast.Assign([asname], attr) results.append(assign) return self.compile_node_list(results) def compile_Expr(self, node): result = self.compile_node(node.value) # Don't wrap in parens if the item in question is an instance of JSCode. if result and not isinstance(self.jseval_to_JSCode(node.value), JSCode): return "(%s)" % result else: return result def compile_JSCode(self, node): return node.code def compile_Call(self, node, use_kwargs = True): func = node.func args = node.args starargs = node.starargs keywords = node.keywords kwargs = node.kwargs if isinstance(func, ast.Name): _type = self.context_stack.find_type(func) elif isinstance(func, ast.Attribute): _type = self.context_stack.find_type(func.value) else: _type = None if _type and self._is_native_type(_type): use_kwargs = False if func is "__test__": print_node(node) if kwargs: if keywords: fn = ast.Name("__merge__", ast.Load()) d = utils.keywords_to_dict(keywords) kwargs = ast.Call(fn, [kwargs, d], None, None, None) else: kwargs = ast.Dict([], []) if starargs: allargs = [] if args: allargs.append(utils.list_to_ast(args)) allargs.append(starargs) if use_kwargs: allargs.append(kwargs) if len(allargs) is 1: args = allargs[0] else: base = allargs[0] rest = allargs[1:] concat = ast.Attribute(base, "concat", ast.Load()) concat_call = ast.Call(concat, rest, None, None, None) args = JSCode(self.compile_node(concat_call, use_kwargs = False)) _apply = ast.Attribute(func, "apply", ast.Load()) call = ast.Call(_apply, [ast.Name("None", ast.Load()), args], None, None, None) return self.compile_node(call, use_kwargs = False) else: if use_kwargs: args = args + [kwargs] return "%s(%s)" % (self.compile_node(func), self.compile_node_list(args)) def compile_ClassCall(self, call): return "new %s" % self.compile_node(call) def compile_BinOp(self, node): op = node.op if self.op_is_function(op): left = node.left right = node.right return self.compile_node(ast.Call(op, [left, right], None, None, None)) else: left = self.compile_node(node.left) op = self.compile_node(node.op) right = self.compile_node(node.right) return "(%s %s %s)" % (left, op, right) def compile_UnaryOp(self, node): op = node.op if self.op_is_function(op): operand = node.operand return self.compile_node(ast.Call(op, [operand], None, None, None)) else: op = self.compile_node(node.op) operand = self.compile_node(node.operand) return "%s%s" % (op, operand) def compile_BoolOp(self, node): op = node.op if self.op_is_function(op): values = node.values return self.compile_node(ast.Call(op, values), None, None, None) else: op = self.compile_node(node.op) return "(" + (" %s " % op).join([self.compile_node(x) for x in node.values]) + ")" def compile_Compare(self, node): left = node.left ops = node.ops comparators = node.comparators results = [] for idx, op in enumerate(ops): comparator = comparators[idx] if self.op_is_function(op): result = ast.Call(op, [left, comparator], None, None, None) result = self.compile_node(result) else: result = "%s %s %s" % (self.compile_node(left), self.compile_node(op), self.compile_node(comparator)) results.append(JSCode(result)) left = comparator bool_op = ast.BoolOp(op = ast.And(), values = results) return self.compile_node(bool_op) def compile_Assign(self, node, assigner = '='): targets = node.targets value = node.value targets_len = len(targets) target0 = targets[0] if targets_len > 1 or (isinstance(target0, ast.Tuple) or isinstance(target0, ast.List)): if targets_len == 1: elts = targets[0].elts else: elts = targets base_name_store = self.gensym("ref", ast.Store()) base_name = base_name_store.id base_name_load = ast.Name(base_name, ast.Load()) base_assign = ast.Assign([base_name_store], value) assignments = [base_assign] for idx, target in enumerate(elts): index = ast.Index(ast.Num(idx)) subscript = ast.Subscript(base_name_load, index, ast.Load()) assign = ast.Assign([target], subscript) assignments.append(assign) return self.compile_node_list(assignments) else: target = targets[0] c_target = self.compile_node(target) c_value = self.compile_node(value) return "%s %s %s" % (c_target, assigner, c_value) def compile_AugAssign(self, node): target = node.target op = node.op value = node.value if self.op_is_function(op): left = ast.Name(target.id, ast.Load()) right = value value = ast.BinOp(left = left, op = op, right = right) assigner = "=" else: assigner = "%s=" % self.compile_node(op) assign = ast.Assign([target], value) return self.compile_node(assign, assigner) def compile_Attribute(self, node, assign = None): ctx = node.ctx value = self.compile_node(node.value) attr = node.attr if attr == "super": attr = "$super" if isinstance(ctx, ast.Store) or isinstance(ctx, ast.Load) or isinstance(ctx, ast.Del): pass else: raise CompileError("Can't compile attribute with context of type %s" % ctx.__class__.__name__) return "%s.%s" % (value, attr) def compile_Subscript(self, node, native_index = False): value = node.value slice = node.slice _type = self.context_stack.find_type(node) is_native = self._is_native_type(_type) if isinstance(slice, ast.Index): kwargs = {'value': value, 'native_index': is_native or native_index} elif isinstance(slice, ast.Slice): if is_native: raise CompilerError("Can't slice a native javascript object") kwargs = {'value': value} else: raise CompilerError("Can't compile subscript unless the slice node is either ast.Index or ast.Slice") return self.compile_node(slice, **kwargs) def compile_Index(self, node, value, native_index = False): if not native_index: fn = ast.Name("__getindex__", ast.Load()) call = ast.Call(fn, [value, node.value], None, None, None) node_value = self.compile_node(call) else: node_value = self.compile_node(node.value) return "%s[%s]" % (self.compile_node(value), node_value) def compile_Slice(self, node, value): undefined = JSCode("void 0") lower = node.lower or undefined upper = node.upper or undefined step = node.step or undefined fn = ast.Name("__slice__", ast.Load()) call = ast.Call(fn, [value, lower, upper, step], None, None, None) return self.compile_node(call) # Binary operators def compile_Add(self, node): return "+" def compile_Sub(self, node): return "-" def compile_Div(self, node): return "/" def compile_FloorDiv(self, node): return "__python__.__floor_div__" def compile_Mult(self, node): return "*" def compile_Pow(self, node): return "Math.pow" def compile_Mod(self, node): return "%" # Boolean operators def compile_Or(self, node): return "||" def compile_And(self, node): return "&&" def compile_Not(self, node): return "!" # Unary operators def compile_USub(self, node): return "-" def compile_UAdd(self, node): return "+" # Bitwise operators def compile_Invert(self, node): return "~" def compile_RShift(self, node): return ">>>" def compile_LShift(self, node): return "<<" def compile_BitAnd(self, node): return "&" def compile_BitOr(self, node): return "|" def compile_BitXor(self, node): return "^" # Comparison operators def compile_Eq(self, node): name = ast.Name("__eq__", ast.Load()) return self.compile_node(name) def compile_NotEq(self, node): name = ast.Name("__eq__", ast.Load()) return "!%s" % self.compile_node(name) def compile_In(self, node): name = ast.Name("__in__", ast.Load()) return self.compile_node(name) def compile_NotIn(self, node): _in = self.compile_node(ast.In()) return "!%s" % _in def compile_Gt(self, node): return ">" def compile_GtE(self, node): return ">=" def compile_Lt(self, node): return "<" def compile_LtE(self, node): return "<=" def compile_Is(self, node): return "===" def compile_IsNot(self, node): return "!==" def compile_Delete(self, node): results = [] for target in node.targets: if isinstance(target, ast.Attribute): results.append(JSCode("delete %s" % self.compile_node(target))) elif isinstance(target, ast.Name): result = ast.Assign([target], JSCode("void 0")) results.append(JSCode(self.compile_node(result))) else: raise Exception("Can't delete instance of type %s" % target.__class__.__name__) return self.compile_statement_list(results) def compile_Pass(selfe, node): return None # Primitives def compile_Num(self, node): n = str(node.n) return n re_dblquote = re.compile(r'"') re_newline = re.compile(r'\n') def compile_Str(self, node): escaped = self.re_dblquote.sub(r'\"', node.s) escaped = self.re_newline.sub(r'\\n', escaped) return '"%s"' % escaped def compile_Name(self, node): id = node.id ctx = node.ctx if id in utils.python_keywords: return utils.python_keywords[id] is_super = id == "super" if id in utils.javascript_reserved_words: id = "$%s" % id node = ast.Name(id, ctx) try: active_ctx = self.context_stack.find(node) except IndexError: active_ctx = None if active_ctx: should_export = active_ctx.is_module_context or active_ctx.is_class_context ctx_is_store = isinstance(ctx, ast.Store) if should_export and ctx_is_store and not isinstance(ctx, StoreLocal): active_ctx.set_export(node) elif ctx_is_store: active_ctx.set_local(node) is_local_module_name = node.id is self.local_module_name.id if is_super: return "__self__.%s" % id elif not is_local_module_name and not self.is_builtins and active_ctx and active_ctx.is_module_context and active_ctx.is_export(node): local_module_name = self.compile_node(self.local_module_name) return "%s.%s" % (local_module_name, id) elif active_ctx and active_ctx.is_class_context and active_ctx.is_export(node) and id is not active_ctx.class_name: return "%s.%s.%s" % (active_ctx.class_name, "prototype", id) else: if not active_ctx or not active_ctx.is_local(node): self.main_compiler.builtins.use(id) return id def compile_If(self, node): indent0 = self.indent() indent1 = self.indent(1) test = self.compile_node(node.test) body = self.compile_statement_list(node.body) if node.orelse: orelse = self.compile_statement_list(node.orelse) orelse = " else {\n%(indent1)s%(orelse)s\n%(indent0)s}" % {'orelse': orelse, 'indent0': indent0, 'indent1': indent1} else: orelse = '' self.indent(-1) return 'if (%(test)s) {\n%(indent1)s%(body)s\n%(indent0)s}%(orelse)s' % {'test': test, 'body': body, 'orelse': orelse, 'indent0': indent0, 'indent1': indent1} def compile_While(self, node): test = node.test body = node.body orelse = node.orelse indent0 = self.indent() indent1 = self.indent(1) if orelse: _if = ast.If(test, orelse) body = body + [_if] result = ("while (%(test)s) {\n" "%(indent1)s%(body)s\n" "%(indent0)s}") % { 'test': self.compile_node(test), 'body': self.compile_statement_list(body), 'indent0': indent0, 'indent1': indent1, } self.indent(-1) return result def compile_For(self, node): indent0 = self.indent() indent1 = self.indent(1) target = node.target iter = node.iter body = node.body orelse = node.orelse iter_store = self.gensym("iter", ast.Store()) iter_load = ast.Name(iter_store.id, ast.Load()) counter_store = self.gensym('i', ast.Store()) counter_load = ast.Name(counter_store.id, ast.Load()) len_store = self.gensym('len', ast.Store()) len_load = ast.Name(len_store.id, ast.Load()) for_condition = "(%(counter_store)s = 0, %(iter_store)s = %(iter)s, %(len_store)s = %(iter_load)s.length;" \ " %(counter_load)s < %(len_load)s;" \ " %(counter_store)s++)" % { 'counter_store': self.compile_node(counter_store), 'counter_load': self.compile_node(counter_load), 'len_store': self.compile_node(len_store), 'len_load': self.compile_node(len_load), 'iter': self.compile_node(iter), 'iter_store': self.compile_node(iter_store), 'iter_load': self.compile_node(iter_load), } target_assign = ast.Assign([target], ast.Subscript(iter_load, ast.Index(counter_load), ast.Load())) body = [target_assign] + body if orelse: _if = ast.If(ast.BinOp(counter_load, ast.Eq(), len_load), orelse) body = body + [_if] result = 'for %(for_condition)s {\n%(indent1)s%(body)s\n%(indent0)s}' % { 'for_condition': for_condition, 'indent0': indent0, 'indent1': indent1, 'body': self.compile_statement_list(body) } self.indent(-1) return result def compile_ListComp(self, node): elt = node.elt generators = node.generators result_name = self.gensym("result", ast.Store()) result_assign = ast.Assign([result_name], ast.List([], ast.Store())) result_name = ast.Name(result_name.id, ast.Load()) pusher = ast.Attribute(result_name, "push", ast.Load()) append_result = JSCode("%s(%s)" % (self.compile_node(pusher), self.compile_node(elt))) body = append_result for generator in reversed(generators): ifs = generator.ifs compare = None for _if in ifs: if not compare: compare = _if else: compare = ast.BinOp(compare, ast.And(), _if) if compare: body = ast.If(compare, [body], None) body = ast.For(generator.target, generator.iter, [body], None) ret = ast.Return(result_name) func = ast.FunctionDef(None, [], [result_assign, body, ret], None, None) call = ast.Call(func, [], None, None, None) return self.compile_node(call) def compile_Try(self, node): body = node.body handlers = node.handlers orelse = node.orelse finalbody = node.finalbody indent0 = self.indent() indent1 = self.indent(1) c_handlers = [] context = self.context_stack.new() err_store = self.gensym("err", ast.Store()) err_load = ast.Name(err_store.id, ast.Load()) c_err_store = self.compile_node(err_store) for handler in handlers: name = handler.name _type = handler.type _body = handler.body if name: assign = ast.Assign([name], err_load) _body = [assign] + _body else: name = err_load if _type: _isinstance = ast.Name("isinstance", ast.Load()) call = ast.Call(_isinstance, [name, _type]) _body = ast.If(call, _body, None) c_handlers.append(_body) if not _type: break c_handlers = self.compile_statement_list(c_handlers) self.context_stack.pop() if orelse: diderr_store = self.gensym("diderr", ast.Store()) diderr_load = ast.Name(diderr_store.id, ast.Load()) assign = ast.Assign([diderr_store], ast.Name("True", ast.Load())) c_handlers = [assign] + c_handlers _if = ast.If(diderr_load, orelse) finalbody = finalbody + [_if] if finalbody: _finally = " finally {\n%(indent1)s" \ "%(finally)s\n%(indent0)s" \ "}" % { 'finally': self.compile_statement_list(finalbody), 'indent0': indent0, 'indent1': indent1, } else: _finally = "" result = "try {\n%(indent1)s" \ "%(body)s\n%(indent0)s" \ "} catch (%(err_store)s) {\n%(indent1)s" \ "%(handlers)s\n%(indent0)s" \ "}%(finally)s"% { 'body': self.compile_statement_list(body), 'err_store': c_err_store, 'handlers': c_handlers, 'finally': _finally, 'indent1': indent1, 'indent0': indent0, } self.indent(-1) return result def compile_Raise(self, node): exc = node.exc cause = node.cause return "throw %s" % self.compile_node(exc) def compile_Global(self, node): ctx = self.context_stack[-1] for name in node.names: ctx.set_global(name) def _dict_is_type_annotation(self, ls): for x in ls: if not isinstance(x, ast.Str): return False return True def compile_Dict(self, node): keys = node.keys values = node.values assert len(node.keys) == len(node.values) container = self._is_statement_of(node) # print("compile_Dict", container) # Ensure this is a standalone statement or the last statement in a container (so it doesn't mess lambdas up). if container and node is not container.body[-1]: keys_work = self._dict_is_type_annotation(keys) values_work = self._dict_is_type_annotation(values) if keys_work and values_work: # We will now proceed assuming the dict is a type annotation. context = self.context_stack[-1] for key, value in zip(keys, values): context.type(key, value) # print("compile_Dict", "Type annotation", end=" ") # print_node(node) return None kvs = [] for key, value in zip(keys, values): kvs.append("%s: %s" % (self.compile_node(key), self.compile_node(value))) if kvs: indent0 = self.indent() indent1 = self.indent(1) result = "{\n%(indent1)s%(kvs)s\n%(indent0)s}" % { 'kvs': (',\n%s' % indent1).join(kvs), 'indent0': indent0, 'indent1': indent1, } self.indent(-1) else: result = "{}" return result def compile_Array(self, node): ls = node.list elts = self.compile_node_list(ls) return "[%s]" % elts def compile_List(self, node): return self.compile_node(Array(node.elts)) def compile_Tuple(self, node): tuplecls = ast.Name('tuple', ast.Load()) call = ClassCall(tuplecls, [JSCode(self.compile_node(Array(node.elts)))], None, None, None) return self.compile_node(call) def compile_Lambda(self, node): ret = ast.Return(node.body) func = ast.FunctionDef(name = None, args = node.args, body = [ret], decorator_list = None) return self.compile_node(func) def compile_FunctionDef(self, node, class_name = None, true_named_function = False): # Compile the function name outside of the function context. is_class_fn = False if node.name: name_ctx = StoreLocal() if true_named_function else ast.Store() name = ast.Name(node.name, name_ctx) ctx = self.context_stack.find(name) _type = ctx.type(name) fn_is_native = self._is_native_type(_type) is_class_fn = ctx.is_class_context c_name = self.compile_node(name) name = JSCode(c_name) else: name = None fn_is_native = False has_super = False if is_class_fn: for body_node in node.body: for child_node in ast.walk(body_node): if isinstance(child_node, ast.Name) and child_node.id in ('super', '$super'): has_super = True break ctx = self.context_stack.new(class_name = class_name) indent0 = self.indent() indent1 = self.indent(1) body = node.body annotations = {} if node.args and not ctx.is_class_context: jsarguments = JSCode("arguments") args = node.args.args argnames = [] argbody = [] def kwarg_error(): if fn_is_native: CompileError("Can't use keyword args in native-style function") kwarg = node.args.kwarg if kwarg: kwarg_error() kwargdict = ast.Name(kwarg, ast.Load()) kwargannotation = node.args.kwargannotation if kwargannotation: annotations[kwarg] = kwargannotation elif not fn_is_native: kwargdict = ast.Name("$kwargdict", ast.Load()) if not fn_is_native and not ctx.is_module_context: # The last argument will always be the kwargs. Make this assignment in the body of the function. kwargsubscript = ast.Subscript(jsarguments, ast.Index(JSCode("arguments.length-1")), ast.Load()) kwargsubscript = JSCode(self.compile_node(kwargsubscript, native_index = True)) kwargassign = ast.Assign([ast.Name(kwargdict.id, ast.Store())], kwargsubscript) argbody.append(kwargassign) def set_arg(arg, value, default = True): name = ast.Name(arg, ast.Store()) kwargpart = " || %s === $kwargdict" % self.compile_node(name) if not fn_is_native else "" assign = ast.Assign([name], value) if default: condition = JSCode("%s === void 0%s" % (self.compile_node(name), kwargpart if not ctx.is_module_context else "")) _if = ast.If(condition, [assign], None) result = _if else: result = assign return result defaults = node.args.defaults or [] if len(defaults) < len(args): diff = len(args) - len(defaults) defaults = ([None] * diff) + defaults for idx, arg in enumerate(args): a = arg.arg ctx.set_argument(a) argnames.append(a) if not fn_is_native and not ctx.is_module_context: subscript = ast.Subscript(kwargdict, ast.Index(ast.Str(a)), ast.Load()) subscript = JSCode(self.compile_node(subscript, native_index = True)) _if = set_arg(a, subscript) if_body = _if.body default = defaults[idx] if default: if_default = set_arg(a, default) if_body.append(if_default) argbody.append(_if) annotation = arg.annotation if annotation: annotations[a] = annotation if not ctx.is_module_context: vararg = node.args.vararg if vararg: start = ast.Num(len(argnames)) end = ast.Num(-1) _slice = ast.Slice(start, end, None) gather = ast.Subscript(jsarguments, _slice, ast.Load()) assign = ast.Assign([ast.Name(vararg, ast.Store())], gather) argbody.append(assign) varargannotation = node.args.varargannotation if varargannotation: annotations[vararg] = varargannotation kwonlyargs = node.args.kwonlyargs if kwonlyargs: kwarg_error() kw_defaults = node.args.kw_defaults or [] for idx, arg in enumerate(kwonlyargs): a = arg.arg subscript = ast.Subscript(kwargdict, ast.Index(ast.Str(a)), ast.Load()) subscript = JSCode(self.compile_node(subscript, native_index = True)) assign = set_arg(a, subscript, False) argbody.append(assign) try: default = kw_defaults[idx] if default: argbody.append(set_arg(a, default)) except IndexError: pass annotation = arg.annotation if annotation: annotations[a] = annotation try: returns = node.returns if returns: annotations["return"] = returns except AttributeError: pass args = ', '.join(argnames) body = argbody + body else: args = '' if annotations: keys = [] values = [] for k, v in annotations.items(): k = ast.Str(k) # Use as type hint. if isinstance(v, ast.Str): ctx.type(k, v) keys.append(k) values.append(v) annotations = ast.Dict(keys, values) if not name: name = self.gensym("func", ast.Store()) if has_super: _self = ast.Name("__self__", ast.Store()) _self_assign = ast.Assign([_self], JSCode("arguments[0]")) body = [_self_assign] + body body = self.compile_statement_list(body) declare_locals = ctx.get_vars() body = self.compile_statement_list([declare_locals, JSCode(body)]) if true_named_function: true_fn_name = name or "" else: true_fn_name = "" value = ("function %s(%s) {\n%s" "%s\n" "%s}") % (true_fn_name, args, indent1, body, indent0) self.context_stack.pop() self.indent(-1) if name and not true_named_function: assign = ast.Assign([name], JSCode(value)) result = self.compile_node(assign) else: result = value if annotations: id = None if isinstance(name, ast.Name): id = name.id elif isinstance(name, JSCode): id = name.code else: raise CompileError("Can't make function annotations without a function name") n = ast.Name(id, ast.Load()) attr = ast.Attribute(n, "__annotations__", ast.Store()) assign = ast.Assign([attr], annotations) return "(" + self.compile_node_list([JSCode(result), assign, n]) + ")" else: return result def compile_Return(self, node): result = "return" if node.value: return "%s %s" % (result, self.compile_node(node.value)) else: return result def compile_ClassDef(self, node): name = node.name body = node.body instantiate = ast.Name("__class_instantiate__", ast.Load()) instantiate_call = ast.Call(instantiate, [ast.Name(name, ast.Load()), JSCode("this"), JSCode("arguments")], None, None, None) ret = ast.Return(instantiate_call) cls = ast.FunctionDef(name, [], [ret], None, None) self.indent(1) # Make sure the class function name is compiled in its own context. self.context_stack.new() cls = JSCode(self.compile_node(cls, true_named_function = True)) self.context_stack.pop() self.indent(-1) if not node.bases: node.bases = [ast.Name('object', ast.Load())] elif len(node.bases) > 1: raise CompileError("Multiple inheritance is not currently supported") basename = node.bases[0] func = ast.Name("__class_extend__", ast.Load()) base = ast.Call(func, [JSCode(name), basename], None, None, None) ret = ast.Return(JSCode(name)) func = ast.FunctionDef(name = None, args = None, body = [base, cls] + body + [ret]) func = JSCode(self.compile_node(func, class_name = name)) call = ast.Call(func, [], None, None, None) cls_name = ast.Name(name, ast.Store()) assign = ast.Assign([cls_name], call) return self.compile_node(assign)