Skip to content
This repository was archived by the owner on Mar 23, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions compiler/expr_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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("""\
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trotterdylan not sure if try/except block is able to catch this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you want to do:

if πRaised != nil {
	πE = πRaised
	πRaised = nil
	if πE != nil {
		continue
	}
}

You could also merge the write() call below into this:

self.writer.write_tmpl(textwrap.dedent("""\
    if πRaised != nil {
    \tπE = πRaised
    \tπRaised = nil
    \tif πE != nil {
    \t\tcontinue
    \t}
    }
    $result = πSent"""), result=result.name)

if πRaised != nil {
\treturn nil, πRaised
}"""))
self.writer.write('{} = πSent'.format(result.name))
return result

Expand Down
7 changes: 6 additions & 1 deletion compiler/stmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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) '
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe call it πThrown

'(*π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')
Expand Down
84 changes: 57 additions & 27 deletions runtime/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package grumpy

import (
"fmt"
"reflect"
"sync"
)
Expand All @@ -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
Expand All @@ -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 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trotterdylan do we still need to update g.state while having throw?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think you can remove this check and update g.state normally for throw.

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)
Expand All @@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can leave out the argc == 1 and argc > 4 checks here. If argc < 2 || argc > 4 then the checkMethodArgs will fail because it won't match expectedTypes. The error message isn't ideal in that case, but I think the simplicity warrants it.

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}
Expand Down
32 changes: 28 additions & 4 deletions runtime/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ 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
}
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()
Expand All @@ -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{
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
}
}