# -*- coding: utf-8 -*- """ Python Builtins --------------- Most builtin functions (that make sense in JS) are automatically translated to JavaScript: isinstance, issubclass, callable, hasattr, getattr, setattr, delattr, print, len, max, min, chr, ord, dict, list, tuple, range, pow, sum, round, int, float, str, bool, abs, divmod, all, any, enumerate, zip, reversed, sorted, filter, map. Further all methods for list, dict and str are implemented (except str methods: encode, decode, format_map, isprintable, maketrans). .. pscript_example:: # "self" is replaced with "this" self.foo # Printing just works print('some test') print(a, b, c, sep='-') # Getting the length of a string or array len(foo) # Rounding and abs round(foo) # round to nearest integer int(foo) # round towards 0 as in Python abs(foo) # min and max min(foo) min(a, b, c) max(foo) max(a, b, c) # divmod a, b = divmod(100, 7) # -> 14, 2 # Aggregation sum(foo) all(foo) any(foo) # Turning things into numbers, bools and strings str(s) float(x) bool(y) int(z) # this rounds towards zero like in Python chr(65) # -> 'A' ord('A') # -> 65 # Turning things into lists and dicts dict([['foo', 1], ['bar', 2]]) # -> {'foo': 1, 'bar': 2} list('abc') # -> ['a', 'b', 'c'] dict(other_dict) # make a copy list(other_list) # make copy The isinstance function (and friends) ------------------------------------- The ``isinstance()`` function works for all JS primitive types, but also for user-defined classes. .. pscript_example:: # Basic types isinstance(3, float) # in JS there are no ints isinstance('', str) isinstance([], list) isinstance({}, dict) isinstance(foo, types.FunctionType) # Can also use JS strings isinstance(3, 'number') isinstance('', 'string') isinstance([], 'array') isinstance({}, 'object') isinstance(foo, 'function') # You can use it on your own types too ... isinstance(x, MyClass) isinstance(x, 'MyClass') # equivalent isinstance(x, 'Object') # also yields true (subclass of Object) # issubclass works too issubclass(Foo, Bar) # As well as callable callable(foo) hasattr, getattr, setattr and delattr ------------------------------------- .. pscript_example:: a = {'foo': 1, 'bar': 2} hasattr(a, 'foo') # -> True hasattr(a, 'fooo') # -> False hasattr(null, 'foo') # -> False getattr(a, 'foo') # -> 1 getattr(a, 'fooo') # -> raise AttributeError getattr(a, 'fooo', 3) # -> 3 getattr(null, 'foo', 3) # -> 3 setattr(a, 'foo', 2) delattr(a, 'foo') Creating sequences ------------------ .. pscript_example:: range(10) range(2, 10, 2) range(100, 0, -1) reversed(foo) sorted(foo) enumerate(foo) zip(foo, bar) filter(func, foo) map(func, foo) List methods ------------ .. pscript_example:: # Call a.append() if it exists, otherwise a.push() a.append(x) # Similar for remove() a.remove(x) Dict methods ------------ .. pscript_example:: a = {'foo': 3} a['foo'] a.get('foo', 0) a.get('foo') a.keys() Str methods ----------- .. pscript_example:: "foobar".startswith('foo') "foobar".replace('foo', 'bar') "foobar".upper() Using JS specific functionality ------------------------------- When writing PScript inside Python modules, we recommend that where specific JavaScript functionality is used, that the references are prefixed with ``window.`` Where ``window`` represents the global JS namespace. All global JavaScript objects, functions, and variables automatically become members of the ``window`` object. This helps make it clear that the functionality is specific to JS, and also helps static code analysis tools like flake8. .. pscript_example:: from pscript import window # this is a stub def foo(a): return window.Math.cos(a) Aside from ``window``, ``pscript`` also provides ``undefined``, ``Inifinity``, and ``NaN``. """ from __future__ import print_function, absolute_import, with_statement, unicode_literals, division from . import commonast as ast from . import stdlib from .parser2 import Parser2, JSError, unify # noqa from .stubs import RawJS # This class has several `function_foo()` and `method_bar()` methods # to implement corresponding functionality. Most of these are # auto-generated from the stdlib. However, some methods need explicit # implementation, e.g. to parse keyword arguments, or are inlined rather # than implemented via the stlib. # # Note that when the number of arguments does not match, almost all # functions raise a compile-time error. The methods, however, will # bypass the stdlib in this case, because it is assumed that the user # intended to call a special method on the object. class Parser3(Parser2): """ Parser to transcompile Python to JS, allowing more Pythonic code, like ``self``, ``print()``, ``len()``, list methods, etc. """ def function_this_is_js(self, node): # Note that we handle this_is_js() shortcuts in the if-statement # directly. This replacement with a string is when this_is_js() # is used outside an if statement. if len(node.arg_nodes) != 0: raise JSError('this_is_js() expects zero arguments.') return ('"this_is_js()"') def function_RawJS(self, node): if len(node.arg_nodes) == 1: if not isinstance(node.arg_nodes[0], ast.Str): raise JSError('RawJS needs a verbatim string (use multiple ' 'args to bypass PScript\'s RawJS).') lines = RawJS._str2lines(node.arg_nodes[0].value.strip()) nl = '\n' + (self._indent * 4) * ' ' return nl.join(lines) else: return None # maybe RawJS is a thing ## Python builtin functions def function_isinstance(self, node): if len(node.arg_nodes) != 2: raise JSError('isinstance() expects two arguments.') ob = unify(self.parse(node.arg_nodes[0])) cls = unify(self.parse(node.arg_nodes[1])) if cls[0] in '"\'': cls = cls[1:-1] # remove quotes BASIC_TYPES = ('number', 'boolean', 'string', 'function', 'array', 'object', 'null', 'undefined') MAP = {'[int, float]': 'number', '[float, int]': 'number', 'float': 'number', 'str': 'string', 'basestring': 'string', 'string_types': 'string', 'bool': 'boolean', 'FunctionType': 'function', 'types.FunctionType': 'function', 'list': 'array', 'tuple': 'array', '[list, tuple]': 'array', '[tuple, list]': 'array', 'dict': 'object', } cmp = MAP.get(cls, cls) if cmp == 'array': return ['Array.isArray(', ob, ')'] elif cmp.lower() in BASIC_TYPES: # Basic type, use Object.prototype.toString return ["Object.prototype.toString.call(", ob , ").slice(8,-1).toLowerCase() === '%s'" % cmp.lower()] # In http://stackoverflow.com/questions/11108877 the following is # proposed, which might be better in theory, but is > 50% slower return ["({}).toString.call(", ob, r").match(/\s([a-zA-Z]+)/)[1].toLowerCase() === ", "'%s'" % cmp.lower() ] else: # User defined type, use instanceof # http://tobyho.com/2011/01/28/checking-types-in-javascript/ cmp = unify(cls) if cmp[0] == '(': raise JSError('isinstance() can only compare to simple types') return ob, " instanceof ", cmp def function_issubclass(self, node): # issubclass only needs to work on custom classes if len(node.arg_nodes) != 2: raise JSError('issubclass() expects two arguments.') cls1 = unify(self.parse(node.arg_nodes[0])) cls2 = unify(self.parse(node.arg_nodes[1])) if cls2 == 'object': cls2 = 'Object' return '(%s.prototype instanceof %s)' % (cls1, cls2) def function_print(self, node): # Process keywords sep, end = '" "', '' for kw in node.kwarg_nodes: if kw.name == 'sep': sep = ''.join(self.parse(kw.value_node)) elif kw.name == 'end': end = ''.join(self.parse(kw.value_node)) elif kw.name in ('file', 'flush'): raise JSError('print() file and flush args not supported') else: raise JSError('Invalid argument for print(): %r' % kw.name) # Combine args args = [unify(self.parse(arg)) for arg in node.arg_nodes] end = (" + %s" % end) if (args and end and end != '\n') else '' combiner = ' + %s + ' % sep args_concat = combiner.join(args) or '""' return 'console.log(' + args_concat + end + ')' def function_len(self, node): if len(node.arg_nodes) == 1: return unify(self.parse(node.arg_nodes[0])), '.length' else: return None # don't apply this feature def function_max(self, node): if len(node.arg_nodes) == 0: raise JSError('max() needs at least one argument') elif len(node.arg_nodes) == 1: arg = ''.join(self.parse(node.arg_nodes[0])) return 'Math.max.apply(null, ', arg, ')' else: args = ', '.join([unify(self.parse(arg)) for arg in node.arg_nodes]) return 'Math.max(', args, ')' def function_min(self, node): if len(node.arg_nodes) == 0: raise JSError('min() needs at least one argument') elif len(node.arg_nodes) == 1: arg = ''.join(self.parse(node.arg_nodes[0])) return 'Math.min.apply(null, ', arg, ')' else: args = ', '.join([unify(self.parse(arg)) for arg in node.arg_nodes]) return 'Math.min(', args, ')' def function_callable(self, node): if len(node.arg_nodes) == 1: arg = unify(self.parse(node.arg_nodes[0])) return '(typeof %s === "function")' % arg else: raise JSError('callable() needs at least one argument') def function_chr(self, node): if len(node.arg_nodes) == 1: arg = ''.join(self.parse(node.arg_nodes[0])) return 'String.fromCharCode(%s)' % arg else: raise JSError('chr() needs at least one argument') def function_ord(self, node): if len(node.arg_nodes) == 1: arg = ''.join(self.parse(node.arg_nodes[0])) return '%s.charCodeAt(0)' % arg else: raise JSError('ord() needs at least one argument') def function_dict(self, node): if len(node.arg_nodes) == 0: kwargs = ['%s:%s' % (arg.name, unify(self.parse(arg.value_node))) for arg in node.kwarg_nodes] return '{%s}' % ', '.join(kwargs) if len(node.arg_nodes) == 1: return self.use_std_function('dict', node.arg_nodes) else: raise JSError('dict() needs at least one argument') def function_list(self, node): if len(node.arg_nodes) == 0: return '[]' if len(node.arg_nodes) == 1: return self.use_std_function('list', node.arg_nodes) else: raise JSError('list() needs at least one argument') def function_tuple(self, node): return self.function_list(node) def function_range(self, node): if len(node.arg_nodes) == 1: args = ast.Num(0), node.arg_nodes[0], ast.Num(1) return self.use_std_function('range', args) elif len(node.arg_nodes) == 2: args = node.arg_nodes[0], node.arg_nodes[1], ast.Num(1) return self.use_std_function('range', args) elif len(node.arg_nodes) == 3: return self.use_std_function('range', node.arg_nodes) else: raise JSError('range() needs 1, 2 or 3 arguments') def function_sorted(self, node): if len(node.arg_nodes) == 1: key, reverse = ast.Name('undefined'), ast.NameConstant(False) for kw in node.kwarg_nodes: if kw.name == 'key': key = kw.value_node elif kw.name == 'reverse': reverse = kw.value_node else: raise JSError('Invalid keyword argument for sorted: %r' % kw.name) return self.use_std_function('sorted', [node.arg_nodes[0], key, reverse]) else: raise JSError('sorted() needs one argument') ## Methods of list/dict/str def method_sort(self, node, base): if len(node.arg_nodes) == 0: # sorts args are keyword-only key, reverse = ast.Name('undefined'), ast.NameConstant(False) for kw in node.kwarg_nodes: if kw.name == 'key': key = kw.value_node elif kw.name == 'reverse': reverse = kw.value_node else: raise JSError('Invalid keyword argument for sort: %r' % kw.name) return self.use_std_method(base, 'sort', [key, reverse]) def method_format(self, node, base): if node.kwarg_nodes: raise JSError('Method format() does not support keyword args.') return self.use_std_method(base, 'format', node.arg_nodes) # Add functions and methods to the class, using the stdib functions ... def make_function(name, nargs, function_deps, method_deps): def function_X(self, node): if node.kwarg_nodes: raise JSError('Function %s does not support keyword args.' % name) if len(node.arg_nodes) not in nargs: raise JSError('Function %s needs #args in %r.' % (name, nargs)) for dep in function_deps: self.use_std_function(dep, []) for dep in method_deps: self.use_std_method('x', dep, []) return self.use_std_function(name, node.arg_nodes) return function_X def make_method(name, nargs, function_deps, method_deps): def method_X(self, node, base): if node.kwarg_nodes: raise JSError('Method %s does not support keyword args.' % name) if len(node.arg_nodes) not in nargs: return None # call as-is, don't use our variant for dep in function_deps: self.use_std_function(dep, []) for dep in method_deps: self.use_std_method('x', dep, []) return self.use_std_method(base, name, node.arg_nodes) return method_X for name, code in stdlib.METHODS.items(): nargs, function_deps, method_deps = stdlib.get_std_info(code) if nargs and not hasattr(Parser3, 'method_' + name): m = make_method(name, tuple(nargs), function_deps, method_deps) setattr(Parser3, 'method_' + name, m) for name, code in stdlib.FUNCTIONS.items(): nargs, function_deps, method_deps = stdlib.get_std_info(code) if nargs and not hasattr(Parser3, 'function_' + name): m = make_function(name, tuple(nargs), function_deps, method_deps) setattr(Parser3, 'function_' + name, m)