You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
879 lines
30 KiB
879 lines
30 KiB
# -*- coding: utf-8 -*- |
|
""" |
|
|
|
The basics |
|
---------- |
|
|
|
Most types just work, common Python names are converted to their JavaScript |
|
equivalents. |
|
|
|
.. pscript_example:: |
|
|
|
# Simple operations |
|
3 + 4 -1 |
|
3 * 7 / 9 |
|
5**2 |
|
pow(5, 2) |
|
7 // 2 |
|
|
|
# Basic types |
|
[True, False, None] |
|
|
|
# Lists and dicts |
|
foo = [1, 2, 3] |
|
bar = {'a': 1, 'b': 2} |
|
|
|
|
|
Slicing and subscriping |
|
----------------------- |
|
|
|
.. pscript_example:: |
|
|
|
# Slicing lists |
|
foo = [1, 2, 3, 4, 5] |
|
foo[2:] |
|
foo[2:-2] |
|
|
|
# Slicing strings |
|
bar = 'abcdefghij' |
|
bar[2:] |
|
bar[2:-2] |
|
|
|
# Subscripting |
|
foo = {'bar': 3} |
|
foo['bar'] |
|
foo.bar # Works in JS, but not in Python |
|
|
|
|
|
String formatting |
|
----------------- |
|
|
|
String formatting is supported in various forms. |
|
|
|
.. pscript_example:: |
|
|
|
# Old school |
|
"value: %g" % val |
|
"%s: %0.2f" % (name, val) |
|
|
|
# Modern |
|
"value: {:g}".format(val) |
|
"{}: {:3.2f}".format(name, val) |
|
|
|
# F-strings (python 3.6+) |
|
#f"value: {val:g}" |
|
#f"{name}: {val:3.2f}" |
|
|
|
# This also works |
|
t = "value: {:g}" |
|
t.format(val) |
|
|
|
# But this does not (because PScript cannot know whether t is str or float) |
|
t = "value: %g" |
|
t % val |
|
|
|
Kinds of formatting that is supported: |
|
|
|
* Float, exponential en "general" number formatting. |
|
* Specifying precision for numbers. |
|
* Padding of number with "+" or " ". |
|
* Repr-formatting. |
|
|
|
At the moment, PScript does not support advanced features such as string |
|
padding. |
|
|
|
|
|
Assignments |
|
----------- |
|
|
|
Declaration of variables is handled automatically. Also support for |
|
tuple packing and unpacking (a.k.a. destructuring assignment). |
|
|
|
.. pscript_example:: |
|
|
|
# Declare foo |
|
foo = 3 |
|
|
|
# But not here |
|
bar.foo = 3 |
|
|
|
# Pack items in an array |
|
a = 1, 2, 3 |
|
|
|
# And unpack them |
|
a1, a2, a3 = a |
|
|
|
# Deleting variables |
|
del bar.foo |
|
|
|
# Functions starting with a capital letter |
|
# are assumed constructors |
|
foo = Foo() |
|
|
|
|
|
Comparisons |
|
----------- |
|
|
|
.. pscript_example:: |
|
|
|
# Identity |
|
foo is bar |
|
|
|
# Equality |
|
foo == bar |
|
|
|
# But comparisons are deep (unlike JS) |
|
(2, 3, 4) == (2, 3, 4) |
|
(2, 3) in [(1,2), (2,3), (3,4)] |
|
|
|
# Test for null |
|
foo is None |
|
|
|
# Test for JS undefined |
|
foo is undefined |
|
|
|
# Testing for containment |
|
"foo" in "this has foo in it" |
|
3 in [0, 1, 2, 3, 4] |
|
|
|
|
|
Truthy and Falsy |
|
---------------- |
|
|
|
In JavaScript, an empty array and an empty dict are interpreted as |
|
truthy. PScript fixes this, so that you can do ``if an_array:`` as |
|
usual. |
|
|
|
.. pscript_example:: |
|
|
|
# These evaluate to False: |
|
0 |
|
NaN |
|
"" # empty string |
|
None # JS null |
|
undefined |
|
[] |
|
{} |
|
|
|
# This still works |
|
a = [] |
|
a = a or [1] # a is now [1] |
|
|
|
|
|
Function calls |
|
-------------- |
|
|
|
As in Python, the default return value of a function is ``None`` (i.e. |
|
``null`` in JS). |
|
|
|
.. pscript_example:: |
|
|
|
# Business as usual |
|
foo(a, b) |
|
|
|
# Support for star args (but not **kwargs) |
|
foo(*a) |
|
|
|
Imports |
|
------- |
|
|
|
Imports are not supported syntax in PScript. Imports "from pscript" |
|
and "from __future__" are ignored to help writing hybrid Python/JS |
|
modules. |
|
|
|
PScript does provide functionality to package code in JS modules, |
|
but these follow the ``require`` pattern. |
|
|
|
""" |
|
|
|
from __future__ import print_function, absolute_import, with_statement, unicode_literals, division |
|
|
|
import re |
|
|
|
from . import commonast as ast |
|
from . import stdlib |
|
from .parser0 import Parser0, JSError, unify, reprs # noqa |
|
|
|
|
|
# Define builtin stuff for which we know that it returns a bool or int |
|
_bool_funcs = 'hasattr', 'all', 'any', 'op_contains', 'op_equals', 'truthy' |
|
_bool_meths = ('count', 'isalnum', 'isalpha', 'isidentifier', 'islower', |
|
'isnumeric', 'isdigit', 'isdecimal', 'isspace', 'istitle', |
|
'isupper', 'startswith') |
|
returning_bool = tuple([stdlib.FUNCTION_PREFIX + x + '(' for x in _bool_funcs] + |
|
[stdlib.METHOD_PREFIX + x + '.' for x in _bool_meths]) |
|
|
|
|
|
# precompile regexp to help determine whether a string is an identifier |
|
isidentifier1 = re.compile(r'^\w+$', re.UNICODE) |
|
|
|
reserved_names = ( |
|
'abstract', 'instanceof', 'boolean', 'enum', 'switch', 'export', |
|
'interface', 'synchronized', 'extends', 'let', 'case', 'throw', |
|
'catch', 'final', 'native', 'throws', 'new', 'transient', |
|
'const', 'package', 'function', 'private', 'typeof', 'debugger', 'goto', |
|
'protected', 'var', 'default', 'public', 'void', 'delete', 'implements', |
|
'volatile', 'do', 'static', |
|
# Commented, because are disallowed in Python too. |
|
# 'else', 'break', 'finally', 'class', 'for', 'try', 'continue', 'if', |
|
# 'return', 'import', 'while', 'in', 'with', |
|
# Commented for pragmatic reasons |
|
# 'super', 'float', 'this', 'int', 'byte', 'long', 'char', 'short', |
|
# 'double', 'null', 'true', 'false', |
|
) |
|
|
|
|
|
class Parser1(Parser0): |
|
""" Parser that add basic functionality like assignments, |
|
operations, function calls, and indexing. |
|
""" |
|
|
|
@property |
|
def _pscript_overload(self): |
|
""" Whether pscript overloads add, mul, equals, and truthy. |
|
This setting applies per scope. |
|
""" |
|
return self._stack[-1][2]._pscript_overload |
|
|
|
## Literals |
|
|
|
def parse_Num(self, node): |
|
return repr(node.value) |
|
|
|
def parse_Str(self, node): |
|
return reprs(node.value) |
|
|
|
def parse_JoinedStr(self, node): |
|
parts, value_nodes = [], [] |
|
for n in node.value_nodes: |
|
if isinstance(n, ast.Str): |
|
parts.append(n.value) |
|
else: |
|
assert isinstance(n, ast.FormattedValue) |
|
parts.append('{' + self._parse_FormattedValue_fmt(n) + '}') |
|
value_nodes.append(n.value_node) |
|
thestring = reprs(''.join(parts)) |
|
return self.use_std_method(thestring, 'format', value_nodes) |
|
|
|
def parse_FormattedValue(self, node): # can als be present standalone |
|
thestring = "{" + self._parse_FormattedValue_fmt(node) + "}" |
|
return self.use_std_method(thestring, 'format', [node.value_node]) |
|
|
|
def _parse_FormattedValue_fmt(self, node): |
|
""" Return fmt for a FormattedValue node. |
|
""" |
|
fmt = '' |
|
if node.conversion: |
|
fmt += '!' + node.conversion |
|
if node.format_node and len(node.format_node.value_nodes) > 0: |
|
if len(node.format_node.value_nodes) > 1: |
|
raise JSError('String formatting only supports singleton format spec.') |
|
spec_node = node.format_node.value_nodes[0] |
|
if not isinstance(spec_node, ast.Str): |
|
raise JSError('String formatting only supports string format spec.') |
|
fmt += ':' + spec_node.value |
|
return fmt |
|
|
|
def parse_Bytes(self, node): |
|
raise JSError('No Bytes in JS') |
|
|
|
def parse_NameConstant(self, node): |
|
M = {True: 'true', False: 'false', None: 'null'} |
|
return M[node.value] |
|
|
|
def parse_List(self, node): |
|
code = ['['] |
|
for child in node.element_nodes: |
|
code += self.parse(child) |
|
code.append(', ') |
|
if node.element_nodes: |
|
code.pop(-1) # skip last comma |
|
code.append(']') |
|
return code |
|
|
|
def parse_Tuple(self, node): |
|
return self.parse_List(node) # tuple = ~ list in JS |
|
|
|
def parse_Dict(self, node): |
|
# Oh JS; without the outer braces, it would only be an Object if used |
|
# in an assignment ... |
|
use_make_dict_func = False |
|
code = ['({'] |
|
for key, val in zip(node.key_nodes, node.value_nodes): |
|
if isinstance(key, (ast.Num, ast.NameConstant)): |
|
code += self.parse(key) |
|
elif (isinstance(key, ast.Str) and isidentifier1.match(key.value) and |
|
key.value[0] not in '0123456789'): |
|
code += key.value |
|
else: |
|
use_make_dict_func = True |
|
break |
|
code.append(': ') |
|
code += self.parse(val) |
|
code.append(', ') |
|
if node.key_nodes: |
|
code.pop(-1) # skip last comma |
|
code.append('})') |
|
|
|
# Do we need to use the fallback? |
|
if use_make_dict_func: |
|
func_args = [] |
|
for key, val in zip(node.key_nodes, node.value_nodes): |
|
func_args += [unify(self.parse(key)), unify(self.parse(val))] |
|
self.use_std_function('create_dict', []) |
|
return stdlib.FUNCTION_PREFIX + 'create_dict(' + ', '.join(func_args) + ')' |
|
return code |
|
|
|
def parse_Set(self, node): |
|
raise JSError('No Set in JS') |
|
|
|
## Variables |
|
|
|
def push_scope_prefix(self, prefix): |
|
# To avoid name clashes e.g. in comprehensions, which have their own |
|
# scope in Python, but we want to apply these as a for loop in JS |
|
# where possible. |
|
assert prefix |
|
self._scope_prefix.append(prefix) |
|
|
|
def pop_scope_prefix(self): |
|
self._scope_prefix.pop(-1) |
|
|
|
def parse_Name(self, node, fullname=None): |
|
# node.ctx can be Load, Store, Del -> can be of use somewhere? |
|
name = node.name |
|
if name in reserved_names: |
|
raise JSError('Cannot use reserved name %s as a variable name!' % name) |
|
if self.vars.is_known(name): |
|
return self.with_prefix(name) |
|
if self._scope_prefix: |
|
for stackitem in reversed(self._stack): |
|
scope = stackitem[2] |
|
for prefix in reversed(self._scope_prefix): |
|
prefixed_name = prefix + name |
|
if prefixed_name in scope: |
|
return prefixed_name |
|
if name in self.NAME_MAP: |
|
return self.NAME_MAP[name] |
|
# Else ... |
|
if not (name in self._functions or name in ('undefined', 'window')): |
|
# mark as used (not defined) |
|
used_name = (name + '.' + fullname) if fullname else name |
|
self.vars.use(name, used_name) |
|
return name |
|
|
|
def parse_Starred(self, node): |
|
# they're present in Call arguments, but we parse them there. |
|
raise JSError('Starred args are not supported.') |
|
|
|
## Expressions |
|
|
|
def parse_Expr(self, node): |
|
# Expression (not stored in a variable) |
|
code = [self.lf()] |
|
code += self.parse(node.value_node) |
|
code.append(';') |
|
return code |
|
|
|
def parse_UnaryOp(self, node): |
|
if node.op == node.OPS.Not: |
|
return '!', self._wrap_truthy(node.right_node) |
|
else: |
|
op = self.UNARY_OP[node.op] |
|
right = unify(self.parse(node.right_node)) |
|
return op, right |
|
|
|
def parse_BinOp(self, node): |
|
if node.op == node.OPS.Mod and isinstance(node.left_node, ast.Str): |
|
# Modulo on a string is string formatting in Python |
|
return self._format_string(node) |
|
|
|
left = unify(self.parse(node.left_node)) |
|
right = unify(self.parse(node.right_node)) |
|
|
|
if node.op == node.OPS.Add: |
|
C = ast.Num, ast.Str |
|
if self._pscript_overload and not ( |
|
isinstance(node.left_node, C) or |
|
isinstance(node.right_node, C) or |
|
(isinstance(node.left_node, ast.BinOp) and |
|
node.left_node.op == node.OPS.Add and "op_add" not in left) or |
|
(isinstance(node.right_node, ast.BinOp) and |
|
node.right_node.op == node.OPS.Add and "op_add" not in right)): |
|
return self.use_std_function('op_add', [left, right]) |
|
elif node.op == node.OPS.Mult: |
|
C = ast.Num |
|
if self._pscript_overload and not ( |
|
isinstance(node.left_node, C) and |
|
isinstance(node.right_node, C)): |
|
return self.use_std_function('op_mult', [left, right]) |
|
elif node.op == node.OPS.Pow: |
|
return ["Math.pow(", left, ", ", right, ")"] |
|
elif node.op == node.OPS.FloorDiv: |
|
return ["Math.floor(", left, "/", right, ")"] |
|
|
|
op = ' %s ' % self.BINARY_OP[node.op] |
|
return [left, op, right] |
|
|
|
def _format_string(self, node): |
|
# Get value_nodes |
|
if isinstance(node.right_node, (ast.Tuple, ast.List)): |
|
value_nodes = node.right_node.element_nodes |
|
else: |
|
value_nodes = [node.right_node] |
|
|
|
# Is the left side a string? If not, exit early |
|
# This works, but we cannot know whether the left was a string or number :P |
|
# if not isinstance(node.left_node, ast.Str): |
|
# thestring = unify(self.parse(node.left_node)) |
|
# thestring += ".replace(/%([0-9\.\+\-\#]*[srdeEfgGioxXc])/g, '{:$1}')" |
|
# return self.use_std_method(thestring, 'format', value_nodes) |
|
|
|
assert isinstance(node.left_node, ast.Str) |
|
left = ''.join(self.parse(node.left_node)) |
|
sep, left = left[0], left[1:-1] |
|
|
|
# Get matches |
|
matches = list(re.finditer(r'%[0-9\.\+\-\#]*[srdeEfgGioxXc]', left)) |
|
if len(matches) != len(value_nodes): |
|
raise JSError('In string formatting, number of placeholders ' |
|
'does not match number of replacements') |
|
# Format |
|
parts = [] |
|
start = 0 |
|
for m in matches: |
|
fmt = m.group(0) |
|
fmt = {'%r': '!r', '%s': ''}.get(fmt, ':' + fmt[1:]) |
|
# Add the part in front of the match (and after prev match) |
|
parts.append(left[start:m.start()]) |
|
parts.append("{%s}" % fmt) |
|
start = m.end() |
|
parts.append(left[start:]) |
|
thestring = sep + ''.join(parts) + sep |
|
return self.use_std_method(thestring, 'format', value_nodes) |
|
|
|
def _wrap_truthy(self, node): |
|
""" Wraps an operation in a truthy call, unless its not necessary. """ |
|
eq_name = stdlib.FUNCTION_PREFIX + 'op_equals' |
|
test = ''.join(self.parse(node)) |
|
if not self._pscript_overload: |
|
return unify(test) |
|
elif ( |
|
test.endswith('.length') or test.startswith('!') or |
|
test.isnumeric() or test == 'true' or test == 'false' or |
|
test.count('==') or test.count('>') or test.count('<') or |
|
test.count(eq_name) or |
|
test == '"this_is_js()"' or test.startswith('Array.isArray(') or |
|
(test.startswith(returning_bool) and '||' not in test) |
|
): |
|
return unify(test) |
|
else: |
|
return self.use_std_function('truthy', [test]) |
|
|
|
def parse_BoolOp(self, node): |
|
op = ' %s ' % self.BOOL_OP[node.op] |
|
if node.op.lower() == 'or': # allow foo = bar or [] |
|
values = [unify(self._wrap_truthy(val)) for val in node.value_nodes[:-1]] |
|
values += [unify(self.parse(node.value_nodes[-1]))] |
|
else: |
|
values = [unify(self._wrap_truthy(val)) for val in node.value_nodes] |
|
return op.join(values) |
|
|
|
def parse_Compare(self, node): |
|
|
|
left = unify(self.parse(node.left_node)) |
|
right = unify(self.parse(node.right_node)) |
|
|
|
if node.op in (node.COMP.Eq, node.COMP.NotEq) and not left.endswith('.length'): |
|
if self._pscript_overload: |
|
code = self.use_std_function('op_equals', [left, right]) |
|
if node.op == node.COMP.NotEq: |
|
code = '!' + code |
|
else: |
|
if node.op == node.COMP.NotEq: |
|
code = [left, "!=", right] |
|
else: |
|
code = [left, "==", right] |
|
return code |
|
elif node.op in (node.COMP.In, node.COMP.NotIn): |
|
self.use_std_function('op_equals', []) # trigger use of equals |
|
code = self.use_std_function('op_contains', [left, right]) |
|
if node.op == node.COMP.NotIn: |
|
code = '!' + code |
|
return code |
|
else: |
|
op = self.COMP_OP[node.op] |
|
return "%s %s %s" % (left, op, right) |
|
|
|
def parse_Call(self, node): |
|
|
|
# Get full function name and method name if it exists |
|
|
|
if isinstance(node.func_node, ast.Attribute): |
|
# We dont want to parse twice, because it may add to the vars_unknown |
|
method_name = node.func_node.attr |
|
nameparts = self.parse(node.func_node) |
|
full_name = unify(nameparts) |
|
nameparts[-1] = nameparts[-1].rsplit('.', 1)[0] |
|
base_name = unify(nameparts) |
|
elif isinstance(node.func_node, ast.Subscript): |
|
base_name = unify(self.parse(node.func_node.value_node)) |
|
full_name = unify(self.parse(node.func_node)) |
|
method_name = '' |
|
else: # ast.Name |
|
method_name = '' |
|
base_name = '' |
|
full_name = unify(self.parse(node.func_node)) |
|
|
|
# Handle special functions and methods |
|
res = None |
|
if method_name in self._methods: |
|
res = self._methods[method_name](node, base_name) |
|
elif full_name in self._functions: |
|
res = self._functions[full_name](node) |
|
if res is not None: |
|
return res |
|
|
|
# Handle normally |
|
if base_name.endswith('._base_class') or base_name == 'super()': |
|
# super() was used, use "call" to pass "this" |
|
return [full_name] + self._get_args(node, 'this', True) |
|
else: |
|
code = [full_name] + self._get_args(node, base_name) |
|
# Insert "new" if this looks like a class |
|
if base_name == 'this': |
|
pass |
|
elif method_name: |
|
if method_name[0].lower() != method_name[0]: |
|
code.insert(0, 'new ') |
|
else: |
|
fn = full_name |
|
if fn in self._seen_func_names and fn not in self._seen_class_names: |
|
pass |
|
elif fn not in self._seen_func_names and fn in self._seen_class_names: |
|
code.insert(0, 'new ') |
|
elif full_name[0].lower() != full_name[0]: |
|
code.insert(0, 'new ') |
|
return code |
|
|
|
def _get_args(self, node, base_name, use_call_or_apply=False): |
|
""" Get arguments for function call. Does checking for keywords and |
|
handles starargs. The first element in the returned list is either |
|
"(" or ".apply(". |
|
""" |
|
|
|
# Can produce: |
|
# normal: foo(.., ..) |
|
# use_call_or_apply: foo.call(base_name, .., ..) |
|
# use_starargs: foo.apply(base_name, vararg_name) |
|
# or: foo.apply(base_name, [].concat([.., ..], vararg_name) |
|
# has_kwargs: foo({__args: [], __kwargs: {} }) |
|
# or: foo.apply(base_name, ({__args: [], __kwargs: {} }) |
|
|
|
base_name = base_name or 'null' |
|
|
|
# Get arguments |
|
args_simple, args_array = self._get_positional_args(node) |
|
kwargs = self._get_keyword_args(node) |
|
|
|
if kwargs is not None: |
|
# Keyword arguments need a whole special treatment |
|
if use_call_or_apply: |
|
start = ['.call(', base_name, ', '] |
|
else: |
|
start = ['('] |
|
return start + ['{', 'flx_args: ', args_array, |
|
', flx_kwargs: ', kwargs, '})'] |
|
elif args_simple is None: |
|
# Need to use apply |
|
return [".apply(", base_name, ', ', args_array, ")"] |
|
elif use_call_or_apply: |
|
# Need to use call (arg_simple can be empty string) |
|
if args_simple: |
|
return [".call(", base_name, ', ', args_simple, ")"] |
|
else: |
|
return [".call(", base_name, ")"] |
|
else: |
|
# Normal function call |
|
return ["(", args_simple, ")"] |
|
|
|
def _get_positional_args(self, node): |
|
""" Returns: |
|
* a string args_simple, which represents the positional args in comma |
|
separated form. Can be None if the args cannot be represented that |
|
way. Note that it can be empty string. |
|
* a string args_array representing the array with positional arguments. |
|
""" |
|
|
|
# Generate list of arg lists (has normal positional args and starargs) |
|
# Note that there can be multiple starargs and these can alternate. |
|
argswithcommas = [] |
|
arglists = [argswithcommas] |
|
for arg in node.arg_nodes: |
|
if isinstance(arg, ast.Starred): |
|
starname = ''.join(self.parse(arg.value_node)) |
|
arglists.append(starname) |
|
argswithcommas = [] |
|
arglists.append(argswithcommas) |
|
else: |
|
argswithcommas.extend(self.parse(arg)) |
|
argswithcommas.append(', ') |
|
|
|
# Clear empty lists and trailing commas |
|
for i in reversed(xrange(len(arglists))): |
|
arglist = arglists[i] |
|
if not arglist: |
|
arglists.pop(i) |
|
elif arglist[-1] == ', ': |
|
arglist.pop(-1) |
|
|
|
# Generate code for positional arguments |
|
if len(arglists) == 0: |
|
return '', '[]' |
|
elif len(arglists) == 1 and isinstance(arglists[0], list): |
|
args_simple = ''.join(argswithcommas) |
|
return args_simple, '[' + args_simple + ']' |
|
elif len(arglists) == 1: |
|
assert isinstance(arglists[0], basestring) |
|
return None, arglists[0] |
|
else: |
|
code = ['[].concat('] |
|
for arglist in arglists: |
|
if isinstance(arglist, list): |
|
code += ['['] |
|
code += arglist |
|
code += [']'] |
|
else: |
|
code += [arglist] |
|
code += [', '] |
|
code.pop(-1) |
|
code += ')' |
|
return None, ''.join(code) |
|
|
|
def _get_keyword_args(self, node): |
|
""" Get a string that represents the dictionary of keyword arguments, |
|
or None if there are no keyword arguments (normal nor double-star). |
|
""" |
|
|
|
# Collect elements that will make up the total kwarg dict |
|
kwargs = [] |
|
for kwnode in node.kwarg_nodes: |
|
if not kwnode.name: # **xx |
|
kwargs.append(unify(self.parse(kwnode.value_node))) |
|
else: # foo=xx |
|
if not (kwargs and isinstance(kwargs[-1], list)): |
|
kwargs.append([]) |
|
kwargs[-1].append('%s: %s' % (kwnode.name, |
|
unify(self.parse(kwnode.value_node)))) |
|
|
|
# Resolve sequneces of loose kwargs |
|
for i in xrange(len(kwargs)): |
|
if isinstance(kwargs[i], list): |
|
kwargs[i] = '{' + ', '.join(kwargs[i]) + '}' |
|
|
|
# Compose, easy if singleton, otherwise we need to merge |
|
if len(kwargs) == 0: |
|
return None |
|
elif len(kwargs) == 1: |
|
return kwargs[0] |
|
else: |
|
# register use of merge_dicts(), but we build the string ourselves |
|
self.use_std_function('merge_dicts', []) |
|
return stdlib.FUNCTION_PREFIX + 'merge_dicts(' + ', '.join(kwargs) + ')' |
|
|
|
def parse_Attribute(self, node, fullname=None): |
|
fullname = node.attr + '.' + fullname if fullname else node.attr |
|
if isinstance(node.value_node, ast.Name): |
|
base_name = self.parse_Name(node.value_node, fullname) |
|
elif isinstance(node.value_node, ast.Attribute): |
|
base_name = self.parse_Attribute(node.value_node, fullname) |
|
else: |
|
base_name = unify(self.parse(node.value_node)) |
|
attr = node.attr |
|
# Double underscore name mangling |
|
if attr.startswith('__') and not attr.endswith('__') and base_name == 'this': |
|
for i in xrange(len(self._stack)-1, -1, -1): |
|
if self._stack[i][0] == 'class': |
|
classname = self._stack[i][1] |
|
attr = '_' + classname + attr |
|
break |
|
if attr in self.ATTRIBUTE_MAP: |
|
return self.ATTRIBUTE_MAP[attr].replace('{}', base_name) |
|
else: |
|
return "%s.%s" % (base_name, attr) |
|
|
|
## Statements |
|
|
|
def parse_Assign(self, node): |
|
""" Variable assignment. """ |
|
code = [self.lf()] |
|
|
|
# Set PScript behavior? Note that its reset on a function exit. |
|
if ( |
|
len(node.target_nodes) == 1 and |
|
isinstance(node.target_nodes[0], ast.Name) and |
|
node.target_nodes[0].name == 'PSCRIPT_OVERLOAD' |
|
): |
|
if self._stack[-1][0] != "function": |
|
raise JSError("Can only set PSCRIPT_OVERLOAD inside a function") |
|
if not isinstance(node.value_node, ast.NameConstant): |
|
raise JSError("Can only set PSCRIPT_OVERLOAD with a bool") |
|
else: |
|
self._stack[-1][2]._pscript_overload = bool(node.value_node.value) |
|
return [] |
|
|
|
# Parse targets |
|
tuple = [] |
|
for target in node.target_nodes: |
|
var = ''.join(self.parse(target)) |
|
if isinstance(target, ast.Name): |
|
if '.' in var: |
|
code.append(var) |
|
else: |
|
self.vars.add(var) |
|
code.append(self.with_prefix(var)) |
|
elif isinstance(target, ast.Attribute): |
|
code.append(var) |
|
elif isinstance(target, ast.Subscript): |
|
code.append(var) |
|
elif isinstance(target, (ast.Tuple, ast.List)): |
|
dummy = self.dummy() |
|
code.append(dummy) |
|
tuple = target.element_nodes |
|
else: |
|
raise JSError("Unsupported assignment type") |
|
code.append(' = ') |
|
|
|
# Parse right side |
|
if isinstance(node.value_node, ast.ListComp) and len(node.target_nodes) == 1: |
|
result_name = self.dummy() |
|
code.append(result_name + ';') |
|
lc_code = self.parse_ListComp_funtionless(node.value_node, result_name) |
|
code = [self.lf(), result_name + ' = [];'] + lc_code + code |
|
else: |
|
code += self.parse(node.value_node) |
|
code.append(';') |
|
|
|
# Handle tuple unpacking |
|
if tuple: |
|
code.append(self.lf()) |
|
for i, x in enumerate(tuple): |
|
var = unify(self.parse(x)) |
|
if isinstance(x, ast.Name): # but not when attr or index |
|
self.vars.add(var) |
|
code.append('%s = %s[%i];' % (var, dummy, i)) |
|
|
|
return code |
|
|
|
def parse_AugAssign(self, node): # -> x += 1 |
|
target = ''.join(self.parse(node.target_node)) |
|
value = ''.join(self.parse(node.value_node)) |
|
|
|
nl = self.lf() |
|
if ( |
|
node.op == node.OPS.Add and |
|
self._pscript_overload and |
|
not isinstance(node.value_node, (ast.Num, ast.Str)) |
|
): |
|
return [nl, target, ' = ', |
|
self.use_std_function('op_add', [target, value]), ';'] |
|
elif node.op == node.OPS.Mult and self._pscript_overload: |
|
return [nl, target, ' = ', |
|
self.use_std_function('op_mult', [target, value]), ';'] |
|
elif node.op == node.OPS.Pow: |
|
return [nl, target, " = Math.pow(", target, ", ", value, ");"] |
|
elif node.op == node.OPS.FloorDiv: |
|
return [nl, target, " = Math.floor(", target, "/", value, ");"] |
|
else: |
|
op = ' %s= ' % self.BINARY_OP[node.op] |
|
return [nl, target, op, value, ';'] |
|
|
|
def parse_Delete(self, node): |
|
code = [] |
|
for target in node.target_nodes: |
|
code.append(self.lf('delete ')) |
|
code += self.parse(target) |
|
code.append(';') |
|
return code |
|
|
|
def parse_Pass(self, node): |
|
return [] |
|
|
|
## Subscripting |
|
|
|
def parse_Subscript(self, node): |
|
|
|
value_list = self.parse(node.value_node) |
|
slice_list = self.parse(node.slice_node) |
|
|
|
code = [] |
|
code += value_list |
|
|
|
if isinstance(node.slice_node, (ast.Slice, ast.Tuple)): |
|
code.append('.slice(') |
|
code += slice_list |
|
code.append(')') |
|
else: |
|
code.append('[') |
|
if slice_list[0].startswith('-'): |
|
code.append(unify(value_list) + '.length ') |
|
code += slice_list |
|
code.append(']') |
|
return code |
|
|
|
def parse_Index(self, node): |
|
return self.parse(node.value_node) |
|
|
|
def parse_Slice(self, node): |
|
code = [] |
|
if node.step_node: |
|
raise JSError('Slicing with step not supported.') |
|
if node.lower_node: |
|
code += self.parse(node.lower_node) |
|
else: |
|
code.append('0') |
|
if node.upper_node: |
|
code.append(',') |
|
code += self.parse(node.upper_node) |
|
return code |
|
|
|
def parse_ExtSlice(self, node): |
|
raise JSError('Multidimensional slicing not supported in JS') |
|
|
|
## Imports |
|
|
|
def parse_Import(self, node): |
|
|
|
if node.root and 'pscript' in node.root: |
|
# User is probably importing names from here to allow |
|
# writing the JS code and command to parse it in one module. |
|
# Ignore this import. |
|
return [] |
|
if node.root and node.root == '__future__': |
|
return [] # stuff to help the parser |
|
if node.root == 'time': |
|
return [] # PScript natively supports time() and perf_counter() |
|
if node.root == 'typing': |
|
# User is probably importing type annotations. Ignore this import. |
|
return [] |
|
raise JSError('PScript does not support imports.') |
|
|
|
def parse_Module(self, node): |
|
# Module level. Every piece of code has a module as the root. |
|
# Just pass body. |
|
|
|
# Get docstring, but only if in module mode |
|
# module_mode = self._stack[0][1] # top stack has a name -> works no more |
|
module_mode = self._pysource and self._pysource[1] == 0 # line nr offset |
|
docstring = '' |
|
if self._docstrings and module_mode: |
|
docstring = self.pop_docstring(node) |
|
|
|
code = [] |
|
if docstring: |
|
for line in docstring.splitlines(): |
|
code.append(self.lf('// ' + line)) |
|
code.append('\n') |
|
for child in node.body_nodes: |
|
code += self.parse(child) |
|
return code
|
|
|