Skip to content

Commit 08f7c83

Browse files
committed
compile: Fix continue and loops in general
1 parent 3965a79 commit 08f7c83

File tree

3 files changed

+214
-13
lines changed

3 files changed

+214
-13
lines changed

compile/compile.go

+47-12
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,21 @@ import (
1414
"github.com/ncw/gpython/vm"
1515
)
1616

17-
// Loop
17+
type loopType byte
18+
19+
// type of loop
20+
const (
21+
loopLoop loopType = iota
22+
exceptLoop
23+
finallyTryLoop
24+
finallyEndLoop
25+
)
26+
27+
// Loop - used to track loops, try/except and try/finally
1828
type loop struct {
19-
Start *Label
20-
End *Label
21-
IsForLoop bool
29+
Start *Label
30+
End *Label
31+
Type loopType
2232
}
2333

2434
// Loopstack
@@ -697,12 +707,16 @@ func (c *compiler) tryFinally(node *ast.Try) {
697707
if len(node.Handlers) > 0 {
698708
c.tryExcept(node)
699709
} else {
710+
c.loops.Push(loop{Type: finallyTryLoop})
700711
c.Stmts(node.Body)
712+
c.loops.Pop()
701713
}
702714
c.Op(vm.POP_BLOCK)
703715
c.LoadConst(py.None)
704716
c.Label(end)
717+
c.loops.Push(loop{Type: finallyEndLoop})
705718
c.Stmts(node.Finalbody)
719+
c.loops.Pop()
706720
c.Op(vm.END_FINALLY)
707721
}
708722

@@ -738,6 +752,7 @@ func (c *compiler) tryFinally(node *ast.Try) {
738752
Of course, parts are not generated if Vi or Ei is not present.
739753
*/
740754
func (c *compiler) tryExcept(node *ast.Try) {
755+
c.loops.Push(loop{Type: exceptLoop})
741756
except := new(Label)
742757
orelse := new(Label)
743758
end := new(Label)
@@ -811,6 +826,7 @@ func (c *compiler) tryExcept(node *ast.Try) {
811826
c.Label(orelse)
812827
c.Stmts(node.Orelse)
813828
c.Label(end)
829+
c.loops.Pop()
814830
}
815831

816832
// Compile a try statement
@@ -1006,7 +1022,7 @@ func (c *compiler) Stmt(stmt ast.Stmt) {
10061022
c.Expr(node.Iter)
10071023
c.Op(vm.GET_ITER)
10081024
forloop := c.NewLabel()
1009-
c.loops.Push(loop{Start: forloop, End: endpopblock, IsForLoop: true})
1025+
c.loops.Push(loop{Start: forloop, End: endpopblock, Type: loopLoop})
10101026
c.Jump(vm.FOR_ITER, endfor)
10111027
c.Expr(node.Target)
10121028
c.Stmts(node.Body)
@@ -1024,7 +1040,7 @@ func (c *compiler) Stmt(stmt ast.Stmt) {
10241040
endpopblock := new(Label)
10251041
c.Jump(vm.SETUP_LOOP, endpopblock)
10261042
while := c.NewLabel()
1027-
c.loops.Push(loop{Start: while, End: endpopblock})
1043+
c.loops.Push(loop{Start: while, End: endpopblock, Type: loopLoop})
10281044
c.Expr(node.Test)
10291045
c.Jump(vm.POP_JUMP_IF_FALSE, endwhile)
10301046
c.Stmts(node.Body)
@@ -1113,16 +1129,35 @@ func (c *compiler) Stmt(stmt ast.Stmt) {
11131129
}
11141130
c.Op(vm.BREAK_LOOP)
11151131
case *ast.Continue:
1132+
const loopError = "'continue' not properly in loop"
1133+
const inFinallyError = "'continue' not supported inside 'finally' clause"
11161134
l := c.loops.Top()
11171135
if l == nil {
1118-
panic(py.ExceptionNewf(py.SyntaxError, "'continue' not properly in loop"))
1136+
panic(py.ExceptionNewf(py.SyntaxError, loopError))
11191137
}
1120-
if l.IsForLoop {
1121-
// FIXME when do we use CONTINUE_LOOP? - need to port the code from compile.c
1122-
c.Jump(vm.JUMP_ABSOLUTE, l.Start)
1123-
//c.Jump(vm.CONTINUE_LOOP, l.Start)
1124-
} else {
1138+
switch l.Type {
1139+
case loopLoop:
11251140
c.Jump(vm.JUMP_ABSOLUTE, l.Start)
1141+
case exceptLoop, finallyTryLoop:
1142+
i := len(c.loops) - 2 // next loop out
1143+
for ; i >= 0; i-- {
1144+
l = &c.loops[i]
1145+
if l.Type == loopLoop {
1146+
break
1147+
}
1148+
// Prevent continue anywhere under a finally even if hidden in a sub-try or except.
1149+
if l.Type == finallyEndLoop {
1150+
panic(py.ExceptionNewf(py.SyntaxError, inFinallyError))
1151+
}
1152+
}
1153+
if i == -1 {
1154+
panic(py.ExceptionNewf(py.SyntaxError, loopError))
1155+
}
1156+
c.Jump(vm.CONTINUE_LOOP, l.Start)
1157+
case finallyEndLoop:
1158+
panic(py.ExceptionNewf(py.SyntaxError, inFinallyError))
1159+
default:
1160+
panic("unknown loop type")
11261161
}
11271162
default:
11281163
panic(py.ExceptionNewf(py.SyntaxError, "Unknown StmtBase: %v", stmt))

compile/compile_data_test.go

+76
Original file line numberDiff line numberDiff line change
@@ -5324,4 +5324,80 @@ var compileTestData = []struct {
53245324
Firstlineno: 1,
53255325
Lnotab: "",
53265326
}, nil, ""},
5327+
{"try:\n continue\nexcept:\n pass\n ", "exec", nil, py.SyntaxError, "'continue' not properly in loop"},
5328+
{"try:\n pass\nexcept:\n continue\n ", "exec", nil, py.SyntaxError, "'continue' not properly in loop"},
5329+
{"for x in xs:\n try:\n f()\n except:\n continue\n f()\n ", "exec", &py.Code{
5330+
Argcount: 0,
5331+
Kwonlyargcount: 0,
5332+
Nlocals: 0,
5333+
Stacksize: 10,
5334+
Flags: 64,
5335+
Code: "\x78\x2e\x00\x65\x00\x00\x44\x5d\x26\x00\x5a\x01\x00\x79\x0b\x00\x65\x02\x00\x83\x00\x00\x01\x57\x6e\x0b\x00\x01\x01\x01\x77\x07\x00\x59\x6e\x01\x00\x58\x65\x02\x00\x83\x00\x00\x01\x71\x07\x00\x57\x64\x00\x00\x53",
5336+
Consts: []py.Object{py.None},
5337+
Names: []string{"xs", "x", "f"},
5338+
Varnames: []string{},
5339+
Freevars: []string{},
5340+
Cellvars: []string{},
5341+
Filename: "<string>",
5342+
Name: "<module>",
5343+
Firstlineno: 1,
5344+
Lnotab: "\x0d\x01\x03\x01\x0b\x01\x03\x01\x08\x01",
5345+
}, nil, ""},
5346+
{"for x in xs:\n try:\n f()\n continue\n finally:\n f()\n ", "exec", &py.Code{
5347+
Argcount: 0,
5348+
Kwonlyargcount: 0,
5349+
Nlocals: 0,
5350+
Stacksize: 11,
5351+
Flags: 64,
5352+
Code: "\x78\x27\x00\x65\x00\x00\x44\x5d\x1f\x00\x5a\x01\x00\x7a\x0e\x00\x65\x02\x00\x83\x00\x00\x01\x77\x07\x00\x57\x64\x00\x00\x65\x02\x00\x83\x00\x00\x01\x58\x71\x07\x00\x57\x64\x00\x00\x53",
5353+
Consts: []py.Object{py.None},
5354+
Names: []string{"xs", "x", "f"},
5355+
Varnames: []string{},
5356+
Freevars: []string{},
5357+
Cellvars: []string{},
5358+
Filename: "<string>",
5359+
Name: "<module>",
5360+
Firstlineno: 1,
5361+
Lnotab: "\x0d\x01\x03\x01\x07\x01\x07\x02",
5362+
}, nil, ""},
5363+
{"for x in xs:\n try:\n f()\n finally:\n continue\n ", "exec", nil, py.SyntaxError, "'continue' not supported inside 'finally' clause"},
5364+
{"for x in xs:\n try:\n f()\n finally:\n try:\n continue\n except:\n pass\n ", "exec", nil, py.SyntaxError, "'continue' not supported inside 'finally' clause"},
5365+
{"try:\n continue\nexcept:\n pass\n ", "exec", nil, py.SyntaxError, "'continue' not properly in loop"},
5366+
{"try:\n pass\nexcept:\n continue\n ", "exec", nil, py.SyntaxError, "'continue' not properly in loop"},
5367+
{"while truth():\n try:\n f()\n except:\n continue\n f()\n ", "exec", &py.Code{
5368+
Argcount: 0,
5369+
Kwonlyargcount: 0,
5370+
Nlocals: 0,
5371+
Stacksize: 9,
5372+
Flags: 64,
5373+
Code: "\x78\x2d\x00\x65\x00\x00\x83\x00\x00\x72\x2f\x00\x79\x0b\x00\x65\x01\x00\x83\x00\x00\x01\x57\x6e\x0b\x00\x01\x01\x01\x77\x03\x00\x59\x6e\x01\x00\x58\x65\x01\x00\x83\x00\x00\x01\x71\x03\x00\x57\x64\x00\x00\x53",
5374+
Consts: []py.Object{py.None},
5375+
Names: []string{"truth", "f"},
5376+
Varnames: []string{},
5377+
Freevars: []string{},
5378+
Cellvars: []string{},
5379+
Filename: "<string>",
5380+
Name: "<module>",
5381+
Firstlineno: 1,
5382+
Lnotab: "\x0c\x01\x03\x01\x0b\x01\x03\x01\x08\x01",
5383+
}, nil, ""},
5384+
{"while truth():\n try:\n f()\n continue\n finally:\n f()\n ", "exec", &py.Code{
5385+
Argcount: 0,
5386+
Kwonlyargcount: 0,
5387+
Nlocals: 0,
5388+
Stacksize: 10,
5389+
Flags: 64,
5390+
Code: "\x78\x26\x00\x65\x00\x00\x83\x00\x00\x72\x28\x00\x7a\x0e\x00\x65\x01\x00\x83\x00\x00\x01\x77\x03\x00\x57\x64\x00\x00\x65\x01\x00\x83\x00\x00\x01\x58\x71\x03\x00\x57\x64\x00\x00\x53",
5391+
Consts: []py.Object{py.None},
5392+
Names: []string{"truth", "f"},
5393+
Varnames: []string{},
5394+
Freevars: []string{},
5395+
Cellvars: []string{},
5396+
Filename: "<string>",
5397+
Name: "<module>",
5398+
Firstlineno: 1,
5399+
Lnotab: "\x0c\x01\x03\x01\x07\x01\x07\x02",
5400+
}, nil, ""},
5401+
{"while truth():\n try:\n f()\n finally:\n continue\n ", "exec", nil, py.SyntaxError, "'continue' not supported inside 'finally' clause"},
5402+
{"while truth():\n try:\n f()\n finally:\n try:\n continue\n except:\n pass\n ", "exec", nil, py.SyntaxError, "'continue' not supported inside 'finally' clause"},
53275403
}

compile/make_compile_test.py

+91-1
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,97 @@ def f():
455455
("del x[a, b, c]", "exec"),
456456
("del x[a, b:c, ::d]", "exec"),
457457
("del x[0, 1:2, ::5, ...]", "exec"),
458-
458+
# continue
459+
('''\
460+
try:
461+
continue
462+
except:
463+
pass
464+
''', "exec", SyntaxError),
465+
('''\
466+
try:
467+
pass
468+
except:
469+
continue
470+
''', "exec", SyntaxError),
471+
('''\
472+
for x in xs:
473+
try:
474+
f()
475+
except:
476+
continue
477+
f()
478+
''', "exec"),
479+
('''\
480+
for x in xs:
481+
try:
482+
f()
483+
continue
484+
finally:
485+
f()
486+
''', "exec"),
487+
('''\
488+
for x in xs:
489+
try:
490+
f()
491+
finally:
492+
continue
493+
''', "exec", SyntaxError),
494+
('''\
495+
for x in xs:
496+
try:
497+
f()
498+
finally:
499+
try:
500+
continue
501+
except:
502+
pass
503+
''', "exec", SyntaxError),
504+
('''\
505+
try:
506+
continue
507+
except:
508+
pass
509+
''', "exec", SyntaxError),
510+
('''\
511+
try:
512+
pass
513+
except:
514+
continue
515+
''', "exec", SyntaxError),
516+
('''\
517+
while truth():
518+
try:
519+
f()
520+
except:
521+
continue
522+
f()
523+
''', "exec"),
524+
('''\
525+
while truth():
526+
try:
527+
f()
528+
continue
529+
finally:
530+
f()
531+
''', "exec"),
532+
('''\
533+
while truth():
534+
try:
535+
f()
536+
finally:
537+
continue
538+
''', "exec", SyntaxError),
539+
('''\
540+
while truth():
541+
try:
542+
f()
543+
finally:
544+
try:
545+
continue
546+
except:
547+
pass
548+
''', "exec", SyntaxError),
459549
]
460550

461551
def string(s):

0 commit comments

Comments
 (0)