Author: Armin Rigo <ar...@tunes.org>
Branch: py3.5-corowrapper
Changeset: r87143:f3788fad20bf
Date: 2016-09-16 17:48 +0200
http://bitbucket.org/pypy/pypy/changeset/f3788fad20bf/

Log:    'yield from' starts to work again, a bit

diff --git a/pypy/interpreter/generator.py b/pypy/interpreter/generator.py
--- a/pypy/interpreter/generator.py
+++ b/pypy/interpreter/generator.py
@@ -1,6 +1,6 @@
 from pypy.interpreter.baseobjspace import W_Root
 from pypy.interpreter.error import OperationError, oefmt
-from pypy.interpreter.pyopcode import LoopBlock, SApplicationException
+from pypy.interpreter.pyopcode import LoopBlock, SApplicationException, Yield
 from pypy.interpreter.pycode import CO_YIELD_INSIDE_TRY
 from pypy.interpreter.astcompiler import consts
 from rpython.rlib import jit
@@ -151,9 +151,10 @@
         # interpretation.
         space = self.space
         if self.w_yielded_from is not None:
-            XXX
-
-        if isinstance(w_arg_or_err, SApplicationException):
+            w_arg_or_err = self.next_yield_from(frame, w_arg_or_err)
+            # Normal case: the call above raises Yield.
+            # We reach this point if the iterable is exhausted.
+        elif isinstance(w_arg_or_err, SApplicationException):
             ec = space.getexecutioncontext()
             return frame.handle_operation_error(ec, w_arg_or_err.operr)
 
@@ -167,6 +168,40 @@
             frame.pushvalue(w_arg_or_err)
         return last_instr + 1
 
+    def next_yield_from(self, frame, w_inputvalue_or_err):
+        """Fetch the next item of the current 'yield from', push it on
+        the frame stack, and raises Yield.  If there isn't one, push
+        w_stopiteration_value and returns.  May also just raise.
+        """
+        space = self.space
+        w_yf = self.w_yielded_from
+        try:
+            if isinstance(w_yf, GeneratorOrCoroutine):
+                w_retval = w_yf.send_ex(w_inputvalue_or_err)
+            elif space.is_w(w_inputvalue_or_err, space.w_None):
+                w_retval = space.next(w_yf)
+            elif isinstance(w_inputvalue_or_err, SApplicationException):
+                raise w_inputvalue_or_err.operr
+            else:
+                w_retval = space.call_method(w_gen, "send", 
w_inputvalue_or_err)
+        except OperationError as e:
+            self.w_yielded_from = None
+            if not e.match(space, space.w_StopIteration):
+                raise
+            e.normalize_exception(space)
+            try:
+                w_stop_value = space.getattr(e.get_w_value(space),
+                                             space.wrap("value"))
+            except OperationError as e:
+                if not e.match(space, space.w_AttributeError):
+                    raise
+                w_value = space.w_None
+            frame.pushvalue(w_stop_value)
+            return
+        else:
+            frame.pushvalue(w_retval)
+            raise Yield
+
     def _leak_stopiteration(self, e):
         # Check for __future__ generator_stop and conditionally turn
         # a leaking StopIteration into RuntimeError (with its cause
@@ -194,66 +229,9 @@
         return self.throw(w_type, w_val, w_tb)
 
     def throw(self, w_type, w_val, w_tb):
+        from pypy.interpreter.pytraceback import check_traceback
         space = self.space
 
-        w_yf = self.w_yielded_from
-        if w_yf is not None:
-            # Paused in a "yield from", pass the throw to the inner generator.
-            return self._throw_delegate(space, w_yf, w_type, w_val, w_tb)
-        else:
-            # Not paused in a "yield from", throw inside this generator
-            return self._throw_here(space, w_type, w_val, w_tb)
-
-    def _throw_delegate(self, space, w_yf, w_type, w_val, w_tb):
-        if space.exception_match(w_type, space.w_GeneratorExit):
-            try:
-                self.running = True
-                try:
-                    gen_close_iter(space, w_yf)
-                finally:
-                    self.running = False
-            except OperationError as e:
-                return self.send_error(e)
-            return self._throw_here(space, w_type, w_val, w_tb)
-        #
-        if isinstance(w_yf, GeneratorIterator):
-            self.running = True
-            try:
-                return w_yf.throw(space, w_type, w_val, w_tb)
-            except OperationError as e:
-                operr = e
-            finally:
-                self.running = False
-        else:
-            try:
-                w_throw = space.getattr(w_yf, space.wrap("throw"))
-            except OperationError as e:
-                if not e.match(space, space.w_AttributeError):
-                    raise
-                return self._throw_here(space, w_type, w_val, w_tb)
-            self.running = True
-            try:
-                return space.call_function(w_throw, w_type, w_val, w_tb)
-            except OperationError as e:
-                operr = e
-            finally:
-                self.running = False
-        # Pop subiterator from stack.
-        w_subiter = self.frame.popvalue()
-        assert space.is_w(w_subiter, w_yf)
-        # Termination repetition of YIELD_FROM
-        self.frame.last_instr += 1
-        if operr.match(space, space.w_StopIteration):
-            operr.normalize_exception(space)
-            w_val = space.getattr(operr.get_w_value(space),
-                                  space.wrap("value"))
-            return self.send_ex(w_val)
-        else:
-            return self.send_error(operr)
-
-    def _throw_here(self, space, w_type, w_val, w_tb):
-        from pypy.interpreter.pytraceback import check_traceback
-
         msg = "throw() third argument must be a traceback object"
         if space.is_none(w_tb):
             tb = None
@@ -278,6 +256,7 @@
                                space.call_function(space.w_GeneratorExit))
         w_yf = self.w_yielded_from
         if w_yf is not None:
+            XXX
             self.running = True
             try:
                 gen_close_iter(space, w_yf)
@@ -286,14 +265,13 @@
             finally:
                 self.running = False
         try:
-            w_retval = self.send_error(operr)
+            self.send_error(operr)
         except OperationError as e:
             if e.match(space, space.w_StopIteration) or \
                     e.match(space, space.w_GeneratorExit):
                 return space.w_None
             raise
-
-        if w_retval is not None:
+        else:
             raise oefmt(space.w_RuntimeError,
                         "%s ignored GeneratorExit", self.KIND)
 
diff --git a/pypy/interpreter/pyframe.py b/pypy/interpreter/pyframe.py
--- a/pypy/interpreter/pyframe.py
+++ b/pypy/interpreter/pyframe.py
@@ -267,6 +267,7 @@
         a generator or coroutine frame; in that case, w_arg_or_err
         is the input argument -or- an SApplicationException instance.
         """
+        from pypy.interpreter import pyopcode
         # the following 'assert' is an annotation hint: it hides from
         # the annotator all methods that are defined in PyFrame but
         # overridden in the {,Host}FrameClass subclasses of PyFrame.
@@ -281,22 +282,24 @@
             #
             # Execution starts just after the last_instr.  Initially,
             # last_instr is -1.  After a generator suspends it points to
-            # the YIELD_VALUE instruction.
-            if in_generator is None:
-                assert self.last_instr == -1
-                next_instr = 0
-            else:
-                next_instr = in_generator.resume_execute_frame(
-                                                self, w_arg_or_err)
-            next_instr = r_uint(next_instr)
-            #
+            # the YIELD_VALUE/YIELD_FROM instruction.
             try:
-                w_exitvalue = self.dispatch(self.pycode, next_instr,
-                                            executioncontext)
-            except Exception:
-                executioncontext.return_trace(self, self.space.w_None)
-                raise
-            executioncontext.return_trace(self, w_exitvalue)
+                if in_generator is None:
+                    assert self.last_instr == -1
+                    next_instr = 0
+                else:
+                    next_instr = in_generator.resume_execute_frame(
+                                                    self, w_arg_or_err)
+                next_instr = r_uint(next_instr)
+                #
+                self.dispatch(self.pycode, next_instr, executioncontext)
+            except pyopcode.Return:
+                self.last_exception = None
+                w_exitvalue = self.popvalue()
+            except pyopcode.Yield:
+                w_exitvalue = self.popvalue()
+            finally:
+                executioncontext.return_trace(self, w_exitvalue)
             # it used to say self.last_exception = None
             # this is now done by the code in pypyjit module
             # since we don't want to invalidate the virtualizable
@@ -898,7 +901,7 @@
         return None
 
     def get_generator(self):
-        if space.config.translation.rweakref:
+        if self.space.config.translation.rweakref:
             return self.f_generator_wref()
         else:
             return self.f_generator_nowref
@@ -935,7 +938,6 @@
 
 def get_block_class(opname):
     # select the appropriate kind of block
-    from pypy.interpreter.pyopcode import block_classes
     return block_classes[opname]
 
 def unpickle_block(space, w_tup):
diff --git a/pypy/interpreter/pyopcode.py b/pypy/interpreter/pyopcode.py
--- a/pypy/interpreter/pyopcode.py
+++ b/pypy/interpreter/pyopcode.py
@@ -67,15 +67,8 @@
         # For the sequel, force 'next_instr' to be unsigned for performance
         next_instr = r_uint(next_instr)
         co_code = pycode.co_code
-
-        try:
-            while True:
-                next_instr = self.handle_bytecode(co_code, next_instr, ec)
-        except Return:
-            self.last_exception = None
-        except Yield:
-            pass
-        return self.popvalue()
+        while True:
+            next_instr = self.handle_bytecode(co_code, next_instr, ec)
 
     def handle_bytecode(self, co_code, next_instr, ec):
         try:
@@ -1034,41 +1027,27 @@
         raise Yield
 
     def YIELD_FROM(self, oparg, next_instr):
-        from pypy.interpreter.astcompiler import consts
+        # Unlike CPython, we handle this not by repeating the same
+        # bytecode over and over until the inner iterator is exhausted.
+        # Instead, we directly set the generator's w_yielded_from.
+        # This asks generator.resume_execute_frame() to exhaust that
+        # sub-iterable first before continuing on the next bytecode.
         from pypy.interpreter.generator import Coroutine
-        space = self.space
-        w_value = self.popvalue()
-        w_gen = self.peekvalue()
-        if isinstance(w_gen, Coroutine):
-            if (w_gen.descr_gi_code(space).co_flags & consts.CO_COROUTINE and
-               not self.pycode.co_flags & (consts.CO_COROUTINE |
-                                       consts.CO_ITERABLE_COROUTINE)):
-                raise oefmt(self.space.w_TypeError,
-                            "cannot 'yield from' a coroutine object "
-                            "from a generator")
-        try:
-            if space.is_none(w_value):
-                w_retval = space.next(w_gen)
-            else:
-                w_retval = space.call_method(w_gen, "send", w_value)
-        except OperationError as e:
-            if not e.match(space, space.w_StopIteration):
-                raise
-            self.popvalue()  # Remove iter from stack
-            e.normalize_exception(space)
-            try:
-                w_value = space.getattr(e.get_w_value(space), 
space.wrap("value"))
-            except OperationError as e:
-                if not e.match(space, space.w_AttributeError):
-                    raise
-                w_value = space.w_None
-            self.pushvalue(w_value)
-        else:
-            # iter remains on stack, w_retval is value to be yielded.
-            self.pushvalue(w_retval)
-            # and repeat...
-            self.last_instr = self.last_instr - 1
-            raise Yield
+        in_generator = self.get_generator()
+        assert in_generator is not None
+        w_inputvalue = self.popvalue()
+        w_gen = self.popvalue()
+        if isinstance(w_gen, Coroutine) and not isinstance(self, Coroutine):
+            raise oefmt(self.space.w_TypeError,
+                        "cannot 'yield from' a coroutine object "
+                        "from a generator")
+        #
+        in_generator.w_yielded_from = w_gen
+        in_generator.next_yield_from(self, w_inputvalue)
+        # Common case: the call above raises Yield.
+        # If instead the iterable is empty, next_yield_from() pushed the
+        # final result and returns.  In that case, we can just continue
+        # with the next bytecode.
 
     def jump_absolute(self, jumpto, ec):
         # this function is overridden by pypy.module.pypyjit.interp_jit
diff --git a/pypy/interpreter/test/test_generator.py 
b/pypy/interpreter/test/test_generator.py
--- a/pypy/interpreter/test/test_generator.py
+++ b/pypy/interpreter/test/test_generator.py
@@ -325,6 +325,24 @@
             assert False, 'Expected StopIteration'
             """
 
+    def test_yield_from_basic(self):
+        """
+        def f1():
+            yield from []
+            yield from [1, 2, 3]
+            yield from f2()
+        def f2():
+            yield 4
+            yield 5
+        gen = f1()
+        assert next(gen) == 1
+        assert next(gen) == 2
+        assert next(gen) == 3
+        assert next(gen) == 4
+        assert next(gen) == 5
+        assert list(gen) == []
+        """
+
     def test_yield_from_return(self):
         """
         def f1():
diff --git a/pypy/module/pypyjit/interp_jit.py 
b/pypy/module/pypyjit/interp_jit.py
--- a/pypy/module/pypyjit/interp_jit.py
+++ b/pypy/module/pypyjit/interp_jit.py
@@ -90,6 +90,7 @@
                 self.valuestackdepth = hint(self.valuestackdepth, promote=True)
                 next_instr = self.handle_bytecode(co_code, next_instr, ec)
                 is_being_profiled = self.get_is_being_profiled()
+        XXX XXX   # please fix, the base dispatch() changed
         except Yield:
             # preserve self.last_exception between generator yields
             w_result = self.popvalue()
_______________________________________________
pypy-commit mailing list
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to