Skip to content

Commit

Permalink
Merge 601a0b4 into 8f49430
Browse files Browse the repository at this point in the history
  • Loading branch information
lfkdsk committed Oct 20, 2018
2 parents 8f49430 + 601a0b4 commit d0004fc
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,4 @@ venv.bak/
.mypy_cache/
.DS_Store
test_*.py
test/ignore_folder
test/test/*
139 changes: 139 additions & 0 deletions yapypy/extended_python/emit_impl/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,3 +552,142 @@ 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:
>>> import unittest
>>> self: unittest.TestCase
>>> from asyncio import sleep, Task, get_event_loop
>>> class AsyncYieldFrom:
>>> def __init__(self, obj):
>>> self.obj = obj
>>>
>>> def __await__(self):
>>> yield from self.obj
>>>
>>> class Manager:
>>> 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
test:
>>> async def foo():
>>> async with Manager("A") as a, Manager("B") as b:
>>> await AsyncYieldFrom([('managers', a.name, b.name)])
>>> 1/0
>>>
>>> f = foo()
>>> result = get_event_loop().run_until_complete(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(),
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()
9 changes: 6 additions & 3 deletions yapypy/extended_python/pycompat.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ 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

paths.append(Path('test').abs())

if is_debug:
print(f'Searching module {fullname} from {paths[:5]}...')
Expand Down Expand Up @@ -52,7 +56,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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 d0004fc

Please sign in to comment.