#!/usr/bin/env python # _*_ coding: utf-8 _*_ # Python to PythonJS Translator # by Amirouche Boubekki and Brett Hartshorn - copyright 2013 # License: "New BSD" import os, sys, copy from types import GeneratorType import ast from ast import Str from ast import Call from ast import Name from ast import Tuple from ast import Assign from ast import keyword from ast import Subscript from ast import Attribute from ast import FunctionDef from ast import BinOp from ast import Pass from ast import Global from ast import With from ast import parse from ast import NodeVisitor import typedpython import ministdlib import inline_function import code_writer from ast_utils import * ## TODO def log(txt): pass POWER_OF_TWO = [ 2**i for i in range(32) ] writer = writer_main = code_writer.Writer() __webworker_writers = dict() def get_webworker_writer( jsfile ): if jsfile not in __webworker_writers: __webworker_writers[ jsfile ] = code_writer.Writer() return __webworker_writers[ jsfile ] class Typedef(object): # http://docs.python.org/2/reference/datamodel.html#emulating-numeric-types _opmap = dict( __add__ = '+', __iadd__ = '+=', __sub__ = '-', __isub__ = '-=', __mul__ = '*', __imul__ = '*=', __div__ = '/', __idiv__ = '/=', __mod__ = '%', __imod__ = '%=', __lshift__ = '<<', __ilshift__ = '<<=', __rshift__ = '>>', __irshift__ = '>>=', __and__ = '&', __iand__ = '&=', __xor__ = '^', __ixor__ = '^=', __or__ = '|', __ior__ = '|=', ) def __init__(self, **kwargs): for name in kwargs.keys(): ## name, methods, properties, attributes, class_attributes, parents setattr( self, name, kwargs[name] ) self.operators = dict() for name in self.methods: if name in self._opmap: op = self._opmap[ name ] self.operators[ op ] = self.get_pythonjs_function_name( name ) def get_pythonjs_function_name(self, name): assert name in self.methods return '__%s_%s' %(self.name, name) ## class name def check_for_parent_with(self, method=None, property=None, operator=None, class_attribute=None): for parent_name in self.parents: if not self.compiler.is_known_class_name( parent_name ): continue typedef = self.compiler.get_typedef( class_name=parent_name ) if method and method in typedef.methods: return typedef elif property and property in typedef.properties: return typedef elif operator and typedef.operators: return typedef elif class_attribute in typedef.class_attributes: return typedef elif typedef.parents: res = typedef.check_for_parent_with( method=method, property=property, operator=operator, class_attribute=class_attribute ) if res: return res class NodeVisitorBase( ast.NodeVisitor ): def __init__(self): self._line = None self._line_number = 0 self._stack = [] ## current path to the root def visit(self, node): """Visit a node.""" ## modified code of visit() method from Python 2.7 stdlib self._stack.append(node) method = 'visit_' + node.__class__.__name__ visitor = getattr(self, method, self.generic_visit) res = visitor(node) self._stack.pop() return res def format_error(self, node): lines = [] if self._line_number > 0: lines.append( self._source[self._line_number-1] ) lines.append( self._source[self._line_number] ) if self._line_number+1 < len(self._source): lines.append( self._source[self._line_number+1] ) msg = 'line %s\n%s\n%s\n' %(self._line_number, '\n'.join(lines), node) msg += 'Depth Stack:\n' for l, n in enumerate(self._stack): #msg += str(dir(n)) msg += '%s%s line:%s col:%s\n' % (' '*(l+1)*2, n.__class__.__name__, n.lineno, n.col_offset) return msg class PythonToPythonJS(NodeVisitorBase, inline_function.Inliner): identifier = 0 _func_typedefs = () def __init__(self, source=None, modules=False, module_path=None, dart=False, coffee=False, lua=False, go=False, fast_javascript=False, pure_javascript=False): #super(PythonToPythonJS, self).__init__() NodeVisitorBase.__init__(self) self._modules = modules ## split into mutiple files by class self._module_path = module_path ## used for user `from xxx import *` to load .py files in the same directory. self._with_lua = lua self._with_coffee = coffee self._with_dart = dart self._with_go = go self._with_gojs = False self._fast_js = fast_javascript self._strict_mode = pure_javascript self._html_tail = []; script = False if source.strip().startswith('') script = list() elif 'src=' in line and '~/' in line: ## external javascripts installed in users home folder x = line.split('src="')[-1].split('"')[0] if os.path.isfile(os.path.expanduser(x)): o = [] o.append( '') if script is True: self._html_tail.extend( o ) else: for y in o: writer.write(y) else: writer.write(line) elif line.strip() == '': if type(script) is list and len(script): source = '\n'.join(script) script = True self._html_tail.append( '') else: writer.write( line ) elif isinstance( script, list ): script.append( line ) elif script is True: self._html_tail.append( line ) else: writer.write( line ) source = typedpython.transform_source( source ) self.setup_inliner( writer ) self._in_catch_exception = False ## optimize "+" and "*" operator if fast_javascript: self._direct_operators = set( ['+', '*'] ) else: self._direct_operators = set() self._with_ll = False ## lowlevel self._with_js = True self._in_lambda = False self._in_while_test = False self._use_threading = False self._use_sleep = False self._use_array = False self._webworker_functions = dict() self._with_webworker = False self._with_rpc = None self._with_rpc_name = None self._with_direct_keys = fast_javascript self._with_glsl = False self._in_gpu_main = False self._gpu_return_types = set() ## 'array' or float32, or array of 'vec4' float32's. self._source = source.splitlines() self._class_stack = list() self._classes = dict() ## class name : [method names] self._class_parents = dict() ## class name : parents self._instance_attributes = dict() ## class name : [attribute names] self._class_attributes = dict() self._catch_attributes = None self._typedef_vars = dict() #self._names = set() ## not used? ## inferred class instances, TODO regtests to confirm that this never breaks ## self._instances = dict() ## instance name : class name self._decorator_properties = dict() self._decorator_class_props = dict() self._function_return_types = dict() self._return_type = None self._typedefs = dict() ## class name : typedef (deprecated - part of the old static type finder) self._globals = dict() self._global_nodes = dict() self._with_static_type = None self._global_typed_lists = dict() ## global name : set (if len(set)==1 then we know it is a typed list) self._global_typed_dicts = dict() self._global_typed_tuples = dict() self._global_functions = dict() self._js_classes = dict() self._in_js_class = False self._in_assign_target = False self._with_runtime_exceptions = True ## this is only used in full python mode. self._iter_ids = 0 self._addop_ids = 0 self._cache_for_body_calls = False self._cache_while_body_calls = False self._comprehensions = [] self._generator_functions = set() self._in_loop_with_else = False self._introspective_functions = False self._custom_operators = {} self._injector = [] ## advanced meta-programming hacks self._in_class = None self._with_fastdef = False self.setup_builtins() source = self.preprocess_custom_operators( source ) ## check for special imports - TODO clean this up ## for line in source.splitlines(): if line.strip().startswith('import tornado'): dirname = os.path.dirname(os.path.abspath(__file__)) header = open( os.path.join(dirname, os.path.join('fakelibs', 'tornado.py')) ).read() source = header + '\n' + source self._source = source.splitlines() elif line.strip().startswith('import os'): dirname = os.path.dirname(os.path.abspath(__file__)) header = open( os.path.join(dirname, os.path.join('fakelibs', 'os.py')) ).read() source = header + '\n' + source self._source = source.splitlines() elif line.strip().startswith('import tempfile'): dirname = os.path.dirname(os.path.abspath(__file__)) header = open( os.path.join(dirname, os.path.join('fakelibs', 'tempfile.py')) ).read() source = header + '\n' + source self._source = source.splitlines() elif line.strip().startswith('import sys'): dirname = os.path.dirname(os.path.abspath(__file__)) header = open( os.path.join(dirname, os.path.join('fakelibs', 'sys.py')) ).read() source = header + '\n' + source self._source = source.splitlines() elif line.strip().startswith('import subprocess'): dirname = os.path.dirname(os.path.abspath(__file__)) header = open( os.path.join(dirname, os.path.join('fakelibs', 'subprocess.py')) ).read() source = header + '\n' + source self._source = source.splitlines() if '--debug--' in sys.argv: try: tree = ast.parse( source ) except SyntaxError: raise SyntaxError(source) else: tree = ast.parse( source ) self._generator_function_nodes = collect_generator_functions( tree ) for node in tree.body: ## skip module level doc strings ## if isinstance(node, ast.Expr) and isinstance(node.value, ast.Str): pass else: self.visit(node) if self._html_tail: for line in self._html_tail: writer.write(line) def has_webworkers(self): return len(self._webworker_functions.keys()) def get_webworker_file_names(self): return set(self._webworker_functions.values()) def preprocess_custom_operators(self, data): ''' custom operators must be defined before they are used ''' code = [] for line in data.splitlines(): if line.strip().startswith('@custom_operator'): l = line.replace('"', "'") a,b,c = l.split("'") op = b.decode('utf-8') self._custom_operators[ op ] = None else: for op in self._custom_operators: op = op.encode('utf-8') line = line.replace(op, '|"%s"|'%op) code.append( line ) data = '\n'.join( code ) return data def setup_builtins(self): self._classes['dict'] = set(['__getitem__', '__setitem__']) self._classes['list'] = set() #['__getitem__', '__setitem__']) self._classes['tuple'] = set() #['__getitem__', '__setitem__']) self._builtin_classes = set(['dict', 'list', 'tuple']) self._builtin_functions = { 'ord':'%s.charCodeAt(0)', 'chr':'String.fromCharCode(%s)', 'abs':'Math.abs(%s)', 'cos':'Math.cos(%s)', 'sin':'Math.sin(%s)', 'sqrt':'Math.sqrt(%s)' } self._builtin_functions_dart = { 'ord':'%s.codeUnitAt(0)', 'chr':'new(String.fromCharCode(%s))', } def is_known_class_name(self, name): return name in self._classes def get_typedef(self, instance=None, class_name=None): assert instance or class_name if isinstance(instance, Name) and instance.id in self._instances: class_name = self._instances[ instance.id ] if class_name: #assert class_name in self._classes if class_name not in self._classes: #log('ERROR: class name not in self._classes: %s'%class_name) #log('self._classes: %s'%self._classes) #raise RuntimeError('class name: %s - not found in self._classes - node:%s '%(class_name, instance)) return None ## TODO hook into self._typedef_vars if class_name not in self._typedefs: self._typedefs[ class_name ] = Typedef( name = class_name, methods = self._classes[ class_name ], #properties = self._decorator_class_props[ class_name ], #attributes = self._instance_attributes[ class_name ], #class_attributes = self._class_attributes[ class_name ], #parents = self._class_parents[ class_name ], properties = self._decorator_class_props.get( class_name, set()), attributes = self._instance_attributes.get( class_name, set()), class_attributes = self._class_attributes.get( class_name, set()), parents = self._class_parents.get( class_name, set()), compiler = self, ) return self._typedefs[ class_name ] def visit_Import(self, node): ''' fallback to requirejs or if in webworker importScripts. some special modules from pythons stdlib can be faked here like: . threading nodejs only: . tornado . os ''' tornado = ['tornado', 'tornado.web', 'tornado.ioloop'] for alias in node.names: if self._with_go: writer.write('import %s' %alias.name) elif alias.name in tornado: pass ## pythonjs/fakelibs/tornado.py elif alias.name == 'tempfile': pass ## pythonjs/fakelibs/tempfile.py elif alias.name == 'sys': pass ## pythonjs/fakelibs/sys.py elif alias.name == 'subprocess': pass ## pythonjs/fakelibs/subprocess.py elif alias.name == 'numpy': pass elif alias.name == 'json' or alias.name == 'os': pass ## part of builtins.py elif alias.name == 'threading': self._use_threading = True #writer.write( 'Worker = require("/usr/local/lib/node_modules/workerjs")') ## note: nodewebkit includes Worker, but only from the main script context, ## there might be a bug in requirejs or nodewebkit where Worker gets lost ## when code is loaded into main as a module using requirejs, as a workaround ## allow "workerjs" to be loaded as a fallback, however this appears to not work in nodewebkit. writer.write( 'if __NODEJS__==True and typeof(Worker)=="undefined": Worker = require("workerjs")') elif alias.asname: #writer.write( '''inline("var %s = requirejs('%s')")''' %(alias.asname, alias.name) ) writer.write( '''inline("var %s = require('%s')")''' %(alias.asname, alias.name.replace('__DASH__', '-')) ) elif '.' in alias.name: raise NotImplementedError('import with dot not yet supported: line %s' % node.lineno) else: #writer.write( '''inline("var %s = requirejs('%s')")''' %(alias.name, alias.name) ) writer.write( '''inline("var %s = require('%s')")''' %(alias.name, alias.name) ) def visit_ImportFrom(self, node): if self._with_dart: lib = ministdlib.DART elif self._with_lua: lib = ministdlib.LUA elif self._with_go: lib = ministdlib.GO else: lib = ministdlib.JS if self._module_path: path = os.path.join( self._module_path, node.module+'.py') else: path = os.path.join( './', node.module+'.py') if node.module == 'time' and node.names[0].name == 'sleep': self._use_sleep = True elif node.module == 'array' and node.names[0].name == 'array': self._use_array = True ## this is just a hint that calls to array call the builtin array elif node.module == 'bisect' and node.names[0].name == 'bisect': ## bisect library is part of the stdlib, ## in pythonjs it is a builtin function defined in builtins.py pass elif node.module in lib: imported = False for n in node.names: if n.name in lib[ node.module ]: if not imported: imported = True if ministdlib.REQUIRES in lib[node.module]: writer.write('import %s' %','.join(lib[node.module][ministdlib.REQUIRES])) writer.write( 'JS("%s")' %lib[node.module][n.name] ) if n.name not in self._builtin_functions: self._builtin_functions[ n.name ] = n.name + '()' elif os.path.isfile(path): ## user import `from mymodule import *` TODO support files from other folders ## this creates a sub-translator, because they share the same `writer` object (a global), ## there is no need to call `writer.write` here. ## note: the current pythonjs.configure mode here maybe different from the subcontext. data = open(path, 'rb').read() subtrans = PythonToPythonJS( data, module_path = self._module_path, fast_javascript = self._fast_js, modules = self._modules, pure_javascript = self._strict_mode, ) self._js_classes.update( subtrans._js_classes ) ## TODO - what other typedef info needs to be copied here? else: msg = 'invalid import - file not found: %s'%path raise SyntaxError( self.format_error(msg) ) def visit_Assert(self, node): ## hijacking "assert isinstance(a,A)" as a type system ## if isinstance( node.test, Call ) and isinstance(node.test.func, Name) and node.test.func.id == 'isinstance': a,b = node.test.args if b.id in self._classes: self._instances[ a.id ] = b.id def visit_Dict(self, node): node.returns_type = 'dict' a = [] for i in range( len(node.keys) ): k = self.visit( node.keys[ i ] ) v = node.values[i] if isinstance(v, ast.Lambda): v.keep_as_lambda = True v = self.visit( v ) if self._with_dart or self._with_ll or self._with_go or self._fast_js: a.append( '%s:%s'%(k,v) ) #if isinstance(node.keys[i], ast.Str): # a.append( '%s:%s'%(k,v) ) #else: # a.append( '"%s":%s'%(k,v) ) elif self._with_js: a.append( '[%s,%s]'%(k,v) ) else: a.append( 'JSObject(key=%s, value=%s)'%(k,v) ) ## this allows non-string keys if self._with_dart or self._with_ll or self._with_go or self._fast_js: b = ','.join( a ) return '{%s}' %b elif self._with_js: b = ','.join( a ) return '__jsdict( [%s] )' %b else: b = '[%s]' %', '.join(a) return '__get__(dict, "__call__")([], {"js_object":%s})' %b def visit_Tuple(self, node): node.returns_type = 'tuple' #a = '[%s]' % ', '.join(map(self.visit, node.elts)) a = [] for e in node.elts: if isinstance(e, ast.Lambda): e.keep_as_lambda = True v = self.visit(e) assert v is not None a.append( v ) a = '[%s]' % ', '.join(a) if self._with_dart: return 'tuple(%s)' %a else: return a def visit_List(self, node): node.returns_type = 'list' a = [] for e in node.elts: if isinstance(e, ast.Lambda): ## inlined and called lambda "(lambda x: x)(y)" e.keep_as_lambda = True v = self.visit(e) assert v is not None a.append( v ) a = '[%s]' % ', '.join(a) if self._with_ll: pass elif self._with_lua: a = '__get__(list, "__call__")({}, {pointer:%s, length:%s})'%(a, len(node.elts)) return a def visit_GeneratorExp(self, node): return self.visit_ListComp(node) _comp_id = 0 def visit_DictComp(self, node): ''' node.key is key name node.value is value ''' #raise SyntaxError(self.visit(node.key)) ## key, value, generators node.returns_type = 'dict' if len(self._comprehensions) == 0: comps = collect_dict_comprehensions( node ) for i,cnode in enumerate(comps): cname = '__comp__%s' % self._comp_id cnode._comp_name = cname self._comprehensions.append( cnode ) self._comp_id += 1 cname = node._comp_name writer.write('var(%s)'%cname) length = len( node.generators ) a = ['idx%s'%i for i in range(length)] writer.write('var( %s )' %','.join(a) ) a = ['iter%s'%i for i in range(length)] writer.write('var( %s )' %','.join(a) ) a = ['get%s'%i for i in range(length)] writer.write('var( %s )' %','.join(a) ) if self._with_go: assert node.go_dictcomp_type k,v = node.go_dictcomp_type writer.write('%s = __go__map__(%s, %s)<<{}' %(cname, k,v)) else: writer.write('%s = {}'%cname) generators = list( node.generators ) generators.reverse() self._gen_comp( generators, node ) self._comprehensions.remove( node ) return cname def visit_ListComp(self, node): node.returns_type = 'list' if len(self._comprehensions) == 0 or True: comps = collect_comprehensions( node ) assert comps for i,cnode in enumerate(comps): cname = '__comp__%s' % self._comp_id cnode._comp_name = cname self._comprehensions.append( cnode ) self._comp_id += 1 cname = node._comp_name writer.write('var(%s)'%cname) #writer.write('var(__comp__%s)'%self._comp_id) length = len( node.generators ) + (len(self._comprehensions)-1) a = ['idx%s'%i for i in range(length)] writer.write('var( %s )' %','.join(a) ) a = ['iter%s'%i for i in range(length)] writer.write('var( %s )' %','.join(a) ) a = ['get%s'%i for i in range(length)] writer.write('var( %s )' %','.join(a) ) if self._with_go: assert node.go_listcomp_type #writer.write('__comp__%s = __go__array__(%s)' %(self._comp_id, node.go_listcomp_type)) writer.write('%s = __go__array__(%s)' %(cname, node.go_listcomp_type)) else: writer.write('%s = JSArray()'%cname) generators = list( node.generators ) generators.reverse() self._gen_comp( generators, node ) #if node in self._comprehensions: # self._comprehensions.remove( node ) if self._with_go: #return '__go__addr__(__comp__%s)' %self._comp_id return '__go__addr__(%s)' %cname else: #return '__comp__%s' %self._comp_id return cname def _gen_comp(self, generators, node): #self._comp_id += 1 #id = self._comp_id gen = generators.pop() id = len(generators) + self._comprehensions.index( node ) assert isinstance(gen.target, Name) writer.write('idx%s = 0'%id) is_range = False if isinstance(gen.iter, ast.Call) and isinstance(gen.iter.func, ast.Name) and gen.iter.func.id in ('range', 'xrange'): is_range = True writer.write('iter%s = %s' %(id, self.visit(gen.iter.args[0])) ) writer.write('while idx%s < iter%s:' %(id,id) ) writer.push() writer.write('var(%s)'%gen.target.id) writer.write('%s=idx%s' %(gen.target.id, id) ) elif self._with_js: ## only works with arrays in javascript mode writer.write('iter%s = %s' %(id, self.visit(gen.iter)) ) writer.write('while idx%s < iter%s.length:' %(id,id) ) writer.push() writer.write('var(%s)'%gen.target.id) writer.write('%s=iter%s[idx%s]' %(gen.target.id, id,id) ) else: writer.write('iter%s = %s' %(id, self.visit(gen.iter)) ) writer.write('get%s = __get__(iter%s, "__getitem__")'%(id,id) ) writer.write('while idx%s < __get__(len, "__call__")([iter%s], JSObject()):' %(id,id) ) ## TODO optimize writer.push() writer.write('var(%s)'%gen.target.id) writer.write('%s=get%s( [idx%s], JSObject() )' %(gen.target.id, id,id) ) if generators: self._gen_comp( generators, node ) else: cname = node._comp_name #self._comprehensions[-1] #cname = '__comp__%s' % self._comp_id if len(gen.ifs): test = [] for compare in gen.ifs: test.append( self.visit(compare) ) writer.write('if %s:' %' and '.join(test)) writer.push() self._gen_comp_helper(cname, node) writer.pull() else: self._gen_comp_helper(cname, node) if self._with_lua: writer.write('idx%s = idx%s + 1' %(id,id) ) else: writer.write('idx%s+=1' %id ) writer.pull() if self._with_lua: ## convert to list writer.write('%s = list.__call__({},{pointer:%s, length:idx%s})' %(cname, cname, id)) def _gen_comp_helper(self, cname, node): if isinstance(node, ast.DictComp): key = self.visit(node.key) val = self.visit(node.value) if self._with_go: writer.write('%s[ %s ] = %s' %(cname, key, val) ) else: writer.write('%s[ %s ] = %s' %(cname, key, val) ) else: if self._with_dart: writer.write('%s.add( %s )' %(cname,self.visit(node.elt)) ) elif self._with_lua: writer.write('table.insert(%s, %s )' %(cname,self.visit(node.elt)) ) elif self._with_go: writer.write('%s = append(%s, %s )' %(cname, cname,self.visit(node.elt)) ) else: writer.write('%s.push( %s )' %(cname,self.visit(node.elt)) ) def visit_In(self, node): return ' in ' def visit_NotIn(self, node): #return ' not in ' raise RuntimeError('"not in" is only allowed in if-test: see method - visit_Compare') ## TODO check if the default visit_Compare always works ## #def visit_Compare(self, node): # raise NotImplementedError( node ) def visit_AugAssign(self, node): self._in_assign_target = True target = self.visit( node.target ) self._in_assign_target = False op = '%s=' %self.visit( node.op ) typedef = self.get_typedef( node.target ) if self._with_lua: if isinstance(node.target, ast.Subscript): name = self.visit(node.target.value) slice = self.visit(node.target.slice) op = self.visit(node.op) a = '__get__(%s, "__setitem__")( [%s, __get__(%s, "__getitem__")([%s], {}) %s (%s)], {} )' a = a %(name, slice, name, slice, op, self.visit(node.value)) writer.write( a ) return elif op == '+=': a = '__add_op(%s,%s)' %(target, self.visit(node.value)) elif op == '-=': a = '(%s - %s)' %(target, self.visit(node.value)) elif op == '*=': a = '(%s * %s)' %(target, self.visit(node.value)) elif op == '/=' or op == '//=': a = '(%s / %s)' %(target, self.visit(node.value)) elif op == '%=': a = '__mod__(%s,%s)' %(target, self.visit(node.value)) elif op == '&=': a = '__and__(%s,%s)' %(target, self.visit(node.value)) elif op == '|=': a = '__or__(%s,%s)' %(target, self.visit(node.value)) elif op == '^=': a = '__xor__(%s,%s)' %(target, self.visit(node.value)) elif op == '<<=': a = '__lshift__(%s,%s)' %(target, self.visit(node.value)) elif op == '>>=': a = '__rshift__(%s,%s)' %(target, self.visit(node.value)) else: raise NotImplementedError(op) writer.write('%s=%s' %(target,a)) elif typedef and op in typedef.operators: func = typedef.operators[ op ] a = '%s( [%s, %s] )' %(func, target, self.visit(node.value)) writer.write( a ) elif op == '//=': if isinstance(node.target, ast.Attribute): name = self.visit(node.target.value) attr = node.target.attr target = '%s.%s' %(name, attr) if self._with_go: a = '%s /= %s' %(target, self.visit(node.value)) elif self._with_dart: a = '%s = (%s/%s).floor()' %(target, target, self.visit(node.value)) else: a = '%s = Math.floor(%s/%s)' %(target, target, self.visit(node.value)) writer.write(a) elif self._with_dart: if op == '+=': a = '%s.__iadd__(%s)' %(target, self.visit(node.value)) elif op == '-=': a = '%s.__isub__(%s)' %(target, self.visit(node.value)) elif op == '*=': a = '%s.__imul__(%s)' %(target, self.visit(node.value)) elif op == '/=': a = '%s.__idiv__(%s)' %(target, self.visit(node.value)) elif op == '%=': a = '%s.__imod__(%s)' %(target, self.visit(node.value)) elif op == '&=': a = '%s.__iand__(%s)' %(target, self.visit(node.value)) elif op == '|=': a = '%s.__ior__(%s)' %(target, self.visit(node.value)) elif op == '^=': a = '%s.__ixor__(%s)' %(target, self.visit(node.value)) elif op == '<<=': a = '%s.__ilshift__(%s)' %(target, self.visit(node.value)) elif op == '>>=': a = '%s.__irshift__(%s)' %(target, self.visit(node.value)) else: raise NotImplementedError b = '%s %s %s' %(target, op, self.visit(node.value)) if isinstance( node.target, ast.Name ) and node.target.id in self._typedef_vars and self._typedef_vars[node.target.id] in typedpython.native_number_types+typedpython.vector_types: writer.write(b) else: ## dart2js is smart enough to optimize this if/else away ## writer.write('if instanceof(%s, Number) or instanceof(%s, String): %s' %(target,target,b) ) writer.write('else: %s' %a) elif self._with_js: ## no operator overloading in with-js mode a = '%s %s %s' %(target, op, self.visit(node.value)) writer.write(a) elif isinstance(node.target, ast.Attribute): name = self.visit(node.target.value) attr = node.target.attr a = '%s.%s %s %s' %(name, attr, op, self.visit(node.value)) writer.write(a) elif isinstance(node.target, ast.Subscript): name = self.visit(node.target.value) slice = self.visit(node.target.slice) #if self._with_js: # a = '%s[ %s ] %s %s' # writer.write(a %(name, slice, op, self.visit(node.value))) #else: op = self.visit(node.op) value = self.visit(node.value) #a = '__get__(%s, "__setitem__")( [%s, __get__(%s, "__getitem__")([%s], {}) %s (%s)], {} )' fallback = '__get__(%s, "__setitem__")( [%s, __get__(%s, "__getitem__")([%s], {}) %s (%s)], {} )'%(name, slice, name, slice, op, value) if isinstance(node.target.value, ast.Name): ## TODO also check for arr.remote (RPC) if defined then __setitem__ can not be bypassed ## the overhead of checking if target is an array, ## and calling __setitem__ directly bypassing a single __get__, ## is greather than simply calling the fallback #writer.write('if instanceof(%s, Array): %s.__setitem__([%s, %s[%s] %s (%s) ], __NULL_OBJECT__)' %(name, name, slice, name,slice, op, value)) writer.write('if instanceof(%s, Array): %s[%s] %s= %s' %(name, name,slice, op, value)) writer.write('else: %s' %fallback) else: writer.write(fallback) else: ## TODO extra checks to make sure the operator type is valid in this context a = '%s %s %s' %(target, op, self.visit(node.value)) writer.write(a) def visit_Yield(self, node): return 'yield %s' % self.visit(node.value) def _get_js_class_base_init(self, node ): for base in node.bases: if base.id == 'object': continue n = self._js_classes[ base.id ] if hasattr(n, '_cached_init'): return n._cached_init else: return self._get_js_class_base_init( n ) ## TODO fixme def _visit_dart_classdef(self, node): name = node.name node._struct_vars = dict() self._js_classes[ name ] = node self._class_stack.append( node ) methods = {} method_list = [] ## getter/setters can have the same name props = set() struct_types = dict() for item in node.body: if isinstance(item, FunctionDef): methods[ item.name ] = item finfo = inspect_method( item ) props.update( finfo['properties'] ) if item.name != '__init__': method_list.append( item ) #if item.name == '__init__': continue continue item.args.args = item.args.args[1:] ## remove self for n in finfo['name_nodes']: if n.id == 'self': n.id = 'this' elif isinstance(item, ast.Expr) and isinstance(item.value, ast.Dict): sdef = [] for i in range( len(item.value.keys) ): k = self.visit( item.value.keys[ i ] ) v = self.visit( item.value.values[i] ) sdef.append( '%s=%s'%(k,v) ) writer.write('@__struct__(%s)' %','.join(sdef)) if self._with_go: pass elif props: writer.write('@properties(%s)'%','.join(props)) for dec in node.decorator_list: writer.write('@%s'%self.visit(dec)) bases = [] for base in node.bases: bases.append( self.visit(base) ) if bases: writer.write('class %s( %s ):'%(node.name, ','.join(bases))) else: writer.write('class %s:' %node.name) init = methods.get( '__init__', None) writer.push() ## declare vars here #for attr in props: # writer.write('JS("var %s")'%attr) ## constructor if init: methods.pop( '__init__' ) if not self._with_go: init.name = node.name self.visit(init) for item in init.body: if isinstance(item, ast.Assign) and isinstance(item.targets[0], ast.Attribute): if isinstance(item.targets[0].value, ast.Name) and item.targets[0].value.id=='self': attr = item.targets[0].attr if attr not in node._struct_vars: node._struct_vars[ attr ] = 'interface' ## methods for method in method_list: self.visit(method) for item in node.body: if isinstance(item, ast.With): s = self.visit(item) if s: writer.write( s ) if not init and not method_list: writer.write( 'pass' ) if node._struct_vars: writer.write('{') for k in node._struct_vars: v = node._struct_vars[k] writer.write(' %s : %s,' %(k,v)) writer.write('}') writer.pull() self._class_stack.pop() def is_gpu_method(self, n): for dec in n.decorator_list: if isinstance(dec, Attribute) and isinstance(dec.value, Name) and dec.value.id == 'gpu': if dec.attr == 'method': return True def _visit_js_classdef(self, node): name = node.name self._js_classes[ name ] = node self._in_js_class = True class_decorators = [] gpu_object = False for decorator in node.decorator_list: ## class decorators if isinstance(decorator, Attribute) and isinstance(decorator.value, Name) and decorator.value.id == 'gpu': if decorator.attr == 'object': gpu_object = True else: raise SyntaxError( self.format_error('invalid gpu class decorator') ) else: class_decorators.append( decorator ) method_names = [] ## write back in order (required by GLSL) methods = {} class_vars = [] for item in node.body: if isinstance(item, FunctionDef): method_names.append(item.name) methods[ item.name ] = item if self.is_gpu_method( item ): item.args.args[0].id = name ## change self to the class name, pythonjs.py changes it to 'ClassName self' else: item.args.args = item.args.args[1:] ## remove self finfo = inspect_function( item ) for n in finfo['name_nodes']: if n.id == 'self': n.id = 'this' elif isinstance(item, ast.Expr) and isinstance(item.value, Str): ## skip doc strings pass else: class_vars.append( item ) #init = methods.pop('__init__') init = methods.get( '__init__', None) if init: args = [self.visit(arg) for arg in init.args.args] node._cached_init = init if init.args.kwarg: args.append( init.args.kwarg ) else: args = [] init = self._get_js_class_base_init( node ) if init: args = [self.visit(arg) for arg in init.args.args] node._cached_init = init writer.write('def %s(%s):' %(name,','.join(args))) writer.push() if init: tail = '' if gpu_object: tail = 'this.__struct_name__="%s"' %name #for b in init.body: # line = self.visit(b) # if line: writer.write( line ) if hasattr(init, '_code'): ## cached ## code = init._code elif args: #code = '%s.__init__(this, %s); %s'%(name, ','.join(args), tail) code = 'this.__init__(%s); %s'%(', '.join(args), tail) init._code = code else: #code = '%s.__init__(this); %s'%(name, tail) code = 'this.__init__(); %s' % tail init._code = code writer.write(code) else: writer.write('pass') if not self._fast_js: ## `self.__class__` pointer ## writer.write('this.__class__ = %s' %name) ## isinstance runtime builtin requires this ## instance UID ## writer.write('this.__uid__ = "" + _PythonJS_UID') writer.write('_PythonJS_UID += 1') writer.pull() if not self._fast_js: ## class UID ## writer.write('%s.__uid__ = "" + _PythonJS_UID' %name) writer.write('_PythonJS_UID += 1') #keys = methods.keys() #keys.sort() for mname in method_names: method = methods[mname] gpu_method = False for dec in method.decorator_list: if isinstance(dec, Attribute) and isinstance(dec.value, Name) and dec.value.id == 'gpu': if dec.attr == 'method': gpu_method = True if gpu_method: method.name = '%s_%s' %(name, method.name) self._in_gpu_method = name ## name of class line = self.visit(method) if line: writer.write( line ) self._in_gpu_method = None else: writer.write('@%s.prototype'%name) line = self.visit(method) if line: writer.write( line ) #writer.write('%s.prototype.%s = %s'%(name,mname,mname)) if not self._fast_js: ## allows subclass method to extend the parent's method by calling the parent by class name, ## `MyParentClass.some_method(self)` f = 'function () { return %s.prototype.%s.apply(arguments[0], Array.prototype.slice.call(arguments,1)) }' %(name, mname) writer.write('%s.%s = JS("%s")'%(name,mname,f)) for base in node.bases: base = self.visit(base) if base == 'object': continue a = [ 'for (var n in %s.prototype) {'%base, ' if (!(n in %s.prototype)) {'%name, ' %s.prototype[n] = %s.prototype[n]'%(name,base), ' }', '}' ] a = ''.join(a) writer.write( "JS('%s')" %a ) for item in class_vars: if isinstance(item, Assign) and isinstance(item.targets[0], Name): item_name = item.targets[0].id item.targets[0].id = '__%s_%s' % (name, item_name) self.visit(item) # this will output the code for the assign writer.write('%s.prototype.%s = %s' % (name, item_name, item.targets[0].id)) if gpu_object: ## TODO check class variables ## writer.write('%s.prototype.__struct_name__ = "%s"' %(name,name)) ## TODO support property decorators in javascript-mode ## #writer.write('%s.prototype.__properties__ = {}' %name) #writer.write('%s.prototype.__unbound_methods__ = {}' %name) self._in_js_class = False def visit_ClassDef(self, node): #raise SyntaxError( self._modules ) if self._modules: writer.write('__new_module__(%s)' %node.name) ## triggers a new file in final stage of translation. if self._with_dart or self._with_go: self._visit_dart_classdef(node) return elif self._with_js: self._visit_js_classdef(node) return name = node.name self._in_class = name self._classes[ name ] = list() ## method names self._class_parents[ name ] = set() self._class_attributes[ name ] = set() self._catch_attributes = None self._decorator_properties = dict() ## property names : {'get':func, 'set':func} self._decorator_class_props[ name ] = self._decorator_properties self._instances[ 'self' ] = name self._injector = [] ## DEPRECATED class_decorators = [] gpu_object = False for decorator in node.decorator_list: ## class decorators if isinstance(decorator, Attribute) and isinstance(decorator.value, Name) and decorator.value.id == 'gpu': if decorator.attr == 'object': gpu_object = True else: raise SyntaxError( self.format_error('invalid gpu class decorator') ) else: class_decorators.append( decorator ) ## always catch attributes ## self._catch_attributes = set() self._instance_attributes[ name ] = self._catch_attributes if not self._with_coffee: writer.write('var(%s, __%s_attrs, __%s_parents)' % (name, name, name)) writer.write('__%s_attrs = JSObject()' % name) writer.write('__%s_parents = JSArray()' % name) writer.write('__%s_properties = JSObject()' % name) for base in node.bases: base = self.visit(base) if base == 'object': continue self._class_parents[ name ].add( base ) if self._with_lua: writer.write('table.insert( __%s_parents, %s)' % (name, base)) else: writer.write('__%s_parents.push(%s)' % (name, base)) for item in node.body: if isinstance(item, FunctionDef): log(' method: %s'%item.name) self._classes[ name ].append( item.name ) item_name = item.name item.original_name = item.name if self.is_gpu_method( item ): item.name = '%s_%s' % (name, item_name) else: item.name = '__%s_%s' % (name, item_name) self.visit(item) # this will output the code for the function if item_name in self._decorator_properties or self.is_gpu_method( item ): pass else: writer.write('__%s_attrs.%s = %s' % (name, item_name, item.name)) elif isinstance(item, Assign) and isinstance(item.targets[0], Name): item_name = item.targets[0].id item.targets[0].id = '__%s_%s' % (name, item_name) self.visit(item) # this will output the code for the assign writer.write('__%s_attrs.%s = %s' % (name, item_name, item.targets[0].id)) self._class_attributes[ name ].add( item_name ) ## should this come before self.visit(item) ?? elif isinstance(item, Pass): pass elif isinstance(item, ast.Expr) and isinstance(item.value, Str): ## skip doc strings pass elif isinstance(item, ast.With) and isinstance( item.context_expr, Name ) and item.context_expr.id == 'javascript': self._with_js = True writer.with_javascript = True for sub in item.body: if isinstance(sub, Assign) and isinstance(sub.targets[0], Name): item_name = sub.targets[0].id sub.targets[0].id = '__%s_%s' % (name, item_name) self.visit(sub) # this will output the code for the assign writer.write('__%s_attrs.%s = %s' % (name, item_name, sub.targets[0].id)) self._class_attributes[ name ].add( item_name ) ## should this come before self.visit(item) ?? else: raise NotImplementedError( sub ) writer.with_javascript = False self._with_js = False else: raise NotImplementedError( item ) for prop_name in self._decorator_properties: getter = self._decorator_properties[prop_name]['get'] writer.write('__%s_properties["%s"] = JSObject()' %(name, prop_name)) writer.write('__%s_properties["%s"]["get"] = %s' %(name, prop_name, getter)) if self._decorator_properties[prop_name]['set']: setter = self._decorator_properties[prop_name]['set'] writer.write('__%s_properties["%s"]["set"] = %s' %(name, prop_name, setter)) self._catch_attributes = None self._decorator_properties = None self._instances.pop('self') self._in_class = False writer.write('%s = __create_class__("%s", __%s_parents, __%s_attrs, __%s_properties)' % (name, name, name, name, name)) ## DEPRECATED #if 'init' in self._injector: # writer.write('%s.init_callbacks = JSArray()' %name) #self._injector = [] for dec in class_decorators: writer.write('%s = __get__(%s,"__call__")( [%s], JSObject() )' % (name, self.visit(dec), name)) def visit_And(self, node): return ' and ' def visit_Or(self, node): return ' or ' def visit_BoolOp(self, node): op = self.visit(node.op) #raise SyntaxError(op) return '('+ op.join( [self.visit(v) for v in node.values] ) + ')' def visit_If(self, node): if self._with_dart and writer.is_at_global_level(): raise SyntaxError( self.format_error('if statements can not be used at module level in dart') ) elif self._with_lua: writer.write('if __test_if_true__(%s):' % self.visit(node.test)) elif isinstance(node.test, ast.Dict): if self._with_js: writer.write('if Object.keys(%s).length:' % self.visit(node.test)) else: writer.write('if %s.keys().length:' % self.visit(node.test)) elif isinstance(node.test, ast.List): writer.write('if %s.length:' % self.visit(node.test)) elif self._with_ll or self._with_glsl or self._fast_js: writer.write('if %s:' % self.visit(node.test)) elif isinstance(node.test, ast.Compare): writer.write('if %s:' % self.visit(node.test)) else: writer.write('if __test_if_true__(%s):' % self.visit(node.test)) writer.push() map(self.visit, node.body) writer.pull() if node.orelse: writer.write('else:') writer.push() map(self.visit, node.orelse) writer.pull() def visit_TryExcept(self, node): if len(node.handlers)==0: raise SyntaxError(self.format_error('no except handlers')) ## by default in js-mode some expections will not be raised, ## this allows those cases to throw proper errors. if node.handlers[0].type: self._in_catch_exception = self.visit(node.handlers[0].type) else: self._in_catch_exception = None writer.write('try:') writer.push() map(self.visit, node.body) writer.pull() map(self.visit, node.handlers) def visit_Raise(self, node): #if self._with_js or self._with_dart: # writer.write('throw Error') #else: #writer.write('raise %s' % self.visit(node.type)) if isinstance(node.type, ast.Name): writer.write('raise %s' % node.type.id) elif isinstance(node.type, ast.Call): if len(node.type.args) > 1: raise SyntaxError( self.format_error('raise Error(x) can only have a single argument') ) if node.type.args: writer.write( 'raise %s(%s)' %(self.visit(node.type.func), self.visit(node.type.args[0])) ) else: writer.write( 'raise %s()' %self.visit(node.type.func) ) def visit_ExceptHandler(self, node): if node.type and node.name: writer.write('except %s, %s:' % (self.visit(node.type), self.visit(node.name))) elif node.type and not node.name: writer.write('except %s:' % self.visit(node.type)) else: writer.write('except:') writer.push() map(self.visit, node.body) writer.pull() def visit_Pass(self, node): writer.write('pass') def visit_Name(self, node): if self._with_js or self._with_dart: if node.id == 'True': return 'true' elif node.id == 'False': return 'false' elif node.id == 'None': if self._with_go: return 'nil' elif self._with_dart: return 'null' else: return 'null' return node.id def visit_Num(self, node): return str(node.n) def visit_Return(self, node): if node.value: if isinstance(node.value, Call) and isinstance(node.value.func, Name) and node.value.func.id in self._classes: self._return_type = node.value.func.id elif isinstance(node.value, Name) and node.value.id == 'self' and 'self' in self._instances: self._return_type = self._instances['self'] if self._with_glsl and self._in_gpu_main: ## _id_ is inserted into all function headers by pythonjs.py for glsl functions. if not self._gpu_return_types: raise SyntaxError( self.format_error('function return type unknown - required decorator `@returns(array/vec4=[w,h])`') ) ## only one return type is allowed ## if 'array' in self._gpu_return_types: writer.write('out_float = %s' %self.visit(node.value)) elif 'vec4' in self._gpu_return_types: writer.write('out_float4 = %s' %self.visit(node.value)) elif 'mat4' in self._gpu_return_types: nv = self.visit(node.value) writer.write('inline("mat4 _res_ = %s; int _row = matrix_row();")' %nv) r0 = 'vec4(_res_[0][0],_res_[0][1],_res_[0][2],_res_[0][3])' r1 = 'vec4(_res_[1][0],_res_[1][1],_res_[1][2],_res_[1][3])' r2 = 'vec4(_res_[2][0],_res_[2][1],_res_[2][2],_res_[2][3])' r3 = 'vec4(_res_[3][0],_res_[3][1],_res_[3][2],_res_[3][3])' writer.write('if _row==0: out_float4 = %s' % r0) writer.write('elif _row==1: out_float4 = %s'%r1) writer.write('elif _row==2: out_float4 = %s'%r2) writer.write('else: out_float4 = %s'%r3) else: raise SyntaxError( self.format_error('invalid GPU return type: %s' %self._gpu_return_types) ) elif self._inline: writer.write('__returns__%s = %s' %(self._inline[-1], self.visit(node.value)) ) if self._inline_breakout: writer.write('break') elif isinstance(node.value, ast.Lambda): self.visit( node.value ) writer.write( 'return __lambda__' ) elif isinstance(node.value, ast.Tuple): writer.write( 'return %s;' % ','.join([self.visit(e) for e in node.value.elts]) ) else: writer.write('return %s' % self.visit(node.value)) else: if self._inline: if self._inline_breakout: writer.write('break') else: writer.write('return') ## empty return def visit_BinOp(self, node): left = self.visit(node.left) op = self.visit(node.op) is_go_listcomp = False if self._with_go: if op == '<<': if isinstance(node.left, ast.Call) and isinstance(node.left.func, ast.Name): if node.left.func.id=='__go__array__' and isinstance(node.right, ast.GeneratorExp): is_go_listcomp = True node.right.go_listcomp_type = node.left.args[0].id elif node.left.func.id=='__go__map__': if isinstance(node.left.args[1], ast.Call): ## map comprehension is_go_listcomp = True node.right.go_dictcomp_type = ( node.left.args[0].id, self.visit(node.left.args[1]) ) else: node.right.go_dictcomp_type = ( node.left.args[0].id, node.left.args[1].id ) right = self.visit(node.right) if self._with_glsl: return '(%s %s %s)' % (left, op, right) elif self._with_go: if op == '//': op = '/' if is_go_listcomp: return right else: return '(%s %s %s)' % (left, op, right) elif op == '|': if isinstance(node.right, Str): self._custom_op_hack = (node.right.s, left) return '' elif hasattr(self, '_custom_op_hack') and isinstance(node.left, BinOp): op,left_operand = self._custom_op_hack right_operand = self.visit(node.right) #return '%s( %s, %s )' %(op, left_operand, right_operand) if op.decode('utf-8') in self._custom_operators: ## swap name to python function op = self._custom_operators[ op.decode('utf-8') ] return '%s( [%s, %s], JSObject() )' %(op, left_operand, right_operand) elif op == '%' and isinstance(node.left, ast.Str): if self._with_js: return '__sprintf( %s, %s )' %(left, right) ## assumes that right is a tuple, or list. else: return '__sprintf( %s, %s )' %(left, right) ## assumes that right is a tuple, or list. elif op == '*' and isinstance(node.left, ast.List): if len(node.left.elts) == 1 and isinstance(node.left.elts[0], ast.Name) and node.left.elts[0].id == 'None': if self._with_dart: return 'JS("__create_list(%s)")' %self.visit(node.right) elif self._with_lua: return 'JS("__create_list(%s)")' %self.visit(node.right) else: return 'JS("new Array(%s)")' %self.visit(node.right) elif isinstance(node.right,ast.Num): n = node.right.n elif isinstance(node.right, Name): if node.right.id in self._global_nodes: n = self._global_nodes[ node.right.id ].n else: raise SyntaxError( self.format_error(node) ) else: #raise SyntaxError( self.format_error(node) ) return '__mul_op(%s,%s)'%(left, right) elts = [ self.visit(e) for e in node.left.elts ] expanded = [] for i in range( n ): expanded.extend( elts ) if self._with_lua: return 'list.__call__([], {pointer:[%s], length:%s})' %(','.join(expanded), n) else: return '[%s]' %','.join(expanded) elif not self._with_dart and left in self._typedef_vars and self._typedef_vars[left]=='long': if op == '*': return '%s.multiply(%s)'%(left, right) elif op == '+': return '%s.add(%s)'%(left, right) elif op == '-': return '%s.subtract(%s)'%(left, right) elif op == '/' or op == '//': return '%s.div(%s)'%(left, right) elif op == '%': return '%s.modulo(%s)'%(left, right) else: raise NotImplementedError('long operator: %s'%op) elif not self._with_dart and op == '*' and left in self._typedef_vars and self._typedef_vars[left]=='int' and isinstance(node.right, ast.Num) and node.right.n in POWER_OF_TWO: power = POWER_OF_TWO.index( node.right.n ) return '%s << %s'%(left, power) elif not self._with_dart and op == '//' and left in self._typedef_vars and self._typedef_vars[left]=='int' and isinstance(node.right, ast.Num) and node.right.n in POWER_OF_TWO: power = POWER_OF_TWO.index( node.right.n ) return '%s >> %s'%(left, power) elif not self._with_dart and op == '*' and '*' in self._direct_operators: return '(%s * %s)'%(left, right) elif not self._with_dart and not self._with_js and op == '*': if left in self._typedef_vars and self._typedef_vars[left] in typedpython.native_number_types: return '(%s * %s)'%(left, right) else: return '__mul_op(%s,%s)'%(left, right) elif op == '//': if self._with_dart: return '(%s/%s).floor()' %(left, right) else: return 'Math.floor(%s/%s)' %(left, right) elif op == '**': return 'Math.pow(%s,%s)' %(left, right) elif op == '+' and not self._with_dart: if '+' in self._direct_operators: return '%s+%s'%(left, right) elif left in self._typedef_vars and self._typedef_vars[left] in typedpython.native_number_types: return '%s+%s'%(left, right) elif self._with_lua or self._in_lambda or self._in_while_test: ## this is also required when in an inlined lambda like "(lambda a,b: a+b)(1,2)" return '__add_op(%s, %s)'%(left, right) else: ## the ternary operator in javascript is fast, the add op needs to be fast for adding numbers, so here typeof is ## used to check if the first variable is a number, and if so add the numbers, otherwise fallback to using the ## __add_op function, the __add_op function checks if the first variable is an Array, and if so then concatenate; ## else __add_op will call the "__add__" method of the left operand, passing right as the first argument. l = '__left%s' %self._addop_ids self._addop_ids += 1 r = '__right%s' %self._addop_ids writer.write('var(%s,%s)' %(l,r)) self._addop_ids += 1 writer.write('%s = %s' %(l,left)) writer.write('%s = %s' %(r,right)) return '__ternary_operator__( typeof(%s)=="number", %s + %s, __add_op(%s, %s))'%(l, l, r, l, r) elif isinstance(node.left, Name): typedef = self.get_typedef( node.left ) if typedef and op in typedef.operators: func = typedef.operators[ op ] node.operator_overloading = func return '%s( [%s, %s], JSObject() )' %(func, left, right) return '(%s %s %s)' % (left, op, right) def visit_Eq(self, node): return '==' def visit_NotEq(self, node): return '!=' def visit_Is(self, node): return 'is' def visit_Pow(self, node): return '**' def visit_Mult(self, node): return '*' def visit_Add(self, node): return '+' def visit_Sub(self, node): return '-' def visit_FloorDiv(self, node): return '//' def visit_Div(self, node): return '/' def visit_Mod(self, node): return '%' def visit_LShift(self, node): return '<<' def visit_RShift(self, node): return '>>' def visit_BitXor(self, node): return '^' def visit_BitOr(self, node): return '|' def visit_BitAnd(self, node): return '&' def visit_Lt(self, node): return '<' def visit_Gt(self, node): return '>' def visit_GtE(self, node): return '>=' def visit_LtE(self, node): return '<=' def visit_Compare(self, node): left = self.visit(node.left) comp = [ left ] for i in range( len(node.ops) ): if i==0 and isinstance(node.left, ast.Name) and node.left.id in self._typedef_vars and self._typedef_vars[node.left.id] == 'long': if isinstance(node.ops[i], ast.Eq): comp = ['%s.equals(%s)' %(left, self.visit(node.comparators[i]))] elif isinstance(node.ops[i], ast.Lt): comp = ['%s.lessThan(%s)' %(left, self.visit(node.comparators[i]))] elif isinstance(node.ops[i], ast.Gt): comp = ['%s.greaterThan(%s)' %(left, self.visit(node.comparators[i]))] elif isinstance(node.ops[i], ast.LtE): comp = ['%s.lessThanOrEqual(%s)' %(left, self.visit(node.comparators[i]))] elif isinstance(node.ops[i], ast.GtE): comp = ['%s.greaterThanOrEqual(%s)' %(left, self.visit(node.comparators[i]))] else: raise NotImplementedError( node.ops[i] ) elif isinstance(node.ops[i], ast.In) or isinstance(node.ops[i], ast.NotIn): if comp[-1] == left: comp.pop() else: comp.append( ' and ' ) if isinstance(node.ops[i], ast.NotIn): comp.append( ' not (') a = ( self.visit(node.comparators[i]), left ) if self._with_dart: ## indexOf works with lists and strings in Dart comp.append( '%s.contains(%s)' %(a[0], a[1]) ) elif self._with_js: ## this makes "if 'x' in Array" work like Python: "if 'x' in list" - TODO fix this for js-objects ## note javascript rules are confusing: "1 in [1,2]" is true, this is because a "in test" in javascript tests for an index ## TODO double check this code #comp.append( '%s in %s or' %(a[1], a[0]) ) ## this is ugly, will break with Arrays #comp.append( '( Object.hasOwnProperty.call(%s, "__contains__") and' %a[0]) #comp.append( "%s['__contains__'](%s) )" %a ) ##comp.append( ' or (instanceof(%s,Object) and %s in %s) ') #comp.append( ' or Object.hasOwnProperty.call(%s, %s)' %(a[0],a[1])) ## fixes 'o' in 'helloworld' in javascript mode ## #comp.append( ' or typeof(%s)=="string" and %s.__contains__(%s)' %(a[0],a[0],a[1])) comp.append( '__contains__(%s, %s)' %(a[0],a[1])) else: comp.append( "__get__(__get__(%s, '__contains__'), '__call__')([%s], JSObject())" %a ) if isinstance(node.ops[i], ast.NotIn): comp.append( ' )') ## it is not required to enclose NotIn else: comp.append( self.visit(node.ops[i]) ) comp.append( self.visit(node.comparators[i]) ) return ' '.join( comp ) def visit_Not(self, node): return ' not ' def visit_IsNot(self, node): return ' is not ' def visit_UnaryOp(self, node): op = self.visit(node.op) if op is None: raise RuntimeError( node.op ) operand = self.visit(node.operand) if operand is None: raise RuntimeError( node.operand ) return op + operand def visit_USub(self, node): return '-' def visit_Attribute(self, node): ## TODO check if this is always safe. if isinstance(node.value, Name): typedef = self.get_typedef( instance=node.value ) elif hasattr(node.value, 'returns_type'): typedef = self.get_typedef( class_name=node.value.returns_type ) else: typedef = None node_value = self.visit(node.value) if self._with_glsl: #if node_value not in self._typedef_vars: ## dynamic var DEPRECATED # return 'glsl_inline(%s.%s)' %(node_value, node.attr) #else: return '%s.%s' %(node_value, node.attr) elif self._with_dart or self._with_ll or self._with_go: return '%s.%s' %(node_value, node.attr) elif self._with_js: if self._in_catch_exception == 'AttributeError': return '__getfast__(%s, "%s")' % (node_value, node.attr) else: return '%s.%s' %(node_value, node.attr) elif self._with_lua and self._in_assign_target: ## this is required because lua has no support for inplace assignment ops like "+=" return '%s.%s' %(node_value, node.attr) elif typedef and node.attr in typedef.attributes: ## optimize away `__get__` return '%s.%s' %(node_value, node.attr) elif hasattr(node, 'lineno'): src = self._source[ node.lineno-1 ] src = src.replace('"', '\\"') err = 'missing attribute `%s` - line %s: %s' %(node.attr, node.lineno, src.strip()) return '__get__(%s, "%s", "%s")' % (node_value, node.attr, err) else: return '__get__(%s, "%s")' % (node_value, node.attr) def visit_Index(self, node): return self.visit(node.value) def visit_Subscript(self, node): name = self.visit(node.value) if isinstance(node.slice, ast.Ellipsis): #return '%s["$wrapped"]' %name return '%s[...]' %name elif self._with_ll or self._with_glsl or self._with_go: return '%s[%s]' %(name, self.visit(node.slice)) elif self._with_js or self._with_dart: if isinstance(node.slice, ast.Slice): ## allow slice on Array if self._with_dart: ## this is required because we need to support slices on String ## return '__getslice__(%s, %s)'%(name, self.visit(node.slice)) else: if not node.slice.lower and not node.slice.upper and not node.slice.step: return '%s.copy()' %name else: return '%s.__getslice__(%s)'%(name, self.visit(node.slice)) elif isinstance(node.slice, ast.Index) and isinstance(node.slice.value, ast.Num): if node.slice.value.n < 0: ## the problem with this is it could be a dict with negative numbered keys return '%s[ %s.length+%s ]' %(name, name, self.visit(node.slice)) else: return '%s[ %s ]' %(name, self.visit(node.slice)) elif self._with_dart: ## --------- dart mode ------- return '%s[ %s ]' %(name, self.visit(node.slice)) else: ## ------------------ javascript mode ------------------------ if self._in_catch_exception == 'KeyError': value = self.visit(node.value) slice = self.visit(node.slice) return '__get__(%s, "__getitem__")([%s], __NULL_OBJECT__)' % (value, slice) elif isinstance(node.slice, ast.Index) and isinstance(node.slice.value, ast.BinOp): ## TODO keep this optimization? in js mode `a[x+y]` is assumed to a direct key, ## it would be safer to check if one of the operands is a number literal, ## in that case it is safe to assume that this is a direct key. return '%s[ %s ]' %(name, self.visit(node.slice)) elif self._with_direct_keys: return '%s[ %s ]' %(name, self.visit(node.slice)) else: s = self.visit(node.slice) #return '%s[ __ternary_operator__(%s.__uid__, %s) ]' %(name, s, s) check_array = '__ternary_operator__( instanceof(%s,Array), JSON.stringify(%s), %s )' %(s, s, s) return '%s[ __ternary_operator__(%s.__uid__, %s) ]' %(name, s, check_array) elif isinstance(node.slice, ast.Slice): return '__get__(%s, "__getslice__")([%s], __NULL_OBJECT__)' % ( self.visit(node.value), self.visit(node.slice) ) elif name in self._func_typedefs and self._func_typedefs[name] == 'list': #return '%s[...][%s]'%(name, self.visit(node.slice)) return '%s[%s]'%(name, self.visit(node.slice)) elif name in self._instances: ## support x[y] operator overloading klass = self._instances[ name ] if '__getitem__' in self._classes[ klass ]: return '__%s___getitem__([%s, %s], JSObject())' % (klass, name, self.visit(node.slice)) else: return '__get__(%s, "__getitem__")([%s], __NULL_OBJECT__)' % ( self.visit(node.value), self.visit(node.slice) ) else: err = "" if hasattr(node, 'lineno'): src = self._source[ node.lineno-1 ] src = src.replace('"', '\\"') err = 'line %s: %s' %(node.lineno, src.strip()) value = self.visit(node.value) slice = self.visit(node.slice) fallback = '__get__(%s, "__getitem__", "%s")([%s], __NULL_OBJECT__)' % (value, err, slice) if not self._with_lua and isinstance(node.value, ast.Name): return '__ternary_operator__(instanceof(%s, Array), %s[%s], %s)' %(value, value,slice, fallback) else: return fallback def visit_Slice(self, node): if self._with_go: lower = upper = step = None elif self._with_dart: lower = upper = step = 'null' elif self._with_js: lower = upper = step = 'undefined' else: lower = upper = step = 'undefined' if node.lower: lower = self.visit(node.lower) if node.upper: upper = self.visit(node.upper) if node.step: step = self.visit(node.step) if self._with_go: if lower and upper: return '%s:%s' %(lower,upper) elif upper: return ':%s' %upper elif lower: return '%s:'%lower else: return "%s, %s, %s" % (lower, upper, step) def visit_Assign(self, node): use_runtime_errors = not (self._with_js or self._with_ll or self._with_dart or self._with_coffee or self._with_lua or self._with_go) use_runtime_errors = use_runtime_errors and self._with_runtime_exceptions lineno = node.lineno if node.lineno < len(self._source): src = self._source[ node.lineno ] self._line_number = node.lineno self._line = src if use_runtime_errors: writer.write('try:') writer.push() targets = list( node.targets ) target = targets[0] if isinstance(target, ast.Name) and target.id in typedpython.types: if len(targets)==2 and isinstance(targets[1], ast.Name): self._typedef_vars[ targets[1].id ] = target.id if target.id == 'long' and isinstance(node.value, ast.Num): ## requires long library ## writer.write('%s = long.fromString("%s")' %(targets[1].id, self.visit(node.value))) return None else: targets = targets[1:] elif len(targets)==2 and isinstance(targets[1], ast.Attribute) and isinstance(targets[1].value, ast.Name) and targets[1].value.id == 'self' and len(self._class_stack): self._class_stack[-1]._struct_vars[ targets[1].attr ] = target.id if target.id == 'long' and isinstance(node.value, ast.Num): ## requires long library ## writer.write('%s = long.fromString("%s")' %(targets[1].value.id, self.visit(node.value))) return None else: targets = targets[1:] elif len(targets)==1 and isinstance(node.value, ast.Name) and target.id in typedpython.types: self._typedef_vars[ node.value.id ] = target.id return None elif isinstance(target, ast.Name) and target.id in typedpython.types: raise SyntaxError( self.format_error(target.id) ) else: #xxx = self.visit(target) + ':' + self.visit(targets[1]) xxx = self.visit(target) + ':' + self.visit(node.value) raise SyntaxError( self.format_error(targets) ) raise SyntaxError( self.format_error(xxx) ) elif self._with_go and isinstance(target, ast.Subscript) and isinstance(target.value, ast.Name) and target.value.id in ('__go__array__', '__go__class__', '__go__pointer__', '__go__func__'): if len(targets)==2 and isinstance(targets[1], ast.Attribute) and isinstance(targets[1].value, ast.Name) and targets[1].value.id == 'self' and len(self._class_stack): if target.value.id == '__go__array__': self._class_stack[-1]._struct_vars[ targets[1].attr ] = '__go__array__(%s< 0 and len(args) <= 2: return '__jsdict_get(%s, %s)' %(self.visit(anode.value), ','.join(args) ) elif anode.attr == 'set' and len(args)==2: return '__jsdict_set(%s, %s)' %(self.visit(anode.value), ','.join(args)) elif anode.attr == 'keys' and not args: if self._strict_mode: raise SyntaxError( self.format_error('method `keys` is not allowed without arguments') ) return '__jsdict_keys(%s)' %self.visit(anode.value) elif anode.attr == 'values' and not args: if self._strict_mode: raise SyntaxError( self.format_error('method `values` is not allowed without arguments') ) return '__jsdict_values(%s)' %self.visit(anode.value) elif anode.attr == 'items' and not args: if self._strict_mode: raise SyntaxError( self.format_error('method `items` is not allowed without arguments') ) return '__jsdict_items(%s)' %self.visit(anode.value) elif anode.attr == 'pop': if args: return '__jsdict_pop(%s, %s)' %(self.visit(anode.value), ','.join(args) ) else: if self._strict_mode: return '%s.pop()' %self.visit(anode.value) else: return '__jsdict_pop(%s)' %self.visit(anode.value) elif anode.attr == 'split' and not args: if self._strict_mode: raise SyntaxError( self.format_error('method `split` is not allowed without arguments') ) return '__split_method(%s)' %self.visit(anode.value) elif anode.attr == 'sort' and not args: if self._strict_mode: raise SyntaxError( self.format_error('method `sort` is not allowed without arguments') ) return '__sort_method(%s)' %self.visit(anode.value) elif anode.attr == 'replace' and len(node.args)==2: if self._strict_mode: raise SyntaxError( self.format_error('method `replace` is not allowed...') ) return '__replace_method(%s, %s)' %(self.visit(anode.value), ','.join(args) ) else: ctx = '.'.join( self.visit(node.func).split('.')[:-1] ) if node.keywords: kwargs = [ '%s:%s'%(x.arg, self.visit(x.value)) for x in node.keywords ] if args: if node.starargs: a = ( method, ctx, ','.join(args), self.visit(node.starargs), ','.join(kwargs) ) ## note: this depends on the fact that [].extend in PythonJS returns self (this), ## which is different from regular python where list.extend returns None return '%s.apply( %s, [].extend([%s]).extend(%s).append({%s}) )' %a else: return '%s(%s, {%s})' %( method, ','.join(args), ','.join(kwargs) ) else: if node.starargs: a = ( self.visit(node.func),ctx, self.visit(node.starargs), ','.join(kwargs) ) return '%s.apply(%s, [].extend(%s).append({%s}) )' %a else: return '%s({%s})' %( method, ','.join(kwargs) ) else: if node.starargs: a = ( self.visit(node.func), ctx, ','.join(args), self.visit(node.starargs) ) return '%s.apply(%s, [].extend([%s]).extend(%s))' %a else: return '%s(%s)' %( method, ','.join(args) ) elif isinstance(node.func, Name) and node.func.id in self._js_classes: if node.keywords: kwargs = [ '%s:%s'%(x.arg, self.visit(x.value)) for x in node.keywords ] if args: a = ','.join(args) return 'new( %s(%s, {%s}) )' %( self.visit(node.func), a, ','.join(kwargs) ) else: return 'new( %s({%s}) )' %( self.visit(node.func), ','.join(kwargs) ) else: if node.kwargs: args.append( self.visit(node.kwargs) ) a = ','.join(args) return 'new( %s(%s) )' %( self.visit(node.func), a ) elif name in self._global_functions and self._with_inline and not self._with_lua: return self.inline_function( node ) elif self._with_dart: ## ------------------ DART -------------------------------------- if isinstance(node.func, ast.Attribute): ## special method calls anode = node.func self._in_assign_target = True method = self.visit( node.func ) self._in_assign_target = False if anode.attr == 'replace' and len(node.args)==2: return '__replace_method(%s, %s)' %(self.visit(anode.value), ','.join(args) ) elif anode.attr == 'split' and len(node.args)==0: return '__split_method(%s)' %self.visit(anode.value) elif anode.attr == 'upper' and len(node.args)==0: return '__upper_method(%s)' %self.visit(anode.value) elif anode.attr == 'lower' and len(node.args)==0: return '__lower_method(%s)' %self.visit(anode.value) ## default ## if node.keywords: kwargs = ','.join( ['%s=%s'%(x.arg, self.visit(x.value)) for x in node.keywords] ) if args: return '%s(%s, %s)' %( self.visit(node.func), ','.join(args), kwargs ) else: return '%s( %s )' %( self.visit(node.func), kwargs ) else: a = ','.join(args) return '%s(%s)' %( self.visit(node.func), a ) else: ## ----------------------------- javascript mode ------------------------ if node.keywords: kwargs = [ '%s:%s'%(x.arg, self.visit(x.value)) for x in node.keywords ] if args: if node.starargs: a = ( self.visit(node.func), self.visit(node.func), ','.join(args), self.visit(node.starargs), ','.join(kwargs) ) return '%s.apply( %s, [].extend([%s]).extend(%s).append({%s}) )' %a else: return '%s(%s, {%s})' %( self.visit(node.func), ','.join(args), ','.join(kwargs) ) else: if node.starargs: a = ( self.visit(node.func),self.visit(node.func), self.visit(node.starargs), ','.join(kwargs) ) return '%s.apply(%s, [].extend(%s).append({%s}) )' %a else: func_name = self.visit(node.func) if func_name == 'dict': return '{%s}' %','.join(kwargs) else: return '%s({%s})' %( func_name, ','.join(kwargs) ) else: if node.starargs: a = ( self.visit(node.func), self.visit(node.func), ','.join(args), self.visit(node.starargs) ) return '%s.apply(%s, [].extend([%s]).extend(%s))' %a else: return '%s(%s)' %( self.visit(node.func), ','.join(args) ) elif isinstance(node.func, Name) and node.func.id in self._generator_functions: args = list( map(self.visit, node.args) ) if name in self._generator_functions: return 'JS("new %s(%s)")' %(name, ','.join(args)) elif name == 'new': tmp = self._with_js self._with_js = True args = list( map(self.visit, node.args) ) self._with_js = tmp assert len(args) == 1 return 'new(%s)' %args[0] elif isinstance(node.func, Name) and node.func.id in ('JS', 'toString', 'JSObject', 'JSArray', 'var', 'instanceof', 'typeof'): args = list( map(self.visit, node.args) ) ## map in py3 returns an iterator not a list if node.func.id == 'var': for k in node.keywords: self._instances[ k.arg ] = k.value.id args.append( k.arg ) else: kwargs = map(lambda x: '%s=%s' % (x.arg, self.visit(x.value)), node.keywords) args.extend(kwargs) args = ', '.join(args) return '%s(%s)' % (node.func.id, args) else: ## check if pushing to a global typed list ## if isinstance(node.func, ast.Attribute) and isinstance(node.func.value, Name) and node.func.value.id in self._global_typed_lists and node.func.attr == 'append': gtype = self._globals[ node.func.value.id ] if gtype == 'list' and node.func.attr == 'append': if isinstance(node.args[0], Name): if node.args[0].id in self._instances: gset = self._global_typed_lists[ node.func.value.id ] gset.add( self._instances[node.args[0].id]) if len(gset) != 1: raise SyntaxError('global lists can only contain one type: instance "%s" is different' %node.args[0].id) else: raise SyntaxError('global lists can only contain one type: instance "%s" is unknown' %node.args[0].id) call_has_args_only = len(node.args) and not (len(node.keywords) or node.starargs or node.kwargs) call_has_args_kwargs_only = len(node.args) and len(node.keywords) and not (node.starargs or node.kwargs) call_has_args = len(node.args) or len(node.keywords) or node.starargs or node.kwargs name = self.visit(node.func) args = None kwargs = None if call_has_args_only: ## lambda only supports simple args for now. args = ', '.join(map(self.visit, node.args)) elif call_has_args_kwargs_only: args = ', '.join(map(self.visit, node.args)) kwargs = ', '.join(map(lambda x: '%s:%s' % (x.arg, self.visit(x.value)), node.keywords)) elif call_has_args: args = ', '.join(map(self.visit, node.args)) kwargs = ', '.join(map(lambda x: '%s=%s' % (x.arg, self.visit(x.value)), node.keywords)) args_name = '__args_%s' % self.identifier kwargs_name = '__kwargs_%s' % self.identifier writer.append('var(%s, %s)' % (args_name, kwargs_name)) self.identifier += 1 writer.append('%s = [%s]' % (args_name, args)) if node.starargs: writer.append('%s.push.apply(%s, %s)' % (args_name, args_name, self.visit(node.starargs))) writer.append('%s = JSObject(%s)' % (kwargs_name, kwargs)) if node.kwargs: kwargs = self.visit(node.kwargs) writer.write('var(__kwargs_temp)') writer.write('__kwargs_temp = %s[...]' %kwargs) #code = "JS('for (var name in %s) { %s[name] = %s[...][name]; }')" % (kwargs, kwargs_name, kwargs) #code = "for __name in %s: %s[__name] = %s[__name]" % (kwargs, kwargs_name, kwargs) code = "JS('for (var name in __kwargs_temp) { %s[name] = __kwargs_temp[name]; }')" %kwargs_name writer.append(code) ####################################### ## special method calls ## if isinstance(node.func, ast.Attribute) and node.func.attr in ('get', 'keys', 'values', 'pop', 'items', 'split', 'replace', 'sort') and not self._with_lua: anode = node.func if anode.attr == 'get' and len(node.args) > 0 and len(node.args) <= 2: return '__jsdict_get(%s, %s)' %(self.visit(anode.value), args ) elif anode.attr == 'keys' and not args: return '__jsdict_keys(%s)' %self.visit(anode.value) elif anode.attr == 'values' and not args: return '__jsdict_values(%s)' %self.visit(anode.value) elif anode.attr == 'items' and not args: return '__jsdict_items(%s)' %self.visit(anode.value) elif anode.attr == 'pop': if args: return '__jsdict_pop(%s, %s)' %(self.visit(anode.value), args ) else: return '__jsdict_pop(%s)' %self.visit(anode.value) elif anode.attr == 'sort' and not args: return '__sort_method(%s)' %self.visit(anode.value) elif anode.attr == 'split' and len(node.args) <= 1: if not args: return '__split_method(%s)' %self.visit(anode.value) else: return '__split_method(%s, %s)' %(self.visit(anode.value), args) elif anode.attr == 'replace' and len(node.args)==2: return '__replace_method(%s, %s)' %(self.visit(anode.value), args ) else: return '%s(%s)' %( self.visit(node.func), args ) elif not self._with_lua and not self._with_dart and isinstance(node.func, ast.Attribute) and isinstance(node.func.value, Name) and node.func.value.id in self._func_typedefs: type = self._func_typedefs[ node.func.value.id ] if type == 'list' and node.func.attr == 'append': return '%s.push(%s)' %(node.func.value.id, self.visit(node.args[0])) else: raise RuntimeError elif hasattr(node,'constant') or name in self._builtin_functions: if args and kwargs: return '%s([%s], {%s})' %(name, args, kwargs) elif args: return '%s([%s], __NULL_OBJECT__)' %(name,args) elif kwargs: return '%s([], {%s})' %(name,kwargs) else: return '%s()' %name elif name in self._global_functions and self._with_inline and not self._with_lua: return self.inline_function( node ) elif call_has_args_only: if name in self._global_functions: return '%s( [%s], __NULL_OBJECT__)' %(name,args) else: return '__get__(%s, "__call__")([%s], __NULL_OBJECT__)' % (name, args) elif call_has_args_kwargs_only: if name in self._global_functions: return '%s( [%s], {%s} )' %(name,args, kwargs) else: return '__get__(%s, "__call__")([%s], {%s} )' % (name, args, kwargs) elif call_has_args: if name == 'dict': return '__get__(%s, "__call__")(%s, JSObject(pointer=%s))' % (name, args_name, kwargs_name) else: return '__get__(%s, "__call__")(%s, %s)' % (name, args_name, kwargs_name) elif name in self._classes: return '__get__(%s, "__call__")( )' %name elif name in self._builtin_classes: return '__get__(%s, "__call__")( )' %name elif name in self._global_functions: #return '__get__(%s, "__call__")( JSArray(), JSObject() )' %name ## SLOW ## return '%s( )' %name ## this is much FASTER ## else: ## if the user is trying to create an instance of some class ## and that class is define in an external binding, ## and they forgot to put "from mylibrary import *" in their script (an easy mistake to make) ## then this fails to call __call__ to initalize the instance, ## or a factory function was used that was passed the class to make, ## it will throw this confusing error: ## Uncaught TypeError: Property 'SomeClass' of object [object Object] is not a function ## TODO - remove this optimization, or provide the user with a better error message. ## So to be safe we still wrap with __get__ and "__call__" return '__get__(%s, "__call__")( )' %name def visit_Lambda(self, node): args = [self.visit(a) for a in node.args.args] ##'__INLINE_FUNCTION__' from typedpython.py if hasattr(node, 'keep_as_lambda') or (args and args[0]=='__INLINE_FUNCTION__'): ## TODO lambda keyword args self._in_lambda = True a = '(lambda %s: %s)' %(','.join(args), self.visit(node.body)) self._in_lambda = False return a else: node.name = '__lambda__' node.decorator_list = [] node.body = [node.body] b = node.body[-1] node.body[-1] = ast.Return( b ) return self.visit_FunctionDef(node) def visit_FunctionDef(self, node): global writer if node in self._generator_function_nodes: self._generator_functions.add( node.name ) if '--native-yield' in sys.argv: raise NotImplementedError ## TODO else: GeneratorFunctionTransformer( node, compiler=self ) return writer.functions.append(node.name) is_worker_entry = False property_decorator = None decorators = [] with_js_decorators = [] with_dart_decorators = [] setter = False return_type = None return_type_keywords = {} fastdef = False javascript = False inline = False threaded = self._with_webworker jsfile = None self._typedef_vars = dict() ## clear typed variables: filled in below by @typedef or in visit_Assign self._gpu_return_types = set() gpu = False gpu_main = False gpu_vectorize = False gpu_method = False local_typedefs = [] typedef_chans = [] func_expr = None ## deprecated? self._cached_property = None self._func_typedefs = {} if writer.is_at_global_level() and not self._with_webworker and not self._with_glsl: self._global_functions[ node.name ] = node ## save ast-node for decorator in reversed(node.decorator_list): log('@decorator: %s' %decorator) if isinstance(decorator, Name) and decorator.id == 'gpu': gpu = True elif isinstance(decorator, Call) and decorator.func.id == 'expression': ## js function expressions are now the default assert len(decorator.args)==1 func_expr = self.visit(decorator.args[0]) elif isinstance(decorator, Call) and decorator.func.id in ('typedef', 'typedef_chan'): c = decorator assert len(c.args) == 0 and len(c.keywords) for kw in c.keywords: #assert isinstance( kw.value, Name) kwval = self.visit(kw.value) self._typedef_vars[ kw.arg ] = kwval self._instances[ kw.arg ] = kwval self._func_typedefs[ kw.arg ] = kwval local_typedefs.append( '%s=%s' %(kw.arg, kwval)) if decorator.func.id=='typedef_chan': typedef_chans.append( kw.arg ) writer.write('@__typedef_chan__(%s=%s)' %(kw.arg, kwval)) else: writer.write('@__typedef__(%s=%s)' %(kw.arg, kwval)) elif isinstance(decorator, Name) and decorator.id == 'inline': inline = True self._with_inline = True elif isinstance(decorator, ast.Call) and isinstance(decorator.func, ast.Name) and decorator.func.id == 'webworker': if not self._with_dart: threaded = True assert len(decorator.args) == 1 jsfile = decorator.args[0].s elif isinstance(decorator, Call) and isinstance(decorator.func, ast.Name) and decorator.func.id == 'returns': if decorator.keywords: for k in decorator.keywords: key = k.arg assert key == 'array' or key == 'vec4' self._gpu_return_types.add(key) ## used in visit_Return ## return_type_keywords[ key ] = self.visit(k.value) else: assert len(decorator.args) == 1 if isinstance( decorator.args[0], Name): return_type = decorator.args[0].id elif isinstance(decorator.args[0], ast.Str): return_type = '"%s"' %decorator.args[0].s else: raise SyntaxError('invalid @returns argument') if return_type in typedpython.glsl_types: self._gpu_return_types.add( return_type ) elif isinstance(decorator, Attribute) and isinstance(decorator.value, Name) and decorator.value.id == 'gpu': gpu = True if decorator.attr == 'vectorize': gpu_vectorize = True elif decorator.attr == 'main': gpu_main = True elif decorator.attr == 'method': gpu_method = True else: raise NotImplementedError(decorator) elif self._with_dart: with_dart_decorators.append( self.visit(decorator) ) elif self._with_js: ## decorators are special in with-js mode self._in_assign_target = True with_js_decorators.append( self.visit( decorator ) ) self._in_assign_target = False elif isinstance(decorator, Name) and decorator.id == 'fastdef': fastdef = True elif isinstance(decorator, Name) and decorator.id == 'javascript': javascript = True elif isinstance(decorator, Name) and decorator.id == 'property': property_decorator = decorator n = node.name + '__getprop__' self._decorator_properties[ node.original_name ] = dict( get=n, set=None ) node.name = n #if decorator.id == 'cached_property': ## TODO DEPRECATE # self._cached_property = node.original_name elif isinstance(decorator, Attribute) and isinstance(decorator.value, Name) and decorator.value.id in self._decorator_properties: if decorator.attr == 'setter': if self._decorator_properties[ decorator.value.id ]['set']: raise SyntaxError( self.format_error("decorator.setter is used more than once") ) n = node.name + '__setprop__' self._decorator_properties[ decorator.value.id ]['set'] = n node.name = n setter = True prop_name = node.original_name elif decorator.attr == 'deleter': raise NotImplementedError else: raise RuntimeError elif isinstance(decorator, Call) and decorator.func.id == 'custom_operator': assert len(decorator.args) == 1 assert isinstance( decorator.args[0], Str ) op = decorator.args[0].s.decode('utf-8') if op not in self._custom_operators: raise RuntimeError( op, self._custom_operators ) self._custom_operators[ op ] = node.name else: decorators.append( decorator ) if gpu: restore_with_glsl = self._with_glsl self._with_glsl = True if gpu_main: ## sets float self._in_gpu_main = True writer.write('@gpu.main') if threaded: if not jsfile: jsfile = 'worker.js' writer_main.write('%s = "%s"' %(node.name, jsfile)) self._webworker_functions[ node.name ] = jsfile writer = get_webworker_writer( jsfile ) if len(writer.functions) <= 1: is_worker_entry = True ## TODO: two-way list and dict sync writer.write('__wargs__ = []') writer.write('def onmessage(e):') writer.push() ## need a better way to quit the worker after work is done, check if threading._blocking_callback is waiting, else terminate writer.write( 'if e.data.type=="execute":' ) writer.push() writer.write( '%s.apply(self, e.data.args)'%node.name ) writer.write( 'if not threading._blocking_callback: self.postMessage({"type":"terminate"})') writer.pull() writer.write( 'elif e.data.type=="append": __wargs__[ e.data.argindex ].push( e.data.value )' ) writer.write( 'elif e.data.type=="__setitem__": __wargs__[ e.data.argindex ][e.data.key] = e.data.value' ) writer.write( 'elif e.data.type=="return_to_blocking_callback": threading._blocking_callback( e.data.result )' ) #writer.push() #writer.write( 'if instanceof(__wargs__[e.data.argindex], Array): __wargs__[ e.data.argindex ][e.data.key] = e.data.value') #writer.write( 'else: __wargs__[ e.data.argindex ][e.data.key] = e.data.value') #writer.pull() writer.pull() writer.write('self.onmessage = onmessage' ) ## force python variable scope, and pass user type information to second stage of translation. ## the dart backend can use this extra type information for speed and debugging. ## the Go and GLSL backends require this extra type information. vars = [] local_typedef_names = set() if not self._with_coffee: try: local_vars, global_vars = retrieve_vars(node.body) except SyntaxError as err: raise SyntaxError( self.format_error(err) ) local_vars = local_vars-global_vars inlined_long = False if local_vars: args_typedefs = [] args = [ a.id for a in node.args.args ] for v in local_vars: usertype = None if '=' in v: t,n = v.split('=') ## unpack type and name if self._with_dart and t in typedpython.simd_types: t = t[0].upper() + t[1:] v = '%s=%s' %(n,t) ## reverse local_typedef_names.add( n ) if t == 'long' and inlined_long == False: inlined_long = True writer.write('''inline("if (__NODEJS__==true) var long = require('long')")''') ## this is ugly if n in args: args_typedefs.append( v ) else: local_typedefs.append( v ) elif v in args or v in local_typedef_names: pass else: vars.append( v ) if args_typedefs: writer.write('@__typedef__(%s)' %','.join(args_typedefs)) if func_expr: writer.write('@expression(%s)' %func_expr) if not self._with_dart and not self._with_lua and not self._with_js and not javascript and not self._with_glsl: writer.write('@__pyfunction__') if return_type or return_type_keywords: if return_type_keywords and return_type: kw = ['%s=%s' %(k,v) for k,v in return_type_keywords.items()] writer.write('@returns(%s, %s)' %(return_type,','.join(kw)) ) elif return_type_keywords: writer.write('@returns(%s)' %','.join( ['%s=%s' %(k,v) for k,v in return_type_keywords.items()] )) else: writer.write('@returns(%s)' %return_type) if gpu_vectorize: writer.write('@gpu.vectorize') if gpu_method: writer.write('@gpu.method') if self._with_dart: ## dart supports optional positional params [x=1, y=2], or optional named {x:1, y:2} ## but not both at the same time. if node.args.kwarg: raise SyntaxError( self.format_error('dart functions can not take variable keyword arguments (**kwargs)' ) ) for dec in with_dart_decorators: writer.write('@%s'%dec) args = [] offset = len(node.args.args) - len(node.args.defaults) for i, arg in enumerate(node.args.args): a = arg.id dindex = i - offset if dindex >= 0 and node.args.defaults: default_value = self.visit( node.args.defaults[dindex] ) args.append( '%s=%s' %(a, default_value) ) else: args.append( a ) if node.args.vararg: if node.args.defaults: raise SyntaxError( self.format_error('dart functions can not use variable arguments (*args) and have keyword arguments' ) ) args.append('__variable_args__%s' %node.args.vararg) writer.write( 'def %s( %s ):' % (node.name, ','.join(args)) ) elif self._with_go: args = [] offset = len(node.args.args) - len(node.args.defaults) for i, arg in enumerate(node.args.args): a = arg.id dindex = i - offset if dindex >= 0 and node.args.defaults: default = self.visit(node.args.defaults[dindex]) args.append( '%s=%s' %(a, default)) else: args.append( a ) if node.args.vararg: args.append( '*%s' %node.args.vararg ) writer.write( 'def %s( %s ):' % (node.name, ','.join(args)) ) elif self._with_js or javascript or self._with_ll or self._with_glsl or self._with_go: if self._with_glsl: writer.write('@__glsl__') if node.args.vararg: #raise SyntaxError( 'pure javascript functions can not take variable arguments (*args)' ) writer.write('#WARNING - NOT IMPLEMENTED: javascript-mode functions with (*args)') kwargs_name = node.args.kwarg or '_kwargs_' args = [] offset = len(node.args.args) - len(node.args.defaults) for i, arg in enumerate(node.args.args): a = arg.id dindex = i - offset if dindex >= 0 and node.args.defaults: pass else: args.append( a ) if len(node.args.defaults) or node.args.kwarg: if args: writer.write( 'def %s( %s, %s ):' % (node.name, ','.join(args), kwargs_name ) ) else: writer.write( 'def %s( %s ):' % (node.name, kwargs_name) ) else: writer.write( 'def %s( %s ):' % (node.name, ','.join(args)) ) else: if len(node.args.defaults) or node.args.kwarg or len(node.args.args) or node.args.vararg: writer.write('def %s(args, kwargs):' % node.name) else: writer.write('def %s():' % node.name) writer.push() ## write local typedefs and var scope ## a = ','.join( vars ) if local_typedefs: if a: a += ',' a += ','.join(local_typedefs) writer.write('var(%s)' %a) ##################################################################### if self._with_dart or self._with_glsl or self._with_go: pass elif self._with_js or javascript or self._with_ll: if node.args.defaults: if not self._fast_js: ## this trys to recover when called in a bad way, ## however, this could be dangerous because the program ## should fail if a function is called this badly. kwargs_name = node.args.kwarg or '_kwargs_' lines = [ 'if (!( %s instanceof Object )) {' %kwargs_name ] a = ','.join( ['%s: arguments[%s]' %(arg.id, i) for i,arg in enumerate(node.args.args)] ) lines.append( 'var %s = {%s}' %(kwargs_name, a)) lines.append( '}') for a in lines: writer.write("JS('''%s''')" %a) offset = len(node.args.args) - len(node.args.defaults) maxlen = 0 maxlen2 = 0 for i, arg in enumerate(node.args.args): dindex = i - offset if dindex >= 0: dval = self.visit( node.args.defaults[dindex] ) if len(arg.id) > maxlen: maxlen = len(arg.id) if len(dval) > maxlen2: maxlen2 = len(dval) for i, arg in enumerate(node.args.args): dindex = i - offset if dindex >= 0: default_value = self.visit( node.args.defaults[dindex] ) #a = (kwargs_name, kwargs_name, arg.id, arg.id, default_value, arg.id, kwargs_name, arg.id) #b = "if (%s === undefined || %s.%s === undefined) {var %s = %s} else {var %s=%s.%s}" %a spaces = ' ' * (maxlen - len(arg.id)) spaces2 = ' ' * (maxlen2 - len(default_value)) a = (arg.id, spaces, kwargs_name, kwargs_name,arg.id, spaces, default_value, spaces2, kwargs_name, arg.id) b = "var %s %s= (%s === undefined || %s.%s === undefined)%s?\t%s %s: %s.%s" %a c = "JS('''%s''')" %b writer.write( c ) elif self._with_fastdef or fastdef: offset = len(node.args.args) - len(node.args.defaults) for i, arg in enumerate(node.args.args): dindex = i - offset if dindex >= 0 and node.args.defaults: default_value = self.visit( node.args.defaults[dindex] ) writer.write('''JS("var %s = kwargs[ '%s' ]")''' % (arg.id, arg.id)) writer.write( '''JS("if (%s == undefined) %s = %s")'''%(arg.id, arg.id, default_value) ) else: writer.write("""JS("var %s = args[ %s ]")""" % (arg.id, i)) elif self._with_lua: writer.write( 'var(%s)' %','.join([arg.id for arg in node.args.args])) offset = len(node.args.args) - len(node.args.defaults) for i,arg in enumerate(node.args.args): dindex = i - offset if dindex >= 0 and node.args.defaults: default_value = self.visit( node.args.defaults[dindex] ) writer.write("%s = kwargs.%s or %s" % (arg.id, arg.id, default_value)) else: writer.write( "%s = args[ %s ]" %(arg.id, i+1) ) elif len(node.args.defaults) or len(node.args.args) or node.args.vararg or node.args.kwarg: # new pythonjs' python function arguments handling # create the structure representing the functions arguments # first create the defaultkwargs JSObject if not self._with_coffee: writer.write('var(__sig__, __args__)') L = len(node.args.defaults) kwargsdefault = map(lambda x: keyword(self.visit(x[0]), x[1]), zip(node.args.args[-L:], node.args.defaults)) kwargsdefault = Call( Name('JSObject', None), [], kwargsdefault, None, None ) args = Call( Name('JSArray', None), map(lambda x: Str(x.id), node.args.args), [], None, None ) keywords = list([ keyword(Name('kwargs', None), kwargsdefault), keyword(Name('args', None), args), ]) if node.args.vararg: keywords.append(keyword(Name('vararg', None), Str(node.args.vararg))) if node.args.kwarg: keywords.append(keyword(Name('varkwarg', None), Str(node.args.kwarg))) # create a JS Object to store the value of each parameter signature = ', '.join(map(lambda x: '%s:%s' % (self.visit(x.arg), self.visit(x.value)), keywords)) writer.write('__sig__ = {%s}' % signature) # First check the arguments are well formed # ie. that this function is not a callback of javascript code if not self._with_go: writer.write("""if instanceof(args,Array) and Object.prototype.toString.call(kwargs) == '[object Object]' and arguments.length==2:""") writer.push() writer.write('pass') # do nothing if it's not called from javascript writer.pull() writer.write('else:') writer.push() # If it's the case, move use ``arguments`` to ``args`` writer.write('args = Array.prototype.slice.call(arguments, 0, __sig__.args.length)') # This means you can't pass keyword argument from javascript but we already knew that writer.write('kwargs = JSObject()') writer.pull() writer.write('__args__ = __getargs__("%s", __sig__, args, kwargs)' %node.name) # # then for each argument assign its value for arg in node.args.args: writer.write("""JS("var %s = __args__['%s']")""" % (arg.id, arg.id)) if node.args.vararg: writer.write("""JS("var %s = __args__['%s']")""" % (node.args.vararg, node.args.vararg)) if node.args.kwarg: writer.write("""JS('var %s = __args__["%s"]')""" % (node.args.kwarg, node.args.kwarg)) else: log('(function has no arguments)') ################# function body ################# if threaded and is_worker_entry: for i,arg in enumerate(node.args.args): writer.write( '%s = __webworker_wrap(%s, %s)' %(arg.id, arg.id, i)) writer.write('__wargs__.push(%s)'%arg.id) #if self._cached_property: ## DEPRECATED # writer.write('if self["__dict__"]["%s"]: return self["__dict__"]["%s"]' %(self._cached_property, self._cached_property)) self._return_type = None # tries to catch a return type in visit_Return ## write function body ## ## if sleep() is called or a new webworker is started, the following function body must be wrapped in ## a closure callback and called later by setTimeout timeouts = [] #continues = [] for b in node.body: if self._use_threading and isinstance(b, ast.Assign) and isinstance(b.value, ast.Call): if isinstance(b.value.func, ast.Attribute) and isinstance(b.value.func.value, Name) and b.value.func.value.id == 'threading': if b.value.func.attr == 'start_new_thread': self.visit(b) writer.write('__run__ = True') writer.write('def __callback%s():' %len(timeouts)) writer.push() ## workerjs for nodejs requires at least 100ms to initalize onmessage/postMessage timeouts.append(0.2) continue elif b.value.func.attr == 'start_webworker': self.visit(b) writer.write('__run__ = True') writer.write('def __callback%s():' %len(timeouts)) writer.push() ## workerjs for nodejs requires at least 100ms to initalize onmessage/postMessage timeouts.append(0.2) continue elif self._with_webworker and isinstance(b, ast.Assign) and isinstance(b.value, ast.Call) and isinstance(b.value.func, ast.Name) and b.value.func.id in self._global_functions: #assert b.value.calling_from_worker #raise SyntaxError(b) self.visit(b) writer.write('def __blocking( %s ):' %self.visit(b.targets[0])) writer.push() timeouts.append('BLOCKING') continue elif self._use_sleep: c = b if isinstance(b, ast.Expr): b = b.value if isinstance(b, ast.Call) and isinstance(b.func, ast.Name) and b.func.id == 'sleep': writer.write('__run__ = True') writer.write('def __callback%s():' %len(timeouts)) writer.push() timeouts.append( self.visit(b.args[0]) ) continue elif isinstance(b, ast.While): ## TODO has_sleep = False for bb in b.body: if isinstance(bb, ast.Expr): bb = bb.value if isinstance(bb, ast.Call) and isinstance(bb.func, ast.Name) and bb.func.id == 'sleep': has_sleep = float(self.visit(bb.args[0])) if has_sleep > 0.0: has_sleep = int(has_sleep*1000) #writer.write('__run_while__ = True') writer.write('__continue__ = True') writer.write('def __while():') writer.push() for bb in b.body: if isinstance(bb, ast.Expr): bb = bb.value if isinstance(bb, ast.Call) and isinstance(bb.func, ast.Name) and bb.func.id == 'sleep': continue #TODO - split body and generate new callback - now sleep is only valid at the end of the while loop else: e = self.visit(bb) if e: writer.write( e ) writer.write( 'if %s: __run_while__ = True' %self.visit(b.test)) writer.write( 'else: __run_while__ = False') writer.write('if __run_while__: setTimeout(__while, %s)' %(has_sleep)) writer.write('elif __continue__: setTimeout(__callback%s, 0)' %len(timeouts)) writer.pull() writer.write('setTimeout(__while, 0)') writer.write('__run__ = True') writer.write('def __callback%s():' %len(timeouts)) writer.push() timeouts.append(None) continue else: self.visit(b) continue b = c ## replace orig b self.visit(b) i = len(timeouts)-1 while timeouts: ms = timeouts.pop() if ms == 'BLOCKING': writer.write( 'threading._blocking_callback = None') writer.pull() writer.write('threading._blocking_callback = __blocking') elif ms is not None: writer.pull() ms = float(ms) ms *= 1000 writer.write('if __run__: setTimeout(__callback%s, %s)' %(i, ms)) writer.write('elif __continue__: setTimeout(__callback%s, %s)' %(i+1, ms)) i -= 1 if self._return_type: ## check if a return type was caught if return_type: assert return_type == self._return_type else: return_type = self._return_type self._function_return_types[ node.name ] = self._return_type self._return_type = None ############################################################ ### DEPRECATED if setter and 'set' in self._injector: ## inject extra code value_name = node.args.args[1].id inject = [ 'if self.property_callbacks["%s"]:' %prop_name, 'self.property_callbacks["%s"](["%s", %s, self], JSObject())' %(prop_name, prop_name, value_name) ] writer.write( ' '.join(inject) ) elif self._injector and node.original_name == '__init__': if 'set' in self._injector: writer.write( 'self.property_callbacks = JSObject()' ) if 'init' in self._injector: writer.write('if self.__class__.init_callbacks.length:') writer.push() writer.write('for callback in self.__class__.init_callbacks:') writer.push() writer.write('callback( [self], JSObject() )') writer.pull() writer.pull() ############################################################ writer.pull() ## end function body #if not self._with_dart and not self._with_lua and not self._with_js and not javascript and not self._with_glsl: # writer.write('%s.pythonscript_function=True'%node.name) if gpu: self._with_glsl = restore_with_glsl if gpu_main: self._in_gpu_main = False self._typedef_vars = dict() ## clear typed variables if inline: self._with_inline = False if self._in_js_class: writer = writer_main return types = [] for x in zip(node.args.args[-len(node.args.defaults):], node.args.defaults): key = x[0] value = x[1] if isinstance(value, ast.Name): value = value.id else: value = type(value).__name__.lower() types.append( '%s : "%s"' %(self.visit(key), value) ) if not self._with_dart and not self._with_lua: ## Dart functions can not have extra attributes? if self._introspective_functions: ## note, in javascript function.name is a non-standard readonly attribute, ## the compiler creates anonymous functions with name set to an empty string. writer.write('%s.NAME = "%s"' %(node.name,node.name)) writer.write( '%s.args_signature = [%s]' %(node.name, ','.join(['"%s"'%n.id for n in node.args.args])) ) defaults = ['%s:%s'%(self.visit(x[0]), self.visit(x[1])) for x in zip(node.args.args[-len(node.args.defaults):], node.args.defaults) ] writer.write( '%s.kwargs_signature = {%s}' %(node.name, ','.join(defaults)) ) if self._with_fastdef or fastdef: writer.write('%s.fastdef = True' %node.name) writer.write( '%s.types_signature = {%s}' %(node.name, ','.join(types)) ) if return_type: writer.write('%s.return_type = "%s"'%(node.name, return_type)) if self._with_js and with_js_decorators: for dec in with_js_decorators: if '.prototype.' in dec: ## these with-js functions are assigned to a some objects prototype, ## here we assume that they depend on the special "this" variable, ## therefore this function can not be marked as f.pythonscript_function, ## because we need __get__(f,'__call__') to dynamically bind "this" #writer.write( '%s=%s'%(dec,node.name) ) ## TODO - @XXX.prototype.YYY sets properties with enumerable as False, ## this fixes external javascript that is using `for (var i in anArray)` head, tail = dec.split('.prototype.') a = (head, tail, node.name) ## these props need to be writeable so that webworkers can redefine methods like: push, __setitem__ ## note to overwrite one of these props Object.defineProperty needs to be called again (ob.xxx=yyy will not work) writer.write('Object.defineProperty(%s.prototype, "%s", {enumerable:False, value:%s, writeable:True, configurable:True})' %a) elif dec == 'javascript': pass elif dec == 'fastdef': pass else: ## TODO: check ifdecorators in javascript mode are working properly writer.write( '%s = __get__(%s,"__call__")( [%s], {} )' %(node.name, dec, node.name)) # apply decorators for decorator in decorators: assert not self._with_js dec = self.visit(decorator) if dec == 'classmethod': writer.write( '%s.is_classmethod = True' %node.name) elif dec == 'staticmethod': writer.write( '%s.is_staticmethod = True' %node.name) writer.write( '%s.is_wrapper = True' %node.name) else: writer.write('%s = __get__(%s,"__call__")( [%s], JSObject() )' % (node.name, dec, node.name)) #if threaded: # writer.write('%s()' %node.name) # writer.write('self.termintate()') writer = writer_main #################### loops ################### ## the old-style for loop that puts a while loop inside a try/except and catches StopIteration, ## has a problem because at runtime if there is an error inside the loop, it will not show up in a strack trace, ## the error is slient. FAST_FOR is safer and faster, although it is not strictly Python because in standard ## Python a list is allowed to grow or string while looping over it. FAST_FOR only deals with a fixed size thing to loop over. FAST_FOR = True def visit_Continue(self, node): if self._with_js: writer.write('continue') else: writer.write('continue') return '' def visit_Break(self, node): if self._in_loop_with_else: writer.write('__break__ = True') writer.write('break') def visit_For(self, node): if node.orelse: raise SyntaxError( self.format_error('the syntax for/else is deprecated') ) if self._cache_for_body_calls: ## TODO add option for this for n in node.body: calls = collect_calls(n) for c in calls: if isinstance(c.func, ast.Name): ## these are constant for sure i = self._call_ids writer.write( '''JS('var __call__%s = __get__(%s,"__call__")')''' %(i,self.visit(c.func)) ) c.func.id = '__call__%s'%i c.constant = True self._call_ids += 1 if self._with_glsl or self._with_go: writer.write( 'for %s in %s:' %(self.visit(node.target), self.visit(node.iter)) ) writer.push() map(self.visit, node.body) writer.pull() return None if self._with_rpc_name and isinstance(node.iter, ast.Attribute) and isinstance(node.iter.value, ast.Name) and node.iter.value.id == self._with_rpc_name: target = self.visit(node.target) writer.write('def __rpc_loop__():') writer.push() writer.write( '%s = __rpc_iter__(%s, "%s")' %(target, self._with_rpc, node.iter.attr) ) writer.write( 'if %s == "__STOP_ITERATION__": __continue__()' %target) writer.write( 'else:') writer.push() map( self.visit, node.body ) writer.write( '__rpc_loop__()') writer.pull() writer.pull() writer.write('__rpc_loop__()') writer.write('def __continue__():') ## because this def comes after, it needs to be `hoisted` up by the javascript VM writer.push() return None iterid = self._iter_ids self._iter_ids += 1 target = node.target enumtar = None if isinstance(node.iter, ast.Call) and isinstance(node.iter.func, Name) and node.iter.func.id == 'enumerate': iter = node.iter.args[0] if isinstance(target, ast.Tuple): enumtar = target.elts[0] target = target.elts[1] else: iter = node.iter if enumtar: writer.write('var(%s)'%enumtar.id) writer.write('%s = 0' %enumtar.id) vars = [] multi_target = [] if isinstance(target, ast.Tuple): vars.append( '__mtarget__%s' %iterid) for elt in target.elts: if isinstance(elt, ast.Name): multi_target.append( elt.id ) vars.append( elt.id ) else: raise NotImplementedError('unknown iterator sub-target type: %s'%target) elif isinstance(target, ast.Name): vars.append( target.id ) else: raise NotImplementedError('unknown iterator target type: %s'%target) if self._with_ll: writer.write('for %s in %s:' %(self.visit(target), self.visit(iter))) writer.push() map(self.visit, node.body) writer.pull() elif self._with_js or self._with_dart: if isinstance(iter, ast.Call) and isinstance(iter.func, Name) and iter.func.id in ('range','xrange'): iter_start = '0' if len(iter.args) == 2: iter_start = self.visit(iter.args[0]) iter_end = self.visit(iter.args[1]) else: iter_end = self.visit(iter.args[0]) iter_name = target.id writer.write('var(%s, %s__end__)' %(iter_name, iter_name)) writer.write('%s = %s' %(iter_name, iter_start)) writer.write('%s__end__ = %s' %(iter_name, iter_end)) writer.write('while %s < %s__end__:' %(iter_name, iter_name)) writer.push() map(self.visit, node.body) writer.write('%s += 1' %iter_name ) if enumtar: writer.write('%s += 1'%enumtar.id) writer.pull() elif isinstance(iter, ast.Call) and isinstance(iter.func, Name) and iter.func.id in self._generator_functions: iter_name = self.visit(target) writer.write('var(%s, __generator__%s)' %(iter_name,iterid)) writer.write('__generator__%s = %s' %(iterid,self.visit(iter))) writer.write('while __generator__%s.__done__ != 1:'%iterid) writer.push() writer.write('%s = __generator__%s.next()'%(iter_name,iterid)) map(self.visit, node.body) writer.pull() else: if multi_target: writer.write('var(%s)' % ','.join(vars)) writer.write('for __mtarget__%s in %s:' %(iterid,self.visit(iter))) writer.push() for i,elt in enumerate(multi_target): writer.write('%s = __mtarget__%s[%s]' %(elt,iterid,i)) else: a = self.visit(target) self._in_assign_target = True b = self.visit(iter) self._in_assign_target = False writer.write('for %s in %s:' %(a, b)) writer.push() map(self.visit, node.body) if enumtar: writer.write('%s += 1'%enumtar.id) writer.pull() else: ## TODO else remove node.target.id from self._instances if isinstance(iter, Name) and iter.id in self._global_typed_lists: self._instances[ target.id ] = list( self._global_typed_lists[ iter.id ] )[0] vars.append('__iterator__%s'%iterid) if not self._with_coffee: writer.write('var(%s)' % ','.join(vars)) is_range = False is_generator = False iter_start = '0' iter_end = None if self.FAST_FOR and isinstance(iter, ast.Call) and isinstance(iter.func, Name) and iter.func.id in ('range','xrange'): is_range = True if len(iter.args) == 2: iter_start = self.visit(iter.args[0]) iter_end = self.visit(iter.args[1]) else: iter_end = self.visit(iter.args[0]) elif isinstance(iter, ast.Call) and isinstance(iter.func, Name) and iter.func.id in self._generator_functions: is_generator = True else: if hasattr(node, 'lineno'): src = self._source[ node.lineno-1 ] src = src.replace('"', '\\"') err = 'no iterator - line %s: %s' %(node.lineno, src.strip()) writer.write('__iterator__%s = __get__(__get__(%s, "__iter__", "%s"), "__call__")([], __NULL_OBJECT__)' %(iterid, self.visit(iter), err)) else: writer.write('__iterator__%s = __get__(__get__(%s, "__iter__"), "__call__")([], __NULL_OBJECT__)' %(iterid, self.visit(iter))) if is_generator: iter_name = self.visit(target) if not self._with_coffee: writer.write('var(%s, __generator__%s)' %(iter_name, iterid)) writer.write('__generator__%s = %s' %(iterid,self.visit(iter))) writer.write('while __generator__%s.__done__ != 1:'%iterid) writer.push() writer.write('%s = __generator__%s.next()'%(iter_name,iterid)) map(self.visit, node.body) writer.pull() elif is_range: iter_name = target.id if not self._with_coffee: writer.write('var(%s, %s__end__)' %(iter_name, iter_name)) writer.write('%s = %s' %(iter_name, iter_start)) writer.write('%s__end__ = %s' %(iter_name, iter_end)) ## assign to a temp variable. #writer.write('while %s < %s:' %(iter_name, iter_end)) ## this fails with the ternary __add_op writer.write('while %s < %s__end__:' %(iter_name, iter_name)) writer.push() map(self.visit, node.body) if self._with_lua: writer.write('%s = %s + 1' %(iter_name, iter_name) ) else: writer.write('%s += 1' %iter_name ) if enumtar: writer.write('%s += 1'%enumtar.id) writer.pull() else: if not self._with_coffee: writer.write('var(__next__%s)'%iterid) writer.write('__next__%s = __get__(__iterator__%s, "next")'%(iterid,iterid)) writer.write('while __iterator__%s.index < __iterator__%s.length:'%(iterid,iterid)) writer.push() if multi_target: writer.write('__mtarget__%s = __next__%s()'%(iterid, iterid)) for i,elt in enumerate(multi_target): if self._with_lua: writer.write('%s = __mtarget__%s[...][%s]' %(elt,iterid,i+1)) else: writer.write('%s = __mtarget__%s[%s]' %(elt,iterid,i)) else: writer.write('%s = __next__%s()' %(target.id, iterid)) map(self.visit, node.body) if enumtar: writer.write('%s += 1'%enumtar.id) writer.pull() return '' _call_ids = 0 def visit_While(self, node): if self._cache_while_body_calls: ## TODO add option for this for n in node.body: calls = collect_calls(n) for c in calls: if isinstance(c.func, ast.Name): ## these are constant for sure i = self._call_ids writer.write( '__call__%s = __get__(%s,"__call__")' %(i,self.visit(c.func)) ) c.func.id = '__call__%s'%i c.constant = True self._call_ids += 1 if node.orelse: raise SyntaxError( self.format_error('the syntax while/else is deprecated')) self._in_loop_with_else = True writer.write('var(__break__)') writer.write('__break__ = False') self._in_while_test = True writer.write('while %s:' % self.visit(node.test)) self._in_while_test = False writer.push() map(self.visit, node.body) writer.pull() if node.orelse: self._in_loop_with_else = False writer.write('if __break__ == False:') writer.push() map(self.visit, node.orelse) writer.pull() def visit_With(self, node): global writer if isinstance( node.context_expr, Name ) and node.context_expr.id == 'glsl': if not isinstance(node.optional_vars, ast.Name): raise SyntaxError( self.format_error('wrapper function name must be given: `with glsl as myfunc:`') ) main_func = None writer.inline_glsl = True self._with_glsl = True for b in node.body: if isinstance(b, ast.FunctionDef) and b.name == 'main': main_func = True writer.write('@__glsl__.%s' %node.optional_vars.id) a = self.visit(b) if a: writer.write(a) self._with_glsl = False writer.inline_glsl = False if not main_func: raise SyntaxError( self.format_error('a function named `main` must be defined as the entry point for the shader program') ) elif isinstance( node.context_expr, ast.Call ) and isinstance(node.context_expr.func, ast.Name) and node.context_expr.func.id == 'rpc': self._with_rpc = self.visit( node.context_expr.args[0] ) if isinstance(node.optional_vars, ast.Name): self._with_rpc_name = node.optional_vars.id for b in node.body: a = self.visit(b) if a: writer.write(a) self._with_rpc = None self._with_rpc_name = None elif isinstance( node.context_expr, Name ) and node.context_expr.id == 'webworker': self._with_webworker = True writer = get_webworker_writer( 'worker.js' ) #writer.write('if typeof(process) != "undefined": requirejs = require("requirejs")') #writer.write('if typeof(process) != "undefined": requirejs = require') writer.write('if typeof(require) != "undefined": requirejs = require') ## compatible with nodewebkit writer.write('else: importScripts("require.js")') for b in node.body: a = self.visit(b) if a: writer.write(a) self._with_webworker = False writer = writer_main elif isinstance( node.context_expr, Name ) and node.context_expr.id == 'inline': writer.write('with inline:') writer.push() for b in node.body: a = self.visit(b) if a: writer.write(a) writer.pull() elif isinstance( node.context_expr, Name ) and node.context_expr.id == 'lowlevel': self._with_ll = True #map(self.visit, node.body) for b in node.body: a = self.visit(b) if a: writer.write(a) self._with_ll = False elif isinstance( node.context_expr, Name ) and node.context_expr.id == 'javascript': self._with_js = True map(self.visit, node.body) self._with_js = False elif isinstance( node.context_expr, Name ) and node.context_expr.id == 'python': if not self._with_js: raise SyntaxError('"with python:" is only used inside of a "with javascript:" block') self._with_js = False map(self.visit, node.body) self._with_js = True elif isinstance( node.context_expr, Name ) and node.context_expr.id == 'fastdef': self._with_fastdef = True map(self.visit, node.body) self._with_fastdef = False elif isinstance( node.context_expr, Name ) and node.context_expr.id == 'static': self._with_static_type = True map(self.visit, node.body) self._with_static_type = False elif isinstance( node.context_expr, Name ) and node.context_expr.id == 'inline_function': self._with_inline = True map(self.visit, node.body) self._with_inline = False elif isinstance( node.context_expr, Name ) and node.context_expr.id in EXTRA_WITH_TYPES: writer.write('with %s:' %self.visit(node.context_expr)) writer.push() for b in node.body: a = self.visit(b) if a: writer.write(a) writer.pull() elif isinstance( node.context_expr, ast.Call ) and isinstance(node.context_expr.func, ast.Name) and node.context_expr.func.id in EXTRA_WITH_TYPES: restore = self._with_js self._with_js = True if node.context_expr.keywords: assert len(node.context_expr.keywords)==1 k = node.context_expr.keywords[0].arg v = self.visit(node.context_expr.keywords[0].value) a = 'with %s(%s=%s):' %( self.visit(node.context_expr.func), k,v ) writer.write(a) else: writer.write('with %s:' %self.visit(node.context_expr)) self._with_js = restore writer.push() for b in node.body: a = self.visit(b) if a: writer.write(a) writer.pull() elif isinstance( node.context_expr, ast.Name ) and node.context_expr.id == 'gojs': raise SyntaxError('deprecated') transform_gopherjs( node ) self._with_gojs = True for b in node.body: a = self.visit(b) if a: writer.write(a) self._with_gojs = False else: raise SyntaxError('invalid use of "with" statement') EXTRA_WITH_TYPES = ('__switch__', '__default__', '__case__', '__select__', 'gojs') class GeneratorFunctionTransformer( PythonToPythonJS ): ''' Translates a simple generator function into a class with state-machine that can be iterated over by calling its next method. A `simple generator` is one with no more than three yield statements, and a single for loop: . the first yield comes before the for loop . the second yield is the one inside the loop . the third yield comes after the for loop ''' def __init__(self, node, compiler=None): assert '_stack' in dir(compiler) #self.__dict___ = compiler.__dict__ ## share all state for name in dir(compiler): if name not in dir(self): setattr(self, name, (getattr(compiler, name))) self._head_yield = False self.visit( node ) compiler._addop_ids = self._addop_ids ## note: need to keep id index insync def visit_Yield(self, node): if self._in_head: if self._with_go: writer.write('self.__head_yield = %s'%self.visit(node.value)) writer.write('self.__head_returned = 0') else: writer.write('this.__head_yield = %s'%self.visit(node.value)) writer.write('this.__head_returned = 0') self._head_yield = True else: writer.write('__yield_return__ = %s'%self.visit(node.value)) def visit_Name(self, node): ## this hack should be replaced to support globals if self._with_go: return 'self.%s' %node.id else: return 'this.%s' %node.id def visit_FunctionDef(self, node): if self._with_go: self._visit_genfunc_go(node) else: self._visit_genfunc_js(node) def _visit_genfunc_go(self, node): writer.write('class %s:' %node.name) writer.push() ## push class typedefs = dict() stypes = dict() ## go struct return_type = None for decorator in reversed(node.decorator_list): if isinstance(decorator, ast.Call) and isinstance(decorator.func, ast.Name) and decorator.func.id == 'returns': if decorator.keywords: raise SyntaxError('invalid go return type') elif isinstance(decorator.args[0], ast.Name): return_type = decorator.args[0].id else: return_type = decorator.args[0].s elif isinstance(decorator, Call) and decorator.func.id in ('typedef', 'typedef_chan'): c = decorator assert len(c.args) == 0 and len(c.keywords) for kw in c.keywords: #assert isinstance( kw.value, Name) kwval = kw.value.id #self._typedef_vars[ kw.arg ] = kwval #self._instances[ kw.arg ] = kwval #self._func_typedefs[ kw.arg ] = kwval #local_typedefs.append( '%s=%s' %(kw.arg, kwval)) if decorator.func.id=='typedef_chan': #typedef_chans.append( kw.arg ) writer.write('@__typedef_chan__(%s=%s)' %(kw.arg, kwval)) else: typedefs[ kw.arg ] = kwval stypes[ kw.arg ] = kwval assert return_type args = [a.id for a in node.args.args] for name in typedefs: kwval = typedefs[name] writer.write('@__typedef__(%s=%s)' %(name, kwval)) writer.write('def __init__(self, %s):' %','.join(args)) writer.push() for arg in args: assert arg in typedefs writer.write('self.%s = %s'%(arg,arg)) self._in_head = True loop_node = None tail_yield = [] for b in node.body: if loop_node: tail_yield.append( b ) elif isinstance(b, ast.For): iter_start = '0' iter = b.iter if isinstance(iter, ast.Call) and isinstance(iter.func, Name) and iter.func.id in ('range','xrange'): if len(iter.args) == 2: iter_start = self.visit(iter.args[0]) iter_end = self.visit(iter.args[1]) else: iter_end = self.visit(iter.args[0]) else: iter_end = self.visit(iter) writer.write('self.__iter_start = %s'%iter_start) writer.write('self.__iter_index = %s'%iter_start) writer.write('self.__iter_end = %s'%iter_end) writer.write('self.__done__ = 0') loop_node = b self._in_head = False else: if isinstance(b, ast.Assign) and isinstance(b.targets[0], ast.Name) and len(b.targets)==2: ## typed local stypes[ b.targets[1].id ] = b.targets[0].id self.visit(b) writer.pull() ## end of init ## write typedef go struct ## for intype in '__iter_start __iter_index __iter_end __done__'.split(): stypes[ intype ] = 'int' writer.write('{') for n in stypes: writer.write( '%s : %s,' %(n, stypes[n])) writer.write('}') ## iterator function `next` writer.write('@returns(%s)' %return_type) writer.write('def next(self):') writer.push() writer.write('inline("var __yield_return__ %s")' %return_type) if self._head_yield: writer.write('if self.__head_returned == 0:') writer.push() writer.write('self.__head_returned = 1') writer.write('return self.__head_yield') writer.pull() writer.write('elif self.__iter_index < self.__iter_end:') else: writer.write('if self.__iter_index < self.__iter_end:') writer.push() for b in loop_node.body: self.visit(b) writer.write('self.__iter_index += 1') if not tail_yield: writer.write('if self.__iter_index == self.__iter_end: self.__done__ = 1') writer.write('return __yield_return__') writer.pull() writer.write('else:') writer.push() writer.write('self.__done__ = 1') if tail_yield: for b in tail_yield: self.visit(b) writer.write('return __yield_return__') writer.pull() writer.pull() ## end of next writer.pull() ## pull class def _visit_genfunc_js(self, node): args = [a.id for a in node.args.args] writer.write('def %s(%s):' %(node.name, ','.join(args))) writer.push() for arg in args: writer.write('this.%s = %s'%(arg,arg)) self._in_head = True loop_node = None tail_yield = [] for b in node.body: if loop_node: tail_yield.append( b ) elif isinstance(b, ast.For): iter_start = '0' iter = b.iter if isinstance(iter, ast.Call) and isinstance(iter.func, Name) and iter.func.id in ('range','xrange'): if len(iter.args) == 2: iter_start = self.visit(iter.args[0]) iter_end = self.visit(iter.args[1]) else: iter_end = self.visit(iter.args[0]) else: iter_end = self.visit(iter) writer.write('this.__iter_start = %s'%iter_start) writer.write('this.__iter_index = %s'%iter_start) writer.write('this.__iter_end = %s'%iter_end) writer.write('this.__done__ = 0') loop_node = b self._in_head = False else: self.visit(b) writer.pull() writer.write('@%s.prototype'%node.name) writer.write('def next():') writer.push() if self._head_yield: writer.write('if this.__head_returned == 0:') writer.push() writer.write('this.__head_returned = 1') writer.write('return this.__head_yield') writer.pull() writer.write('elif this.__iter_index < this.__iter_end:') else: writer.write('if this.__iter_index < this.__iter_end:') writer.push() for b in loop_node.body: self.visit(b) if self._with_lua: writer.write('this.__iter_index = this.__iter_index + 1') else: writer.write('this.__iter_index += 1') if not tail_yield: writer.write('if this.__iter_index == this.__iter_end: this.__done__ = 1') writer.write('return __yield_return__') writer.pull() writer.write('else:') writer.push() writer.write('this.__done__ = 1') if tail_yield: for b in tail_yield: self.visit(b) writer.write('return __yield_return__') writer.pull() writer.pull() class CollectCalls(NodeVisitor): _calls_ = [] def visit_Call(self, node): self._calls_.append( node ) def collect_calls(node): CollectCalls._calls_ = calls = [] CollectCalls().visit( node ) return calls class CollectDictComprehensions(NodeVisitor): _comps_ = [] def visit_GeneratorExp(self,node): self._comps_.append( node ) self.visit( node.elt ) for gen in node.generators: self.visit( gen.iter ) self.visit( gen.target ) def visit_DictComp(self, node): self._comps_.append( node ) self.visit( node.key ) self.visit( node.value ) for gen in node.generators: self.visit( gen.iter ) self.visit( gen.target ) def collect_dict_comprehensions(node): CollectDictComprehensions._comps_ = comps = [] CollectDictComprehensions().visit( node ) return comps class CollectComprehensions(NodeVisitor): _comps_ = [] def visit_GeneratorExp(self,node): self._comps_.append( node ) self.visit( node.elt ) for gen in node.generators: self.visit( gen.iter ) self.visit( gen.target ) def visit_ListComp(self, node): self._comps_.append( node ) self.visit( node.elt ) for gen in node.generators: self.visit( gen.iter ) self.visit( gen.target ) def collect_comprehensions(node): CollectComprehensions._comps_ = comps = [] CollectComprehensions().visit( node ) return comps class CollectGenFuncs(NodeVisitor): _funcs = [] _genfuncs = [] def visit_FunctionDef(self, node): self._funcs.append( node ) node._yields = [] node._loops = [] for b in node.body: self.visit(b) self._funcs.pop() def visit_Yield(self, node): func = self._funcs[-1] func._yields.append( node ) if func not in self._genfuncs: self._genfuncs.append( func ) def visit_For(self, node): if len(self._funcs): self._funcs[-1]._loops.append( node ) for b in node.body: self.visit(b) def visit_While(self, node): if len(self._funcs): self._funcs[-1]._loops.append( node ) for b in node.body: self.visit(b) def collect_generator_functions(node): CollectGenFuncs._funcs = [] CollectGenFuncs._genfuncs = gfuncs = [] CollectGenFuncs().visit( node ) return gfuncs def main(script, **kwargs): translator = PythonToPythonJS( source = script, **kwargs ) code = writer.getvalue() if translator.has_webworkers(): res = {'main':code} for jsfile in translator.get_webworker_file_names(): res[ jsfile ] = get_webworker_writer( jsfile ).getvalue() return res else: if '--debug' in sys.argv: try: open('/tmp/python-to-pythonjs.debug.py', 'wb').write(code) except: pass return code if __name__ == '__main__': ## if run directly prints source transformed to python-js-subset, this is just for debugging ## scripts = [] if len(sys.argv) > 1: argv = sys.argv[1:] for i,arg in enumerate(argv): if arg.endswith('.py'): scripts.append( arg ) if len(scripts): a = [] for script in scripts: a.append( open(script, 'rb').read() ) data = '\n'.join( a ) else: data = sys.stdin.read() compiler = PythonToPythonJS( source=data, dart='--dart' in sys.argv ) output = writer.getvalue() print( output ) ## pipe to stdout