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

Lines changed: 69 additions & 11 deletions
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

Lines changed: 108 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy