From a5b9dc92e470435e85d8add3bdf91f178f1addc0 Mon Sep 17 00:00:00 2001 From: Mansour Rahimi Date: Sun, 23 Jul 2017 21:30:01 +0200 Subject: [PATCH 1/3] Implement generator.throw() --- compiler/expr_visitor.py | 4 ++ compiler/stmt.py | 7 +++- runtime/generator.go | 84 ++++++++++++++++++++++++++------------- runtime/generator_test.go | 32 +++++++++++++-- 4 files changed, 95 insertions(+), 32 deletions(-) diff --git a/compiler/expr_visitor.py b/compiler/expr_visitor.py index 267abdf7..c2590899 100644 --- a/compiler/expr_visitor.py +++ b/compiler/expr_visitor.py @@ -379,6 +379,10 @@ def visit_Yield(self, node): self.writer.write('return {}, nil'.format(value.expr)) self.writer.write_label(resume_label) result = self.block.alloc_temp() + self.writer.write(textwrap.dedent("""\ + if πRaised != nil { + \treturn nil, πRaised + }""")) self.writer.write('{} = πSent'.format(result.name)) return result diff --git a/compiler/stmt.py b/compiler/stmt.py index 0b25f32f..8b017bb5 100644 --- a/compiler/stmt.py +++ b/compiler/stmt.py @@ -590,9 +590,14 @@ def visit_function_inline(self, node): self.writer.write('var πE *πg.BaseException; _ = πE') if func_block.is_generator: self.writer.write( - 'return πg.NewGenerator(πF, func(πSent *πg.Object) ' + 'return πg.NewGenerator(πF, ' + 'func(πSent *πg.Object, πRaised *πg.BaseException) ' '(*πg.Object, *πg.BaseException) {') with self.writer.indent_block(): + self.writer.write(textwrap.dedent("""\ + if πRaised != nil { + \treturn nil, πRaised + }""")) self.writer.write_block(func_block, visitor.writer.getvalue()) self.writer.write('return nil, πE') self.writer.write('}).ToObject(), nil') diff --git a/runtime/generator.go b/runtime/generator.go index 2d2bba66..e646bbc9 100644 --- a/runtime/generator.go +++ b/runtime/generator.go @@ -15,6 +15,7 @@ package grumpy import ( + "fmt" "reflect" "sync" ) @@ -39,11 +40,11 @@ type Generator struct { mutex sync.Mutex state generatorState frame *Frame - fn func(*Object) (*Object, *BaseException) + fn func(*Object, *BaseException) (*Object, *BaseException) } // NewGenerator returns a new Generator object that runs the given Block b. -func NewGenerator(f *Frame, fn func(*Object) (*Object, *BaseException)) *Generator { +func NewGenerator(f *Frame, fn func(*Object, *BaseException) (*Object, *BaseException)) *Generator { f.taken = true // Claim the frame from being returned. // The code generator basically gives us the Frame, so we can tare it @@ -57,33 +58,35 @@ func toGeneratorUnsafe(o *Object) *Generator { return (*Generator)(o.toPointer()) } -func (g *Generator) resume(f *Frame, sendValue *Object) (*Object, *BaseException) { - var raised *BaseException - g.mutex.Lock() - oldState := g.state - switch oldState { - case generatorStateCreated: - if sendValue != None { - raised = f.RaiseType(TypeErrorType, "can't send non-None value to a just-started generator") - } else { +func (g *Generator) resume(f *Frame, sendValue *Object, raisedValue *BaseException) (*Object, *BaseException) { + if raisedValue == nil { + var raised *BaseException + g.mutex.Lock() + oldState := g.state + switch oldState { + case generatorStateCreated: + if sendValue != None { + raised = f.RaiseType(TypeErrorType, "can't send non-None value to a just-started generator") + } else { + g.state = generatorStateRunning + } + case generatorStateReady: g.state = generatorStateRunning + case generatorStateRunning: + raised = f.RaiseType(ValueErrorType, "generator already executing") + case generatorStateDone: + raised = f.Raise(StopIterationType.ToObject(), nil, nil) + } + g.mutex.Unlock() + // Concurrent attempts to transition to running state will raise here + // so it's guaranteed that only one thread will proceed to execute the + // block below. + if raised != nil { + return nil, raised } - case generatorStateReady: - g.state = generatorStateRunning - case generatorStateRunning: - raised = f.RaiseType(ValueErrorType, "generator already executing") - case generatorStateDone: - raised = f.Raise(StopIterationType.ToObject(), nil, nil) - } - g.mutex.Unlock() - // Concurrent attempts to transition to running state will raise here - // so it's guaranteed that only one thread will proceed to execute the - // block below. - if raised != nil { - return nil, raised } g.frame.pushFrame(f) - result, raised := g.fn(sendValue) + result, raised := g.fn(sendValue, raisedValue) g.mutex.Lock() if result == nil && raised == nil { raised = f.Raise(StopIterationType.ToObject(), nil, nil) @@ -108,18 +111,45 @@ func generatorIter(f *Frame, o *Object) (*Object, *BaseException) { } func generatorNext(f *Frame, o *Object) (*Object, *BaseException) { - return toGeneratorUnsafe(o).resume(f, None) + return toGeneratorUnsafe(o).resume(f, None, nil) } func generatorSend(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) { if raised := checkMethodArgs(f, "send", args, GeneratorType, ObjectType); raised != nil { return nil, raised } - return toGeneratorUnsafe(args[0]).resume(f, args[1]) + return toGeneratorUnsafe(args[0]).resume(f, args[1], nil) +} + +func generatorThrow(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) { + argc := len(args) + if argc == 1 { + return nil, f.RaiseType(TypeErrorType, "throw expected at least 1 arguments, got 0") + } + if argc > 4 { + return nil, f.RaiseType(TypeErrorType, fmt.Sprintf("throw expected at most 3 arguments, got %v", argc-1)) + } + expectedTypes := []*Type{GeneratorType, ObjectType, ObjectType, ObjectType} + if argc > 1 && argc < 4 { + expectedTypes = expectedTypes[:argc] + } + if raised := checkMethodArgs(f, "throw", args, expectedTypes...); raised != nil { + return nil, raised + } + var v *Object + if argc > 2 { + v = args[2] + } + var tb *Object + if argc > 3 { + tb = args[3] + } + return toGeneratorUnsafe(args[0]).resume(f, nil, f.Raise(args[1], v, tb)) } func initGeneratorType(dict map[string]*Object) { dict["send"] = newBuiltinFunction("send", generatorSend).ToObject() + dict["throw"] = newBuiltinFunction("throw", generatorThrow).ToObject() GeneratorType.flags &= ^(typeFlagBasetype | typeFlagInstantiable) GeneratorType.slots.Iter = &unaryOpSlot{generatorIter} GeneratorType.slots.Next = &unaryOpSlot{generatorNext} diff --git a/runtime/generator_test.go b/runtime/generator_test.go index 7edbb8be..ba2c416b 100644 --- a/runtime/generator_test.go +++ b/runtime/generator_test.go @@ -21,7 +21,7 @@ import ( func TestGeneratorNext(t *testing.T) { f := NewRootFrame() var recursive *Object - recursiveFn := func(*Object) (*Object, *BaseException) { + recursiveFn := func(*Object, *BaseException) (*Object, *BaseException) { next, raised := GetAttr(f, recursive, NewStr("next"), nil) if raised != nil { return nil, raised @@ -29,7 +29,7 @@ func TestGeneratorNext(t *testing.T) { return next.Call(f, nil, nil) } recursive = NewGenerator(f, recursiveFn).ToObject() - emptyFn := func(*Object) (*Object, *BaseException) { + emptyFn := func(*Object, *BaseException) (*Object, *BaseException) { return nil, nil } exhausted := NewGenerator(NewRootFrame(), emptyFn).ToObject() @@ -46,7 +46,7 @@ func TestGeneratorNext(t *testing.T) { } func TestGeneratorSend(t *testing.T) { - emptyFn := func(*Object) (*Object, *BaseException) { + emptyFn := func(*Object, *BaseException) (*Object, *BaseException) { return nil, nil } cases := []invokeTestCase{ @@ -62,7 +62,7 @@ func TestGeneratorSend(t *testing.T) { func TestGeneratorSimple(t *testing.T) { f := NewRootFrame() - fn := func(*Object) (*Object, *BaseException) { + fn := func(*Object, *BaseException) (*Object, *BaseException) { switch f.State() { case 0: goto Start @@ -90,3 +90,27 @@ func TestGeneratorSimple(t *testing.T) { t.Error(err) } } + +func TestGeneratorThrow(t *testing.T) { + emptyFn := func(*Object, *BaseException) (*Object, *BaseException) { + return nil, nil + } + yieldedFn := func(*Object, *BaseException) (*Object, *BaseException) { + return NewStr("foo").ToObject(), nil + } + raisedFn := func(*Object, *BaseException) (*Object, *BaseException) { + return nil, NewRootFrame().RaiseType(ValueErrorType, "bar") + } + cases := []invokeTestCase{ + invokeTestCase{args: wrapArgs(NewGenerator(NewRootFrame(), emptyFn), TypeErrorType.ToObject()), wantExc: toBaseExceptionUnsafe(mustNotRaise(StopIterationType.Call(NewRootFrame(), nil, nil)))}, + invokeTestCase{args: wrapArgs(NewGenerator(NewRootFrame(), yieldedFn), TypeErrorType.ToObject()), want: NewStr("foo").ToObject()}, + invokeTestCase{args: wrapArgs(NewGenerator(NewRootFrame(), raisedFn), TypeErrorType.ToObject()), wantExc: mustCreateException(ValueErrorType, "bar")}, + invokeTestCase{args: wrapArgs(NewGenerator(NewRootFrame(), emptyFn)), wantExc: mustCreateException(TypeErrorType, "throw expected at least 1 arguments, got 0")}, + invokeTestCase{args: wrapArgs(NewGenerator(NewRootFrame(), emptyFn), "foo", "bar", "baz", "qux"), wantExc: mustCreateException(TypeErrorType, "throw expected at most 3 arguments, got 4")}, + } + for _, cas := range cases { + if err := runInvokeMethodTestCase(GeneratorType, "throw", &cas); err != "" { + t.Error(err) + } + } +} From 15f1f748bef10472e66db6014f74132f91911f73 Mon Sep 17 00:00:00 2001 From: Mansour Rahimi Date: Thu, 27 Jul 2017 00:29:57 +0200 Subject: [PATCH 2/3] Some fixes on generator.throw() --- compiler/expr_visitor.py | 13 +++++++--- compiler/stmt.py | 6 ++--- runtime/generator.go | 51 ++++++++++++++++----------------------- runtime/generator_test.go | 4 +-- testing/generator_test.py | 24 ++++++++++++++++++ 5 files changed, 59 insertions(+), 39 deletions(-) diff --git a/compiler/expr_visitor.py b/compiler/expr_visitor.py index c2590899..706cd40f 100644 --- a/compiler/expr_visitor.py +++ b/compiler/expr_visitor.py @@ -379,10 +379,15 @@ def visit_Yield(self, node): self.writer.write('return {}, nil'.format(value.expr)) self.writer.write_label(resume_label) result = self.block.alloc_temp() - self.writer.write(textwrap.dedent("""\ - if πRaised != nil { - \treturn nil, πRaised - }""")) + self.writer.write_tmpl(textwrap.dedent("""\ + if πThrown != nil { + \tπE = πThrown + \tπThrown = nil + \tif πE != nil { + \t\tcontinue + \t} + } + $result = πSent"""), result=result.name) self.writer.write('{} = πSent'.format(result.name)) return result diff --git a/compiler/stmt.py b/compiler/stmt.py index 8b017bb5..967b63d6 100644 --- a/compiler/stmt.py +++ b/compiler/stmt.py @@ -591,12 +591,12 @@ def visit_function_inline(self, node): if func_block.is_generator: self.writer.write( 'return πg.NewGenerator(πF, ' - 'func(πSent *πg.Object, πRaised *πg.BaseException) ' + 'func(πSent *πg.Object, πThrown *πg.BaseException) ' '(*πg.Object, *πg.BaseException) {') with self.writer.indent_block(): self.writer.write(textwrap.dedent("""\ - if πRaised != nil { - \treturn nil, πRaised + if πThrown != nil { + \treturn nil, πThrown }""")) self.writer.write_block(func_block, visitor.writer.getvalue()) self.writer.write('return nil, πE') diff --git a/runtime/generator.go b/runtime/generator.go index e646bbc9..403cc9fe 100644 --- a/runtime/generator.go +++ b/runtime/generator.go @@ -15,7 +15,6 @@ package grumpy import ( - "fmt" "reflect" "sync" ) @@ -59,31 +58,29 @@ func toGeneratorUnsafe(o *Object) *Generator { } func (g *Generator) resume(f *Frame, sendValue *Object, raisedValue *BaseException) (*Object, *BaseException) { - if raisedValue == nil { - var raised *BaseException - g.mutex.Lock() - oldState := g.state - switch oldState { - case generatorStateCreated: - if sendValue != None { - raised = f.RaiseType(TypeErrorType, "can't send non-None value to a just-started generator") - } else { - g.state = generatorStateRunning - } - case generatorStateReady: + var raised *BaseException + g.mutex.Lock() + oldState := g.state + switch oldState { + case generatorStateCreated: + if sendValue != None && raisedValue == nil { + raised = f.RaiseType(TypeErrorType, "can't send non-None value to a just-started generator") + } else { g.state = generatorStateRunning - case generatorStateRunning: - raised = f.RaiseType(ValueErrorType, "generator already executing") - case generatorStateDone: - raised = f.Raise(StopIterationType.ToObject(), nil, nil) - } - g.mutex.Unlock() - // Concurrent attempts to transition to running state will raise here - // so it's guaranteed that only one thread will proceed to execute the - // block below. - if raised != nil { - return nil, raised } + case generatorStateReady: + g.state = generatorStateRunning + case generatorStateRunning: + raised = f.RaiseType(ValueErrorType, "generator already executing") + case generatorStateDone: + raised = f.Raise(StopIterationType.ToObject(), nil, nil) + } + g.mutex.Unlock() + // Concurrent attempts to transition to running state will raise here + // so it's guaranteed that only one thread will proceed to execute the + // block below. + if raised != nil { + return nil, raised } g.frame.pushFrame(f) result, raised := g.fn(sendValue, raisedValue) @@ -123,12 +120,6 @@ func generatorSend(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) { func generatorThrow(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) { argc := len(args) - if argc == 1 { - return nil, f.RaiseType(TypeErrorType, "throw expected at least 1 arguments, got 0") - } - if argc > 4 { - return nil, f.RaiseType(TypeErrorType, fmt.Sprintf("throw expected at most 3 arguments, got %v", argc-1)) - } expectedTypes := []*Type{GeneratorType, ObjectType, ObjectType, ObjectType} if argc > 1 && argc < 4 { expectedTypes = expectedTypes[:argc] diff --git a/runtime/generator_test.go b/runtime/generator_test.go index ba2c416b..573caeef 100644 --- a/runtime/generator_test.go +++ b/runtime/generator_test.go @@ -105,8 +105,8 @@ func TestGeneratorThrow(t *testing.T) { invokeTestCase{args: wrapArgs(NewGenerator(NewRootFrame(), emptyFn), TypeErrorType.ToObject()), wantExc: toBaseExceptionUnsafe(mustNotRaise(StopIterationType.Call(NewRootFrame(), nil, nil)))}, invokeTestCase{args: wrapArgs(NewGenerator(NewRootFrame(), yieldedFn), TypeErrorType.ToObject()), want: NewStr("foo").ToObject()}, invokeTestCase{args: wrapArgs(NewGenerator(NewRootFrame(), raisedFn), TypeErrorType.ToObject()), wantExc: mustCreateException(ValueErrorType, "bar")}, - invokeTestCase{args: wrapArgs(NewGenerator(NewRootFrame(), emptyFn)), wantExc: mustCreateException(TypeErrorType, "throw expected at least 1 arguments, got 0")}, - invokeTestCase{args: wrapArgs(NewGenerator(NewRootFrame(), emptyFn), "foo", "bar", "baz", "qux"), wantExc: mustCreateException(TypeErrorType, "throw expected at most 3 arguments, got 4")}, + invokeTestCase{args: wrapArgs(NewGenerator(NewRootFrame(), emptyFn)), wantExc: mustCreateException(TypeErrorType, "'throw' of 'generator' requires 4 arguments")}, + invokeTestCase{args: wrapArgs(NewGenerator(NewRootFrame(), emptyFn), "foo", "bar", "baz", "qux"), wantExc: mustCreateException(TypeErrorType, "'throw' of 'generator' requires 4 arguments")}, } for _, cas := range cases { if err := runInvokeMethodTestCase(GeneratorType, "throw", &cas); err != "" { diff --git a/testing/generator_test.py b/testing/generator_test.py index 182249c5..37f10b98 100644 --- a/testing/generator_test.py +++ b/testing/generator_test.py @@ -74,3 +74,27 @@ def gen6(): g = gen6() assert list(g) == [1] assert list(g) == [] + + +def gen7(): + yield +g = gen7() +try: + g.throw(TypeError, 'foo') +except TypeError as e: + assert "foo" in str(e) +else: + raise AssertionError + + +def gen8(): + try: + yield + except ValueError as e: + assert "foo" in str(e) + yield + else: + raise AssertionError +g = gen8() +g.next() +g.throw(ValueError, 'foo') From bb52e799d9e0afe21f32a18cdb6bbfc5643c7099 Mon Sep 17 00:00:00 2001 From: Mansour Rahimi Date: Thu, 27 Jul 2017 01:00:05 +0200 Subject: [PATCH 3/3] Remove extra writer in visit_yield --- compiler/expr_visitor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/expr_visitor.py b/compiler/expr_visitor.py index 706cd40f..fb9d2825 100644 --- a/compiler/expr_visitor.py +++ b/compiler/expr_visitor.py @@ -388,7 +388,6 @@ def visit_Yield(self, node): \t} } $result = πSent"""), result=result.name) - self.writer.write('{} = πSent'.format(result.name)) return result _BIN_OP_TEMPLATES = {