Skip to content

Commit 9e25827

Browse files
committed
Generators and YIELD_VALUE
1 parent aa100dc commit 9e25827

File tree

5 files changed

+178
-45
lines changed

5 files changed

+178
-45
lines changed

py/frame.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ type Frame struct {
2424
// Next free slot in f_valuestack. Frame creation sets to f_valuestack.
2525
// Frame evaluation usually NULLs it, but a frame that yields sets it
2626
// to the current stack top.
27-
Stacktop *Object
28-
Trace Object // Trace function
27+
// Stacktop *Object
28+
Yielded bool // set if the function yielded, cleared otherwise
29+
Trace Object // Trace function
2930

3031
// In a generator, we need to be able to swap between the exception
3132
// state inside the generator and the exception state of the calling
@@ -62,6 +63,17 @@ func (o *Frame) Type() *Type {
6263
return FrameType
6364
}
6465

66+
// Make a new frame for a code object
67+
func NewFrame(globals, locals StringDict, code *Code) *Frame {
68+
return &Frame{
69+
Globals: globals,
70+
Locals: locals,
71+
Code: code,
72+
Builtins: Builtins.Globals,
73+
Stack: make([]Object, 0, code.Stacksize),
74+
}
75+
}
76+
6577
// Python names are looked up in three scopes
6678
//
6779
// First the local scope

py/generator.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Generator objects
2+
3+
package py
4+
5+
// A python Generator object
6+
type Generator struct {
7+
// Note: gi_frame can be NULL if the generator is "finished"
8+
Frame *Frame
9+
10+
// True if generator is being executed.
11+
Running bool
12+
13+
// The code object backing the generator
14+
Code *Code
15+
16+
// List of weak reference.
17+
Weakreflist Object
18+
}
19+
20+
var GeneratorType = NewType("generator", "generator object")
21+
22+
// Type of this object
23+
func (o *Generator) Type() *Type {
24+
return GeneratorType
25+
}
26+
27+
// Define a new generator
28+
func NewGenerator(frame *Frame) *Generator {
29+
g := &Generator{
30+
Frame: frame,
31+
Running: false,
32+
Code: frame.Code,
33+
}
34+
return g
35+
}
36+
37+
func (it *Generator) M__iter__() Object {
38+
return it
39+
}
40+
41+
// generator.__next__()
42+
//
43+
// Starts the execution of a generator function or resumes it at the
44+
// last executed yield expression. When a generator function is
45+
// resumed with a __next__() method, the current yield expression
46+
// always evaluates to None. The execution then continues to the next
47+
// yield expression, where the generator is suspended again, and the
48+
// value of the expression_list is returned to next()‘s caller. If the
49+
// generator exits without yielding another value, a StopIteration
50+
// exception is raised.
51+
//
52+
// This method is normally called implicitly, e.g. by a for loop, or by the built-in next() function.
53+
func (it *Generator) M__next__() Object {
54+
it.Running = true
55+
res, err := RunFrame(it.Frame)
56+
it.Running = false
57+
// Push a None on the stack for next time
58+
// FIXME this value is the one sent by Send
59+
it.Frame.Stack = append(it.Frame.Stack, None)
60+
// FIXME not correct
61+
if err != nil {
62+
panic(err)
63+
}
64+
if it.Frame.Yielded {
65+
return res
66+
}
67+
panic(StopIteration)
68+
}
69+
70+
// generator.send(value)
71+
//
72+
// Resumes the execution and “sends” a value into the generator
73+
// function. The value argument becomes the result of the current
74+
// yield expression. The send() method returns the next value yielded
75+
// by the generator, or raises StopIteration if the generator exits
76+
// without yielding another value. When send() is called to start the
77+
// generator, it must be called with None as the argument, because
78+
// there is no yield expression that could receive the value.
79+
func (it *Generator) Send(value Object) {
80+
panic("generator send not implemented")
81+
}
82+
83+
// generator.throw(type[, value[, traceback]])
84+
//
85+
// Raises an exception of type type at the point where generator was
86+
// paused, and returns the next value yielded by the generator
87+
// function. If the generator exits without yielding another value, a
88+
// StopIteration exception is raised. If the generator function does
89+
// not catch the passed-in exception, or raises a different exception,
90+
// then that exception propagates to the caller.
91+
func (it *Generator) Throw(args Tuple, kwargs StringDict) {
92+
panic("generator throw not implemented")
93+
}
94+
95+
// generator.close()
96+
//
97+
// Raises a GeneratorExit at the point where the generator function
98+
// was paused. If the generator function then raises StopIteration (by
99+
// exiting normally, or due to already being closed) or GeneratorExit
100+
// (by not catching the exception), close returns to its caller. If
101+
// the generator yields a value, a RuntimeError is raised. If the
102+
// generator raises any other exception, it is propagated to the
103+
// caller. close() does nothing if the generator has already exited
104+
// due to an exception or normal exit.
105+
func (it *Generator) Close() {
106+
panic("generator close not implemented")
107+
}
108+
109+
// Check interface is satisfied
110+
var _ I_iterator = (*Generator)(nil)

py/py.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ type Object interface {
99
// Some well known objects
1010
var (
1111
Ellipsis Object
12-
// See vm.Run - set to avoid circular import
13-
Run func(globals, locals StringDict, code *Code) (res Object, err error)
12+
// See vm/eval.go - set to avoid circular import
13+
Run func(globals, locals StringDict, code *Code) (res Object, err error)
14+
RunFrame func(frame *Frame) (res Object, err error)
1415
)
1516

1617
// Called to create a new instance of class cls. __new__() is a static method (special-cased so you need not declare it as such) that takes the class of which an instance was requested as its first argument. The remaining arguments are those passed to the object constructor expression (the call to the class). The return value of __new__() should be the new object instance (usually an instance of cls).

vm/eval.go

Lines changed: 32 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,8 @@ func do_RETURN_VALUE(vm *Vm, arg int32) {
417417
fmt.Printf("vmstack = %#v\n", vm.frame.Stack)
418418
panic("vm stack should be empty at this point")
419419
}
420-
vm.PopFrame()
420+
vm.frame.Yielded = false
421+
vm.exit = exitReturn
421422
}
422423

423424
// Pops TOS and delegates to it as a subiterator from a generator.
@@ -427,7 +428,9 @@ func do_YIELD_FROM(vm *Vm, arg int32) {
427428

428429
// Pops TOS and yields it from a generator.
429430
func do_YIELD_VALUE(vm *Vm, arg int32) {
430-
vm.NotImplemented("YIELD_VALUE", arg)
431+
vm.result = vm.POP()
432+
vm.frame.Yielded = true
433+
vm.exit = exitYield
431434
}
432435

433436
// Loads all symbols not starting with '_' directly from the module
@@ -958,43 +961,13 @@ func (vm *Vm) Call(fnObj py.Object, args []py.Object, kwargsTuple []py.Object) {
958961
vm.PUSH(py.Call(fnObj, args, kwargs))
959962
}
960963

961-
// Make a new Frame with globals, locals and Code on the frames stack
962-
func (vm *Vm) PushFrame(globals, locals py.StringDict, code *py.Code) {
963-
frame := py.Frame{
964-
Globals: globals,
965-
Locals: locals,
966-
Code: code,
967-
Builtins: py.Builtins.Globals,
968-
Stack: make([]py.Object, 0, code.Stacksize),
969-
}
970-
vm.frames = append(vm.frames, frame)
971-
vm.frame = &vm.frames[len(vm.frames)-1]
972-
}
973-
974-
// Drop the current frame
975-
func (vm *Vm) PopFrame() {
976-
vm.frames = vm.frames[:len(vm.frames)-1]
977-
if len(vm.frames) > 0 {
978-
vm.frame = &vm.frames[len(vm.frames)-1]
979-
} else {
980-
vm.frame = nil
981-
}
982-
}
983-
984-
// Write the py global to avoid circular import
985-
func init() {
986-
py.Run = Run
987-
}
988-
989-
// Run the virtual machine on the code object in the module
964+
// Run the virtual machine on a Frame object
990965
//
991966
// FIXME figure out how we are going to signal exceptions!
992967
//
993-
// Any parameters are expected to have been decoded into locals
994-
//
995968
// Returns an Object and an error
996-
func Run(globals, locals py.StringDict, code *py.Code) (res py.Object, err error) {
997-
vm := NewVm()
969+
func RunFrame(frame *py.Frame) (res py.Object, err error) {
970+
vm := NewVm(frame)
998971
defer func() {
999972
if r := recover(); r != nil {
1000973
switch x := r.(type) {
@@ -1010,11 +983,10 @@ func Run(globals, locals py.StringDict, code *py.Code) (res py.Object, err error
1010983
debug.PrintStack()
1011984
}
1012985
}()
1013-
vm.PushFrame(globals, locals, code)
1014986

1015987
var opcode byte
1016988
var arg int32
1017-
for vm.frame != nil {
989+
for vm.exit == exitNot {
1018990
frame := vm.frame
1019991
fmt.Printf("* %4d:", frame.Lasti)
1020992
opcodes := frame.Code.Code
@@ -1045,3 +1017,26 @@ func Run(globals, locals py.StringDict, code *py.Code) (res py.Object, err error
10451017
}
10461018
return vm.result, nil
10471019
}
1020+
1021+
// Run the virtual machine on a Code object
1022+
//
1023+
// Any parameters are expected to have been decoded into locals
1024+
//
1025+
// Returns an Object and an error
1026+
func Run(globals, locals py.StringDict, code *py.Code) (res py.Object, err error) {
1027+
frame := py.NewFrame(globals, locals, code)
1028+
1029+
// If this is a generator then make a generator object from
1030+
// the frame and return that instead
1031+
if code.Flags&py.CO_GENERATOR != 0 {
1032+
return py.NewGenerator(frame), nil
1033+
}
1034+
1035+
return RunFrame(frame)
1036+
}
1037+
1038+
// Write the py global to avoid circular import
1039+
func init() {
1040+
py.Run = Run
1041+
py.RunFrame = RunFrame
1042+
}

vm/vm.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,23 @@ import (
55
"github.com/ncw/gpython/py"
66
)
77

8+
// VM exit type
9+
type vmExit byte
10+
11+
// VM exit values
12+
const (
13+
exitNot = vmExit(iota) // No error
14+
exitException // Exception occurred
15+
exitReraise // Exception re-raised by 'finally'
16+
exitReturn // 'return' statement
17+
exitBreak // 'break' statement
18+
exitContinue // 'continue' statement
19+
exitYield // 'yield' operator
20+
exitSilenced // Exception silenced by 'with'
21+
)
22+
823
// Virtual machine state
924
type Vm struct {
10-
// Frame stack
11-
frames []py.Frame
1225
// Current frame
1326
frame *py.Frame
1427
// Whether ext should be added to the next arg
@@ -17,11 +30,13 @@ type Vm struct {
1730
ext int32
1831
// Return value
1932
result py.Object
33+
// Exit value
34+
exit vmExit
2035
}
2136

2237
// Make a new VM
23-
func NewVm() *Vm {
38+
func NewVm(frame *py.Frame) *Vm {
2439
return &Vm{
25-
frames: make([]py.Frame, 0, 16),
40+
frame: frame,
2641
}
2742
}

0 commit comments

Comments
 (0)