#!/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<