diff --git a/README.md b/README.md index aaed747..db3e43f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,18 @@ Introduction ------------ PythonJS is a transpiler written in Python that converts a python like language into fast -JavaScript. With a few exceptions, the PythonJS language is a superset of Python that is extended to support new syntax inspired by JavaScript and Go. +JavaScript. It also includes experimental backends that translate to: Dart, Lua, CoffeeScript, and Go. [Syntax Documentation](https://github.com/PythonJS/PythonJS/blob/master/doc/syntax.md) +Go backend +---------- +The Go backend uses a fully typed subset of Python, mixed with extra syntax inspired by Golang to output Go programs that can be compiled to native executeables, or translated to JavaScript using GopherJS. + +[Syntax Documentation](https://github.com/PythonJS/PythonJS/blob/master/doc/go_syntax.md) + + Getting Started =============== PythonJS can be run with regular Python, or fully self-hosted within @@ -59,7 +66,7 @@ will be converted into JavaScript. Usage:: - translator.py [--help|--dart|--coffee|--lua|--no-wrapper|--analyzer] file.py + translator.py [--help|--go|--dart|--coffee|--lua|--no-wrapper|--analyzer] file.py Examples:: diff --git a/doc/go_syntax.md b/doc/go_syntax.md new file mode 100644 index 0000000..598778a --- /dev/null +++ b/doc/go_syntax.md @@ -0,0 +1,164 @@ +PythonJS Go Syntax +=============== + +PythonJS supports a fully typed subset of Python with extra syntax to support the Golang backend. + + +select +------- +Below `A` and `B` are typed as `chan int`. Data is read from a channel with `<-`. +``` + def select_loop(A:chan int, B:chan int): + print('starting select loop') + y = 0 + while True: + select: + case x = <- A: + y += x + case x = <- B: + y += x + +``` + +maps +------- +Go maps store key value pairs. The key type is given first enclosed in brackets, the value type is given after. +The example below shows a map with string keys and integer values + +``` + a = map[string]int{ + 'x': 1, + 'y': 2, + 'z': 3, + } +``` + +map iteration +------------- +The key value pairs can be looped over with a for loop. +``` + def main(): + a = map[string]int{'x':100, 'y':200} + b = '' + c = 0 + for key,value in a: + b += key + c += value +``` + +arrays +------ +Go typed arrays are defined with an optional size followed by the type, and values passed as arguments to the constructor. +Items in an array can be iterated over with a normal `for x in a` loop. Arrays also support index value pair loops using `enumerate` + +``` + a = []int(1,2,3) + b = [2]int(100,200) + +``` + +classes +------- +A Python class is translated into a Go struct with methods. Below a dict is used to type all the attribute variables that `self` will use. +``` + class A: + { + x:int, + y:int, + z:int, + } + def __init__(self, x:int, y:int, z:int=1): + self.x = x + self.y = y + self.z = z + + +``` + +subclasses +---------- +Subclasses can mix multiple classes, and override methods from the parent class. + +``` + class A: + def foo(self) -> int: + return 1 + + class B: + def bar(self) -> int: + return 2 + + class C( A, B ): + def call_foo_bar(self) -> int: + a = self.foo() + a += self.bar() + return a + + ## override foo ## + def foo(self) -> int: + return 100 + + def main(): + a = A() + b = B() + + c = C() + + ## below is all true ## + b.bar()==2 + c.bar()==2 + c.foo()==100 + c.call_foo_bar()==102 + +``` + +callbacks +--------- +Functions and methods can be passed as callbacks to other functions. The function argument type must contain the keyword `func` followed by the type signature of the callback (argument types) and (return type). Below the method is typed as `func(int)(int)` + +``` +class A: + { + x:int, + y:int, + z:int, + } + def __init__(self, x:int, y:int, z:int=1): + self.x = x + self.y = y + self.z = z + + def mymethod(self, m:int) -> int: + return self.x * m + + def call_method( cb:func(int)(int), mx:int ) ->int: + return cb(mx) + + def main(): + a = A( 100, 200, z=9999 ) + c = call_method( a.mymethod, 4 ) + print( c ) + +``` + +goroutines +---------- +The function `go` can be called to spawn a new function call as a goroutine +``` + go( myfunc(x,y,z) ) + +``` + +channels +-------- +To make a new Go channel call `go.channel(type)`, this is the same as in Go calling `make(chan type)`. +``` + c = go.channel(int) +``` + +list comprehensions +------------------- + +``` + a = []int(x for x in range(3)) +``` \ No newline at end of file diff --git a/pythonjs/python_to_pythonjs.py b/pythonjs/python_to_pythonjs.py index f16a369..f05a92c 100755 --- a/pythonjs/python_to_pythonjs.py +++ b/pythonjs/python_to_pythonjs.py @@ -144,14 +144,41 @@ def __init__(self, source=None, module=None, module_path=None, dart=False, coffe if source.strip().startswith('') - script = list() + if line.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 script: + if type(script) is list and len(script): source = '\n'.join(script) - script = True - self._html_tail.append( '') + script = True + self._html_tail.append( '') + else: + writer.write( line ) elif isinstance( script, list ): script.append( line ) @@ -174,7 +201,7 @@ def __init__(self, source=None, module=None, module_path=None, dart=False, coffe self._direct_operators = set() ## optimize "+" and "*" operator self._with_ll = False ## lowlevel - self._with_js = False + self._with_js = True self._in_lambda = False self._in_while_test = False self._use_threading = False @@ -272,8 +299,14 @@ def __init__(self, source=None, module=None, module_path=None, dart=False, coffe self._source = source.splitlines() + if '--debug--' in sys.argv: + try: + tree = ast.parse( source ) + except SyntaxError: + raise SyntaxError(source) + else: + tree = ast.parse( source ) - tree = parse( source ) ## ast.parse self._generator_function_nodes = collect_generator_functions( tree ) for node in tree.body: @@ -391,7 +424,9 @@ def visit_Import(self, node): tornado = ['tornado', 'tornado.web', 'tornado.ioloop'] for alias in node.names: - if alias.name in tornado: + 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 @@ -571,7 +606,11 @@ def visit_ListComp(self, node): a = ['get%s'%i for i in range(length)] writer.write('var( %s )' %','.join(a) ) - writer.write('%s = JSArray()'%cname) + if self._with_go: + assert 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() @@ -628,6 +667,8 @@ def _gen_comp(self, generators, node): 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)) ) writer.pull() @@ -637,6 +678,8 @@ def _gen_comp(self, generators, node): 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)) ) if self._with_lua: @@ -1332,10 +1375,25 @@ def visit_Return(self, node): 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) and node.left.func.id=='__go__array__': + if isinstance(node.right, ast.GeneratorExp): + is_go_listcomp = True + node.right.go_listcomp_type = node.left.args[0].id + + right = self.visit(node.right) - if self._with_glsl or self._with_go: + if self._with_glsl: return '(%s %s %s)' % (left, op, right) + elif self._with_go: + if is_go_listcomp: + return right + else: + return '(%s %s %s)' % (left, op, right) elif op == '|': if isinstance(node.right, Str): @@ -1865,7 +1923,7 @@ def _visit_assign_helper(self, node, target): elif isinstance(target.slice, ast.Slice): code = '%s.__setslice__(%s, %s)' %(self.visit(target.value), self.visit(target.slice), self.visit(node.value)) - elif self._with_dart or self._with_ll or self._with_glsl: + elif self._with_dart or self._with_ll or self._with_glsl or self._with_go: code = '%s[ %s ] = %s' code = code % (self.visit(target.value), self.visit(target.slice.value), self.visit(node.value)) @@ -2074,7 +2132,7 @@ def _visit_assign_helper(self, node, target): writer.write("%s = __ternary_operator__(instanceof(%s,Array), %s[%s], %s)" % (self.visit(target), r, r,i, fallback )) def visit_Print(self, node): - writer.write('print %s' % ', '.join(map(self.visit, node.values))) + writer.write('print(%s)' % ', '.join(map(self.visit, node.values))) def visit_Str(self, node): s = node.s.replace('\\','\\\\').replace('\n', '\\n').replace('\r', '\\r').replace('\0', '\\0') @@ -2148,7 +2206,8 @@ def visit_Call(self, node): name = self.visit(node.func) if name in typedpython.GO_SPECIAL_CALLS: name = typedpython.GO_SPECIAL_CALLS[ name ] - return '%s( %s )' %(name, self.visit(node.args[0])) + args = [self.visit(e) for e in node.args ] + return '%s( %s )' %(name, ','.join(args)) if self._with_rpc: if not self._with_rpc_name: @@ -2747,6 +2806,7 @@ def visit_FunctionDef(self, node): local_typedefs.append( '%s=%s' %(kw.arg, kwval)) if decorator.func.id=='typedef_chan': typedef_chans.append( kw.arg ) + writer.write('@__typedef_chan__(%s=%s)' %(kw.arg, kwval)) else: writer.write('@__typedef__(%s=%s)' %(kw.arg, kwval)) @@ -3817,38 +3877,15 @@ class GeneratorFunctionTransformer( PythonToPythonJS ): ''' def __init__(self, node, compiler=None): - self._with_ll = False - self._with_js = False - self._with_dart = False - self._with_coffee = False - self._with_lua = False - self._with_rpc = None - self._with_rpc_name = None - self._with_inline = False - self._in_while_test = False - self._in_lambda = False - - if compiler._with_dart: ## TODO - self._with_dart = True - elif compiler._with_coffee: - self._with_coffee = True - elif compiler._with_lua: - self._with_lua = True - else: - self._with_js = True + assert '_stack' in dir(compiler) + #self.__dict___ = compiler.__dict__ ## share all state + for name in dir(compiler): + if name not in dir(self): + setattr(self, name, (getattr(compiler, name))) - self._typedef_vars = compiler._typedef_vars - self._direct_operators = compiler._direct_operators - self._builtin_functions = compiler._builtin_functions - self._js_classes = compiler._js_classes - self._global_functions = compiler._global_functions - self._addop_ids = compiler._addop_ids - self._cache_for_body_calls = False - self._source = compiler._source - self._instances = dict() self._head_yield = False self.visit( node ) - compiler._addop_ids = self._addop_ids + compiler._addop_ids = self._addop_ids ## note: need to keep id index insync def visit_Yield(self, node): if self._in_head: diff --git a/pythonjs/pythonjs.js b/pythonjs/pythonjs.js index 8feb509..fa6a00b 100644 --- a/pythonjs/pythonjs.js +++ b/pythonjs/pythonjs.js @@ -484,15 +484,6 @@ var __getargs__ = function(func_name, signature, args, kwargs) { return out; } -try { -/*pass*/ -} catch(__exception__) { -console.trace(); -console.error(__exception__, __exception__.message); -console.error("line 4: pythonjs.configure( runtime_exceptions=False )"); -throw new RuntimeError("line 4: pythonjs.configure( runtime_exceptions=False )"); - -} _PythonJS_UID = 0; IndexError = function(msg) {this.message = msg || "";}; IndexError.prototype = Object.create(Error.prototype); IndexError.prototype.name = "IndexError"; KeyError = function(msg) {this.message = msg || "";}; KeyError.prototype = Object.create(Error.prototype); KeyError.prototype.name = "KeyError"; @@ -507,7 +498,8 @@ var __getfast__ = function(ob, attr) { } else { return v; } -};__getfast__.is_wrapper = true; +} + var __wrap_function__ = function(f) { f.is_wrapper = true; @@ -1459,7 +1451,7 @@ var __create_class__ = function(class_name, parents, attrs, props) { } else { wrapper = __get__(object, name); if (__test_if_true__(! (wrapper.is_wrapper))) { - console.log("RUNTIME ERROR: failed to get wrapper for:", name); + console.log(["RUNTIME ERROR: failed to get wrapper for:", name]); } } } @@ -1496,7 +1488,7 @@ var type = function(args, kwargs) { var ob_or_class_name = __args__['ob_or_class_name']; var bases = __args__['bases']; var class_dict = __args__['class_dict']; - "\n type(object) -> the object's type\n type(name, bases, dict) -> a new(type ## broken? - TODO test)\n "; + "\n type(object) -> the object's type\n type(name, bases, dict) -> a __new__>>type ## broken? - TODO test\n "; if (__test_if_true__((( bases ) === null && ( class_dict ) === null))) { return ob_or_class_name.__class__; } else { @@ -1537,7 +1529,7 @@ var getattr = function(args, kwargs) { if (__test_if_true__((prop && prop["get"]))) { return prop["get"]([ob], __jsdict([])); } else { - console.log("ERROR: getattr property error", prop); + console.log(["ERROR: getattr property error", prop]); } } else { return __get__(ob, attr); @@ -1563,7 +1555,7 @@ var setattr = function(args, kwargs) { if (__test_if_true__((prop && prop["set"]))) { prop["set"]([ob, value], __jsdict([])); } else { - console.log("ERROR: setattr property error", prop); + console.log(["ERROR: setattr property error", prop]); } } else { __set__(ob, attr, value); @@ -2007,7 +1999,9 @@ var _setup_array_prototype = function() { var i,arr,n; arr = []; start = (start | 0); - stop = (stop | this.length); + if (( stop ) === undefined) { + stop = this.length; + } if (( start ) < 0) { start = (this.length + start); } @@ -2327,7 +2321,7 @@ var sum = function(args, kwargs) { var arr = __args__['arr']; a = 0; var b,__iterator__40; - __iterator__40 = __get__(__get__(arr, "__iter__", "no iterator - line 1064: for b in arr:"), "__call__")([], __NULL_OBJECT__); + __iterator__40 = __get__(__get__(arr, "__iter__", "no iterator - line 1065: for b in arr:"), "__call__")([], __NULL_OBJECT__); var __next__40; __next__40 = __get__(__iterator__40, "next"); while (( __iterator__40.index ) < __iterator__40.length) { @@ -2383,7 +2377,7 @@ var next = function(args, kwargs) { } __args__ = __getargs__("next", __sig__, args, kwargs); var obj = __args__['obj']; - return __get__(__get__(obj, "next", "missing attribute `next` - line 1082: return obj.next()"), "__call__")(); + return __get__(__get__(obj, "next", "missing attribute `next` - line 1083: return obj.next()"), "__call__")(); };next.is_wrapper = true; var map = function(args, kwargs) { var arr,v; @@ -2400,7 +2394,7 @@ var map = function(args, kwargs) { var objs = __args__['objs']; arr = []; var ob,__iterator__41; - __iterator__41 = __get__(__get__(objs, "__iter__", "no iterator - line 1085: for ob in objs:"), "__call__")([], __NULL_OBJECT__); + __iterator__41 = __get__(__get__(objs, "__iter__", "no iterator - line 1086: for ob in objs:"), "__call__")([], __NULL_OBJECT__); var __next__41; __next__41 = __get__(__iterator__41, "next"); while (( __iterator__41.index ) < __iterator__41.length) { @@ -2425,7 +2419,7 @@ var filter = function(args, kwargs) { var objs = __args__['objs']; arr = []; var ob,__iterator__42; - __iterator__42 = __get__(__get__(objs, "__iter__", "no iterator - line 1092: for ob in objs:"), "__call__")([], __NULL_OBJECT__); + __iterator__42 = __get__(__get__(objs, "__iter__", "no iterator - line 1093: for ob in objs:"), "__call__")([], __NULL_OBJECT__); var __next__42; __next__42 = __get__(__iterator__42, "next"); while (( __iterator__42.index ) < __iterator__42.length) { @@ -2450,7 +2444,7 @@ var min = function(args, kwargs) { var lst = __args__['lst']; a = null; var value,__iterator__43; - __iterator__43 = __get__(__get__(lst, "__iter__", "no iterator - line 1099: for value in lst:"), "__call__")([], __NULL_OBJECT__); + __iterator__43 = __get__(__get__(lst, "__iter__", "no iterator - line 1100: for value in lst:"), "__call__")([], __NULL_OBJECT__); var __next__43; __next__43 = __get__(__iterator__43, "next"); while (( __iterator__43.index ) < __iterator__43.length) { @@ -2479,7 +2473,7 @@ var max = function(args, kwargs) { var lst = __args__['lst']; a = null; var value,__iterator__44; - __iterator__44 = __get__(__get__(lst, "__iter__", "no iterator - line 1105: for value in lst:"), "__call__")([], __NULL_OBJECT__); + __iterator__44 = __get__(__get__(lst, "__iter__", "no iterator - line 1106: for value in lst:"), "__call__")([], __NULL_OBJECT__); var __next__44; __next__44 = __get__(__iterator__44, "next"); while (( __iterator__44.index ) < __iterator__44.length) { @@ -2585,7 +2579,7 @@ var __Iterator___init__ = function(args, kwargs) { self.obj = obj; self.index = index; self.length = len([obj], __NULL_OBJECT__); - self.obj_get = __get__(obj, "get", "missing attribute `get` - line 1133: self.obj_get = obj.get ## cache this for speed"); + self.obj_get = __get__(obj, "get", "missing attribute `get` - line 1134: self.obj_get = obj.get ## cache this for speed"); };__Iterator___init__.is_wrapper = true; __Iterator_attrs.__init__ = __Iterator___init__; var __Iterator_next = function(args, kwargs) { @@ -2716,7 +2710,7 @@ var __dict___init__ = function(args, kwargs) { ob = js_object; if (__test_if_true__(ob instanceof Array)) { var o,__iterator__45; - __iterator__45 = __get__(__get__(ob, "__iter__", "no iterator - line 1195: for o in ob:"), "__call__")([], __NULL_OBJECT__); + __iterator__45 = __get__(__get__(ob, "__iter__", "no iterator - line 1196: for o in ob:"), "__call__")([], __NULL_OBJECT__); var __next__45; __next__45 = __get__(__iterator__45, "next"); while (( __iterator__45.index ) < __iterator__45.length) { @@ -2729,7 +2723,7 @@ var __dict___init__ = function(args, kwargs) { v = o["value"]; } try { -__get__(__get__(self, "__setitem__", "missing attribute `__setitem__` - line 1202: self.__setitem__( k,v )"), "__call__")([k, v], __NULL_OBJECT__); +__get__(__get__(self, "__setitem__", "missing attribute `__setitem__` - line 1203: self.__setitem__( k,v )"), "__call__")([k, v], __NULL_OBJECT__); } catch(__exception__) { if (__exception__ == KeyError || __exception__ instanceof KeyError) { throw new KeyError("error in dict init, bad key"); @@ -2740,16 +2734,16 @@ throw new KeyError("error in dict init, bad key"); } else { if (__test_if_true__(isinstance([ob, dict], __NULL_OBJECT__))) { var key,__iterator__46; - __iterator__46 = __get__(__get__(__jsdict_keys(ob), "__iter__", "no iterator - line 1206: for key in ob.keys():"), "__call__")([], __NULL_OBJECT__); + __iterator__46 = __get__(__get__(__jsdict_keys(ob), "__iter__", "no iterator - line 1207: for key in ob.keys():"), "__call__")([], __NULL_OBJECT__); var __next__46; __next__46 = __get__(__iterator__46, "next"); while (( __iterator__46.index ) < __iterator__46.length) { key = __next__46(); - value = ((ob instanceof Array) ? ob[key] : __get__(ob, "__getitem__", "line 1207: value = ob[ key ]")([key], __NULL_OBJECT__)); - __get__(__get__(self, "__setitem__", "missing attribute `__setitem__` - line 1208: self.__setitem__( key, value )"), "__call__")([key, value], __NULL_OBJECT__); + value = ((ob instanceof Array) ? ob[key] : __get__(ob, "__getitem__", "line 1208: value = ob[ key ]")([key], __NULL_OBJECT__)); + __get__(__get__(self, "__setitem__", "missing attribute `__setitem__` - line 1209: self.__setitem__( key, value )"), "__call__")([key, value], __NULL_OBJECT__); } } else { - console.log("ERROR init dict from:", js_object); + console.log(["ERROR init dict from:", js_object]); throw new TypeError; } } @@ -2771,15 +2765,15 @@ var __dict_jsify = function(args, kwargs) { var self = __args__['self']; keys = __object_keys__([self["$wrapped"]], __NULL_OBJECT__); var key,__iterator__47; - __iterator__47 = __get__(__get__(keys, "__iter__", "no iterator - line 1215: for key in keys:"), "__call__")([], __NULL_OBJECT__); + __iterator__47 = __get__(__get__(keys, "__iter__", "no iterator - line 1216: for key in keys:"), "__call__")([], __NULL_OBJECT__); var __next__47; __next__47 = __get__(__iterator__47, "next"); while (( __iterator__47.index ) < __iterator__47.length) { key = __next__47(); - value = __get__(self["$wrapped"], "__getitem__", "line 1216: value = self[...][key]")([key], __NULL_OBJECT__); + value = __get__(self["$wrapped"], "__getitem__", "line 1217: value = self[...][key]")([key], __NULL_OBJECT__); if ((typeof(value) instanceof Array ? JSON.stringify(typeof(value))==JSON.stringify("object") : typeof(value)==="object")) { if (__test_if_true__(hasattr([value, "jsify"], __NULL_OBJECT__))) { - __get__(__get__(self["$wrapped"], "__setitem__"), "__call__")([key, __get__(__get__(value, "jsify", "missing attribute `jsify` - line 1219: self[...][key] = value.jsify()"), "__call__")()], {}); + __get__(__get__(self["$wrapped"], "__setitem__"), "__call__")([key, __get__(__get__(value, "jsify", "missing attribute `jsify` - line 1220: self[...][key] = value.jsify()"), "__call__")()], {}); } } else { if ((typeof(value) instanceof Array ? JSON.stringify(typeof(value))==JSON.stringify("function") : typeof(value)==="function")) { @@ -2835,7 +2829,7 @@ var __dict_has_key = function(args, kwargs) { var key = __args__['key']; __dict = self["$wrapped"]; if (__test_if_true__(typeof(key) === 'object' || typeof(key) === 'function')) { - key = __get__(key, "__uid__", "missing attribute `__uid__` - line 1232: key = key.__uid__"); + key = __get__(key, "__uid__", "missing attribute `__uid__` - line 1233: key = key.__uid__"); } if (__test_if_true__(key in __dict)) { return true; @@ -2858,12 +2852,12 @@ var __dict_update = function(args, kwargs) { var self = __args__['self']; var other = __args__['other']; var key,__iterator__48; - __iterator__48 = __get__(__get__(other, "__iter__", "no iterator - line 1238: for key in other:"), "__call__")([], __NULL_OBJECT__); + __iterator__48 = __get__(__get__(other, "__iter__", "no iterator - line 1239: for key in other:"), "__call__")([], __NULL_OBJECT__); var __next__48; __next__48 = __get__(__iterator__48, "next"); while (( __iterator__48.index ) < __iterator__48.length) { key = __next__48(); - __get__(__get__(self, "__setitem__", "missing attribute `__setitem__` - line 1239: self.__setitem__( key, other[key] )"), "__call__")([key, ((other instanceof Array) ? other[key] : __get__(other, "__getitem__", "line 1239: self.__setitem__( key, other[key] )")([key], __NULL_OBJECT__))], __NULL_OBJECT__); + __get__(__get__(self, "__setitem__", "missing attribute `__setitem__` - line 1240: self.__setitem__( key, other[key] )"), "__call__")([key, ((other instanceof Array) ? other[key] : __get__(other, "__getitem__", "line 1240: self.__setitem__( key, other[key] )")([key], __NULL_OBJECT__))], __NULL_OBJECT__); } };__dict_update.is_wrapper = true; __dict_attrs.update = __dict_update; @@ -2881,12 +2875,12 @@ var __dict_items = function(args, kwargs) { var self = __args__['self']; arr = []; var key,__iterator__49; - __iterator__49 = __get__(__get__(__jsdict_keys(self), "__iter__", "no iterator - line 1242: for key in self.keys():"), "__call__")([], __NULL_OBJECT__); + __iterator__49 = __get__(__get__(__jsdict_keys(self), "__iter__", "no iterator - line 1243: for key in self.keys():"), "__call__")([], __NULL_OBJECT__); var __next__49; __next__49 = __get__(__iterator__49, "next"); while (( __iterator__49.index ) < __iterator__49.length) { key = __next__49(); - __get__(__get__(arr, "append", "missing attribute `append` - line 1243: arr.append( [key, self[key]] )"), "__call__")([[key, __get__(self, "__getitem__")([key], __NULL_OBJECT__)]], __NULL_OBJECT__); + __get__(__get__(arr, "append", "missing attribute `append` - line 1244: arr.append( [key, self[key]] )"), "__call__")([[key, __get__(self, "__getitem__")([key], __NULL_OBJECT__)]], __NULL_OBJECT__); } return arr; };__dict_items.is_wrapper = true; @@ -2927,7 +2921,7 @@ var __dict_set = function(args, kwargs) { var self = __args__['self']; var key = __args__['key']; var value = __args__['value']; - __get__(__get__(self, "__setitem__", "missing attribute `__setitem__` - line 1251: self.__setitem__(key, value)"), "__call__")([key, value], __NULL_OBJECT__); + __get__(__get__(self, "__setitem__", "missing attribute `__setitem__` - line 1252: self.__setitem__(key, value)"), "__call__")([key, value], __NULL_OBJECT__); };__dict_set.is_wrapper = true; __dict_attrs.set = __dict_set; var __dict___len__ = function(args, kwargs) { @@ -3135,7 +3129,7 @@ var set = function(args, kwargs) { } __args__ = __getargs__("set", __sig__, args, kwargs); var a = __args__['a']; - "\n This returns an array that is a minimal implementation of set.\n Often sets are used simply to remove duplicate entries from a list, \n and then it get converted back to a list, it is safe to use fastset for this.\n The array prototype is overloaded with basic set functions:\n difference\n intersection\n issubset\n Note: sets in Python are not subscriptable, but can be iterated over.\n Python docs say that set are unordered, some programs may rely on this disorder\n for randomness, for sets of integers we emulate the unorder only uppon initalization \n of the set, by masking the value by bits-1. Python implements sets starting with an \n array of length 8, and mask of 7, if set length grows to 6 (3/4th), then it allocates \n a new(array of length 32 and mask of 31. This is only emulated for arrays of )\n integers up to an array length of 1536.\n "; + "\n This returns an array that is a minimal implementation of set.\n Often sets are used simply to remove duplicate entries from a list, \n and then it get converted back to a list, it is safe to use fastset for this.\n The array prototype is overloaded with basic set functions:\n difference\n intersection\n issubset\n Note: sets in Python are not subscriptable, but can be iterated over.\n Python docs say that set are unordered, some programs may rely on this disorder\n for randomness, for sets of integers we emulate the unorder only uppon initalization \n of the set, by masking the value by bits-1. Python implements sets starting with an \n array of length 8, and mask of 7, if set length grows to 6 (3/4th), then it allocates \n a __new__>>array of length 32 and mask of 31. This is only emulated for arrays of \n integers up to an array length of 1536.\n "; hashtable = null; if (( a.length ) <= 1536) { hashtable = __jsdict([]); @@ -3235,7 +3229,7 @@ var __array___init__ = function(args, kwargs) { var initializer = __args__['initializer']; var little_endian = __args__['little_endian']; self.typecode = typecode; - self.itemsize = __get__(__get__(self, "typecodes", "missing attribute `typecodes` - line 1435: self.itemsize = self.typecodes[ typecode ]"), "__getitem__", "line 1435: self.itemsize = self.typecodes[ typecode ]")([typecode], __NULL_OBJECT__); + self.itemsize = __get__(__get__(self, "typecodes", "missing attribute `typecodes` - line 1436: self.itemsize = self.typecodes[ typecode ]"), "__getitem__", "line 1436: self.itemsize = self.typecodes[ typecode ]")([typecode], __NULL_OBJECT__); self.little_endian = little_endian; if (__test_if_true__(initializer)) { self.length = len([initializer], __NULL_OBJECT__); @@ -3259,7 +3253,7 @@ var __array___init__ = function(args, kwargs) { buff = new ArrayBuffer(size); self.dataview = new DataView(buff); self.buffer = buff; - __get__(__get__(self, "fromlist", "missing attribute `fromlist` - line 1456: self.fromlist( initializer )"), "__call__")([initializer], __NULL_OBJECT__); + __get__(__get__(self, "fromlist", "missing attribute `fromlist` - line 1457: self.fromlist( initializer )"), "__call__")([initializer], __NULL_OBJECT__); };__array___init__.is_wrapper = true; __array_attrs.__init__ = __array___init__; var __array___len__ = function(args, kwargs) { @@ -3290,7 +3284,7 @@ var __array___contains__ = function(args, kwargs) { __args__ = __getargs__("__array___contains__", __sig__, args, kwargs); var self = __args__['self']; var value = __args__['value']; - arr = __get__(__get__(self, "to_array", "missing attribute `to_array` - line 1462: arr = self.to_array()"), "__call__")(); + arr = __get__(__get__(self, "to_array", "missing attribute `to_array` - line 1463: arr = self.to_array()"), "__call__")(); if ((arr.indexOf(value) instanceof Array ? JSON.stringify(arr.indexOf(value))==JSON.stringify(-1) : arr.indexOf(value)===-1)) { return false; } else { @@ -3314,7 +3308,7 @@ var __array___getitem__ = function(args, kwargs) { step = self.itemsize; offset = (step * index); dataview = self.dataview; - func_name = ("get" + __get__(__get__(self, "typecode_names", "missing attribute `typecode_names` - line 1470: func_name = 'get'+self.typecode_names[ self.typecode ]"), "__getitem__", "line 1470: func_name = 'get'+self.typecode_names[ self.typecode ]")([self.typecode], __NULL_OBJECT__)); + func_name = ("get" + __get__(__get__(self, "typecode_names", "missing attribute `typecode_names` - line 1471: func_name = 'get'+self.typecode_names[ self.typecode ]"), "__getitem__", "line 1471: func_name = 'get'+self.typecode_names[ self.typecode ]")([self.typecode], __NULL_OBJECT__)); func = dataview[func_name].bind(dataview); if (( offset ) < self.bytes) { value = func(offset); @@ -3351,7 +3345,7 @@ var __array___setitem__ = function(args, kwargs) { } offset = (step * index); dataview = self.dataview; - func_name = ("set" + __get__(__get__(self, "typecode_names", "missing attribute `typecode_names` - line 1486: func_name = 'set'+self.typecode_names[ self.typecode ]"), "__getitem__", "line 1486: func_name = 'set'+self.typecode_names[ self.typecode ]")([self.typecode], __NULL_OBJECT__)); + func_name = ("set" + __get__(__get__(self, "typecode_names", "missing attribute `typecode_names` - line 1487: func_name = 'set'+self.typecode_names[ self.typecode ]"), "__getitem__", "line 1487: func_name = 'set'+self.typecode_names[ self.typecode ]")([self.typecode], __NULL_OBJECT__)); func = dataview[func_name].bind(dataview); if (( offset ) < self.bytes) { if ((self.typecode instanceof Array ? JSON.stringify(self.typecode)==JSON.stringify("float8") : self.typecode==="float8")) { @@ -3416,13 +3410,13 @@ var __array_fromlist = function(args, kwargs) { typecode = self.typecode; size = (length * step); dataview = self.dataview; - func_name = ("set" + __get__(__get__(self, "typecode_names", "missing attribute `typecode_names` - line 1506: func_name = 'set'+self.typecode_names[ typecode ]"), "__getitem__", "line 1506: func_name = 'set'+self.typecode_names[ typecode ]")([typecode], __NULL_OBJECT__)); + func_name = ("set" + __get__(__get__(self, "typecode_names", "missing attribute `typecode_names` - line 1507: func_name = 'set'+self.typecode_names[ typecode ]"), "__getitem__", "line 1507: func_name = 'set'+self.typecode_names[ typecode ]")([typecode], __NULL_OBJECT__)); func = dataview[func_name].bind(dataview); if (( size ) <= self.bytes) { i = 0; offset = 0; while (( i ) < length) { - item = ((lst instanceof Array) ? lst[i] : __get__(lst, "__getitem__", "line 1511: item = lst[i]")([i], __NULL_OBJECT__)); + item = ((lst instanceof Array) ? lst[i] : __get__(lst, "__getitem__", "line 1512: item = lst[i]")([i], __NULL_OBJECT__)); if ((typecode instanceof Array ? JSON.stringify(typecode)==JSON.stringify("float8") : typecode==="float8")) { item *= self._norm_set; } else { @@ -3478,7 +3472,7 @@ var __array_append = function(args, kwargs) { var self = __args__['self']; var value = __args__['value']; length = self.length; - __get__(__get__(self, "resize", "missing attribute `resize` - line 1534: self.resize( self.length + 1 )"), "__call__")([(self.length + 1)], __NULL_OBJECT__); + __get__(__get__(self, "resize", "missing attribute `resize` - line 1535: self.resize( self.length + 1 )"), "__call__")([(self.length + 1)], __NULL_OBJECT__); __get__(__get__(self, "__setitem__"), "__call__")([length, value], {}); };__array_append.is_wrapper = true; __array_attrs.append = __array_append; @@ -3496,12 +3490,12 @@ var __array_extend = function(args, kwargs) { var self = __args__['self']; var lst = __args__['lst']; var value,__iterator__54; - __iterator__54 = __get__(__get__(lst, "__iter__", "no iterator - line 1537: for value in lst:"), "__call__")([], __NULL_OBJECT__); + __iterator__54 = __get__(__get__(lst, "__iter__", "no iterator - line 1538: for value in lst:"), "__call__")([], __NULL_OBJECT__); var __next__54; __next__54 = __get__(__iterator__54, "next"); while (( __iterator__54.index ) < __iterator__54.length) { value = __next__54(); - __get__(__get__(self, "append", "missing attribute `append` - line 1538: self.append( value )"), "__call__")([value], __NULL_OBJECT__); + __get__(__get__(self, "append", "missing attribute `append` - line 1539: self.append( value )"), "__call__")([value], __NULL_OBJECT__); } };__array_extend.is_wrapper = true; __array_attrs.extend = __array_extend; @@ -3539,7 +3533,7 @@ var __array_to_list = function(args, kwargs) { } __args__ = __getargs__("__array_to_list", __sig__, args, kwargs); var self = __args__['self']; - return __get__(__get__(self, "to_array", "missing attribute `to_array` - line 1548: return self.to_array()"), "__call__")(); + return __get__(__get__(self, "to_array", "missing attribute `to_array` - line 1549: return self.to_array()"), "__call__")(); };__array_to_list.is_wrapper = true; __array_attrs.to_list = __array_to_list; var __array_to_ascii = function(args, kwargs) { @@ -3555,9 +3549,9 @@ var __array_to_ascii = function(args, kwargs) { __args__ = __getargs__("__array_to_ascii", __sig__, args, kwargs); var self = __args__['self']; string = ""; - arr = __get__(__get__(self, "to_array", "missing attribute `to_array` - line 1551: arr = self.to_array()"), "__call__")(); + arr = __get__(__get__(self, "to_array", "missing attribute `to_array` - line 1552: arr = self.to_array()"), "__call__")(); i = 0; - length = __get__(arr, "length", "missing attribute `length` - line 1552: i = 0; length = arr.length"); + length = __get__(arr, "length", "missing attribute `length` - line 1553: i = 0; length = arr.length"); while (( i ) < length) { var num = arr[i]; var char = String.fromCharCode(num); @@ -3618,14 +3612,14 @@ var __file_read = function(args, kwargs) { _fs = __get__(require, "__call__")(["fs"], __NULL_OBJECT__); path = self.path; if (__test_if_true__((binary || self.binary))) { - return _fs.readFileSync(path); + return _fs.readFileSync(path, { encoding:null }); } else { return _fs.readFileSync(path, __jsdict([["encoding", "utf8"]])); } };__file_read.is_wrapper = true; __file_attrs.read = __file_read; var __file_write = function(args, kwargs) { - var _fs,path; + var path,buff,_fs; var __sig__,__args__; __sig__ = { kwargs:{"binary": false},args:["self", "data", "binary"] }; if ((args instanceof Array && (Object.prototype.toString.call(kwargs) instanceof Array ? JSON.stringify(Object.prototype.toString.call(kwargs))==JSON.stringify("[object Object]") : Object.prototype.toString.call(kwargs)==="[object Object]") && (arguments.length instanceof Array ? JSON.stringify(arguments.length)==JSON.stringify(2) : arguments.length===2))) { @@ -3641,7 +3635,13 @@ var __file_write = function(args, kwargs) { _fs = __get__(require, "__call__")(["fs"], __NULL_OBJECT__); path = self.path; if (__test_if_true__((binary || self.binary))) { - _fs.writeFileSync(path, data); + binary = (binary || self.binary); + if ((binary instanceof Array ? JSON.stringify(binary)==JSON.stringify("base64") : binary==="base64")) { + buff = new Buffer(data, "base64"); + _fs.writeFileSync(path, buff, __jsdict([["encoding", null]])); + } else { + _fs.writeFileSync(path, data, __jsdict([["encoding", null]])); + } } else { _fs.writeFileSync(path, data, __jsdict([["encoding", "utf8"]])); } diff --git a/pythonjs/pythonjs.py b/pythonjs/pythonjs.py index d9db207..159f25c 100755 --- a/pythonjs/pythonjs.py +++ b/pythonjs/pythonjs.py @@ -25,7 +25,7 @@ def __init__(self, node): RuntimeError.__init__(self) class JSGenerator(NodeVisitor): #, inline_function.Inliner): - def __init__(self, requirejs=True, insert_runtime=True, webworker=False, function_expressions=False): + 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 @@ -919,7 +919,9 @@ 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 '"' in s or "'" in s: ## can not trust direct-replace hacks + 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: @@ -950,13 +952,17 @@ def visit_BinOp(self, node): op = self.visit(node.op) right = self.visit(node.right) - if op == '<<': + 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('}'): @@ -968,6 +974,8 @@ def visit_BinOp(self, node): 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_]' @@ -1261,10 +1269,11 @@ def generate_runtime(): ] return '\n'.join( lines ) -def main(source, requirejs=True, insert_runtime=True, webworker=False, function_expressions=False): +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 script: + if type(script) is list: source = '\n'.join(script) - script = True - tail.append( '') + script = True + tail.append( '') + elif script is True: + tail.append( '') + else: + head.append( '') elif isinstance( script, list ): script.append( line ) @@ -1315,6 +1328,10 @@ def main(source, requirejs=True, insert_runtime=True, webworker=False, function_ 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 ) diff --git a/pythonjs/pythonjs_to_go.py b/pythonjs/pythonjs_to_go.py index 9eecbe4..c0ff87c 100644 --- a/pythonjs/pythonjs_to_go.py +++ b/pythonjs/pythonjs_to_go.py @@ -18,11 +18,15 @@ def __init__(self, requirejs=False, insert_runtime=False): self._class_props = dict() self._vars = set() + self._known_vars = set() self._kwargs_type_ = dict() + self._imports = [] + def visit_ClassDef(self, node): self._class_stack.append( node ) node._parents = set() + node._struct_def = dict() out = [] sdef = dict() props = set() @@ -31,6 +35,27 @@ def visit_ClassDef(self, node): self._classes[ node.name ] = node self._class_props[ node.name ] = props + + + for base in node.bases: + n = self.visit(base) + if n == 'object': + continue + node._parents.add( n ) + + bases.add( n ) + if n in self._class_props: + props.update( self._class_props[n] ) + base_classes.add( self._classes[n] ) + #else: ## special case - subclassing a builtin like `list` + # continue + + for p in self._classes[ n ]._parents: + bases.add( p ) + props.update( self._class_props[p] ) + base_classes.add( self._classes[p] ) + + for decor in node.decorator_list: ## class decorators if isinstance(decor, ast.Call): assert decor.func.id=='__struct__' @@ -39,21 +64,46 @@ def visit_ClassDef(self, node): props.add( kw.arg ) sdef[ kw.arg ] = kw.value.id + node._struct_def.update( sdef ) out.append( 'type %s struct {' %node.name) + if base_classes: + for bnode in base_classes: + ## Go only needs the name of the parent struct and all its items are inserted automatically ## + out.append('%s' %bnode.name) + for name in sdef: out.append('%s %s' %(name, sdef[name])) out.append('}') init = None - + method_names = set() for b in node.body: - out.append( self.visit(b) ) assert isinstance(b, ast.FunctionDef) + method_names.add( b.name ) + out.append( self.visit(b) ) if b.name == '__init__': init = b - if init: + + parent_init = None + if base_classes: + for bnode in base_classes: + for b in bnode.body: + if isinstance(b, ast.FunctionDef): + if b.name in method_names: + continue + if b.name == '__init__': + parent_init = {'class':bnode, 'init':b} + #continue + out.append( self.visit(b) ) + + if init or parent_init: + if parent_init: + classname = parent_init['class'].name + init = parent_init['init'] + else: + classname = node.name out.append( 'func __new__%s( %s ) *%s {' %(node.name, init._args_signature, node.name)) out.append( ' ob := %s{}' %node.name ) @@ -89,18 +139,24 @@ def visit_Slice(self, node): def visit_Print(self, node): - r = [ 'fmt.Println(%s);' %self.visit(e) for e in node.values] + r = [] + for e in node.values: + s = self.visit(e) + if isinstance(e, ast.List): + r.append('fmt.Println(%s);' %s[1:-1]) + else: + r.append('fmt.Println(%s);' %s) return ''.join(r) def visit_Expr(self, node): return self.visit(node.value) def visit_Import(self, node): - r = [alias.name for alias in node.names] + r = [alias.name.replace('__SLASH__', '/') for alias in node.names] if r: - return 'import "%s"' %';'.join(r) - else: - return '' + for name in r: + self._imports.append('import("%s");' %name) + return '' def visit_Module(self, node): header = [ @@ -119,7 +175,10 @@ def visit_Module(self, node): else: lines.append( sub ) else: - raise SyntaxError(b) + if isinstance(b, ast.Import): + pass + else: + raise SyntaxError(b) lines.append('type _kwargs_type_ struct {') for name in self._kwargs_type_: @@ -128,7 +187,7 @@ def visit_Module(self, node): lines.append( ' __use__%s bool' %name) lines.append('}') - lines = header + lines + lines = header + self._imports + lines return '\n'.join( lines ) @@ -153,11 +212,20 @@ def visit_For(self, node): target = self.visit(node.target) lines = [] if isinstance(node.iter, ast.Call) and isinstance(node.iter.func, ast.Name): - iter = self.visit(node.iter.args[0]) if node.iter.func.id == 'range': - lines.append('for %s := 0; %s < %s; %s++ {' %(target, target, iter, target)) + if len(node.iter.args)==1: + iter = self.visit(node.iter.args[0]) + lines.append('for %s := 0; %s < %s; %s++ {' %(target, target, iter, target)) + elif len(node.iter.args)==2: + start = self.visit(node.iter.args[0]) + iter = self.visit(node.iter.args[1]) + lines.append('for %s := %s; %s < %s; %s++ {' %(target, start, target, iter, target)) + else: + raise SyntaxError('invalid for range loop') + elif node.iter.func.id == 'enumerate': + iter = self.visit(node.iter.args[0]) idx = self.visit(node.target.elts[0]) tar = self.visit(node.target.elts[1]) lines.append('for %s,%s := range %s {' %(idx,tar, iter)) @@ -215,22 +283,48 @@ def _visit_call_helper_go(self, node): name = self.visit(node.func) if name == '__go__': return 'go %s' %self.visit(node.args[0]) - elif name == '__gomake__': - return 'make(%s)' %self.visit(node.args[0]) + elif name == '__go_make__': + if len(node.args)==2: + return 'make(%s, %s)' %(self.visit(node.args[0]), self.visit(node.args[1])) + elif len(node.args)==3: + return 'make(%s, %s, %s)' %(self.visit(node.args[0]), self.visit(node.args[1]), self.visit(node.args[1])) + else: + raise SyntaxError('go make requires 2 or 3 arguments') + elif name == '__go_make_chan__': + return 'make(chan %s)' %self.visit(node.args[0]) + elif name == '__go__array__': + if isinstance(node.args[0], ast.BinOp):# and node.args[0].op == '<<': ## todo assert right is `typedef` + a = self.visit(node.args[0].left) + return '[]%s' %a + else: + a = self.visit(node.args[0]) + return '[]%s{}' %a else: - return SyntaxError('invalid special go call') + raise SyntaxError(name) + + def visit_Return(self, node): + if isinstance(node.value, ast.Tuple): + return 'return %s' % ', '.join(map(self.visit, node.value.elts)) + if node.value: + return 'return %s' % self.visit(node.value) + return 'return' def _visit_function(self, node): if self._function_stack[0] is node: self._vars = set() + self._known_vars = set() args_typedefs = {} + chan_args_typedefs = {} return_type = None for decor in node.decorator_list: if 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 args_typedefs[ key.arg ] = self.visit(key.value) + elif isinstance(decor, ast.Call) and isinstance(decor.func, ast.Name) and decor.func.id == '__typedef_chan__': + for key in decor.keywords: + chan_args_typedefs[ key.arg ] = self.visit(key.value) elif isinstance(decor, ast.Call) and isinstance(decor.func, ast.Name) and decor.func.id == 'returns': if decor.keywords: raise SyntaxError('invalid go return type') @@ -248,7 +342,7 @@ def _visit_function(self, node): for i, arg in enumerate(node.args.args): arg_name = arg.id - if arg_name not in args_typedefs: + if arg_name not in args_typedefs.keys()+chan_args_typedefs.keys(): if arg_name=='self': assert i==0 is_method = True @@ -258,8 +352,13 @@ def _visit_function(self, node): err += '\n missing typedef: %s' %arg.id raise SyntaxError(err) - arg_type = args_typedefs[arg_name] - a = '%s %s' %(arg_name, arg_type) + if arg_name in args_typedefs: + arg_type = args_typedefs[arg_name] + a = '%s %s' %(arg_name, arg_type) + else: + arg_type = chan_args_typedefs[arg_name] + a = '%s chan %s' %(arg_name, arg_type) + dindex = i - offset if a.startswith('__variable_args__'): ## TODO support go `...` varargs @@ -339,6 +438,47 @@ def _visit_call_helper_var(self, node): #return '\n'.join(out) return '' + def visit_With(self, node): + r = [] + is_switch = False + if isinstance( node.context_expr, ast.Name ) and node.context_expr.id == '__default__': + r.append('default:') + elif isinstance( node.context_expr, ast.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) + ## need to catch if this is a new variable ## + name = node.context_expr.keywords[0].arg + if name not in self._known_vars: + a = '%s := %s' %(name, self.visit(node.context_expr.keywords[0].value)) + else: + a = '%s = %s' %(name, 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_Assign(self, node): target = node.targets[0] if isinstance(target, ast.Tuple): @@ -347,7 +487,7 @@ def visit_Assign(self, node): elif isinstance(node.value, ast.BinOp) and self.visit(node.value.op)=='<<' and isinstance(node.value.left, ast.Name) and node.value.left.id=='__go__send__': target = self.visit(target) value = self.visit(node.value.right) - return 'var %s <- %s;' % (target, value) + return '%s <- %s;' % (target, value) elif not self._function_stack: target = self.visit(target) @@ -358,15 +498,22 @@ def visit_Assign(self, node): target = self.visit(target) value = self.visit(node.value) self._vars.remove( target ) + self._known_vars.add( target ) return '%s := %s;' % (target, value) else: target = self.visit(target) value = self.visit(node.value) + + #if '<-' in value: + # raise RuntimeError(target+value) + return '%s = %s;' % (target, value) def visit_While(self, node): - body = [ 'for %s {' %self.visit(node.test)] + cond = self.visit(node.test) + if cond == 'true' or cond == '1': cond = '' + body = [ 'for %s {' %cond] self.push() for line in list( map(self.visit, node.body) ): body.append( self.indent()+line ) @@ -376,12 +523,14 @@ def visit_While(self, node): def _inline_code_helper(self, s): return s + #return 'js.Global.Call("eval", "%s")' %s ## TODO inline JS() def main(script, insert_runtime=True): + if insert_runtime: dirname = os.path.dirname(os.path.abspath(__file__)) dirname = os.path.join(dirname, 'runtime') diff --git a/pythonjs/runtime/builtins.py b/pythonjs/runtime/builtins.py index d493cdd..7047472 100644 --- a/pythonjs/runtime/builtins.py +++ b/pythonjs/runtime/builtins.py @@ -1030,10 +1030,11 @@ def func(): @Array.prototype.__getslice__ def func(start, stop, step): - arr = [] #new(Array(this.length)) + arr = [] start = start | 0 - stop = stop | this.length + if stop is undefined: + stop = this.length if start < 0: start = this.length + start @@ -1878,7 +1879,7 @@ def read(self, binary=False): path = self.path with javascript: if binary or self.binary: - return _fs.readFileSync( path ) + return _fs.readFileSync( path, encoding=None ) else: return _fs.readFileSync( path, {'encoding':'utf8'} ) @@ -1887,8 +1888,18 @@ def write(self, data, binary=False): path = self.path with javascript: if binary or self.binary: - _fs.writeFileSync( path, data ) + binary = binary or self.binary + if binary == 'base64': ## TODO: fixme, something bad in this if test + #print('write base64 data') + buff = new Buffer(data, 'base64') + _fs.writeFileSync( path, buff, {'encoding':None}) + + else: + #print('write binary data') + #print(binary) + _fs.writeFileSync( path, data, {'encoding':None}) else: + #print('write utf8 data') _fs.writeFileSync( path, data, {'encoding':'utf8'} ) def close(self): diff --git a/pythonjs/translator.py b/pythonjs/translator.py index 0d12938..0b772f9 100755 --- a/pythonjs/translator.py +++ b/pythonjs/translator.py @@ -27,6 +27,19 @@ def main(script, module_path=None): if '--go' in sys.argv: a = python_to_pythonjs(script, go=True, module_path=module_path) code = pythonjs_to_go( a ) + elif '--gopherjs' in sys.argv: + a = python_to_pythonjs(script, go=True, module_path=module_path) + code = pythonjs_to_go( a ) + + exe = os.path.expanduser('~/go/bin/gopherjs') + if not os.path.isfile(exe): + raise RuntimeError('gopherjs not installed to ~/go/bin/gopherjs') + import subprocess + path = '/tmp/gopherjs-input.go' + open(path, 'wb').write(code) + subprocess.check_call([exe, 'build', path], cwd='/tmp') + code = open('/tmp/gopherjs-input.js', 'rb').read() + elif '--dart' in sys.argv: a = python_to_pythonjs(script, dart=True, module_path=module_path) code = pythonjs_to_dart( a ) @@ -51,6 +64,7 @@ def main(script, module_path=None): code = pythonjs_to_luajs( a ) else: a = python_to_pythonjs(script, module_path=module_path) + if isinstance(a, dict): res = {} for jsfile in a: diff --git a/pythonjs/typedpython.py b/pythonjs/typedpython.py index d4d813c..f5bf3f3 100644 --- a/pythonjs/typedpython.py +++ b/pythonjs/typedpython.py @@ -23,7 +23,9 @@ GO_SPECIAL_CALLS = { 'go' : '__go__', - 'go.channel' : '__gomake__', + 'go.channel' : '__go_make_chan__', + 'go.array' : '__go__array__', + 'go.make' : '__go_make__' } def transform_source( source, strip=False ): @@ -43,7 +45,7 @@ def transform_source( source, strip=False ): if nextchar.strip(): break j += 1 - if a and char==']' and j==i+1 and nextchar!=None and nextchar in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ': + if a and char==']' and j==i+1 and nextchar!=None and nextchar in '[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ': assert '[' in a gotype = [] b = a.pop() @@ -53,7 +55,10 @@ def transform_source( source, strip=False ): gotype.reverse() gotype = ''.join(gotype) if not gotype: - a.append('__go__array__(') + if nextchar=='[': + a.append('__go__array__<<') + else: + a.append('__go__array__(') elif gotype.isdigit(): a.append('__go__arrayfixed__(%s,' %gotype) else: @@ -68,6 +73,11 @@ def transform_source( source, strip=False ): elif hit_go_typedef and char=='{': a.append(')<<{') hit_go_typedef = False + elif hit_go_typedef and char==',': + #a.append(', type=True),') ## this breaks function annotations that splits on ',' + a.append('<>') + if '\tnew ' in c: + c = c.replace('\tnew ', ' __new__>>') + ## golang @@ -254,20 +272,18 @@ def transform_source( source, strip=False ): tailend = tail[ tail.rindex(')')+1 : ] tail = tail[ : tail.rindex(')') ] - #print(head) - #print(tail) - #raise RuntimeError for x in tail.split(','): - y = x #x.split(')')[0] + y = x if ':' in y: kw = None if '=' in y: y, kw = y.split('=') arg, typedef = y.split(':') chan = False - if len(arg.split()) == 2: - chan, arg = arg.split() + if len(typedef.strip().split()) == 2: + chan = True + typedef = typedef.strip().split()[-1] if '*' in arg: arg_name = arg.split('*')[-1] else: @@ -384,7 +400,7 @@ def xxx(): def call_method( cb:func(int)(int) ) ->int: return cb(3) -def wrapper(a:int, chan c:int): +def wrapper(a:int, c:chan int): result = longCalculation(a) c <- result @@ -425,6 +441,13 @@ def f(a:int=100, b:int=100) ->int: def f(*args:int, **kwargs:int) ->int: return a+b +a = []int(x for x in range(3)) + +y = go.make([]float64, 1000) + +def plot(id:string, latency:[]float64, xlabel:string, title:string ): + pass + ''' if __name__ == '__main__': diff --git a/regtests/class/mi.py b/regtests/class/mi.py index 0b96290..5c1a591 100644 --- a/regtests/class/mi.py +++ b/regtests/class/mi.py @@ -2,21 +2,21 @@ multiple inheritance ''' class A: - def foo(self): + def foo(self) -> int: return 1 class B: - def bar(self): + def bar(self) -> int: return 2 class C( A, B ): - def call_foo_bar(self): + def call_foo_bar(self) -> int: a = self.foo() a += self.bar() return a ## extend foo ## - def foo(self): + def foo(self) -> int: a = A.foo(self) a += 100 return a diff --git a/regtests/class/mi_override.py b/regtests/class/mi_override.py new file mode 100644 index 0000000..d36fb6f --- /dev/null +++ b/regtests/class/mi_override.py @@ -0,0 +1,32 @@ +''' +multiple inheritance +''' +class A: + def foo(self) -> int: + return 1 + +class B: + def bar(self) -> int: + return 2 + +class C( A, B ): + def call_foo_bar(self) -> int: + a = self.foo() + a += self.bar() + return a + + ## override foo ## + def foo(self) -> int: + return 100 + +def main(): + a = A() + TestError( a.foo()==1 ) + b = B() + TestError( b.bar()==2 ) + + c = C() + TestError( c.foo()==100 ) + TestError( c.bar()==2 ) + + TestError( c.call_foo_bar()==102 ) diff --git a/regtests/go/chan-transfer-speed.py b/regtests/go/chan-transfer-speed.py new file mode 100644 index 0000000..9b0c7fb --- /dev/null +++ b/regtests/go/chan-transfer-speed.py @@ -0,0 +1,107 @@ +# based on the go test by Dennis Francis +# https://github.com/dennisfrancis/gopherjs-channeltransfer-speed + + +import "github.com/gopherjs/gopherjs/js" + + +data_chan = go.channel(int) + +document = js.Global.Get("document") + +def main(): + js.Global.Get("window").Set("onload", setup) + +def setup(): + + go( receive() ) + bt = document.Call("getElementById", "startbt") + bt.Set("onclick", runtests) + + +def runtests(): + var bt = document.Call("getElementById", "startbt") + bt.Set("disabled", true) + + #go func() { + # test_calldepth() + # test_localmem() + #}() + + go( test_calldepth() ) + go( test_localmem() ) + + +def test_calldepth(): + + latency = go.make([]float64, 1000) + perf = js.Global.Get("performance") + #for cd := 1; cd <= 1000; cd++ { + for cd in range(1, 1000): + t0 = perf.Call("now").Float() + #for ii:=0; ii<50; ii++ { + for ii in range(50): + send_func(cd, 1, ii) + t1 = perf.Call("now").Float() + latency[cd-1] = (t1 - t0)/50.0 + print("test1 calldepth =", cd) + plot("ctsgraph1", latency, "Call depth", "Variation of Kilo Channel Transfers per second (KCTps) with call depth") + +def test_localmem(): + + latency = go.make([]float64, 1000) + perf = js.Global.Get("performance") + #for varsz := 1; varsz <= 1000; varsz++ { + for varsz in range(1, 1000): + t0 = perf.Call("now").Float() + #for ii:=0; ii<50; ii++ { + for ii in range(50): + send_func(1, varsz, ii) + + t1 = perf.Call("now").Float() + latency[varsz-1] = (t1 - t0)/50.0 + plot("ctsgraph2", latency, "Local variable size", "Variation of Kilo Channel Transfers per second (KCTps) with local variable size") + + +def plot(id:string, latency:[]float64, xlabel:string, title:string ): + + div = document.Call("getElementById", id) + #options = map[string]interface{}{ + options = map[string]interface{ + #"legend" : "always", + "title" : title, + "showRoller" : true, + "rollPeriod" : 1, + "ylabel" : "KCTps", + "labels" : []string("x", "CTS"), + } + + data = go.make([][]float64, len(latency)) + + #for rowid := 0; rowid < len(latency); rowid++ { + for rowid in range(len(latency)): + data[rowid] = []float64( + float64(rowid + 1), + 1.0/latency[rowid] + ) + + + js.Global.Get("Dygraph").New(div, data, options) + + +def send_func(call_depth:int, varsize:int, data:int ): + locvar = go.make([]int, varsize) + + if call_depth <= 1: + data_chan <- data + return + + send_func(call_depth-1, varsize, data) + + print(locvar) + +def receive(): + int data = 0 + while True: + data = <-data_chan + print("Received data =", data) diff --git a/regtests/go/chan.py b/regtests/go/chan.py index 1ddbcf1..fdf6e02 100644 --- a/regtests/go/chan.py +++ b/regtests/go/chan.py @@ -1,17 +1,16 @@ -"""a <- """ +"""send int over channel""" + +def wrapper(a:int, c: chan int): + result = 100 + c <- result def main(): c = go.channel(int) - - def wrapper(a:int, chan c:int): - result = 100 - c <- result - go( - wrapper(17, c) - ) + go( wrapper(17, c) ) # Do other work in the current goroutine until the channel has a result. x = <-c print(x) + TestError(x==100) diff --git a/regtests/go/list_comprehension.py b/regtests/go/list_comprehension.py new file mode 100644 index 0000000..75c7f7a --- /dev/null +++ b/regtests/go/list_comprehension.py @@ -0,0 +1,11 @@ +''' +go list comprehensions +''' + +def main(): + a = []int(x for x in range(3)) + + TestError( len(a)==3 ) + TestError( a[0]==0 ) + TestError( a[1]==1 ) + TestError( a[2]==2 ) diff --git a/regtests/go/select.py b/regtests/go/select.py index f0cb7f0..6bcb8fe 100644 --- a/regtests/go/select.py +++ b/regtests/go/select.py @@ -1,17 +1,43 @@ """go select""" +def send_data( A:chan int, B:chan int, X:int, Y:int): + while True: + print('sending data..') + A <- X + B <- Y + +def select_loop(A:chan int, B:chan int, W:chan int) -> int: + print('starting select loop') + y = 0 + while True: + print('select loop:',y) + select: + case x = <- A: + y += x + W <- y + case x = <- B: + y += x + W <- y + print('end select loop', y) + return y + def main(): a = go.channel(int) b = go.channel(int) + w = go.channel(int) - a <- 1 - b <- 2 - y = 0 + go( + select_loop(a,b, w) + ) + + + go( + send_data(a,b, 5, 10) + ) - select: - case x = <- a: - y += x - case x = <- b: - y += x + z = 0 + while z < 100: + z = <- w + print('main loop', z) - print(y) + print('end test') \ No newline at end of file diff --git a/regtests/go/subclass.py b/regtests/go/subclass.py new file mode 100644 index 0000000..69e87b9 --- /dev/null +++ b/regtests/go/subclass.py @@ -0,0 +1,43 @@ +''' +simple class +''' +class A: + { + x:int, + y:int, + z:int, + } + def __init__(self, x:int, y:int, z:int=1): + self.x = x + self.y = y + self.z = z + + def mymethod(self, m:int) -> int: + return self.x * m + +class B(A): + { + w:string + } + + def method2(self, v:string) ->string: + self.w = v + return self.w + +def call_method( cb:func(int)(int), mx:int ) ->int: + return cb(mx) + +def main(): + a = A( 100, 200, z=9999 ) + print( a.x ) + print( a.y ) + print( a.z ) + + b = a.mymethod(3) + print( b ) + + c = call_method( a.mymethod, 4 ) + print( c ) + + x = B(1,2,z=3) + print( x.method2('hello world') ) \ No newline at end of file diff --git a/regtests/html/date.html b/regtests/html/date.html new file mode 100644 index 0000000..97b3f50 --- /dev/null +++ b/regtests/html/date.html @@ -0,0 +1,18 @@ + + + + + + + + + + \ No newline at end of file diff --git a/regtests/lang/new.py b/regtests/lang/new.py new file mode 100644 index 0000000..b65296b --- /dev/null +++ b/regtests/lang/new.py @@ -0,0 +1,8 @@ +''' +js new keyword +''' + +def main(): + #a = new Date() ## this also works + a = JS(' new Date()') + TestError( a.getFullYear()==2014 ) diff --git a/regtests/run.py b/regtests/run.py index 9a8702a..cf5ff34 100755 --- a/regtests/run.py +++ b/regtests/run.py @@ -194,6 +194,7 @@ def run_old_pypy_test_on(filename): luajs_runnable = os.path.isfile( lua2js ) and '--lua2js' in sys.argv go_runnable = runnable( 'go version') +gopherjs_runnable = runnable( 'gopherjs') assert rhino_runnable or node_runnable @@ -474,7 +475,7 @@ def run_python3_test_on(filename): -def translate_js(filename, javascript=False, dart=False, coffee=False, lua=False, luajs=False, go=False, multioutput=False, requirejs=True): +def translate_js(filename, javascript=False, dart=False, coffee=False, lua=False, luajs=False, go=False, gopherjs=False, multioutput=False, requirejs=True): global tmpname tmpname = os.path.join( tempfile.gettempdir(), @@ -506,7 +507,7 @@ def translate_js(filename, javascript=False, dart=False, coffee=False, lua=False ] content = '\n'.join( source ) - elif go: + elif go or gopherjs: content = patch_python(filename, backend='GO') else: @@ -535,6 +536,8 @@ def translate_js(filename, javascript=False, dart=False, coffee=False, lua=False cmd.append( '--luajs') elif go: cmd.append( '--go' ) + elif gopherjs: + cmd.append( '--gopherjs' ) if not requirejs: cmd.append( '--no-wrapper' ) @@ -799,6 +802,15 @@ def run_go(content): return run_command( '/tmp/regtest-go' ) +def run_pythonjs_gopherjs_test(dummy_filename): + """PythonJS (Gopherjs)""" + return run_if_no_error(run_gopherjs_node) + +def run_gopherjs_node(content): + """Run Gopherjs using Node""" + write("%s.js" % tmpname, content) + return run_command("node %s.js" % tmpname) + def run_html_test( filename, sum_errors ): lines = open(filename, 'rb').read().decode('utf-8').splitlines() filename = os.path.split(filename)[-1] @@ -967,6 +979,9 @@ def display(function): js = translate_js(filename, go=True) display(run_pythonjs_go_test) + if gopherjs_runnable: + js = translate_js(filename, gopherjs=True) + display(run_pythonjs_gopherjs_test) print() return sum_errors