Skip to content

Commit

Permalink
Merge pull request #56 from Xython/feature-async-with-syntax
Browse files Browse the repository at this point in the history
[feature-async-with] add syntax async with
  • Loading branch information
lfkdsk committed Oct 24, 2018
2 parents bc46a4e + 09781f8 commit ad9f6a9
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 4 deletions.
2 changes: 1 addition & 1 deletion auto_test.py
Expand Up @@ -19,7 +19,7 @@
Keyword := 'test:' 'prepare:' '>>>' 'title:'
NoSwitch ::= ~Keyword
Doctest ::= [(~'title:')* 'title:' name=(~NL)+]
[(~'prepare:')* 'prepare:' (NoSwitch* '>>>' prepare_lines<<((~NL)+) NL+)*]
[(~'prepare:')* 'prepare:' (NoSwitch* '>>>' prepare_lines<<((~NL)*) NL+)*]
(~'test:')* 'test:' (NoSwitch* '>>>' test_lines<<((~NL)+))*
->
prepare_lines = recover_codes(sum(prepare_lines, [])) if prepare_lines else ''
Expand Down
147 changes: 147 additions & 0 deletions yapypy/extended_python/emit_impl/control.py
Expand Up @@ -552,3 +552,150 @@ def py_emit(node: ast.With, ctx: Context):
ctx.bc.append(WITH_CLEANUP_FINISH(lineno=node.lineno))
ctx.bc.append(END_FINALLY(lineno=node.lineno))
ctx.pop_current_block(BlockType.FINALLY_END, node.lineno)


@py_emit.case(ast.AsyncWith)
def py_emit(node: ast.AsyncWith, ctx: Context):
"""
title: async with.
prepare:
>>> def run_async(coro):
>>> buffer = []
>>> result = None
>>> while True:
>>> try:
>>> buffer.append(coro.send(None))
>>> except StopIteration as ex:
>>> result = ex.args[0] if ex.args else None
>>> break
>>> return buffer, result
>>> class Manager:
>>> name: str
>>> def __init__(self, name):
>>> self.name = name
>>>
>>> async def __aenter__(self):
>>> await AsyncYieldFrom(['enter-1-' + self.name,
>>> 'enter-2-' + self.name])
>>> return self
>>>
>>> async def __aexit__(self, *args):
>>> await AsyncYieldFrom(['exit-1-' + self.name,
>>> 'exit-2-' + self.name])
>>>
>>> if self.name == 'B':
>>> return True
>>>
>>> class AsyncYieldFrom:
>>> obj = None
>>> def __init__(self, obj):
>>> self.obj = obj
>>>
>>> def __await__(self):
>>> yield from self.obj
test:
>>> async def foo():
>>> async with Manager("A") as a, Manager("B") as b:
>>> await AsyncYieldFrom([('managers', a.name, b.name)])
>>> f = foo()
>>> result, _ = run_async(f)
>>> assert result == ['enter-1-A', 'enter-2-A', 'enter-1-B', 'enter-2-B',
>>> ('managers', 'A', 'B'),
>>> 'exit-1-B', 'exit-2-B', 'exit-1-A', 'exit-2-A']
"""

final_labels: typing.List[Label] = []

"""
Implements the async with statement.
The semantics outlined in that PEP are as follows:
async with EXPR as VAR:
BLOCK
It is implemented roughly as:
context = EXPR
exit = context.__aexit__ # not calling it
value = await context.__aenter__()
try:
VAR = value # if VAR present in the syntax
BLOCK
finally:
if an exception was raised:
exc = copy of (exception, instance, traceback)
else:
exc = (None, None, None)
if not (await exit(*exc)):
raise
"""

def emit_async_with_push_iter(node_item: ast.withitem):
if ContextType.Coroutine not in ctx.cts:
raise SyntaxError("'async with' outside async function")

byte_code = ctx.bc
block = Label()
final = Label()

final_labels.append(final)

if block is None or final is None:
return

# Evaluate EXPR
py_emit(node_item.context_expr, ctx)

byte_code.extend([
BEFORE_ASYNC_WITH(),
GET_AWAITABLE(),
LOAD_CONST(None),
YIELD_FROM(),
# SETUP_ASYNC_WITH pushes a finally block.
SETUP_ASYNC_WITH(final, lineno=node.lineno),
])

ctx.push_current_block(BlockType.FINALLY_TRY)

if node_item.optional_vars:
py_emit(node_item.optional_vars, ctx)
else:
byte_code.append(POP_TOP(lineno=node.lineno))

def emit_async_with_pop_iter():
# End of try block; start the finally block
byte_code = ctx.bc
byte_code.append(POP_BLOCK(lineno=node.lineno))
ctx.pop_current_block(BlockType.FINALLY_TRY, lineno=node.lineno)

byte_code.append(LOAD_CONST(None, lineno=node.lineno))
finally_label = final_labels.pop(-1)
ctx.push_current_block(BlockType.FINALLY_END, finally_label)
byte_code.append(finally_label)

# Finally block starts; context.__exit__ is on the stack under
# the exception or return information. Just issue our magic
# opcode.

byte_code.extend([
WITH_CLEANUP_START(),
GET_AWAITABLE(),
LOAD_CONST(None),
YIELD_FROM(),
WITH_CLEANUP_FINISH(),
])

# end final
byte_code.append(END_FINALLY(lineno=node.lineno))
ctx.pop_current_block(BlockType.FINALLY_END, lineno=node.lineno)
pass

for item in node.items:
emit_async_with_push_iter(item)

for each in node.body:
py_emit(each, ctx)

while final_labels:
emit_async_with_pop_iter()
7 changes: 4 additions & 3 deletions yapypy/extended_python/pycompat.py
Expand Up @@ -16,9 +16,11 @@ class YAPyPyFinder(MetaPathFinder):

@classmethod
def find_spec(cls, fullname: str, paths, target=None):

paths = paths if isinstance(
paths, list) else [paths] if isinstance(paths, str) else sys.path
paths, list,
) else [paths] if isinstance(
paths, str,
) else sys.path

if is_debug:
print(f'Searching module {fullname} from {paths[:5]}...')
Expand Down Expand Up @@ -52,7 +54,6 @@ def create_module(self, spec):


def find_yapypy_module_spec(names, paths):

def try_find(prospective_path):
path_secs = (prospective_path, *names.split('.'))
*init, end = path_secs
Expand Down
15 changes: 15 additions & 0 deletions yapypy/utils/easy_debug.py
Expand Up @@ -19,6 +19,21 @@ def _read_test_file(file_name: str) -> AnyStr:
return test_code


def _read_abs_test_file(file_path: str) -> AnyStr:
with open(file_path) as f:
test_code = f.read()
return test_code


def yapypy_test_abs(file_path: str, should_exec=False, ctx=None):
code = _read_abs_test_file(file_path)
if code is None:
return

yapypy_test_code(code, should_exec, ctx)
return True


def yapypy_test(file_name: str, should_exec=False, ctx=None):
code = _read_test_file(file_name)
if code is None:
Expand Down
8 changes: 8 additions & 0 deletions yapypy/utils/instrs.py
Expand Up @@ -291,3 +291,11 @@ def check_tos(f=print, n=1):
CALL_FUNCTION(1),
POP_TOP(),
]


def BEFORE_ASYNC_WITH(*, lineno=None):
return Instr('BEFORE_ASYNC_WITH', lineno=lineno)


def SETUP_ASYNC_WITH(label: Label, *, lineno=None):
return Instr('SETUP_ASYNC_WITH', label, lineno=lineno)

0 comments on commit ad9f6a9

Please sign in to comment.