#!/usr/bin/env python
# PythonJS to JavaScript Translator
# by Amirouche Boubekki and Brett Hartshorn - copyright 2013
# License: "New BSD"
import os, sys
from types import GeneratorType
import ast
from ast import Str
from ast import Name
from ast import Tuple
from ast import parse
from ast import Attribute
from ast import NodeVisitor
#import inline_function
#import code_writer
import typedpython
class SwapLambda( RuntimeError ):
def __init__(self, node):
self.node = node
RuntimeError.__init__(self)
class JSGenerator(NodeVisitor): #, inline_function.Inliner):
def __init__(self, requirejs=True, insert_runtime=True, webworker=False, function_expressions=True):
#writer = code_writer.Writer()
#self.setup_inliner( writer )
self._func_expressions = function_expressions
self._indent = 0
self._global_functions = {}
self._function_stack = []
self._requirejs = requirejs
self._insert_runtime = insert_runtime
self._webworker = webworker
self._exports = set()
self._inline_lambda = False
self.special_decorators = set(['__typedef__', '__glsl__', '__pyfunction__', 'expression'])
self._glsl = False
self._has_glsl = False
self._typed_vars = dict()
## the helper function below _mat4_to_vec4 is invalid because something can only be indexed
## with a constant expression. The GLSL compiler will throw this ERROR: 0:19: '[]' : Index expression must be constant"
#self.glsl_runtime = 'vec4 _mat4_to_vec4( mat4 a, int col) { return vec4(a[col][0], a[col][1], a[col][2],a[col][3]); }'
self.glsl_runtime = 'int _imod(int a, int b) { return int(mod(float(a),float(b))); }'
def indent(self): return ' ' * self._indent
def push(self): self._indent += 1
def pull(self):
if self._indent > 0: self._indent -= 1
def visit_ClassDef(self, node):
raise NotImplementedError(node)
def visit_Global(self, node):
return '/*globals: %s */' %','.join(node.names)
def visit_Assign(self, node):
# XXX: I'm not sure why it is a list since, mutiple targets are inside a tuple
target = node.targets[0]
if isinstance(target, Tuple):
raise NotImplementedError('target tuple assignment should have been transformed to flat assignment by python_to_pythonjs.py')
else:
target = self.visit(target)
value = self.visit(node.value)
## visit_BinOp checks for `numpy.float32` and changes the operands from `a*a` to `a[id]*a[id]`
if self._glsl and value.startswith('numpy.'):
self._typed_vars[ target ] = value
return ''
else:
code = '%s = %s;' % (target, value)
if self._requirejs and target not in self._exports and self._indent == 0 and '.' not in target:
self._exports.add( target )
return code
def visit_AugAssign(self, node):
## n++ and n-- are slightly faster than n+=1 and n-=1
target = self.visit(node.target)
op = self.visit(node.op)
value = self.visit(node.value)
if op=='+' and isinstance(node.value, ast.Num) and node.value.n == 1:
a = '%s ++;' %target
if op=='-' and isinstance(node.value, ast.Num) and node.value.n == 1:
a = '%s --;' %target
else:
a = '%s %s= %s;' %(target, op, value)
return a
def visit_With(self, node):
r = []
is_switch = False
if isinstance( node.context_expr, Name ) and node.context_expr.id == '__default__':
r.append('default:')
elif isinstance( node.context_expr, Name ) and node.context_expr.id == '__select__':
r.append('select {')
is_switch = True
elif isinstance( node.context_expr, ast.Call ):
if not isinstance(node.context_expr.func, ast.Name):
raise SyntaxError( self.visit(node.context_expr))
if len(node.context_expr.args):
a = self.visit(node.context_expr.args[0])
else:
assert len(node.context_expr.keywords)
a = '%s = %s' %(node.context_expr.keywords[0].arg, self.visit(node.context_expr.keywords[0].value))
if node.context_expr.func.id == '__case__':
r.append('case %s:' %a)
elif node.context_expr.func.id == '__switch__':
r.append('switch (%s) {' %self.visit(node.context_expr.args[0]))
is_switch = True
else:
raise SyntaxError( 'invalid use of with')
for b in node.body:
a = self.visit(b)
if a: r.append(a)
if is_switch:
r.append('}')
return '\n'.join(r)
def visit_Module(self, node):
header = []
lines = []
if self._requirejs and not self._webworker:
header.extend([
'define( function(){',
'__module__ = {}'
])
if self._insert_runtime:
dirname = os.path.dirname(os.path.abspath(__file__))
runtime = open( os.path.join(dirname, 'pythonjs.js') ).read()
lines.append( runtime ) #.replace('\n', ';') )
for b in node.body:
line = self.visit(b)
if line: lines.append( line )
else:
#raise b
pass
if self._requirejs and not self._webworker:
for name in self._exports:
if name.startswith('__'): continue
lines.append( '__module__.%s = %s' %(name,name))
lines.append( 'return __module__')
lines.append('}) //end requirejs define')
if self._has_glsl:
header.append( 'var __shader_header__ = ["%s"]'%self.glsl_runtime )
lines = header + lines
## fixed by Foxboron
return '\n'.join(l if isinstance(l,str) else l.encode("utf-8") for l in lines)
def visit_Expr(self, node):
# XXX: this is UGLY
s = self.visit(node.value)
if s.strip() and not s.endswith(';'):
s += ';'
if s==';': return ''
else: return s
def visit_In(self, node):
return ' in '
def visit_Tuple(self, node):
return '[%s]' % ', '.join(map(self.visit, node.elts))
def visit_List(self, node):
a = []
for elt in node.elts:
b = self.visit(elt)
if b is None: raise SyntaxError(elt)
a.append( b )
return '[%s]' % ', '.join(a)
def visit_TryExcept(self, node):
out = []
out.append( self.indent() + 'try {' )
self.push()
out.extend(
list( map(self.visit, node.body) )
)
self.pull()
out.append( self.indent() + '} catch(__exception__) {' )
self.push()
out.extend(
list( map(self.visit, node.handlers) )
)
self.pull()
out.append( '}' )
return '\n'.join( out )
def visit_Raise(self, node):
return 'throw new %s;' % self.visit(node.type)
def visit_Yield(self, node):
return 'yield %s' % self.visit(node.value)
def visit_ImportFrom(self, node):
# print node.module
# print node.names[0].name
# print node.level
return ''
def visit_ExceptHandler(self, node):
out = ''
if node.type:
out = 'if (__exception__ == %s || __exception__ instanceof %s) {\n' % (self.visit(node.type), self.visit(node.type))
if node.name:
out += 'var %s = __exception__;\n' % self.visit(node.name)
out += '\n'.join(map(self.visit, node.body)) + '\n'
if node.type:
out += '}\n'
return out
def visit_Lambda(self, node):
args = [self.visit(a) for a in node.args.args]
if args and args[0]=='__INLINE_FUNCTION__':
self._inline_lambda = True
#return '' ## skip node, the next function contains the real def
raise SwapLambda( node )
else:
return '(function (%s) {return %s;})' %(','.join(args), self.visit(node.body))
def visit_FunctionDef(self, node):
self._function_stack.append( node )
node._local_vars = set()
buffer = self._visit_function( node )
if node == self._function_stack[0]: ## could do something special here with global function
#buffer += 'pythonjs.%s = %s' %(node.name, node.name) ## this is no longer needed
self._global_functions[ node.name ] = node
self._function_stack.pop()
return buffer
def _visit_call_helper_var_glsl(self, node):
lines = []
for key in node.keywords:
ptrs = key.value.id.count('POINTER')
if ptrs:
## TODO - preallocate array size - if nonliteral arrays are used later ##
#name = key.arg
#pid = '[`%s.length`]' %name
#ptrs = pid * ptrs
#lines.append( '%s %s' %(key.value.id.replace('POINTER',''), name+ptrs))
## assume that this is a dynamic variable and will be typedef'ed by
## __glsl_dynamic_typedef() is inserted just before the assignment.
pass
else:
self._typed_vars[ key.arg ] = key.value.id
lines.append( '%s %s' %(key.value.id, key.arg))
return ';'.join(lines)
def _visit_function(self, node):
is_main = node.name == 'main'
is_annon = node.name == ''
is_pyfunc = False
return_type = None
glsl = False
glsl_wrapper_name = False
gpu_return_types = {}
gpu_vectorize = False
gpu_method = False
args_typedefs = {}
func_expr = False
for decor in node.decorator_list:
if isinstance(decor, ast.Call) and isinstance(decor.func, ast.Name) and decor.func.id == 'expression':
assert len(decor.args)==1
func_expr = True
node.name = self.visit(decor.args[0])
elif isinstance(decor, ast.Name) and decor.id == '__pyfunction__':
is_pyfunc = True
elif isinstance(decor, ast.Name) and decor.id == '__glsl__':
glsl = True
elif isinstance(decor, ast.Attribute) and isinstance(decor.value, ast.Name) and decor.value.id == '__glsl__':
glsl_wrapper_name = decor.attr
elif isinstance(decor, ast.Call) and isinstance(decor.func, ast.Name) and decor.func.id == '__typedef__':
for key in decor.keywords:
args_typedefs[ key.arg ] = key.value.id
elif isinstance(decor, ast.Call) and isinstance(decor.func, ast.Name) and decor.func.id == 'returns':
if decor.keywords:
for k in decor.keywords:
key = k.arg
assert key == 'array' or key == 'vec4'
gpu_return_types[ key ] = self.visit(k.value)
else:
return_type = decor.args[0].id
if return_type in typedpython.glsl_types:
gpu_return_types[ return_type ] = True
elif isinstance(decor, Attribute) and isinstance(decor.value, Name) and decor.value.id == 'gpu':
if decor.attr == 'vectorize':
gpu_vectorize = True
elif decor.attr == 'main':
is_main = True
elif decor.attr == 'method':
gpu_method = True
args = self.visit(node.args)
if glsl:
self._has_glsl = True ## triggers extras in header
lines = []
x = []
for i,arg in enumerate(args):
if gpu_vectorize and arg not in args_typedefs:
x.append( 'float* %s' %arg )
else:
if arg in args_typedefs:
x.append( '%s %s' %(args_typedefs[arg].replace('POINTER', '*'), arg) )
elif gpu_method and i==0:
x.append( '%s self' %arg )
else:
#x.append( 'float* %s' %arg ) ## this could be a way to default to the struct.
raise SyntaxError('GLSL functions require a typedef: %s' %arg)
if is_main:
lines.append( 'var glsljit = glsljit_runtime(__shader_header__);') ## each call to the wrapper function recompiles the shader
if x:
lines.append( 'glsljit.push("void main(%s) {");' %','.join(x) )
else:
lines.append( 'glsljit.push("void main( ) {");') ## WebCLGL parser requires the space in `main( )`
elif return_type:
#if gpu_method:
# lines.append( '__shader_header__.push("%s %s(struct this, %s ) {");' %(return_type, node.name, ', '.join(x)) )
#else:
lines.append( '__shader_header__.push("%s %s( %s ) {");' %(return_type, node.name, ', '.join(x)) )
else:
lines.append( '__shader_header__.push("void %s( %s ) {");' %(node.name, ', '.join(x)) )
self.push()
# `_id_` always write out an array of floats or array of vec4floats
if is_main:
#lines.append( 'glsljit.push("vec2 _id_ = get_global_id(); int _FRAGMENT_ID_ = int(_id_.x + _id_.y * 100.0);");')
pass
else:
lines.append( '__shader_header__.push("vec2 _id_ = get_global_id();");')
self._glsl = True
for child in node.body:
if isinstance(child, Str):
continue
else:
for sub in self.visit(child).splitlines():
if is_main:
if '`' in sub: ## "`" runtime lookups
if '``' in sub:
raise SyntaxError('inliner syntax error: %s'%sub)
sub = sub.replace('``', '')
chunks = sub.split('`')
if len(chunks) == 1:
raise RuntimeError(chunks)
sub = []
for ci,chk in enumerate(chunks):
#if not chk.startswith('@'): ## special inline javascript.
# chk = '```'+chk+'```'
#chk = chk.replace('$', '```')
if not ci%2:
if '@' in chk:
raise SyntaxError(chunks)
if ci==0:
if chk:
sub.append('"%s"'%chk)
else:
if chk:
if sub:
sub.append(' + "%s"'%chk)
else:
sub.append('"%s"'%chk)
elif chk.startswith('@'): ## special inline javascript.
lines.append( chk[1:] )
else:
if sub:
sub.append(' + %s' %chk)
else:
sub.append(chk)
if sub:
lines.append( 'glsljit.push(%s);' %''.join(sub))
else:
sub = sub.replace('$', '```')
lines.append( 'glsljit.push("%s");' %(self.indent()+sub) )
else: ## subroutine or method
if '`' in sub: sub = sub.replace('`', '')
lines.append( '__shader_header__.push("%s");' %sub )
self._glsl = False
self.pull()
if is_main:
lines.append('glsljit.push(";(1+1);");') ## fixes WebCLGL 2.0 parser
lines.append('glsljit.push("}");')
else:
lines.append('__shader_header__.push("%s}");' %self.indent())
lines.append(';')
if is_main:
#insert = lines
#lines = []
if not glsl_wrapper_name:
glsl_wrapper_name = node.name
if args:
lines.append('function %s( %s, __offset ) {' %(glsl_wrapper_name, ','.join(args)) )
else:
lines.append('function %s( __offset ) {' %glsl_wrapper_name )
lines.append(' __offset = __offset || 1024') ## note by default: 0 allows 0-1.0 ## TODO this needs to be set per-buffer
#lines.extend( insert )
lines.append(' var __webclgl = new WebCLGL()')
lines.append(' var header = glsljit.compile_header()')
lines.append(' var shader = glsljit.compile_main()')
#lines.append(' console.log(header)')
lines.append(' console.log("-----------")')
lines.append(' console.log(shader)')
## create the webCLGL kernel, compiles GLSL source
lines.append(' var __kernel = __webclgl.createKernel( shader, header );')
if gpu_return_types:
if 'array' in gpu_return_types:
if ',' in gpu_return_types['array']:
w,h = gpu_return_types['array'][1:-1].split(',')
lines.append(' var __return_length = %s * %s' %(w,h))
else:
lines.append(' var __return_length = %s' %gpu_return_types['array'])
elif 'vec4' in gpu_return_types:
if ',' in gpu_return_types['vec4']:
w,h = gpu_return_types['vec4'][1:-1].split(',')
lines.append(' var __return_length_vec4 = %s * %s' %(w,h))
else:
lines.append(' var __return_length_vec4 = %s' %gpu_return_types['vec4'])
elif 'mat4' in gpu_return_types:
lines.append(' var __return_length = 64') ## minimum size is 64
else:
raise NotImplementedError
else:
lines.append(' var __return_length = 64') ## minimum size is 64
for i,arg in enumerate(args):
lines.append(' if (%s instanceof Array) {' %arg)
#lines.append(' __return_length = %s.length==2 ? %s : %s.length' %(arg,arg, arg) )
lines.append(' var %s_buffer = __webclgl.createBuffer(%s.dims || %s.length, "FLOAT", %s.scale || __offset)' %(arg,arg,arg,arg))
lines.append(' __webclgl.enqueueWriteBuffer(%s_buffer, %s)' %(arg, arg))
lines.append(' __kernel.setKernelArg(%s, %s_buffer)' %(i, arg))
lines.append(' } else { __kernel.setKernelArg(%s, %s) }' %(i, arg))
#lines.append(' console.log("kernel.compile...")')
lines.append(' __kernel.compile()')
#lines.append(' console.log("kernel.compile OK")')
if gpu_return_types:
if 'vec4' in gpu_return_types:
dim = gpu_return_types[ 'vec4' ]
lines.append(' var rbuffer_vec4 = __webclgl.createBuffer(%s, "FLOAT4", __offset)' %dim)
lines.append(' __webclgl.enqueueNDRangeKernel(__kernel, rbuffer_vec4)')
lines.append(' var __res = __webclgl.enqueueReadBuffer_Float4( rbuffer_vec4 )')
lines.append(' return glsljit.unpack_vec4(__res, %s)' %gpu_return_types['vec4'])
elif 'array' in gpu_return_types:
dim = gpu_return_types[ 'array' ]
lines.append(' var rbuffer_array = __webclgl.createBuffer(%s, "FLOAT", __offset)' %dim)
lines.append(' __webclgl.enqueueNDRangeKernel(__kernel, rbuffer_array)')
lines.append(' var __res = __webclgl.enqueueReadBuffer_Float( rbuffer_array )')
lines.append(' return glsljit.unpack_array2d(__res, %s)' %gpu_return_types['array'])
elif 'mat4' in gpu_return_types:
lines.append(' var rbuffer = __webclgl.createBuffer([4,glsljit.matrices.length], "FLOAT4", __offset)')
lines.append(' __webclgl.enqueueNDRangeKernel(__kernel, rbuffer)')
lines.append(' var __res = __webclgl.enqueueReadBuffer_Float4( rbuffer )') ## slow
lines.append(' return glsljit.unpack_mat4(__res)')
else:
raise SyntaxError('invalid GPU return type: %s' %gpu_return_types)
else:
raise SyntaxError('GPU return type must be given')
lines.append(' var __return = __webclgl.createBuffer(__return_length, "FLOAT", __offset)')
lines.append(' __webclgl.enqueueNDRangeKernel(__kernel, __return)')
lines.append(' return __webclgl.enqueueReadBuffer_Float( __return )')
lines.append('} // end of wrapper')
lines.append('%s.return_matrices = glsljit.matrices' %glsl_wrapper_name )
return '\n'.join(lines)
elif len(node.decorator_list)==1 and not (isinstance(node.decorator_list[0], ast.Call) and node.decorator_list[0].func.id in self.special_decorators ) and not (isinstance(node.decorator_list[0], ast.Name) and node.decorator_list[0].id in self.special_decorators):
dec = self.visit(node.decorator_list[0])
buffer = self.indent() + '%s.%s = function(%s) {\n' % (dec,node.name, ', '.join(args))
elif len(self._function_stack) == 1:
## this style will not make function global to the eval context in NodeJS ##
#buffer = self.indent() + 'function %s(%s) {\n' % (node.name, ', '.join(args))
## note if there is no var keyword and this function is at the global level,
## then it should be callable from eval in NodeJS - this is not correct.
## infact, var should always be used with function expressions.
if self._func_expressions or func_expr:
buffer = self.indent() + 'var %s = function(%s) {\n' % (node.name, ', '.join(args))
else:
buffer = self.indent() + 'function %s(%s) {\n' % (node.name, ', '.join(args))
if self._requirejs and node.name not in self._exports:
self._exports.add( node.name )
else:
if self._func_expressions or func_expr:
buffer = self.indent() + 'var %s = function(%s) {\n' % (node.name, ', '.join(args))
else:
buffer = self.indent() + 'function %s(%s) {\n' % (node.name, ', '.join(args))
self.push()
body = list()
next = None
for i,child in enumerate(node.body):
if isinstance(child, Str) or hasattr(child, 'SKIP'):
continue
#try:
# v = self.visit(child)
#except SwapLambda as error:
# error.node.__class__ = ast.FunctionDef
# next = node.body[i+1]
# if not isinstance(next, ast.FunctionDef):
# raise SyntaxError('inline def is only allowed in javascript mode')
# error.node.__dict__ = next.__dict__
# error.node.name = ''
# v = self.visit(child)
v = self.try_and_catch_swap_lambda(child, node.body)
if v is None:
msg = 'error in function: %s'%node.name
msg += '\n%s' %child
raise SyntaxError(msg)
else:
body.append( self.indent()+v)
buffer += '\n'.join(body)
self.pull()
buffer += '\n%s}' %self.indent()
#if self._inline_lambda:
# self._inline_lambda = False
if is_annon:
buffer = '__wrap_function__(' + buffer + ')'
elif is_pyfunc:
## TODO change .is_wrapper to .__pyfunc__
buffer += ';%s.is_wrapper = true;' %node.name
else:
buffer += '\n'
return self.indent() + buffer
def try_and_catch_swap_lambda(self, child, body):
try:
return self.visit(child)
except SwapLambda as e:
next = None
for i in range( body.index(child), len(body) ):
n = body[ i ]
if isinstance(n, ast.FunctionDef):
if hasattr(n, 'SKIP'):
continue
else:
next = n
break
assert next
next.SKIP = True
e.node.__class__ = ast.FunctionDef
e.node.__dict__ = next.__dict__
e.node.name = ''
return self.try_and_catch_swap_lambda( child, body )
def _visit_subscript_ellipsis(self, node):
name = self.visit(node.value)
return '%s["$wrapped"]' %name
def visit_Subscript(self, node):
if isinstance(node.slice, ast.Ellipsis):
if self._glsl:
#return '%s[_id_]' % self.visit(node.value)
return '%s[matrix_index()]' % self.visit(node.value)
else:
return self._visit_subscript_ellipsis( node )
else:
return '%s[%s]' % (self.visit(node.value), self.visit(node.slice))
def visit_Index(self, node):
return self.visit(node.value)
def visit_Slice(self, node):
raise SyntaxError('list slice') ## slicing not allowed here at js level
def visit_arguments(self, node):
out = []
for name in [self.visit(arg) for arg in node.args]:
out.append(name)
return out
def visit_Name(self, node):
if node.id == 'None':
return 'null'
elif node.id == 'True':
return 'true'
elif node.id == 'False':
return 'false'
elif node.id == 'null':
return 'null'
return node.id
def visit_Attribute(self, node):
name = self.visit(node.value)
attr = node.attr
if self._glsl and name not in ('self', 'this'):
if name not in self._typed_vars:
return '`%s.%s`' % (name, attr)
else:
return '%s.%s' % (name, attr)
return '%s.%s' % (name, attr)
def visit_Print(self, node):
args = [self.visit(e) for e in node.values]
s = 'console.log(%s);' % ', '.join(args)
return s
def visit_keyword(self, node):
if isinstance(node.arg, basestring):
return node.arg, self.visit(node.value)
return self.visit(node.arg), self.visit(node.value)
def _visit_call_helper_instanceof(self, node):
args = map(self.visit, node.args)
if len(args) == 2:
return '%s instanceof %s' %tuple(args)
else:
raise SyntaxError( args )
def _visit_call_helper_new(self, node):
args = map(self.visit, node.args)
if len(args) == 1:
return ' new %s' %args[0]
else:
raise SyntaxError( args )
def _visit_call_helper_go( self, node ):
raise NotImplementedError('go call')
def visit_Call(self, node):
name = self.visit(node.func)
if name in typedpython.GO_SPECIAL_CALLS.values():
return self._visit_call_helper_go( node )
elif self._glsl and isinstance(node.func, ast.Attribute):
if isinstance(node.func.value, ast.Name) and node.func.value.id in self._typed_vars:
args = ','.join( [self.visit(a) for a in node.args] )
return '`__struct_name__`_%s(%s, %s)' %(node.func.attr, node.func.value.id, args)
else:
return '`%s`' %self._visit_call_helper(node)
elif self._glsl and name == 'len':
if isinstance(node.args[0], ast.Name):
return '`%s.length`' %node.args[0].id
elif isinstance(node.args[0], ast.Subscript):
s = node.args[0]
v = self.visit(s).replace('`', '')
return '`%s.length`' %v
elif isinstance(node.args[0], ast.Attribute): ## assume struct array attribute
s = node.args[0]
v = self.visit(s).replace('`', '')
return '`%s.length`' %v
elif name == 'glsl_inline_assign_from_iterable':
## the target must be declared without a typedef, because if declared first, it can not be redeclared,
## in the if-assignment block, the typedef is not given because `Iter_n` already has been typed beforehand.
sname = node.args[0].s
target = node.args[1].s
iter = node.args[2].id
self._typed_vars[ target ] = sname
lines = [
'`@var __length__ = %s.length;`' %iter,
#'`@console.log("DEBUG iter: "+%s);`' %iter,
#'`@console.log("DEBUG first item: "+%s[0]);`' %iter,
#'`@var __struct_name__ = %s[0].__struct_name__;`' %iter,
##same as above - slower ##'`@var __struct_name__ = glsljit.define_structure(%s[0]);`' %iter,
#'`@console.log("DEBUG sname: "+__struct_name__);`',
'`@var %s = %s[0];`' %(target, iter) ## capture first item with target name so that for loops can get the length of member arrays
]
#lines.append('for (int _iter=0; _iter < `__length__`; _iter++) {' )
## declare struct variable ##
#lines.append( '%s %s;' %(sname, target))
## at runtime loop over subarray, for each index inline into the shader's for-loop an if test,
lines.append( '`@for (var __j=0; __j<__length__; __j++) {`')
#lines.append( '`@glsljit.push("if (OUTPUT_INDEX==" +__j+ ") { %s %s=%s_" +__j+ ";}");`' %(sname, target, iter))
lines.append( '`@glsljit.push("if (matrix_index()==" +__j+ ") { %s=%s_" +__j+ ";}");`' %(target, iter))
lines.append( '`@}`')
#lines.append( '}' ) ## end of for loop
return '\n'.join(lines)
elif name == 'glsl_inline_push_js_assign':
# '@' triggers a new line of generated code
n = node.args[0].s
if isinstance(node.args[1], ast.Attribute): ## special case bypass visit_Attribute
v = '%s.%s' %(node.args[1].value.id, node.args[1].attr )
else:
v = self.visit(node.args[1])
v = v.replace('`', '') ## this is known this entire expression is an external call.
## check if number is required because literal floats like `1.0` will get transformed to `1` by javascript toString
orelse = 'typeof(%s)=="object" ? glsljit.object(%s, "%s") : glsljit.push("%s="+%s+";")' %(n, n,n, n,n)
## if a constant number literal directly inline
if v.isdigit() or (v.count('.')==1 and v.split('.')[0].isdigit() and v.split('.')[1].isdigit()):
#if_number = ' if (typeof(%s)=="number") { glsljit.push("%s=%s;") } else {' %(n, n,v)
#return '`@%s=%s; %s if (%s instanceof Array) {glsljit.array(%s, "%s")} else {%s}};`' %(n,v, if_number, n, n,n, orelse)
return '`@%s=%s; glsljit.push("%s=%s;");`' %(n,v, n,v)
else:
return '`@%s=%s; if (%s instanceof Array) {glsljit.array(%s, "%s")} else { if (%s instanceof Int16Array) {glsljit.int16array(%s,"%s")} else {%s} };`' %(n,v, n, n,n, n,n,n, orelse)
#elif name == 'glsl_inline':
# return '`%s`' %self.visit(node.args[0])
#elif name == 'glsl_inline_array':
# raise NotImplementedError
# return '`__glsl_inline_array(%s, "%s")`' %(self.visit(node.args[0]), node.args[1].s)
elif name == 'instanceof': ## this gets used by "with javascript:" blocks to test if an instance is a JavaScript type
return self._visit_call_helper_instanceof( node )
elif name == 'new':
return self._visit_call_helper_new( node )
elif name == '__ternary_operator__':
args = map(self.visit, node.args)
if len(args) == 2:
return '((%s) ? %s : %s)' %(args[0], args[0], args[1])
elif len(args) == 3:
return '((%s) ? %s : %s)' %(args[0], args[1], args[2])
else:
raise SyntaxError( args )
elif name == 'numpy.array':
return self._visit_call_helper_numpy_array(node)
elif name == 'JSObject':
return self._visit_call_helper_JSObject( node )
elif name == 'var':
if self._glsl:
return self._visit_call_helper_var_glsl( node )
else:
return self._visit_call_helper_var( node )
elif name == 'JSArray':
return self._visit_call_helper_JSArray( node )
elif name == 'inline' or name == 'JS':
assert len(node.args)==1 and isinstance(node.args[0], ast.Str)
return self._inline_code_helper( node.args[0].s )
elif name == 'dart_import':
if len(node.args) == 1:
return 'import "%s";' %node.args[0].s
elif len(node.args) == 2:
return 'import "%s" as %s;' %(node.args[0].s, node.args[1].s)
else:
raise SyntaxError
elif name == 'list':
return self._visit_call_helper_list( node )
elif name == '__get__' and len(node.args)==2 and isinstance(node.args[1], ast.Str) and node.args[1].s=='__call__':
return self._visit_call_helper_get_call_special( node )
#elif name in self._global_functions:
# return_id = self.inline_function( node )
# code = self.writer.getvalue()
# return '\n'.join([code, return_id])
elif name.split('.')[-1] == '__go__receive__':
raise SyntaxError('__go__receive__')
else:
return self._visit_call_helper(node)
def _visit_call_helper(self, node):
if node.args:
args = [self.visit(e) for e in node.args]
args = ', '.join([e for e in args if e])
else:
args = ''
fname = self.visit(node.func)
if fname=='__DOLLAR__': fname = '$'
return '%s(%s)' % (fname, args)
def inline_helper_remap_names(self, remap):
return "var %s;" %','.join(remap.values())
def inline_helper_return_id(self, return_id):
return "var __returns__%s = null;"%return_id
def _visit_call_helper_numpy_array(self, node):
if self._glsl:
return self.visit(node.keywords[0].value)
else:
return self.visit(node.args[0])
def _visit_call_helper_list(self, node):
name = self.visit(node.func)
if node.args:
args = [self.visit(e) for e in node.args]
args = ', '.join([e for e in args if e])
else:
args = ''
return '%s(%s)' % (name, args)
def _visit_call_helper_get_call_special(self, node):
name = self.visit(node.func)
if node.args:
args = [self.visit(e) for e in node.args]
args = ', '.join([e for e in args if e])
else:
args = ''
return '%s(%s)' % (name, args)
def _visit_call_helper_JSArray(self, node):
if node.args:
args = map(self.visit, node.args)
out = ', '.join(args)
#return '__create_array__(%s)' % out
return '[%s]' % out
else:
return '[]'
def _visit_call_helper_JSObject(self, node):
if node.keywords:
kwargs = map(self.visit, node.keywords)
f = lambda x: '"%s": %s' % (x[0], x[1])
out = ', '.join(map(f, kwargs))
return '{%s}' % out
else:
return '{}'
def _visit_call_helper_var(self, node):
args = [ self.visit(a) for a in node.args ]
if self._function_stack:
fnode = self._function_stack[-1]
rem = []
for arg in args:
if arg in fnode._local_vars:
rem.append( arg )
else:
fnode._local_vars.add( arg )
for arg in rem:
args.remove( arg )
out = []
if args:
out.append( 'var ' + ','.join(args) )
if node.keywords:
out.append( 'var ' + ','.join([key.arg for key in node.keywords]) )
return ';'.join(out)
def _inline_code_helper(self, s):
## TODO, should newline be changed here?
s = s.replace('\n', '\\n').replace('\0', '\\0') ## AttributeError: 'BinOp' object has no attribute 's' - this is caused by bad quotes
if s.strip().startswith('#'): s = '/*%s*/'%s
if '__new__>>' in s: ## fixes inline `JS("new XXX")`
s = s.replace('__new__>>', ' new ')
elif '"' in s or "'" in s: ## can not trust direct-replace hacks
pass
else:
if ' or ' in s:
s = s.replace(' or ', ' || ')
if ' not ' in s:
s = s.replace(' not ', ' ! ')
if ' and ' in s:
s = s.replace(' and ', ' && ')
return s
def visit_While(self, node):
body = [ 'while (%s) {' %self.visit(node.test)]
self.push()
for line in list( map(self.visit, node.body) ):
body.append( self.indent()+line )
self.pull()
body.append( self.indent() + '}' )
return '\n'.join( body )
def visit_Str(self, node):
s = node.s.replace("\\", "\\\\").replace('\n', '\\n').replace('\r', '\\r').replace('"', '\\"')
#if '"' in s:
# return "'%s'" % s
return '"%s"' % s
def visit_BinOp(self, node):
left = self.visit(node.left)
op = self.visit(node.op)
right = self.visit(node.right)
if op == '>>' and left == '__new__':
return ' new %s' %right
elif op == '<<':
if left in ('__go__receive__', '__go__send__'):
return '<- %s' %right
elif isinstance(node.left, ast.Call) and isinstance(node.left.func, ast.Name) and node.left.func.id in ('__go__array__', '__go__arrayfixed__', '__go__map__'):
if node.left.func.id == '__go__map__':
key_type = self.visit(node.left.args[0])
value_type = self.visit(node.left.args[1])
if value_type == 'interface': value_type = 'interface{}'
return 'map[%s]%s%s' %(key_type, value_type, right)
else:
if not right.startswith('{') and not right.endswith('}'):
right = '{%s}' %right[1:-1]
if node.left.func.id == '__go__array__':
return '[]%s%s' %(self.visit(node.left.args[0]), right)
elif node.left.func.id == '__go__arrayfixed__':
asize = self.visit(node.left.args[0])
atype = self.visit(node.left.args[1])
return '[%s]%s%s' %(asize, atype, right)
elif isinstance(node.left, ast.Name) and node.left.id=='__go__array__' and op == '<<':
return '[]%s' %self.visit(node.right)
if left in self._typed_vars and self._typed_vars[left] == 'numpy.float32':
left += '[_id_]'
if right in self._typed_vars and self._typed_vars[right] == 'numpy.float32':
right += '[_id_]'
return '(%s %s %s)' % (left, op, right)
def visit_Mult(self, node):
return '*'
def visit_Add(self, node):
return '+'
def visit_Sub(self, node):
return '-'
def visit_Div(self, node):
return '/'
def visit_Mod(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_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_Return(self, node):
if isinstance(node.value, Tuple):
return 'return [%s];' % ', '.join(map(self.visit, node.value.elts))
if node.value:
return 'return %s;' % self.visit(node.value)
return 'return undefined;'
def visit_Pass(self, node):
return '/*pass*/'
def visit_Eq(self, node):
return '=='
def visit_NotEq(self, node):
return '!='
def visit_Num(self, node):
return str(node.n)
def visit_Is(self, node):
return '==='
def visit_Compare(self, node):
if self._glsl:
comp = [self.visit(node.left)]
elif isinstance(node.ops[0], ast.Eq):
left = self.visit(node.left)
right = self.visit(node.comparators[0])
return '(%s instanceof Array ? JSON.stringify(%s)==JSON.stringify(%s) : %s===%s)' %(left, left, right, left, right)
elif isinstance(node.ops[0], ast.NotEq):
left = self.visit(node.left)
right = self.visit(node.comparators[0])
return '(!(%s instanceof Array ? JSON.stringify(%s)==JSON.stringify(%s) : %s===%s))' %(left, left, right, left, right)
else:
comp = [ '(']
comp.append( self.visit(node.left) )
comp.append( ')' )
for i in range( len(node.ops) ):
comp.append( self.visit(node.ops[i]) )
if isinstance(node.ops[i], ast.Eq):
raise SyntaxError('TODO')
elif isinstance(node.comparators[i], ast.BinOp):
comp.append('(')
comp.append( self.visit(node.comparators[i]) )
comp.append(')')
else:
comp.append( self.visit(node.comparators[i]) )
return ' '.join( comp )
def visit_Not(self, node):
return '!'
def visit_IsNot(self, node):
return '!=='
def visit_UnaryOp(self, node):
#return self.visit(node.op) + self.visit(node.operand)
return '%s (%s)' %(self.visit(node.op),self.visit(node.operand))
def visit_USub(self, node):
return '-'
def visit_And(self, node):
return ' && '
def visit_Or(self, node):
return ' || '
def visit_BoolOp(self, node):
op = self.visit(node.op)
return '('+ op.join( [self.visit(v) for v in node.values] ) +')'
def visit_If(self, node):
out = []
out.append( 'if (%s) {' %self.visit(node.test) )
self.push()
for line in list(map(self.visit, node.body)):
if line is None: continue
out.append( self.indent() + line )
orelse = []
for line in list(map(self.visit, node.orelse)):
orelse.append( self.indent() + line )
self.pull()
if orelse:
out.append( self.indent() + '} else {')
out.extend( orelse )
out.append( self.indent() + '}' )
return '\n'.join( out )
def visit_Dict(self, node):
a = []
for i in range( len(node.keys) ):
k = self.visit( node.keys[ i ] )
v = self.visit( node.values[i] )
a.append( '%s:%s'%(k,v) )
b = ','.join( a )
return '{ %s }' %b
def _visit_for_prep_iter_helper(self, node, out, iter_name):
## support "for key in JSObject" ##
#out.append( self.indent() + 'if (! (iter instanceof Array) ) { iter = Object.keys(iter) }' )
## new style - Object.keys only works for normal JS-objects, not ones created with `Object.create(null)`
out.append(
self.indent() + 'if (! (%s instanceof Array || typeof %s == "string" || __is_typed_array(%s) || __is_some_array(%s) )) { %s = __object_keys__(%s) }' %(iter_name, iter_name, iter_name, iter_name, iter_name, iter_name)
)
_iter_id = 0
def visit_For(self, node):
'''
for loops inside a `with javascript:` block will produce this faster for loop.
note that the rules are python-style, even though we are inside a `with javascript:` block:
. an Array is like a list, `for x in Array` gives you the value (not the index as you would get in pure javascript)
. an Object is like a dict, `for v in Object` gives you the key (not the value as you would get in pure javascript)
if your are trying to opitmize looping over a PythonJS list, you can do this:
for v in mylist[...]:
print v
above works because [...] returns the internal Array of mylist
'''
if self._glsl:
target = self.visit(node.target)
if isinstance(node.iter, ast.Call) and isinstance(node.iter.func, ast.Name) and node.iter.func.id=='iter': ## `for i in iter(n):`
assert isinstance(node.iter.args[0], ast.Name)
iter = node.iter.args[0].id
self._typed_vars[target] = 'struct*' ## this fixes attributes on structs
lines = [
'`@var __length__ = %s.length;`' %iter,
#'`@console.log("DEBUG iter: "+%s);`' %iter,
#'`@console.log("DEBUG first item: "+%s[0]);`' %iter,
'`@var __struct_name__ = %s[0].__struct_name__;`' %iter,
##same as above - slower ##'`@var __struct_name__ = glsljit.define_structure(%s[0]);`' %iter,
#'`@console.log("DEBUG sname: "+__struct_name__);`',
'`@var %s = %s[0];`' %(target, iter) ## capture first item with target name so that for loops can get the length of member arrays
]
##TODO## lines.append('$') ## optimizes webclgl parser
lines.append('for (int _iter=0; _iter < `__length__`; _iter++) {' )
## declare struct variable ##
lines.append( '`__struct_name__` %s;' %target)
## at runtime loop over subarray, for each index inline into the shader's for-loop an if test,
lines.append( '`@for (var __j=0; __j<__length__; __j++) {`')
lines.append( '`@glsljit.push("if (_iter==" +__j+ ") { %s=%s_" +__j+ ";}");`' %(target, iter))
lines.append( '`@}`')
##TODO## lines.append('$') ## optimizes webclgl parser
elif isinstance(node.iter, ast.Call): ## `for i in range(n):`
iter = self.visit(node.iter.args[0])
lines = ['for (int %s=0; %s < %s; %s++) {' %(target, target, iter, target)]
elif isinstance(node.iter, ast.Name): ## `for subarray in arrayofarrays:`
## capture the length of the subarray into the current javascript scope
## this is required to inline the lengths as constants into the GLSL for loops
lines = ['`@var __length__ = %s[0].length;`' %node.iter.id]
## start the GLSL for loop - `__length__` is set above ##
lines.append('for (int _iter=0; _iter < `__length__`; _iter++) {' )
## declare subarray with size ##
lines.append( 'float %s[`__length__`];' %target)
## at runtime loop over subarray, for each index inline into the shader's for-loop an if test,
lines.append( '`@for (var __j=0; __j<__length__; __j++) {`')
## below checks if the top-level iterator is the same index, and if so copy its contents into the local subarray,
lines.append( '`@glsljit.push("if (_iter==" +__j+ ") { for (int _J=0; _J<" +__length__+ "; _J++) {%s[_J] = %s_" +__j+ "[_J];} }");`' %(target, node.iter.id))
lines.append( '`@}`')
## this works because the function glsljit.array will unpack an array of arrays using the variable name with postfix "_n"
## note the extra for loop `_J` is required because the local subarray can not be assigned to `A_n`
else:
raise SyntaxError(node.iter)
for b in node.body:
lines.append( self.visit(b) )
lines.append( '}' ) ## end of for loop
return '\n'.join(lines)
self._iter_id += 1
iname = '__iter%s' %self._iter_id
index = '__idx%s' %self._iter_id
target = node.target.id
iter = self.visit(node.iter) # iter is the python iterator
out = []
out.append( self.indent() + 'var %s = %s;' % (iname, iter) )
#out.append( self.indent() + 'var %s = 0;' % index )
self._visit_for_prep_iter_helper(node, out, iname)
out.append( self.indent() + 'for (var %s=0; %s < %s.length; %s++) {' % (index, index, iname, index) )
self.push()
body = []
# backup iterator and affect value of the next element to the target
#pre = 'var backup = %s; %s = iter[%s];' % (target, target, target)
body.append( self.indent() + 'var %s = %s[ %s ];' %(target, iname, index) )
for line in list(map(self.visit, node.body)):
body.append( self.indent() + line )
# replace the replace target with the javascript iterator
#post = '%s = backup;' % target
#body.append( self.indent() + post )
self.pull()
out.extend( body )
out.append( self.indent() + '}' )
return '\n'.join( out )
def visit_Continue(self, node):
return 'continue'
def visit_Break(self, node):
return 'break;'
def generate_runtime():
from python_to_pythonjs import main as py2pyjs
lines = [
main( open('runtime/pythonpythonjs.py', 'rb').read(), requirejs=False, insert_runtime=False, function_expressions=True ), ## lowlevel pythonjs
main( py2pyjs(open('runtime/builtins.py', 'rb').read()), requirejs=False, insert_runtime=False, function_expressions=True )
]
return '\n'.join( lines )
def main(source, requirejs=True, insert_runtime=True, webworker=False, function_expressions=True):
head = []
tail = []
script = False
osource = source
if source.strip().startswith('')
script = list()
elif line.strip() == '':
if type(script) is list:
source = '\n'.join(script)
script = True
tail.append( '')
elif script is True:
tail.append( '')
else:
head.append( '')
elif isinstance( script, list ):
script.append( line )
elif script is True:
tail.append( line )
else:
head.append( line )
try:
tree = ast.parse( source )
#raise SyntaxError(source)
except SyntaxError:
import traceback
err = traceback.format_exc()
sys.stderr.write( err )
sys.stderr.write( '\n--------------error in second stage translation--------------\n' )
lineno = 0
for line in err.splitlines():
if "" in line:
lineno = int(line.split()[-1])
lines = source.splitlines()
if lineno > 10:
for i in range(lineno-5, lineno+5):
sys.stderr.write( 'line %s->'%i )
sys.stderr.write( lines[i] )
if i==lineno-1:
sys.stderr.write(' <>')
sys.stderr.write( '\n' )
else:
sys.stderr.write( lines[lineno] )
sys.stderr.write( '\n' )
if '--debug' in sys.argv:
sys.stderr.write( osource )
sys.stderr.write( '\n' )
sys.exit(1)
gen = JSGenerator( requirejs=requirejs, insert_runtime=insert_runtime, webworker=webworker, function_expressions=function_expressions )
output = gen.visit(tree)
if head:
head.append( output )
head.extend( tail )
output = '\n'.join( head )
return output
def command():
scripts = []
if len(sys.argv) > 1:
for arg in sys.argv[1:]:
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()
js = main( data )
print( js )
if __name__ == '__main__':
if '--runtime' in sys.argv:
print('creating new runtime: pythonjs.js')
open('pythonjs.js', 'wb').write( generate_runtime() )
else:
command()