Skip to content

Commit 6a2dcc9

Browse files
committed
vm: Implement SETUP_WITH and WITH_CLEANUP
1 parent 1d7e528 commit 6a2dcc9

File tree

2 files changed

+177
-11
lines changed

2 files changed

+177
-11
lines changed

vm/eval.go

+69-11
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ func do_UNPACK_EX(vm *Vm, counts int32) {
550550
after := int(counts >> 8)
551551
totalargs := 1 + before + after
552552
seq := vm.POP()
553-
sp := len(vm.frame.Stack)
553+
sp := vm.STACK_LEVEL()
554554
vm.EXTEND(make([]py.Object, totalargs))
555555
unpack_iterable(vm, seq, before, after, sp+totalargs)
556556
}
@@ -590,10 +590,6 @@ func do_MAP_ADD(vm *Vm, i int32) {
590590
func do_RETURN_VALUE(vm *Vm, arg int32) {
591591
defer vm.CheckException()
592592
vm.retval = vm.POP()
593-
if len(vm.frame.Stack) != 0 {
594-
debugf("vmstack = %#v\n", vm.frame.Stack)
595-
panic("vm stack should be empty at this point")
596-
}
597593
vm.frame.Yielded = false
598594
vm.why = whyReturn
599595
}
@@ -747,7 +743,16 @@ func do_LOAD_BUILD_CLASS(vm *Vm, arg int32) {
747743
// UNPACK_SEQUENCE).
748744
func do_SETUP_WITH(vm *Vm, delta int32) {
749745
defer vm.CheckException()
750-
vm.NotImplemented("SETUP_WITH", delta)
746+
mgr := vm.TOP()
747+
// exit := py.ObjectLookupSpecial(mgr, "__exit__")
748+
exit := py.GetAttrString(mgr, "__exit__")
749+
vm.SET_TOP(exit)
750+
// enter := py.ObjectLookupSpecial(mgr, "__enter__")
751+
enter := py.GetAttrString(mgr, "__enter__")
752+
res := py.Call(enter, nil, nil) // FIXME method for this?
753+
// Setup the finally block before pushing the result of __enter__ on the stack.
754+
vm.frame.PushBlock(py.TryBlockSetupFinally, vm.frame.Lasti+delta, vm.STACK_LEVEL())
755+
vm.PUSH(res)
751756
}
752757

753758
// Cleans up the stack when a with statement block exits. On top of
@@ -770,7 +775,60 @@ func do_SETUP_WITH(vm *Vm, delta int32) {
770775
// exception. (But non-local gotos should still be resumed.)
771776
func do_WITH_CLEANUP(vm *Vm, arg int32) {
772777
defer vm.CheckException()
773-
vm.NotImplemented("WITH_CLEANUP", arg)
778+
var exit_func py.Object
779+
780+
exc := vm.TOP()
781+
var val py.Object = py.None
782+
var tb py.Object = py.None
783+
if exc == py.None {
784+
vm.DROP()
785+
exit_func = vm.TOP()
786+
vm.SET_TOP(exc)
787+
} else if excInt, ok := exc.(py.Int); ok {
788+
vm.DROP()
789+
switch vmStatus(excInt) {
790+
case whyReturn, whyContinue:
791+
/* Retval in TOP. */
792+
exit_func = vm.SECOND()
793+
vm.SET_SECOND(vm.TOP())
794+
vm.SET_TOP(exc)
795+
default:
796+
exit_func = vm.TOP()
797+
vm.SET_TOP(exc)
798+
}
799+
exc = py.None
800+
} else {
801+
val = vm.SECOND()
802+
tb = vm.THIRD()
803+
tp2 := vm.FOURTH()
804+
exc2 := vm.PEEK(5)
805+
tb2 := vm.PEEK(6)
806+
exit_func = vm.PEEK(7)
807+
vm.SET_VALUE(7, tb2)
808+
vm.SET_VALUE(6, exc2)
809+
vm.SET_VALUE(5, tp2)
810+
/* UNWIND_EXCEPT_HANDLER will pop this off. */
811+
vm.SET_FOURTH(nil)
812+
/* We just shifted the stack down, so we have
813+
to tell the except handler block that the
814+
values are lower than it expects. */
815+
block := vm.frame.Block
816+
if block.Type != py.TryBlockExceptHandler {
817+
panic("vm: WITH_CLEANUP expecting TryBlockExceptHandler")
818+
}
819+
block.Level--
820+
}
821+
/* XXX Not the fastest way to call it... */
822+
res := py.Call(exit_func, []py.Object{exc, val, tb}, nil)
823+
824+
err := false
825+
if exc != py.None {
826+
err = res == py.True
827+
}
828+
if err {
829+
/* There was an exception and a True return */
830+
vm.PUSH(py.Int(whySilenced))
831+
}
774832
}
775833

776834
// All of the following opcodes expect arguments. An argument is two bytes, with the more significant byte last.
@@ -807,7 +865,7 @@ func do_UNPACK_SEQUENCE(vm *Vm, count int32) {
807865
} else if list, ok := it.(*py.List); ok && list.Len() == args {
808866
vm.EXTEND_REVERSED(list.Items)
809867
} else {
810-
sp := len(vm.frame.Stack)
868+
sp := vm.STACK_LEVEL()
811869
vm.EXTEND(make([]py.Object, args))
812870
unpack_iterable(vm, it, args, -1, sp+args)
813871
}
@@ -1087,21 +1145,21 @@ func do_LOAD_GLOBAL(vm *Vm, namei int32) {
10871145
// from the current instruction with a size of delta bytes.
10881146
func do_SETUP_LOOP(vm *Vm, delta int32) {
10891147
defer vm.CheckException()
1090-
vm.frame.PushBlock(py.TryBlockSetupLoop, vm.frame.Lasti+delta, len(vm.frame.Stack))
1148+
vm.frame.PushBlock(py.TryBlockSetupLoop, vm.frame.Lasti+delta, vm.STACK_LEVEL())
10911149
}
10921150

10931151
// Pushes a try block from a try-except clause onto the block
10941152
// stack. delta points to the first except block.
10951153
func do_SETUP_EXCEPT(vm *Vm, delta int32) {
10961154
defer vm.CheckException()
1097-
vm.frame.PushBlock(py.TryBlockSetupExcept, vm.frame.Lasti+delta, len(vm.frame.Stack))
1155+
vm.frame.PushBlock(py.TryBlockSetupExcept, vm.frame.Lasti+delta, vm.STACK_LEVEL())
10981156
}
10991157

11001158
// Pushes a try block from a try-except clause onto the block
11011159
// stack. delta points to the finally block.
11021160
func do_SETUP_FINALLY(vm *Vm, delta int32) {
11031161
defer vm.CheckException()
1104-
vm.frame.PushBlock(py.TryBlockSetupFinally, vm.frame.Lasti+delta, len(vm.frame.Stack))
1162+
vm.frame.PushBlock(py.TryBlockSetupFinally, vm.frame.Lasti+delta, vm.STACK_LEVEL())
11051163
}
11061164

11071165
// Store a key and value pair in a dictionary. Pops the key and value

vm/tests/with.py

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
class Context():
2+
def __init__(self):
3+
self.state = "__init__"
4+
def __enter__(self):
5+
self.state = "__enter__"
6+
return 42
7+
def __exit__(self, type, value, traceback):
8+
self.state = "__exit__"
9+
10+
doc="with"
11+
c = Context()
12+
assert c.state == "__init__"
13+
with c:
14+
assert c.state == "__enter__"
15+
16+
assert c.state == "__exit__"
17+
18+
doc="with as"
19+
c = Context()
20+
assert c.state == "__init__"
21+
with c as a:
22+
assert a == 42
23+
assert c.state == "__enter__"
24+
25+
assert c.state == "__exit__"
26+
27+
doc="with exception"
28+
c = Context()
29+
ok = False
30+
try:
31+
assert c.state == "__init__"
32+
with c:
33+
assert c.state == "__enter__"
34+
raise ValueError("potato")
35+
except ValueError:
36+
ok = True
37+
assert c.state == "__exit__"
38+
assert ok, "ValueError not raised"
39+
40+
class SilencedContext():
41+
def __init__(self):
42+
self.state = "__init__"
43+
def __enter__(self):
44+
self.state = "__enter__"
45+
def __exit__(self, type, value, traceback):
46+
"""Return True to silence the error"""
47+
self.type = type
48+
self.value = value
49+
self.traceback = traceback
50+
self.state = "__exit__"
51+
return True
52+
53+
doc="with silenced error"
54+
c = SilencedContext()
55+
assert c.state == "__init__"
56+
with c:
57+
assert c.state == "__enter__"
58+
raise ValueError("potato")
59+
assert c.state == "__exit__"
60+
assert c.type == ValueError
61+
assert c.value is not None
62+
assert c.traceback is not None
63+
64+
doc="with silenced error no error"
65+
c = SilencedContext()
66+
assert c.state == "__init__"
67+
with c:
68+
assert c.state == "__enter__"
69+
assert c.state == "__exit__"
70+
assert c.type is None
71+
assert c.value is None
72+
assert c.traceback is None
73+
74+
doc="with in loop: break"
75+
c = Context()
76+
assert c.state == "__init__"
77+
while True:
78+
with c:
79+
assert c.state == "__enter__"
80+
break
81+
assert c.state == "__exit__"
82+
83+
doc="with in loop: continue"
84+
c = Context()
85+
assert c.state == "__init__"
86+
first = True
87+
while True:
88+
if not first:
89+
break
90+
with c:
91+
assert c.state == "__enter__"
92+
first = False
93+
continue
94+
assert c.state == "__exit__"
95+
96+
doc="return in with"
97+
c = Context()
98+
def return_in_with():
99+
assert c.state == "__init__"
100+
first = True
101+
with c:
102+
assert c.state == "__enter__"
103+
first = False
104+
return "potato"
105+
assert return_in_with() == "potato"
106+
assert c.state == "__exit__"
107+
108+
doc="finished"

0 commit comments

Comments
 (0)