""" 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# 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#= 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