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.
 
 

1131 lines
40 KiB

"""
If statements
-------------
.. pscript_example::
if val > 7:
result = 42
elif val > 1:
result = 1
else:
result = 0
# One-line if
result = 42 if truth else 0
Looping
-------
There is support for while loops and for-loops in several forms.
Both support ``continue``, ``break`` and the ``else`` clause.
While loops map well to JS
.. pscript_example::
val = 0
while val < 10:
val += 1
Explicit iterating over arrays (and strings):
.. pscript_example::
# Using range() yields true for-loops
for i in range(10):
print(i)
for i in range(100, 10, -2):
print(i)
# One way to iterate over an array
for i in range(len(arr)):
print(arr[i])
# But this is equally valid (and fast)
for element in arr:
print(element)
Iterations over dicts:
.. pscript_example::
# Plain iteration over a dict has a minor overhead
for key in d:
print(key)
# Which is why we recommend using keys(), values(), or items()
for key in d.keys():
print(key)
for val in d.values():
print(val)
for key, val in d.items():
print(key, val, sep=': ')
We can iterate over anything:
.. pscript_example::
# Strings
for char in "foo bar":
print(c)
# More complex data structes
for i, j in [[1, 2], [3, 4]]:
print(i+j)
Builtin functions intended for iterations are supported too:
enumerate, zip, reversed, sorted, filter, map.
.. pscript_example::
for i, x in enumerate(foo):
pass
for a, b in zip(foo, bar):
pass
for x in reversed(sorted(foo)):
pass
for x in map(lambda x: x+1, foo):
pass
for x in filter(lambda x: x>0, foo):
pass
Comprehensions
--------------
.. pscript_example::
# List comprehensions just work
x = [i*2 for i in some_array if i>0]
y = [i*j for i in a for j in b]
Defining functions
------------------
.. pscript_example::
def display(val):
print(val)
# Support for *args
def foo(x, *values):
bar(x+1, *values)
# To write the function in raw JS, use the RawJS call
def bar(a, b):
RawJS('''
var c = 4;
return a + b + c;
''')
# Lambda expressions
foo = lambda x: x**2
PScript also supports async functions and await syntax. (These map to
``async`` and ``await`` in JS, which work in about every browser except IE.):
.. pscript_example::
async def getresult(uri):
response = await window.fetch(uri)
return await response.text()
Defining classes
----------------
Classes are translated to the JavaScript prototypal class paragigm,
which means that they should play well with other JS libraries and e.g.
`instanceof`. Inheritance is supported, but not multiple inheritance.
Further, `super()` works just as in Python 3.
.. pscript_example::
class Foo:
a_class_attribute = 4
def __init__(self):
self.x = 3
class Bar(Foo):
def __init__(self):
super.__init__()
self.x += 1
def add1(self):
self.x += 1
# Methods are bound functions, like in Python
b = Bar()
setTimeout(b.add1, 1000)
# Functions defined in methods (and that do not start with self or this)
# have ``this`` bound the the same object.
class Spam(Bar):
def add_later(self):
setTimeout(lambda ev: self.add1(), 1000)
Exceptions
----------
Raised exceptions are translated to a JavaScript Error objects, for
which the ``name`` attribute is set to the type of the exception being
raised. When catching exceptions the name attribute is checked (if its
an Error object. You can raise strings or any other kind of object, but
you can only catch Error objects.
.. pscript_example::
# Throwing/raising exceptions
raise SomeError('asd')
raise AnotherError()
raise "In JS you can throw anything"
raise 4
# Assertions work too
assert foo == 3
assert bar == 4, "bar should be 4"
# Catching exceptions
try:
raise IndexError('blabla')
except IndexError as err:
print(err)
except Exception:
print('something went wrong')
Globals and nonlocal
--------------------
.. pscript_example::
a = 3
def foo():
global a
a = 4
foo()
# a is now 4
"""
from . import commonast as ast
from . import stdlib
from . import logger
from .parser1 import Parser1, JSError, unify, reprs # noqa
RAW_DOC_WARNING = ('Function %s only has a docstring, which used to be '
'intepreted as raw JS. Wrap a call to RawJS(...) around the '
'docstring, or add "pass" to the function body to prevent '
'this behavior.')
JS_RESERVED_WORDS = set()
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar
RESERVED = {'true', 'false', 'null',
# Reserved keywords as of ECMAScript 6
'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
'default', 'delete', 'do', 'else', 'export', 'extends', 'finally',
'for', 'function', 'if', 'import', 'in', 'instanceof', 'new',
'return', 'super', 'switch', 'this', 'throw', 'try', 'typeof',
'var', 'void', 'while', 'with', 'yield',
# Future reserved keywords
'implements', 'interface', 'let', 'package', 'private',
'protected', 'public', 'static', 'enum',
'await', # only in module code
}
class Parser2(Parser1):
""" Parser that adds control flow, functions, classes, and exceptions.
"""
## Exceptions
def parse_Raise(self, node):
# We raise the exception as an Error object
if node.exc_node is None:
raise JSError('When raising, provide an error object.')
if node.cause_node is not None:
raise JSError('When raising, "cause" is not supported.')
err_node = node.exc_node
# Get cls and msg
err_cls, err_msg = None, "''"
if isinstance(err_node, ast.Name):
if err_node.name[0].islower(): # raise an (error) object
return [self.lf("throw " + err_node.name + ';')]
err_cls = err_node.name
elif isinstance(err_node, ast.Call):
err_cls = err_node.func_node.name
err_msg = ''.join([unify(self.parse(arg)) for arg in err_node.arg_nodes])
else:
err_msg = ''.join(self.parse(err_node))
err_name = 'err_%i' % self._indent
self.vars.add(err_name)
# Build code to throw
if err_cls:
code = self.use_std_function('op_error',
["'%s'" % err_cls, err_msg or '""'])
else:
code = err_msg
return [self.lf('throw ' + code + ';')]
def parse_Assert(self, node):
test = ''.join(self.parse(node.test_node))
msg = test
if node.msg_node:
msg = ''.join(self.parse(node.msg_node))
code = []
code.append(self.lf('if (!('))
code += test
code.append(')) { throw ')
code.append(self.use_std_function('op_error', ["'AssertionError'", reprs(msg)]))
code.append(";}")
return code
def parse_Try(self, node):
if node.else_nodes:
raise JSError('No support for try-else clause.')
code = []
# Try
if True:
code.append(self.lf('try {'))
self._indent += 1
for n in node.body_nodes:
code += self.parse(n)
self._indent -= 1
code.append(self.lf('}'))
# Except
if node.handler_nodes:
self._indent += 1
err_name = 'err_%i' % self._indent
code.append(' catch(%s) {' % err_name)
subcode = []
for i, handler in enumerate(node.handler_nodes):
if i == 0:
code.append(self.lf(''))
else:
code.append(' else ')
subcode = self.parse(handler)
code += subcode
# Rethrow?
if subcode and subcode[0].startswith('if'):
code.append(' else { throw %s; }' % err_name)
self._indent -= 1
code.append(self.lf('}')) # end catch
# Finally
if node.finally_nodes:
code.append(' finally {')
self._indent += 1
for n in node.finally_nodes:
code += self.parse(n)
self._indent -= 1
code.append(self.lf('}')) # end finally
return code
def parse_ExceptHandler(self, node):
err_name = 'err_%i' % self._indent
# Setup the catch
code = []
err_type = unify(self.parse(node.type_node)) if node.type_node else ''
self.vars.discard(err_type)
if err_type and err_type != 'Exception':
code.append('if (%s instanceof Error && %s.name === "%s") {' %
(err_name, err_name, err_type))
else:
code.append('{')
self._indent += 1
if node.name:
code.append(self.lf('%s = %s;' % (node.name, err_name)))
self.vars.add(node.name)
# Insert the body
for n in node.body_nodes:
code += self.parse(n)
self._indent -= 1
code.append(self.lf('}'))
return code
def parse_With(self, node):
code = []
if len(node.item_nodes) != 1:
raise JSError('With statement only supported for singleton contexts.')
with_item = node.item_nodes[0]
context_name = unify(self.parse(with_item.expr_node))
# Store context expression in a variable?
if '(' in context_name or '[' in context_name:
ctx = self.dummy('context')
code.append(self.lf(ctx + ' = ' + context_name + ';'))
context_name = ctx
err_name1 = 'err_%i' % self._indent
err_name2 = self.dummy('err')
# Enter
# for with_item in node.item_nodes: ...
if with_item.as_node is None:
code.append(self.lf(''))
elif isinstance(with_item.as_node, ast.Name):
self.vars.add(with_item.as_node.name)
code.append(self.lf(with_item.as_node.name + ' = '))
elif isinstance(with_item.as_node, ast.Attribute):
code += [self.lf()] + self.parse(with_item.as_node) + [' = ']
else:
raise JSError('The as-node in a with-statement must be a name or attr.')
code += [context_name, '.__enter__();']
# Try
code.append(self.lf('try {'))
self._indent += 1
for n in node.body_nodes:
code += self.parse(n)
self._indent -= 1
code.append(self.lf('}'))
# Exit
code.append(' catch(%s) { %s=%s;' % (err_name1, err_name2, err_name1))
code.append(self.lf('} finally {'))
self._indent += 1
code.append(self.lf() + 'if (%s) { '
'if (!%s.__exit__(%s.name || "error", %s, null)) '
'{ throw %s; }' %
(err_name2, context_name, err_name2, err_name2, err_name2))
code.append(self.lf() + '} else { %s.__exit__(null, null, null); }' %
context_name)
self._indent -= 1
code.append(self.lf('}'))
return code
# def parse_Withitem(self, node) -> handled in parse_With
## Control flow
def parse_IfExp(self, node):
# in "a if b else c"
a = self.parse(node.body_node)
b = self._wrap_truthy(node.test_node)
c = self.parse(node.else_node)
code = []
code.append('(')
code += b
code.append(')? (')
code += a
code.append(') : (')
code += c
code.append(')')
return code
def parse_If(self, node):
if (True and isinstance(node.test_node, ast.Compare) and
isinstance(node.test_node.left_node, ast.Name) and
node.test_node.left_node.name == '__name__'):
# Ignore ``__name__ == '__main__'``, since it may be
# used inside a PScript file for the compiling.
return []
# Shortcut for this_is_js() cases, discarting the else to reduce code
if (True and isinstance(node.test_node, ast.Call) and
isinstance(node.test_node.func_node, ast.Name) and
node.test_node.func_node.name == 'this_is_js'):
code = [self.lf('if ('), 'true', ') ', '{ /* if this_is_js() */']
self._indent += 1
for stmt in node.body_nodes:
code += self.parse(stmt)
self._indent -= 1
code.append(self.lf('}'))
return code
# Disable body if "not this_is_js()"
if (True and isinstance(node.test_node, ast.UnaryOp) and
node.test_node.op == 'Not' and
isinstance(node.test_node.right_node, ast.Call) and
isinstance(node.test_node.right_node.func_node, ast.Name) and
node.test_node.right_node.func_node.name == 'this_is_js'):
node.body_nodes = []
code = [self.lf('if (')] # first part (popped in elif parsing)
code.append(self._wrap_truthy(node.test_node))
code.append(') {')
self._indent += 1
for stmt in node.body_nodes:
code += self.parse(stmt)
self._indent -= 1
if node.else_nodes:
if len(node.else_nodes) == 1 and isinstance(node.else_nodes[0], ast.If):
code.append(self.lf("} else if ("))
code += self.parse(node.else_nodes[0])[1:-1] # skip first and last
else:
code.append(self.lf("} else {"))
self._indent += 1
for stmt in node.else_nodes:
code += self.parse(stmt)
self._indent -= 1
code.append(self.lf("}")) # last part (popped in elif parsing)
return code
def parse_For(self, node):
# Note that enumerate, reversed, sorted, filter, map are handled in parser3
METHODS = 'keys', 'values', 'items'
iter = None # what to iterate over
sure_is_dict = False # flag to indicate that we're sure iter is a dict
sure_is_range = False # dito for range
# First see if this for-loop is something that we support directly
if isinstance(node.iter_node, ast.Call):
f = node.iter_node.func_node
if (isinstance(f, ast.Attribute) and
not node.iter_node.arg_nodes and f.attr in METHODS):
sure_is_dict = f.attr
iter = ''.join(self.parse(f.value_node))
elif isinstance(f, ast.Name) and f.name in ('xrange', 'range'):
sure_is_range = [''.join(self.parse(arg)) for arg in
node.iter_node.arg_nodes]
iter = 'range' # stub to prevent the parsing of iter_node below
# Otherwise we parse the iter
if iter is None:
iter = ''.join(self.parse(node.iter_node))
# Get target
if isinstance(node.target_node, ast.Name):
target = [node.target_node.name]
if sure_is_dict == 'values':
target.append(target[0])
elif sure_is_dict == 'items':
raise JSError('Iteration over a dict with .items() '
'needs two iterators.')
elif isinstance(node.target_node, ast.Tuple):
target = [''.join(self.parse(t)) for t in node.target_node.element_nodes]
if sure_is_dict:
if not (sure_is_dict == 'items' and len(target) == 2):
raise JSError('Iteration over a dict needs one iterator, '
'or 2 when using .items()')
elif sure_is_range:
raise JSError('Iterarion via range() needs one iterator.')
else:
raise JSError('Invalid iterator in for-loop')
# Collect body and else-body
for_body = []
for_else = []
self._indent += 1
for n in node.body_nodes:
for_body += self.parse(n)
for n in node.else_nodes:
for_else += self.parse(n)
self._indent -= 1
# Init code
code = []
# Prepare variable to detect else
if node.else_nodes:
else_dummy = self.dummy('els')
code.append(self.lf('%s = true;' % else_dummy))
# Declare iteration variables if necessary
for t in target:
self.vars.add(t)
if sure_is_range: # Explicit iteration
# Get range args
nums = sure_is_range # The range() arguments
assert len(nums) in (1, 2, 3)
if len(nums) == 1:
start, end, step = '0', nums[0], '1'
elif len(nums) == 2:
start, end, step = nums[0], nums[1], '1'
elif len(nums) == 3:
start, end, step = nums[0], nums[1], nums[2]
# Build for-loop in JS
t = 'for ({i} = {start}; {i} < {end}; {i} += {step})'
if step.lstrip('+-').isdecimal() and float(step) < 0:
t = t.replace('<', '>')
assert len(target) == 1
t = t.format(i=target[0], start=start, end=end, step=step) + ' {'
code.append(self.lf(t))
self._indent += 1
elif sure_is_dict: # Enumeration over an object (i.e. a dict)
# Create dummy vars
d_seq = self.dummy('seq')
code.append(self.lf('%s = %s;' % (d_seq, iter)))
# The loop
code += self.lf(), 'for (', target[0], ' in ', d_seq, ') {'
self._indent += 1
code.append(self.lf('if (!%s.hasOwnProperty(%s)){ continue; }' %
(d_seq, target[0])))
# Set second/alt iteration variable
if len(target) > 1:
code.append(self.lf('%s = %s[%s];' % (target[1], d_seq, target[0])))
else: # Enumeration
# We cannot know whether the thing to iterate over is an
# array or a dict. We use a for-iterarion (otherwise we
# cannot be sure of the element order for arrays). Before
# running the loop, we test whether its an array. If its
# not, we replace the sequence with the keys of that
# sequence. Peformance for arrays should be good. For
# objects probably slightly less.
# Create dummy vars
d_seq = self.dummy('seq')
d_iter = self.dummy('itr')
d_target = target[0] if (len(target) == 1) else self.dummy('tgt')
# Ensure our iterable is indeed iterable
code.append(self._make_iterable(iter, d_seq))
# The loop
code.append(self.lf('for (%s = 0; %s < %s.length; %s += 1) {' %
(d_iter, d_iter, d_seq, d_iter)))
self._indent += 1
code.append(self.lf('%s = %s[%s];' % (d_target, d_seq, d_iter)))
if len(target) > 1:
code.append(self.lf(self._iterator_assign(d_target, *target)))
# The body of the loop
code += for_body
self._indent -= 1
code.append(self.lf('}'))
# Handle else
if node.else_nodes:
code.append(' if (%s) {' % else_dummy)
code += for_else
code.append(self.lf("}"))
# Update all breaks to set the dummy. We overwrite the
# "break;" so it will not be detected by a parent loop
ii = [i for i, part in enumerate(code) if part=='break;']
for i in ii:
code[i] = '%s = false; break;' % else_dummy
return code
def _make_iterable(self, name1, name2, newlines=True):
code = []
lf = self.lf
if not newlines: # pragma: no cover
lf = lambda x: x
if name1 != name2:
code.append(lf('%s = %s;' % (name2, name1)))
code.append(lf('if ((typeof %s === "object") && '
'(!Array.isArray(%s))) {' % (name2, name2)))
code.append(' %s = Object.keys(%s);' % (name2, name2))
code.append('}')
return ''.join(code)
def parse_While(self, node):
test = ''.join(self.parse(node.test_node))
# Collect body and else-body
for_body = []
for_else = []
self._indent += 1
for n in node.body_nodes:
for_body += self.parse(n)
for n in node.else_nodes:
for_else += self.parse(n)
self._indent -= 1
# Init code
code = []
# Prepare variable to detect else
if node.else_nodes:
else_dummy = self.dummy('els')
code.append(self.lf('%s = true;' % else_dummy))
# The loop itself
code.append(self.lf("while (%s) {" % test))
self._indent += 1
code += for_body
self._indent -= 1
code.append(self.lf('}'))
# Handle else
if node.else_nodes:
code.append(' if (%s) {' % else_dummy)
code += for_else
code.append(self.lf("}"))
# Update all breaks to set the dummy. We overwrite the
# "break;" so it will not be detected by a parent loop
ii = [i for i, part in enumerate(code) if part=='break;']
for i in ii:
code[i] = '%s = false; break;' % else_dummy
return code
def parse_Break(self, node):
# Note that in parse_For, we detect breaks and modify them to
# deal with the for-else clause
return [self.lf(), 'break;']
def parse_Continue(self, node):
return self.lf('continue;')
## Comprehensions
def parse_ListComp_funtionless(self, node, result_name):
prefix = result_name
self.push_scope_prefix(prefix)
code = []
for iter, comprehension in enumerate(node.comp_nodes):
cc = []
# Get target (can be multiple vars)
if isinstance(comprehension.target_node, ast.Tuple):
target = [namenode.name for namenode in
comprehension.target_node.element_nodes]
else:
target = [comprehension.target_node.name]
for i in range(len(target)):
if not self.vars.is_known(target[i]):
target[i] = prefix + target[i]
self.vars.add(target[i])
self.vars.add(prefix + 'i%i' % iter)
self.vars.add(prefix + 'iter%i' % iter)
# comprehension(target_node, iter_node, if_nodes)
cc.append('iter# = %s;' % ''.join(self.parse(comprehension.iter_node)))
cc.append('if ((typeof iter# === "object") && '
'(!Array.isArray(iter#))) {iter# = Object.keys(iter#);}')
cc.append('for (i#=0; i#<iter#.length; i#++) {')
cc.append(self._iterator_assign('iter#[i#]', *target))
# Ifs
if comprehension.if_nodes:
cc.append('if (!(')
for iff in comprehension.if_nodes:
cc += unify(self.parse(iff))
cc.append('&&')
cc.pop(-1) # pop '&&'
cc.append(')) {continue;}')
# Insert code for this comprehension loop
code.append(''.join(cc).replace('i#', prefix + 'i%i' % iter).replace(
'iter#', prefix + 'iter%i' % iter))
# Push result
elt = ''.join(self.parse(node.element_node))
code.append('{%s.push(%s);}' % (result_name, elt))
for comprehension in node.comp_nodes:
code.append('}') # end for
self.pop_scope_prefix()
return code
def parse_ListComp(self, node):
self.push_stack('function', 'listcomp')
elt = ''.join(self.parse(node.element_node))
code = ['(function list_comprehension (iter0) {', 'var res = [];']
vars = []
for iter, comprehension in enumerate(node.comp_nodes):
cc = []
# Get target (can be multiple vars)
if isinstance(comprehension.target_node, ast.Tuple):
target = [''.join(self.parse(t)) for t in
comprehension.target_node.element_nodes]
else:
target = [''.join(self.parse(comprehension.target_node))]
for t in target:
vars.append(t)
vars.append('i%i' % iter)
# comprehension(target_node, iter_node, if_nodes)
if iter > 0: # first one is passed to function as an arg
cc.append('iter# = %s;' % ''.join(self.parse(comprehension.iter_node)))
vars.append('iter%i' % iter)
cc.append('if ((typeof iter# === "object") && '
'(!Array.isArray(iter#))) {iter# = Object.keys(iter#);}')
cc.append('for (i#=0; i#<iter#.length; i#++) {')
cc.append(self._iterator_assign('iter#[i#]', *target))
# Ifs
if comprehension.if_nodes:
cc.append('if (!(')
for iff in comprehension.if_nodes:
cc += unify(self.parse(iff))
cc.append('&&')
cc.pop(-1) # pop '&&'
cc.append(')) {continue;}')
# Insert code for this comprehension loop
code.append(''.join(cc).replace('i#', 'i%i' % iter).replace(
'iter#', 'iter%i' % iter))
# Push result
code.append('{res.push(%s);}' % elt)
for comprehension in node.comp_nodes:
code.append('}') # end for
# Finalize
code.append('return res;})') # end function
iter0 = ''.join(self.parse(node.comp_nodes[0].iter_node))
code.append('.call(this, ' + iter0 + ')') # call funct with iter as 1st arg
code.insert(2, 'var %s;' % ', '.join(vars))
# Clean vars
for var in vars:
self.vars.add(var)
self.pop_stack()
return code
# todo: apply the apply(this) trick everywhere where we use a function
# SetComp
# GeneratorExp
# DictComp
# comprehension
def _iterator_assign(self, val, *names):
if len(names) == 1:
return '%s = %s;' % (names[0], val)
else:
code = []
for i, name in enumerate(names):
code.append('%s = %s[%i];' % (name, val, i))
return ' '.join(code)
## Functions and class definitions
def parse_FunctionDef(self, node, lambda_=False, asyn=False):
# Common code for the FunctionDef and Lambda nodes.
has_self = node.arg_nodes and node.arg_nodes[0].name in ('self', 'this')
# Bind if this function is inside a function, and does not have self
binder = '' # code to add to the end
if len(self._stack) >= 1 and self._stack[-1][0] == 'function':
if not has_self:
binder = ').bind(this)'
# Init function definition
# Non-anonymouse functions get a name so that they are debugged more
# easily and resolve to the correct event labels in flexx.event. However,
# we cannot use the exact name, since we don't want to actually *use* it.
# Classes give their methods a __name__, so no need to name these.
code = []
func_name = ''
if not lambda_:
if not has_self:
func_name = 'flx_' + node.name
prefixed = self.with_prefix(node.name)
if prefixed == node.name: # normal function vs method
self.vars.add(node.name)
self._seen_func_names.add(node.name)
code.append(self.lf('%s = ' % prefixed))
code.append('%s%sfunction %s%s(' % ('(' if binder else '',
'async ' if asyn else '',
func_name,
' ' if func_name else ''))
# Collect args
argnames = []
for arg in node.arg_nodes: # ast.Arg nodes
name = self.NAME_MAP.get(arg.name, arg.name)
if name != 'this':
argnames.append(name)
# Add code and comma
code.append(name)
code.append(', ')
if argnames:
code.pop(-1) # pop last comma
# Check
if (not lambda_) and node.decorator_nodes:
if not (len(node.decorator_nodes) == 1 and
isinstance(node.decorator_nodes[0], ast.Name) and
node.decorator_nodes[0].name == 'staticmethod'):
raise JSError('No support for function decorators')
# Prepare for content
code.append(') {')
pre_code, code = code, []
self._indent += 1
self.push_stack('function', '' if lambda_ else node.name)
# Add argnames to known vars
for name in argnames:
self.vars.add(name)
# Prepare code for varargs
vararg_code1 = vararg_code2 = ''
if node.args_node:
name = node.args_node.name # always an ast.Arg
self.vars.add(name)
if not argnames:
# Make available under *arg name
#code.append(self.lf('%s = arguments;' % name))
vararg_code1 = '%s = Array.prototype.slice.call(arguments);' % name
vararg_code2 = '%s = arguments[0].flx_args;' % name
else:
# Slice it
x = name, len(argnames)
vararg_code1 = '%s = Array.prototype.slice.call(arguments, %i);' % x
vararg_code2 = '%s = arguments[0].flx_args.slice(%i);' % x
# Handle keyword arguments and kwargs
kw_argnames = set() # variables that come from keyword args, or helper vars
if node.kwarg_nodes or node.kwargs_node:
# Collect names and default values
names, values = [], []
for arg in node.kwarg_nodes:
self.vars.add(arg.name)
kw_argnames.add(arg.name)
names.append("'%s'" % arg.name)
values.append(''.join(self.parse(arg.value_node)))
# Turn into string representation
names = '[' + ', '.join(names) + ']'
values = '[' + ', '.join(values) + ']'
# Write code to prepare for kwargs
if node.kwargs_node:
code.append(self.lf('%s = {};' % node.kwargs_node.name))
if node.kwarg_nodes:
values_var = self.dummy('kw_values')
kw_argnames.add(values_var)
code += [self.lf(values_var), ' = ', values, ';']
else:
values_var = values
# Enter if to actually parse kwargs
code.append(self.lf(
"if (arguments.length == 1 && typeof arguments[0] == 'object' && "
"Object.keys(arguments[0]).toString() == 'flx_args,flx_kwargs') {"))
self._indent += 1
# Call function to parse args
code += [self.lf()]
if node.kwargs_node:
kw_argnames.add(node.kwargs_node.name)
self.vars.add(node.kwargs_node.name)
code += [node.kwargs_node.name, ' = ']
self.use_std_function('op_parse_kwargs', [])
code += [stdlib.FUNCTION_PREFIX + 'op_parse_kwargs(',
names, ', ', values_var, ', arguments[0].flx_kwargs']
if not node.kwargs_node:
code.append(", '%s'" % func_name or 'anonymous')
code.append(');')
# Apply values of positional args
# inside if, because standard arguments are invalid
args_var = 'arguments[0].flx_args'
if len(argnames) > 1:
args_var = self.dummy('args')
code.append(self.lf('%s = arguments[0].flx_args;' % args_var))
for i, name in enumerate(argnames):
code.append(self.lf('%s = %s[%i];' % (name, args_var, i)))
# End if
if vararg_code2:
code.append(self.lf(vararg_code2))
self._indent -= 1
code.append(self.lf('}'))
if vararg_code1:
code += [' else {', vararg_code1, '}']
# Apply values of keyword-only args
# outside if, because these need to be assigned always
# Note that we cannot use destructuring assignment because not all
# browsers support it (meh IE and Safari!)
for i, arg in enumerate(node.kwarg_nodes):
code.append(self.lf('%s = %s[%i];' % (arg.name, values_var, i)))
else:
if vararg_code1:
code.append(self.lf(vararg_code1))
# Apply defaults of positional arguments
for arg in node.arg_nodes:
if arg.value_node is not None:
name = arg.name
d = ''.join(self.parse(arg.value_node))
x = '%s = (%s === undefined) ? %s: %s;' % (name, name, d, name)
code.append(self.lf(x))
# Apply content
if lambda_:
code.append('return ')
code += self.parse(node.body_node)
code.append(';')
else:
docstring = self.pop_docstring(node)
if docstring and not node.body_nodes:
# Raw JS - but deprecated
logger.warning(RAW_DOC_WARNING % node.name)
for line in docstring.splitlines():
code.append(self.lf(line))
else:
# Normal function
if self._docstrings:
for line in docstring.splitlines():
code.append(self.lf('// ' + line))
for child in node.body_nodes:
code += self.parse(child)
# Wrap up
if lambda_:
code.append('}%s' % binder)
# ns should only consist only of arg names (or helpers)
for name in argnames:
self.vars.discard(name)
if node.args_node:
self.vars.discard(node.args_node.name)
ns = self.pop_stack()
assert set(ns) == kw_argnames
pre_code.append(self.get_declarations(ns))
else:
if not (code and code[-1].strip().startswith('return ')):
code.append(self.lf('return null;'))
# Declare vars, but exclude our argnames
for name in argnames:
self.vars.discard(name)
ns = self.pop_stack()
pre_code.append(self.get_declarations(ns))
self._indent -= 1
if not lambda_:
code.append(self.lf('}%s;\n' % binder))
return pre_code + code
def parse_Lambda(self, node):
return self.parse_FunctionDef(node, True)
def parse_AsyncFunctionDef(self, node):
return self.parse_FunctionDef(node, False, True)
def parse_Return(self, node):
if node.value_node is not None:
return self.lf('return %s;' % ''.join(self.parse(node.value_node)))
else:
return self.lf("return null;")
def parse_ClassDef(self, node):
# Checks
if len(node.arg_nodes) > 1:
raise JSError('Multiple inheritance not (yet) supported.')
if node.kwarg_nodes:
raise JSError('Metaclasses not supported.')
if node.decorator_nodes:
raise JSError('Class decorators not supported.')
# Get base class (not the constructor)
base_class = 'Object'
if node.arg_nodes:
base_class = ''.join(self.parse(node.arg_nodes[0]))
if not base_class.replace('.', '_').isalnum():
raise JSError('Base classes must be simple names')
elif base_class.lower() == 'object': # maybe Python "object"
base_class = 'Object'
else:
base_class = base_class + '.prototype'
# Define function that acts as class constructor
code = []
docstring = self.pop_docstring(node)
docstring = docstring if self._docstrings else ''
for line in get_class_definition(node.name, base_class, docstring):
code.append(self.lf(line))
self.use_std_function('op_instantiate', [])
# Body ...
self.vars.add(node.name)
self._seen_class_names.add(node.name)
self.push_stack('class', node.name)
for sub in node.body_nodes:
code += self.parse(sub)
code.append('\n')
self.pop_stack()
# no need to declare variables, because they're prefixed
return code
def function_super(self, node):
# allow using super() in methods
# Note that in parse_Call() we ensure that a call using super
# uses .call(this, ...) so that the instance is handled ok.
if node.arg_nodes:
#raise JSError('super() accepts 0 or 1 arguments.')
pass # In Python 2, arg nodes are provided, and we ignore them
if len(self._stack) < 3: # module, class, function
#raise JSError('can only use super() inside a method.')
# We just provide "super()" and hope that the user will
# replace the code (as we do in the Model class).
return 'super()'
# Find the class of this function. Using this._base_class would work
# in simple situations, but not when there's two levels of super().
nstype1, nsname1, _ = self._stack[-1]
nstype2, nsname2, _ = self._stack[-2]
if not (nstype1 == 'function' and nstype2 == 'class'):
raise JSError('can only use super() inside a method.')
base_class = nsname2
return '%s.prototype._base_class' % base_class
#def parse_Yield
#def parse_YieldFrom
def parse_Await(self, node):
return 'await %s' % ''.join(self.parse(node.value_node))
def parse_Global(self, node):
for name in node.names:
self.vars.set_global(name)
return ''
def parse_Nonlocal(self, node):
for name in node.names:
self.vars.set_nonlocal(name)
return ''
def get_class_definition(name, base='Object', docstring=''):
""" Get a list of lines that defines a class in JS.
Used in the parser as well as by flexx.app.Component.
"""
code = []
code.append('%s = function () {' % name)
for line in docstring.splitlines():
code.append(' // ' + line)
code.append(' %sop_instantiate(this, arguments);' % stdlib.FUNCTION_PREFIX)
code.append('}')
if base != 'Object':
code.append('%s.prototype = Object.create(%s);' % (name, base))
code.append('%s.prototype._base_class = %s;' % (name, base))
code.append('%s.prototype.__name__ = %s;' % (name, reprs(name.split('.')[-1])))
code.append('')
return code