Skip to content

Commit

Permalink
compile: implement try/except/finally
Browse files Browse the repository at this point in the history
  • Loading branch information
ncw committed May 12, 2015
1 parent 54e8dcb commit 8072a76
Show file tree
Hide file tree
Showing 3 changed files with 299 additions and 40 deletions.
228 changes: 189 additions & 39 deletions compile/compile.go
Expand Up @@ -347,13 +347,6 @@ func (c *compiler) Jump(Op byte, Dest *Label) {
}
}

// Compile statements
func (c *compiler) Stmts(stmts []ast.Stmt) {
for _, stmt := range stmts {
c.Stmt(stmt)
}
}

/* The test for LOCAL must come before the test for FREE in order to
handle classes where name is both local and free. The local var is
a method and the free var is a free var referenced within a method.
Expand Down Expand Up @@ -494,9 +487,7 @@ func (c *compiler) compileFunc(compilerScope compilerScopeType, Ast ast.Ast, Arg
code.Kwonlyargcount = int32(len(Args.Kwonlyargs))

// Defaults
for _, expr := range Args.Defaults {
c.Expr(expr)
}
c.Exprs(Args.Defaults)

// KwDefaults
if len(Args.Kwonlyargs) != len(Args.KwDefaults) {
Expand Down Expand Up @@ -532,9 +523,7 @@ func (c *compiler) compileFunc(compilerScope compilerScopeType, Ast ast.Ast, Arg
}

// Load decorators onto stack
for _, expr := range DecoratorList {
c.Expr(expr)
}
c.Exprs(DecoratorList)

// Make function or closure, leaving it on the stack
posdefaults := uint32(len(Args.Defaults))
Expand All @@ -551,9 +540,7 @@ func (c *compiler) compileFunc(compilerScope compilerScopeType, Ast ast.Ast, Arg
// Compile class definition
func (c *compiler) class(Ast ast.Ast, class *ast.ClassDef) {
// Load decorators onto stack
for _, expr := range class.DecoratorList {
c.Expr(expr)
}
c.Exprs(class.DecoratorList)

/* ultimately generate code for:
<name> = __build_class__(<func>, <name>, *<bases>, **<keywords>)
Expand Down Expand Up @@ -643,9 +630,7 @@ func (c *compiler) with(node *ast.With, pos int) {
pos++
if pos == len(node.Items) {
/* BLOCK code */
for _, stmt := range node.Body {
c.Stmt(stmt)
}
c.Stmts(node.Body)
} else {
c.with(node, pos)
}
Expand All @@ -664,6 +649,176 @@ func (c *compiler) with(node *ast.With, pos int) {
c.Op(vm.END_FINALLY)
}

/* Code generated for "try: <body> finally: <finalbody>" is as follows:
SETUP_FINALLY L
<code for body>
POP_BLOCK
LOAD_CONST <None>
L: <code for finalbody>
END_FINALLY
The special instructions use the block stack. Each block
stack entry contains the instruction that created it (here
SETUP_FINALLY), the level of the value stack at the time the
block stack entry was created, and a label (here L).
SETUP_FINALLY:
Pushes the current value stack level and the label
onto the block stack.
POP_BLOCK:
Pops en entry from the block stack, and pops the value
stack until its level is the same as indicated on the
block stack. (The label is ignored.)
END_FINALLY:
Pops a variable number of entries from the *value* stack
and re-raises the exception they specify. The number of
entries popped depends on the (pseudo) exception type.
The block stack is unwound when an exception is raised:
when a SETUP_FINALLY entry is found, the exception is pushed
onto the value stack (and the exception condition is cleared),
and the interpreter jumps to the label gotten from the block
stack.
*/
func (c *compiler) tryFinally(node *ast.Try) {
end := new(Label)
c.Jump(vm.SETUP_FINALLY, end)
if len(node.Handlers) > 0 {
c.tryExcept(node)
} else {
c.Stmts(node.Body)
}
c.Op(vm.POP_BLOCK)
c.LoadConst(py.None)
c.Label(end)
c.Stmts(node.Finalbody)
c.Op(vm.END_FINALLY)
}

/*
Code generated for "try: S except E1 as V1: S1 except E2 as V2: S2 ...":
(The contents of the value stack is shown in [], with the top
at the right; 'tb' is trace-back info, 'val' the exception's
associated value, and 'exc' the exception.)
Value stack Label Instruction Argument
[] SETUP_EXCEPT L1
[] <code for S>
[] POP_BLOCK
[] JUMP_FORWARD L0
[tb, val, exc] L1: DUP )
[tb, val, exc, exc] <evaluate E1> )
[tb, val, exc, exc, E1] COMPARE_OP EXC_MATCH ) only if E1
[tb, val, exc, 1-or-0] POP_JUMP_IF_FALSE L2 )
[tb, val, exc] POP
[tb, val] <assign to V1> (or POP if no V1)
[tb] POP
[] <code for S1>
JUMP_FORWARD L0
[tb, val, exc] L2: DUP
.............................etc.......................
[tb, val, exc] Ln+1: END_FINALLY # re-raise exception
[] L0: <next statement>
Of course, parts are not generated if Vi or Ei is not present.
*/
func (c *compiler) tryExcept(node *ast.Try) {
except := new(Label)
orelse := new(Label)
end := new(Label)
c.Jump(vm.SETUP_EXCEPT, except)
c.Stmts(node.Body)
c.Op(vm.POP_BLOCK)
c.Jump(vm.JUMP_FORWARD, orelse)
n := len(node.Handlers)
c.Label(except)
for i, handler := range node.Handlers {
if handler.ExprType == nil && i < n-1 {
panic(py.ExceptionNewf(py.SyntaxError, "default 'except:' must be last"))
}
// FIXME c.u.u_lineno_set = 0
// c.u.u_lineno = handler.lineno
// c.u.u_col_offset = handler.col_offset
except := new(Label)
if handler.ExprType != nil {
c.Op(vm.DUP_TOP)
c.Expr(handler.ExprType)
c.OpArg(vm.COMPARE_OP, vm.PyCmp_EXC_MATCH)
c.Jump(vm.POP_JUMP_IF_FALSE, except)
}
c.Op(vm.POP_TOP)
if handler.Name != "" {
cleanup_end := new(Label)
c.NameOp(string(handler.Name), ast.Store)
c.Op(vm.POP_TOP)

/*
try:
# body
except type as name:
try:
# body
finally:
name = None
del name
*/

/* second try: */
c.Jump(vm.SETUP_FINALLY, cleanup_end)

/* second # body */
c.Stmts(handler.Body)
c.Op(vm.POP_BLOCK)
c.Op(vm.POP_EXCEPT)

/* finally: */
c.LoadConst(py.None)
c.Label(cleanup_end)

/* name = None */
c.LoadConst(py.None)
c.NameOp(string(handler.Name), ast.Store)

/* del name */
c.NameOp(string(handler.Name), ast.Del)

c.Op(vm.END_FINALLY)
} else {
c.Op(vm.POP_TOP)
c.Op(vm.POP_TOP)
c.Stmts(handler.Body)
c.Op(vm.POP_EXCEPT)
}
c.Jump(vm.JUMP_FORWARD, end)
c.Label(except)
}
c.Op(vm.END_FINALLY)
c.Label(orelse)
c.Stmts(node.Orelse)
c.Label(end)
}

// Compile a try statement
func (c *compiler) try(node *ast.Try) {
if len(node.Finalbody) > 0 {
c.tryFinally(node)
} else {
c.tryExcept(node)
}
}

// Compile statements
func (c *compiler) Stmts(stmts []ast.Stmt) {
for _, stmt := range stmts {
c.Stmt(stmt)
}
}

// Compile statement
func (c *compiler) Stmt(stmt ast.Stmt) {
switch node := stmt.(type) {
Expand Down Expand Up @@ -766,16 +921,12 @@ func (c *compiler) Stmt(stmt ast.Stmt) {
c.loops.Push(loop{Start: forloop, End: endpopblock, IsForLoop: true})
c.Jump(vm.FOR_ITER, endfor)
c.Expr(node.Target)
for _, stmt := range node.Body {
c.Stmt(stmt)
}
c.Stmts(node.Body)
c.Jump(vm.JUMP_ABSOLUTE, forloop)
c.Label(endfor)
c.Op(vm.POP_BLOCK)
c.loops.Pop()
for _, stmt := range node.Orelse {
c.Stmt(stmt)
}
c.Stmts(node.Orelse)
c.Label(endpopblock)
case *ast.While:
// Test Expr
Expand All @@ -788,16 +939,12 @@ func (c *compiler) Stmt(stmt ast.Stmt) {
c.loops.Push(loop{Start: while, End: endpopblock})
c.Expr(node.Test)
c.Jump(vm.POP_JUMP_IF_FALSE, endwhile)
for _, stmt := range node.Body {
c.Stmt(stmt)
}
c.Stmts(node.Body)
c.Jump(vm.JUMP_ABSOLUTE, while)
c.Label(endwhile)
c.Op(vm.POP_BLOCK)
c.loops.Pop()
for _, stmt := range node.Orelse {
c.Stmt(stmt)
}
c.Stmts(node.Orelse)
c.Label(endpopblock)
case *ast.If:
// Test Expr
Expand All @@ -807,17 +954,13 @@ func (c *compiler) Stmt(stmt ast.Stmt) {
endif := new(Label)
c.Expr(node.Test)
c.Jump(vm.POP_JUMP_IF_FALSE, orelse)
for _, stmt := range node.Body {
c.Stmt(stmt)
}
c.Stmts(node.Body)
// FIXME this puts a JUMP_FORWARD in when not
// necessary (when no Orelse statements) but it
// matches python3.4 (this is fixed in py3.5)
c.Jump(vm.JUMP_FORWARD, endif)
c.Label(orelse)
for _, stmt := range node.Orelse {
c.Stmt(stmt)
}
c.Stmts(node.Orelse)
c.Label(endif)
case *ast.With:
// Items []*WithItem
Expand All @@ -841,7 +984,7 @@ func (c *compiler) Stmt(stmt ast.Stmt) {
// Handlers []*ExceptHandler
// Orelse []Stmt
// Finalbody []Stmt
panic("FIXME compile: Try not implemented")
c.try(node)
case *ast.Assert:
// Test Expr
// Msg Expr
Expand Down Expand Up @@ -913,7 +1056,7 @@ func (c *compiler) NameOp(name string, ctx ast.ExprContext) {
// PyObject *mangled;
/* XXX AugStore isn't used anywhere! */

// FIXME mangled = _Py_Mangle(c->u->u_private, name);
// FIXME mangled = _Py_Mangle(c.u.u_private, name);
mangled := name

if name == "None" || name == "True" || name == "False" {
Expand Down Expand Up @@ -1143,6 +1286,13 @@ func (c *compiler) comprehension(expr ast.Expr, generators []ast.Comprehension)
}

// Compile expressions
func (c *compiler) Exprs(exprs []ast.Expr) {
for _, expr := range exprs {
c.Expr(expr)
}
}

// Compile and expression
func (c *compiler) Expr(expr ast.Expr) {
switch node := expr.(type) {
case *ast.BoolOp:
Expand Down
69 changes: 69 additions & 0 deletions compile/compile_data_test.go
Expand Up @@ -3483,4 +3483,73 @@ var compileTestData = []struct {
Firstlineno: 1,
Lnotab: "\x0c\x01\x0c\x01",
}, nil, ""},
{"try:\n f()\nexcept Exception:\n h()\n", "exec", &py.Code{
Argcount: 0,
Kwonlyargcount: 0,
Nlocals: 0,
Stacksize: 11,
Flags: 64,
Code: "\x79\x0b\x00\x65\x00\x00\x83\x00\x00\x01\x57\x6e\x19\x00\x04\x65\x01\x00\x6b\x0a\x00\x72\x26\x00\x01\x01\x01\x65\x02\x00\x83\x00\x00\x01\x59\x6e\x01\x00\x58\x64\x00\x00\x53",
Consts: []py.Object{py.None},
Names: []string{"f", "Exception", "h"},
Varnames: []string{},
Freevars: []string{},
Cellvars: []string{},
Filename: "<string>",
Name: "<module>",
Firstlineno: 1,
Lnotab: "\x03\x01\x0b\x01\x0d\x01",
}, nil, ""},
{"try:\n f()\nexcept Exception as e:\n h(e)\nexcept (Exception1, Exception2) as e:\n i(e)\nexcept:\n j()\nelse:\n potato()\n", "exec", &py.Code{
Argcount: 0,
Kwonlyargcount: 0,
Nlocals: 0,
Stacksize: 16,
Flags: 64,
Code: "\x79\x0b\x00\x65\x00\x00\x83\x00\x00\x01\x57\x6e\x71\x00\x04\x65\x01\x00\x6b\x0a\x00\x72\x3c\x00\x01\x5a\x02\x00\x01\x7a\x0f\x00\x65\x03\x00\x65\x02\x00\x83\x01\x00\x01\x57\x59\x64\x00\x00\x64\x00\x00\x5a\x02\x00\x5b\x02\x00\x58\x6e\x4a\x00\x04\x65\x04\x00\x65\x05\x00\x66\x02\x00\x6b\x0a\x00\x72\x70\x00\x01\x5a\x02\x00\x01\x7a\x0f\x00\x65\x06\x00\x65\x02\x00\x83\x01\x00\x01\x57\x59\x64\x00\x00\x64\x00\x00\x5a\x02\x00\x5b\x02\x00\x58\x6e\x16\x00\x01\x01\x01\x65\x07\x00\x83\x00\x00\x01\x59\x6e\x08\x00\x58\x65\x08\x00\x83\x00\x00\x01\x64\x00\x00\x53",
Consts: []py.Object{py.None},
Names: []string{"f", "Exception", "e", "h", "Exception1", "Exception2", "i", "j", "potato"},
Varnames: []string{},
Freevars: []string{},
Cellvars: []string{},
Filename: "<string>",
Name: "<module>",
Firstlineno: 1,
Lnotab: "\x03\x01\x0b\x01\x12\x01\x1c\x01\x18\x01\x1c\x01\x03\x01\x0c\x02",
}, nil, ""},
{"try:\n f()\nexcept:\n j()\nexcept Exception as e:\n h(e)\n ", "exec", nil, py.SyntaxError, "default 'except:' must be last"},
{"try:\n f()\nfinally:\n j()\n ", "exec", &py.Code{
Argcount: 0,
Kwonlyargcount: 0,
Nlocals: 0,
Stacksize: 10,
Flags: 64,
Code: "\x7a\x0b\x00\x65\x00\x00\x83\x00\x00\x01\x57\x64\x00\x00\x65\x01\x00\x83\x00\x00\x01\x58\x64\x00\x00\x53",
Consts: []py.Object{py.None},
Names: []string{"f", "j"},
Varnames: []string{},
Freevars: []string{},
Cellvars: []string{},
Filename: "<string>",
Name: "<module>",
Firstlineno: 1,
Lnotab: "\x03\x01\x0b\x02",
}, nil, ""},
{"try:\n f()\nexcept Exception as e:\n h(e)\nfinally:\n j()\n ", "exec", &py.Code{
Argcount: 0,
Kwonlyargcount: 0,
Nlocals: 0,
Stacksize: 22,
Flags: 64,
Code: "\x7a\x41\x00\x79\x0b\x00\x65\x00\x00\x83\x00\x00\x01\x57\x6e\x2f\x00\x04\x65\x01\x00\x6b\x0a\x00\x72\x3f\x00\x01\x5a\x02\x00\x01\x7a\x0f\x00\x65\x03\x00\x65\x02\x00\x83\x01\x00\x01\x57\x59\x64\x00\x00\x64\x00\x00\x5a\x02\x00\x5b\x02\x00\x58\x6e\x01\x00\x58\x57\x64\x00\x00\x65\x04\x00\x83\x00\x00\x01\x58\x64\x00\x00\x53",
Consts: []py.Object{py.None},
Names: []string{"f", "Exception", "e", "h", "j"},
Varnames: []string{},
Freevars: []string{},
Cellvars: []string{},
Filename: "<string>",
Name: "<module>",
Firstlineno: 1,
Lnotab: "\x06\x01\x0b\x01\x12\x01\x21\x02",
}, nil, ""},
}

0 comments on commit 8072a76

Please sign in to comment.