Skip to content

Commit

Permalink
add support for async and await
Browse files Browse the repository at this point in the history
  • Loading branch information
almarklein committed Aug 10, 2018
1 parent fd25551 commit 9a490a9
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 12 deletions.
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ Installation
PScript is pure Python and requires Python 2.7 or 3.5+ (including Pypy).
It has no further dependencies.

* ``conda install pscript -c conda-forge``, or
* ``pip install pscript``
* ``pip install pscript``, or
* ``conda install pscript -c conda-forge``



Short example
Expand All @@ -46,6 +47,14 @@ Gives:
};
```


Supported browsers
------------------

PScript aims to support all modern browsers, including Firefox, Chrome and Edge.
Some constructs (e.g. ``async`` and ``await``) do not work in Internet Explorer though.


License
-------

Expand Down
35 changes: 30 additions & 5 deletions pscript/commonast.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,16 @@ class Lambda(Node):
"""
__slots__ = ('arg_nodes', 'kwarg_nodes', 'args_node', 'kwargs_node',
'body_node')

class AsyncFunctionDef(Node):
""" Asynchronous function definition.
Same as FunctionDef, but async.
"""
__slots__ = ('name', 'decorator_nodes', 'annotation_node',
'arg_nodes', 'kwarg_nodes', 'args_node', 'kwargs_node',
'body_nodes')

class Arg(Node):
""" Function argument for a FunctionDef.
Expand Down Expand Up @@ -703,6 +712,13 @@ class YieldFrom(Node):
"""
__slots__ = 'value_node',

class Await(Node):
"""
Attributes:
value_node: the value to return.
"""
__slots__ = 'value_node',

class Global(Node):
"""
Attributes:
Expand Down Expand Up @@ -1123,7 +1139,7 @@ def _convert_withitem(self, n):

## Function and class definitions

def _convert_FunctionDef(self, n):
def _convert_functiondefs(self, n, cls):
c = self._convert
args = n.args
# Parse arg_nodes and kwarg_nodes
Expand Down Expand Up @@ -1154,9 +1170,9 @@ def _convert_FunctionDef(self, n):
kwargs_node = c(args.kwarg)

returns = None if pyversion < (3, ) else c(n.returns)
node = FunctionDef(n.name, [c(x) for x in n.decorator_list], returns,
arg_nodes, kwarg_nodes, args_node, kwargs_node,
[])
Cls = cls # noqa
node = Cls(n.name, [c(x) for x in n.decorator_list], returns,
arg_nodes, kwarg_nodes, args_node, kwargs_node, [])
if docheck:
assert isinstance(node.args_node, (NoneType, Arg))
assert isinstance(node.kwargs_node, (NoneType, Arg))
Expand All @@ -1166,6 +1182,9 @@ def _convert_FunctionDef(self, n):
self._stack.append((node.body_nodes, n.body))
return node

def _convert_FunctionDef(self, n):
return self._convert_functiondefs(n, FunctionDef)

def _convert_Lambda(self, n):
c = self._convert
args = n.args
Expand All @@ -1181,7 +1200,10 @@ def _convert_Lambda(self, n):

return Lambda(arg_nodes, kwarg_nodes,
c(args.vararg), c(args.kwarg), c(n.body))


def _convert_AsyncFunctionDef(self, n):
return self._convert_functiondefs(n, AsyncFunctionDef)

def _convert_arg(self, n):
# Value is initially None
return Arg(n.arg or None, None, self._convert(n.annotation))
Expand All @@ -1195,6 +1217,9 @@ def _convert_Yield(self, n):
def _convert_YieldFrom(self, n):
return YieldFrom(self._convert(n.value))

def _convert_Await(self, n):
return Await(self._convert(n.value))

def _convert_Global(self, n):
return Global(n.names)

Expand Down
4 changes: 3 additions & 1 deletion pscript/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def py2js_(ob):
else:
lines[i] = line[indent:]
# Skip any decorators
while not lines[0].lstrip().startswith(thetype):
while not lines[0].lstrip().startswith((thetype, 'async ' + thetype)):
lines.pop(0)
# join lines and rename
pycode = ''.join(lines)
Expand Down Expand Up @@ -200,6 +200,8 @@ def js_rename(jscode, cur_name, new_name):
# Always do this
jscode = jscode.replace('%s = function' % cur_name,
'%s = function' % new_name, 1)
jscode = jscode.replace('%s = async function' % cur_name,
'%s = async function' % new_name, 1)
if '.' in new_name:
jscode = jscode.replace('var %s;\n' % cur_name, '', 1)
else:
Expand Down
23 changes: 20 additions & 3 deletions pscript/parser2.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ def bar(a, b):
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
----------------
Expand Down Expand Up @@ -808,8 +818,8 @@ def _iterator_assign(self, val, *names):
return ' '.join(code)

## Functions and class definitions
def parse_FunctionDef(self, node, lambda_=False):

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')
Expand All @@ -835,7 +845,8 @@ def parse_FunctionDef(self, node, lambda_=False):
self.vars.add(node.name)
self._seen_func_names.add(node.name)
code.append(self.lf('%s = ' % prefixed))
code.append('%sfunction %s%s(' % ('(' if binder else '',
code.append('%s%sfunction %s%s(' % ('(' if binder else '',
'async ' if asyn else '',
func_name,
' ' if func_name else ''))

Expand Down Expand Up @@ -1004,6 +1015,9 @@ def parse_FunctionDef(self, node, lambda_=False):
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)))
Expand Down Expand Up @@ -1078,6 +1092,9 @@ def function_super(self, node):
#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)
Expand Down
16 changes: 16 additions & 0 deletions pscript/tests/test_parser2.py
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,22 @@ def func(a, b):
assert evaljs(code + 'func("x", 10)') == 'x10'

assert code.count('// docstring') == 1

def test_async_and_await(self):
if sys.version_info < (3, 6):
return

foo = py2js('async def foo(): return 42\n\n')
spam = py2js('async def spam(): print(await foo())\n\n')
eggs = py2js('async def eggs(): return await foo()\n\n')
js = foo + spam + eggs

assert 'Promise' in evaljs(js + 'foo()')
assert 'Promise' in evaljs(js + 'spam()')
assert 'Promise' in evaljs(js + 'eggs()')

assert '42' in evaljs(js + 'spam()')
assert '42' not in evaljs(js + 'eggs()')


class TestClasses:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def copy_for_legacy_python(src_dir, dest_dir):
# entry_points={'console_scripts': ['pscript = pscript.__main__:main'], },
zip_safe=True,
classifiers=[
'Development Status :: 3 - Alpha',
'Development Status :: 4 - Beta',
'Intended Audience :: Science/Research',
'Intended Audience :: Education',
'Intended Audience :: Developers',
Expand Down

0 comments on commit 9a490a9

Please sign in to comment.