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

# -*- 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