Populate exception variable value in catch blocks

This commit is contained in:
joeduffy 2017-01-25 13:58:48 -08:00
parent 240a08b73e
commit 43556de6d7
2 changed files with 61 additions and 41 deletions

View file

@ -56,6 +56,18 @@ func (e *evaluator) EvaluatePackage(pkg *pack.Package) graph.Graph {
return nil
}
// Utility functions
func (e *evaluator) pushScope() {
e.locals.Push() // for local variables
e.ctx.Scope.Pop() // for symbol bindings
}
func (e *evaluator) popScope() {
e.locals.Pop() // for local variables.
e.ctx.Scope.Pop() // for symbol bindings.
}
// Statements
func (e *evaluator) evalStatement(node ast.Statement) *unwind {
@ -95,12 +107,8 @@ func (e *evaluator) evalStatement(node ast.Statement) *unwind {
func (e *evaluator) evalBlock(node *ast.Block) *unwind {
// Push a scope at the start, and pop it at afterwards; both for the symbol context and local variable values.
e.ctx.Scope.Push()
e.locals.Push()
defer func() {
e.locals.Pop()
e.ctx.Scope.Pop()
}()
e.pushScope()
defer e.popScope()
for _, stmt := range node.Statements {
if uw := e.evalStatement(stmt); uw != nil {
@ -119,29 +127,12 @@ func (e *evaluator) evalLocalVariableDeclaration(node *ast.LocalVariableDeclarat
// If there is a default value, set it now.
if node.Local.Default != nil {
obj := NewConstantObject(*node.Local.Default)
contract.Assert(obj.Type == sym.Type())
e.locals.Values[sym] = obj
e.locals.SetValue(sym, obj)
}
return nil
}
func (e *evaluator) evalBreakStatement(node *ast.BreakStatement) *unwind {
var label *tokens.Name
if node.Label != nil {
label = &node.Label.Ident
}
return breakUnwind(label)
}
func (e *evaluator) evalContinueStatement(node *ast.ContinueStatement) *unwind {
var label *tokens.Name
if node.Label != nil {
label = &node.Label.Ident
}
return continueUnwind(label)
}
func (e *evaluator) evalTryCatchFinally(node *ast.TryCatchFinally) *unwind {
// First, execute the TryBlock.
uw := e.evalBlock(node.TryBlock)
@ -149,13 +140,16 @@ func (e *evaluator) evalTryCatchFinally(node *ast.TryCatchFinally) *unwind {
// The try block threw something; see if there is a handler that covers this.
if node.CatchBlocks != nil {
for _, catch := range *node.CatchBlocks {
ex := e.ctx.RequireVariable(catch.Exception).Type()
contract.Assert(types.CanConvert(ex, types.Error))
if types.CanConvert(uw.Thrown.Type, ex) {
// This type matched; set the exception type and swap the unwind information with the catch block's.
// This has the effect of "handling" the exception (i.e., only reraise if the handler does).
// TODO: set the value of the exception somehow.
ex := e.ctx.RequireVariable(catch.Exception).(*symbols.LocalVariable)
exty := ex.Type()
contract.Assert(types.CanConvert(exty, types.Error))
if types.CanConvert(uw.Thrown.Type, exty) {
// This type matched, so this handler will catch the exception. Set the exception variable,
// evaluate the block, and swap the unwind information (thereby "handling" the in-flight exception).
e.pushScope()
e.locals.SetValue(ex, uw.Thrown)
uw = e.evalBlock(catch.Block)
e.popScope()
break
}
}
@ -175,6 +169,22 @@ func (e *evaluator) evalTryCatchFinally(node *ast.TryCatchFinally) *unwind {
return uw
}
func (e *evaluator) evalBreakStatement(node *ast.BreakStatement) *unwind {
var label *tokens.Name
if node.Label != nil {
label = &node.Label.Ident
}
return breakUnwind(label)
}
func (e *evaluator) evalContinueStatement(node *ast.ContinueStatement) *unwind {
var label *tokens.Name
if node.Label != nil {
label = &node.Label.Ident
}
return continueUnwind(label)
}
func (e *evaluator) evalIfStatement(node *ast.IfStatement) *unwind {
// Evaluate the branches explicitly based on the result of the condition node.
cond, uw := e.evalExpression(node.Condition)

View file

@ -4,32 +4,42 @@ package eval
import (
"github.com/marapongo/mu/pkg/compiler/symbols"
"github.com/marapongo/mu/pkg/compiler/types"
"github.com/marapongo/mu/pkg/util/contract"
)
// localScope is a kind of scope that holds local variable values.
type localScope struct {
Slot **localScope
Parent *localScope
Values ValueMap
slot **localScope
parent *localScope
values valueMap
}
// valueMap maps local variables to their current known object value (if any).
type valueMap map[*symbols.LocalVariable]*Object
func initLocalScope(slot **localScope) *localScope {
return &localScope{
Slot: slot,
Parent: *slot,
Values: make(ValueMap),
slot: slot,
parent: *slot,
values: make(valueMap),
}
}
func (s *localScope) Push() *localScope {
return initLocalScope(s.Slot)
return initLocalScope(s.slot)
}
func (s *localScope) Pop() {
contract.Assert(*s.Slot == s)
*s.Slot = s.Parent
contract.Assert(*s.slot == s)
*s.slot = s.parent
}
// ValueMap maps local variables to their current known object value (if any).
type ValueMap map[*symbols.LocalVariable]*Object
func (s *localScope) GetValue(sym *symbols.LocalVariable) *Object {
return s.values[sym]
}
func (s *localScope) SetValue(sym *symbols.LocalVariable, obj *Object) {
contract.Assert(obj == nil || types.CanConvert(obj.Type, sym.Type()))
s.values[sym] = obj
}