Implement dynamic loading

This change implements dynamic loads in the binder and evaluator.
This commit is contained in:
joeduffy 2017-02-12 08:22:44 -08:00
parent 93d9df1c4c
commit a16bb714e4
6 changed files with 239 additions and 64 deletions

View file

@ -81,7 +81,7 @@ func (a *astBinder) After(node ast.Node) {
case *ast.LoadLocationExpression: case *ast.LoadLocationExpression:
a.checkLoadLocationExpression(n) a.checkLoadLocationExpression(n)
case *ast.LoadDynamicExpression: case *ast.LoadDynamicExpression:
a.b.ctx.RegisterType(n, types.Dynamic) // register as a dynamic type. a.checkLoadDynamicExpression(n)
case *ast.NewExpression: case *ast.NewExpression:
a.checkNewExpression(n) a.checkNewExpression(n)
case *ast.InvokeFunctionExpression: case *ast.InvokeFunctionExpression:
@ -312,12 +312,25 @@ func (a *astBinder) checkLoadLocationExpression(node *ast.LoadLocationExpression
if sym == nil { if sym == nil {
ty = types.Error // no symbol found, use an error type. ty = types.Error // no symbol found, use an error type.
} else { } else {
var usesThis bool
for ty == nil { for ty == nil {
// Check that the this object is well-formed and extract the symbol.
switch s := sym.(type) { switch s := sym.(type) {
case symbols.Function: case *symbols.LocalVariable:
ty = s.FuncType() // use the func's type. ty = s.Type()
case symbols.Variable: usesThis = false
ty = s.Type() // use the variable's type. case *symbols.ClassMethod:
ty = s.FuncType()
usesThis = !s.Static()
case *symbols.ClassProperty:
ty = s.Type()
usesThis = !s.Static()
case *symbols.ModuleMethod:
ty = s.FuncType()
usesThis = false
case *symbols.ModuleProperty:
ty = s.Type()
usesThis = false
case *symbols.Export: case *symbols.Export:
// For exports, let's keep digging until we hit something concrete. // For exports, let's keep digging until we hit something concrete.
sym = s.Referent sym = s.Referent
@ -325,12 +338,36 @@ func (a *astBinder) checkLoadLocationExpression(node *ast.LoadLocationExpression
contract.Failf("Unrecognized load location token '%v' symbol type: %v", sym.Token(), reflect.TypeOf(s)) contract.Failf("Unrecognized load location token '%v' symbol type: %v", sym.Token(), reflect.TypeOf(s))
} }
} }
if usesThis {
if node.Object == nil {
a.b.Diag().Errorf(errors.ErrorExpectedObject.At(node))
}
} else if node.Object != nil {
a.b.Diag().Errorf(errors.ErrorUnexpectedObject.At(node))
}
} }
// Register the type; not that, although this is a valid l-value, we do not create a pointer out of it implicitly. // Register the type; not that, although this is a valid l-value, we do not create a pointer out of it implicitly.
a.b.ctx.RegisterType(node, ty) a.b.ctx.RegisterType(node, ty)
} }
func (a *astBinder) checkLoadDynamicExpression(node *ast.LoadDynamicExpression) {
// Ensure the object is non-nil.
if node.Object == nil {
a.b.Diag().Errorf(errors.ErrorExpectedObject.At(node))
}
// Now ensure that the right hand side is either a string or a dynamic.
name := a.b.ctx.RequireType(node.Name)
if name != types.Dynamic && !types.CanConvert(name, types.String) {
}
// No matter the outcome, a load dynamic always produces a dynamically typed thing.
a.b.ctx.RegisterType(node, types.Dynamic)
}
func (a *astBinder) checkNewExpression(node *ast.NewExpression) { func (a *astBinder) checkNewExpression(node *ast.NewExpression) {
// Figure out which type we're instantiating. // Figure out which type we're instantiating.
var ty symbols.Type var ty symbols.Type

View file

@ -13,4 +13,6 @@ var (
ErrorFunctionArgNotFound = newError(1006, "Function argument '%v' was not supplied") ErrorFunctionArgNotFound = newError(1006, "Function argument '%v' was not supplied")
ErrorFunctionArgUnknown = newError(1007, "Function argument '%v' was not recognized") ErrorFunctionArgUnknown = newError(1007, "Function argument '%v' was not recognized")
ErrorIllegalReadonlyLValue = newError(1008, "A readonly target cannot be used as an assignment target") ErrorIllegalReadonlyLValue = newError(1008, "A readonly target cannot be used as an assignment target")
ErrorExpectedObject = newError(1009, "Expected an object target for this instance member load operation")
ErrorUnexpectedObject = newError(1010, "Unexpected object target for this static or module load operation")
) )

View file

@ -4,7 +4,6 @@ package eval
import ( import (
"github.com/marapongo/mu/pkg/compiler/symbols" "github.com/marapongo/mu/pkg/compiler/symbols"
"github.com/marapongo/mu/pkg/compiler/types"
"github.com/marapongo/mu/pkg/eval/rt" "github.com/marapongo/mu/pkg/eval/rt"
) )
@ -34,28 +33,35 @@ func (a *Allocator) New(t symbols.Type) *rt.Object {
// NewPrimitive creates a new primitive object with the given primitive type. // NewPrimitive creates a new primitive object with the given primitive type.
func (a *Allocator) NewPrimitive(t symbols.Type, v interface{}) *rt.Object { func (a *Allocator) NewPrimitive(t symbols.Type, v interface{}) *rt.Object {
obj := rt.NewObject(t, v, nil) obj := rt.NewPrimitiveObject(t, v)
a.onNewObject(obj) a.onNewObject(obj)
return obj return obj
} }
// NewBool creates a new primitive number object. // NewBool creates a new primitive number object.
func (a *Allocator) NewBool(v bool) *rt.Object { func (a *Allocator) NewBool(v bool) *rt.Object {
obj := rt.NewPrimitiveObject(types.Bool, v) obj := rt.NewBoolObject(v)
a.onNewObject(obj) a.onNewObject(obj)
return obj return obj
} }
// NewNumber creates a new primitive number object. // NewNumber creates a new primitive number object.
func (a *Allocator) NewNumber(v float64) *rt.Object { func (a *Allocator) NewNumber(v float64) *rt.Object {
obj := rt.NewPrimitiveObject(types.Number, v) obj := rt.NewNumberObject(v)
a.onNewObject(obj)
return obj
}
// NewNull creates a new null object.
func (a *Allocator) NewNull() *rt.Object {
obj := rt.NewNullObject()
a.onNewObject(obj) a.onNewObject(obj)
return obj return obj
} }
// NewString creates a new primitive number object. // NewString creates a new primitive number object.
func (a *Allocator) NewString(v string) *rt.Object { func (a *Allocator) NewString(v string) *rt.Object {
obj := rt.NewPrimitiveObject(types.String, v) obj := rt.NewStringObject(v)
a.onNewObject(obj) a.onNewObject(obj)
return obj return obj
} }

View file

@ -197,7 +197,7 @@ func (e *evaluator) EvaluateFunction(fnc symbols.Function, this *rt.Object, args
if e.Diag().Success() { if e.Diag().Success() {
// If the arguments bound correctly, make the call. // If the arguments bound correctly, make the call.
_, uw := e.evalCall(fnc, this, argos...) _, uw := e.evalCall(fnc.Tree(), fnc, this, argos...)
if uw != nil { if uw != nil {
// If the call had an unwind out of it, then presumably we have an unhandled exception. // If the call had an unwind out of it, then presumably we have an unhandled exception.
e.issueUnhandledException(uw, errors.ErrorUnhandledException.At(fnc.Tree())) e.issueUnhandledException(uw, errors.ErrorUnhandledException.At(fnc.Tree()))
@ -244,7 +244,7 @@ func (e *evaluator) ensureClassInit(class *symbols.Class) {
glog.V(7).Infof("Initializing class: %v", class) glog.V(7).Infof("Initializing class: %v", class)
contract.Assert(len(init.Ty.Parameters) == 0) contract.Assert(len(init.Ty.Parameters) == 0)
contract.Assert(init.Ty.Return == nil) contract.Assert(init.Ty.Return == nil)
ret, uw := e.evalCall(init, nil) ret, uw := e.evalCall(class.Tree(), init, nil)
contract.Assert(ret == nil) contract.Assert(ret == nil)
if uw != nil { if uw != nil {
// Must be an unhandled exception; spew it as an error (but keep going). // Must be an unhandled exception; spew it as an error (but keep going).
@ -273,7 +273,7 @@ func (e *evaluator) ensureModuleInit(mod *symbols.Module) {
glog.V(7).Infof("Initializing module: %v", mod) glog.V(7).Infof("Initializing module: %v", mod)
contract.Assert(len(init.Type.Parameters) == 0) contract.Assert(len(init.Type.Parameters) == 0)
contract.Assert(init.Type.Return == nil) contract.Assert(init.Type.Return == nil)
ret, uw := e.evalCall(init, nil) ret, uw := e.evalCall(mod.Tree(), init, nil)
contract.Assert(ret == nil) contract.Assert(ret == nil)
if uw != nil { if uw != nil {
// Must be an unhandled exception; spew it as an error (but keep going). // Must be an unhandled exception; spew it as an error (but keep going).
@ -290,7 +290,7 @@ func (e *evaluator) issueUnhandledException(uw *Unwind, err *diag.Diag, args ...
contract.Assert(uw.Throw()) contract.Assert(uw.Throw())
var msg string var msg string
if thrown := uw.Thrown(); thrown != nil { if thrown := uw.Thrown(); thrown != nil {
msg = thrown.StringValue() msg = thrown.ExceptionMessage()
} }
if msg == "" { if msg == "" {
msg = "no details available" msg = "no details available"
@ -313,9 +313,27 @@ func (e *evaluator) popScope() {
// Functions // Functions
func (e *evaluator) evalCall(fnc symbols.Function, this *rt.Object, args ...*rt.Object) (*rt.Object, *Unwind) { func (e *evaluator) evalCall(node diag.Diagable, fnc symbols.Function,
this *rt.Object, args ...*rt.Object) (*rt.Object, *Unwind) {
glog.V(7).Infof("Evaluating call to fnc %v; this=%v args=%v", fnc, this != nil, len(args)) glog.V(7).Infof("Evaluating call to fnc %v; this=%v args=%v", fnc, this != nil, len(args))
// First check the this pointer, since it might throw before the call even happens.
var thisVariable *symbols.LocalVariable
var superVariable *symbols.LocalVariable
switch f := fnc.(type) {
case *symbols.ClassMethod:
if f.Static() {
contract.Assert(this == nil)
} else if uw := e.checkThis(node, this); uw != nil {
return nil, uw
} else {
thisVariable = f.Parent.This
superVariable = f.Parent.Super
}
default:
contract.Assert(this == nil)
}
// Save the prior func, set the new one, and restore upon exit. // Save the prior func, set the new one, and restore upon exit.
prior := fnc prior := fnc
e.fnc = fnc e.fnc = fnc
@ -332,33 +350,20 @@ func (e *evaluator) evalCall(fnc symbols.Function, this *rt.Object, args ...*rt.
} }
// If the target is an instance method, the "this" and "super" variables must be bound to values. // If the target is an instance method, the "this" and "super" variables must be bound to values.
if this != nil { if thisVariable != nil {
switch f := fnc.(type) { contract.Assert(this != nil)
case *symbols.ClassMethod: e.ctx.Scope.Register(thisVariable)
contract.Assertf(!f.Static(), e.locals.InitValueAddr(thisVariable, rt.NewPointer(this, true))
"Static methods don't have 'this' arguments, but we got a non-nil one") }
contract.Assertf(types.CanConvert(this.Type(), f.Parent), if superVariable != nil {
"'this' argument was of the wrong type") contract.Assert(this != nil)
e.ctx.Scope.Register(f.Parent.This) e.ctx.Scope.Register(superVariable)
e.locals.InitValueAddr(f.Parent.This, rt.NewPointer(this, true)) e.locals.InitValueAddr(superVariable, rt.NewPointer(this, true))
if f.Parent.Super != nil {
e.ctx.Scope.Register(f.Parent.Super)
e.locals.InitValueAddr(f.Parent.Super, rt.NewPointer(this, true))
}
default:
contract.Failf("Only class methods should have 'this' arguments, but we got a non-nil one")
}
} else {
// If no this was supplied, we expect that this is either not a class method, or it is a static.
switch f := fnc.(type) {
case *symbols.ClassMethod:
contract.Assertf(f.Static(), "Instance methods require 'this' arguments, but we got nil")
}
} }
// Ensure that the arguments line up to the parameter slots and add them to the frame. // Ensure that the arguments line up to the parameter slots and add them to the frame.
node := fnc.FuncNode() fnode := fnc.FuncNode()
params := node.GetParameters() params := fnode.GetParameters()
if params == nil { if params == nil {
contract.Assert(len(args) == 0) contract.Assert(len(args) == 0)
} else { } else {
@ -373,7 +378,7 @@ func (e *evaluator) evalCall(fnc symbols.Function, this *rt.Object, args ...*rt.
} }
// Now perform the invocation by visiting the body. // Now perform the invocation by visiting the body.
uw := e.evalBlock(node.GetBody()) uw := e.evalBlock(fnode.GetBody())
// Check that the unwind is as expected. In particular: // Check that the unwind is as expected. In particular:
// 1) no breaks or continues are expected; // 1) no breaks or continues are expected;
@ -400,6 +405,10 @@ func (e *evaluator) evalCall(fnc symbols.Function, this *rt.Object, args ...*rt.
// Statements // Statements
func (e *evaluator) evalStatement(node ast.Statement) *Unwind { func (e *evaluator) evalStatement(node ast.Statement) *Unwind {
if glog.V(7) {
glog.V(7).Infof("Evaluating statement: %v", reflect.TypeOf(node))
}
// Simply switch on the node type and dispatch to the specific function, returning the Unwind info. // Simply switch on the node type and dispatch to the specific function, returning the Unwind info.
switch n := node.(type) { switch n := node.(type) {
case *ast.Block: case *ast.Block:
@ -609,6 +618,10 @@ func (e *evaluator) evalExpressionStatement(node *ast.ExpressionStatement) *Unwi
// Expressions // Expressions
func (e *evaluator) evalExpression(node ast.Expression) (*rt.Object, *Unwind) { func (e *evaluator) evalExpression(node ast.Expression) (*rt.Object, *Unwind) {
if glog.V(7) {
glog.V(7).Infof("Evaluating expression: %v", reflect.TypeOf(node))
}
// Simply switch on the node type and dispatch to the specific function, returning the object and Unwind info. // Simply switch on the node type and dispatch to the specific function, returning the object and Unwind info.
switch n := node.(type) { switch n := node.(type) {
case *ast.NullLiteral: case *ast.NullLiteral:
@ -660,6 +673,8 @@ func (e *evaluator) evalLValueExpression(node ast.Expression) (location, *Unwind
switch n := node.(type) { switch n := node.(type) {
case *ast.LoadLocationExpression: case *ast.LoadLocationExpression:
return e.evalLoadLocation(n, true) return e.evalLoadLocation(n, true)
case *ast.LoadDynamicExpression:
return e.evalLoadDynamic(n, true)
case *ast.UnaryOperatorExpression: case *ast.UnaryOperatorExpression:
contract.Assert(n.Operator == ast.OpDereference) contract.Assert(n.Operator == ast.OpDereference)
obj, uw := e.evalUnaryOperatorExpressionFor(n, true) obj, uw := e.evalUnaryOperatorExpressionFor(n, true)
@ -671,19 +686,19 @@ func (e *evaluator) evalLValueExpression(node ast.Expression) (location, *Unwind
} }
func (e *evaluator) evalNullLiteral(node *ast.NullLiteral) (*rt.Object, *Unwind) { func (e *evaluator) evalNullLiteral(node *ast.NullLiteral) (*rt.Object, *Unwind) {
return e.alloc.NewPrimitive(types.Null, nil), nil return e.alloc.NewNull(), nil
} }
func (e *evaluator) evalBoolLiteral(node *ast.BoolLiteral) (*rt.Object, *Unwind) { func (e *evaluator) evalBoolLiteral(node *ast.BoolLiteral) (*rt.Object, *Unwind) {
return e.alloc.NewPrimitive(types.Bool, node.Value), nil return e.alloc.NewBool(node.Value), nil
} }
func (e *evaluator) evalNumberLiteral(node *ast.NumberLiteral) (*rt.Object, *Unwind) { func (e *evaluator) evalNumberLiteral(node *ast.NumberLiteral) (*rt.Object, *Unwind) {
return e.alloc.NewPrimitive(types.Number, node.Value), nil return e.alloc.NewNumber(node.Value), nil
} }
func (e *evaluator) evalStringLiteral(node *ast.StringLiteral) (*rt.Object, *Unwind) { func (e *evaluator) evalStringLiteral(node *ast.StringLiteral) (*rt.Object, *Unwind) {
return e.alloc.NewPrimitive(types.String, node.Value), nil return e.alloc.NewString(node.Value), nil
} }
func (e *evaluator) evalArrayLiteral(node *ast.ArrayLiteral) (*rt.Object, *Unwind) { func (e *evaluator) evalArrayLiteral(node *ast.ArrayLiteral) (*rt.Object, *Unwind) {
@ -704,7 +719,7 @@ func (e *evaluator) evalArrayLiteral(node *ast.ArrayLiteral) (*rt.Object, *Unwin
sz := int(sze.NumberValue()) sz := int(sze.NumberValue())
if sz < 0 { if sz < 0 {
// If the size is less than zero, raise a new error. // If the size is less than zero, raise a new error.
return nil, NewThrowUnwind(e.alloc.NewException("Invalid array size (must be >= 0)")) return nil, NewThrowUnwind(e.NewNegativeArrayLengthException())
} }
arr = make([]rt.Value, sz) arr = make([]rt.Value, sz)
} }
@ -718,9 +733,7 @@ func (e *evaluator) evalArrayLiteral(node *ast.ArrayLiteral) (*rt.Object, *Unwin
arr = make([]rt.Value, 0, len(*node.Elements)) arr = make([]rt.Value, 0, len(*node.Elements))
} else if len(*node.Elements) > *sz { } else if len(*node.Elements) > *sz {
// The element count exceeds the size; raise an error. // The element count exceeds the size; raise an error.
return nil, NewThrowUnwind( return nil, NewThrowUnwind(e.NewIncorrectArrayElementCountException(*sz, len(*node.Elements)))
e.alloc.NewException("Invalid number of array elements; expected <=%v, got %v",
*sz, len(*node.Elements)))
} }
for i, elem := range *node.Elements { for i, elem := range *node.Elements {
@ -782,10 +795,10 @@ type location struct {
Obj *rt.Object // the resulting object (pointer if lval, object otherwise). Obj *rt.Object // the resulting object (pointer if lval, object otherwise).
} }
// evalLoadLocationExpressionInfo evaluates and loads information about the target. It takes an lval bool which // evalLoadLocation evaluates and loads information about the target. It takes an lval bool which
// determines whether the resulting location object is an lval (pointer) or regular object. // determines whether the resulting location object is an lval (pointer) or regular object.
func (e *evaluator) evalLoadLocation(node *ast.LoadLocationExpression, lval bool) (location, *Unwind) { func (e *evaluator) evalLoadLocation(node *ast.LoadLocationExpression, lval bool) (location, *Unwind) {
// If there's a 'this', evaluate it. // If there's a target object, evaluate it.
var this *rt.Object var this *rt.Object
if node.Object != nil { if node.Object != nil {
var uw *Unwind var uw *Unwind
@ -799,8 +812,9 @@ func (e *evaluator) evalLoadLocation(node *ast.LoadLocationExpression, lval bool
var ty symbols.Type var ty symbols.Type
var sym symbols.Symbol var sym symbols.Symbol
tok := node.Name.Tok tok := node.Name.Tok
if this == nil && tok.Simple() { if tok.Simple() {
// If there is no object and the name is simple, it refers to a local variable in the current scope. // If there is no object and the name is simple, it refers to a local variable in the current scope.
contract.Assert(this == nil)
loc := e.ctx.Scope.Lookup(tok.Name()) loc := e.ctx.Scope.Lookup(tok.Name())
contract.Assert(loc != nil) contract.Assert(loc != nil)
pv = e.locals.GetValueAddr(loc, true) pv = e.locals.GetValueAddr(loc, true)
@ -812,19 +826,30 @@ func (e *evaluator) evalLoadLocation(node *ast.LoadLocationExpression, lval bool
contract.Assert(sym != nil) // don't issue errors; we shouldn't ever get here if verification failed. contract.Assert(sym != nil) // don't issue errors; we shouldn't ever get here if verification failed.
switch s := sym.(type) { switch s := sym.(type) {
case *symbols.ClassProperty: case *symbols.ClassProperty:
// Ensure that a this is present if instance, and not if static.
if s.Static() {
contract.Assert(this == nil)
} else if uw := e.checkThis(node, this); uw != nil {
return location{}, uw
}
// Search the class's properties and, if not present, allocate a new one. // Search the class's properties and, if not present, allocate a new one.
contract.Assert(this != nil)
pv = this.GetPropertyAddr(rt.PropertyKey(sym.Name()), true) pv = this.GetPropertyAddr(rt.PropertyKey(sym.Name()), true)
ty = s.Type() ty = s.Type()
case *symbols.ClassMethod: case *symbols.ClassMethod:
// Ensure that a this is present if instance, and not if static.
if s.Static() {
contract.Assert(this == nil)
} else if uw := e.checkThis(node, this); uw != nil {
return location{}, uw
}
// Create a new readonly ref slot, pointing to the method, that will abandon if overwritten. // Create a new readonly ref slot, pointing to the method, that will abandon if overwritten.
contract.Assert(this != nil)
// TODO[marapongo/mu#56]: consider permitting "dynamic" method overwriting. // TODO[marapongo/mu#56]: consider permitting "dynamic" method overwriting.
pv = rt.NewPointer(e.alloc.NewFunction(s, this), true) pv = rt.NewPointer(e.alloc.NewFunction(s, this), true)
ty = s.Type() ty = s.Type()
case *symbols.ModuleProperty: case *symbols.ModuleProperty:
// Search the globals table and, if not present, allocate a new property. // Search the globals table and, if not present, allocate a new property.
contract.Assert(this == nil)
ref, has := e.globals[s] ref, has := e.globals[s]
if !has { if !has {
ref = rt.NewPointer(nil, s.Readonly()) ref = rt.NewPointer(nil, s.Readonly())
@ -832,12 +857,17 @@ func (e *evaluator) evalLoadLocation(node *ast.LoadLocationExpression, lval bool
} }
pv = ref pv = ref
ty = s.Type() ty = s.Type()
if this != nil {
e.Diag().Errorf(errors.ErrorUnexpectedObject.At(node))
}
case *symbols.ModuleMethod: case *symbols.ModuleMethod:
// Create a new readonly ref slot, pointing to the method, that will abandon if overwritten. // Create a new readonly ref slot, pointing to the method, that will abandon if overwritten.
contract.Assert(this == nil)
// TODO[marapongo/mu#56]: consider permitting "dynamic" method overwriting. // TODO[marapongo/mu#56]: consider permitting "dynamic" method overwriting.
pv = rt.NewPointer(e.alloc.NewFunction(s, nil), true) pv = rt.NewPointer(e.alloc.NewFunction(s, nil), true)
ty = s.Type ty = s.Type
if this != nil {
e.Diag().Errorf(errors.ErrorUnexpectedObject.At(node))
}
case *symbols.Export: case *symbols.Export:
// Simply chase the referent symbol until we bottom out on one of the above cases. // Simply chase the referent symbol until we bottom out on one of the above cases.
sym = s.Referent sym = s.Referent
@ -867,9 +897,62 @@ func (e *evaluator) evalLoadLocation(node *ast.LoadLocationExpression, lval bool
}, nil }, nil
} }
// checkThis checks a this object, raising a runtime error if it is the runtime null value.
func (e *evaluator) checkThis(node diag.Diagable, this *rt.Object) *Unwind {
contract.Assert(this != nil) // binder should catch cases where this isn't true
if this.Type() == types.Null {
return NewThrowUnwind(e.NewNullObjectException())
}
return nil
}
func (e *evaluator) evalLoadDynamicExpression(node *ast.LoadDynamicExpression) (*rt.Object, *Unwind) { func (e *evaluator) evalLoadDynamicExpression(node *ast.LoadDynamicExpression) (*rt.Object, *Unwind) {
contract.Failf("Evaluation of %v nodes not yet implemented", reflect.TypeOf(node)) loc, uw := e.evalLoadDynamic(node, false)
return nil, nil return loc.Obj, uw
}
func (e *evaluator) evalLoadDynamic(node *ast.LoadDynamicExpression, lval bool) (location, *Unwind) {
var uw *Unwind
// Evaluate the object and then the property expression.
var this *rt.Object
if this, uw = e.evalExpression(node.Object); uw != nil {
return location{}, uw
}
var name *rt.Object
if name, uw = e.evalExpression(node.Name); uw != nil {
return location{}, uw
}
// Check that the object isn't null; if it is, raise an exception.
if uw = e.checkThis(node, this); uw != nil {
return location{}, uw
}
// Now go ahead and search the object for a property with the given name.
contract.Assertf(name.Type() == types.String, "At the moment, only string names are supported")
pv := this.GetPropertyAddr(rt.PropertyKey(name.String()), true)
// If this isn't for an l-value, return the raw object. Otherwise, make sure it's not readonly, and return it.
var obj *rt.Object
if lval {
// A readonly reference cannot be used as an l-value.
if pv.Readonly() {
e.Diag().Errorf(errors.ErrorIllegalReadonlyLValue.At(node))
}
obj = e.alloc.NewPointer(types.Dynamic, pv)
} else {
obj = pv.Obj()
}
contract.Assert(obj != nil)
// TODO: we need to produce a symbol (or something) for the graph hooks to use.
return location{
This: this,
Sym: nil,
Lval: lval,
Obj: obj,
}, nil
} }
func (e *evaluator) evalNewExpression(node *ast.NewExpression) (*rt.Object, *Unwind) { func (e *evaluator) evalNewExpression(node *ast.NewExpression) (*rt.Object, *Unwind) {
@ -900,7 +983,7 @@ func (e *evaluator) evalNewExpression(node *ast.NewExpression) (*rt.Object, *Unw
} }
// Now dispatch the function call using the fresh object as the constructor's `this` argument. // Now dispatch the function call using the fresh object as the constructor's `this` argument.
if _, uw := e.evalCall(ctormeth, obj, args...); uw != nil { if _, uw := e.evalCall(node, ctormeth, obj, args...); uw != nil {
return nil, uw return nil, uw
} }
} else { } else {
@ -944,7 +1027,7 @@ func (e *evaluator) evalInvokeFunctionExpression(node *ast.InvokeFunctionExpress
} }
// Finally, actually dispatch the call; this will create the activation frame, etc. for us. // Finally, actually dispatch the call; this will create the activation frame, etc. for us.
return e.evalCall(fnc.Func, fnc.This, args...) return e.evalCall(node, fnc.Func, fnc.This, args...)
} }
func (e *evaluator) evalLambdaExpression(node *ast.LambdaExpression) (*rt.Object, *Unwind) { func (e *evaluator) evalLambdaExpression(node *ast.LambdaExpression) (*rt.Object, *Unwind) {

19
pkg/eval/exceptions.go Normal file
View file

@ -0,0 +1,19 @@
// Copyright 2016 Marapongo, Inc. All rights reserved.
package eval
import (
"github.com/marapongo/mu/pkg/eval/rt"
)
func (e *evaluator) NewNullObjectException() *rt.Object {
return e.alloc.NewException("Target object is null")
}
func (e *evaluator) NewNegativeArrayLengthException() *rt.Object {
return e.alloc.NewException("Invalid array size (must be >= 0)")
}
func (e *evaluator) NewIncorrectArrayElementCountException(expect int, got int) *rt.Object {
return e.alloc.NewException("Invalid number of array elements; expected <=%v, got %v", expect, got)
}

View file

@ -81,6 +81,15 @@ func (o *Object) PointerValue() *Pointer {
return r return r
} }
// ExceptionMessage asserts that the target is an exception and returns its message.
func (o *Object) ExceptionMessage() string {
contract.Assertf(o.t == types.Exception, "Expected object type to be Exception; got %v", o.t)
contract.Assertf(o.value != nil, "Expected Exception object to carry a Value; got nil")
s, ok := o.value.(string)
contract.Assertf(ok, "Expected Exception object's Value to be string")
return s
}
// GetPropertyAddr returns the reference to an object's property, lazily initializing if 'init' is true, or // GetPropertyAddr returns the reference to an object's property, lazily initializing if 'init' is true, or
// returning nil otherwise. // returning nil otherwise.
func (o *Object) GetPropertyAddr(key PropertyKey, init bool) *Pointer { func (o *Object) GetPropertyAddr(key PropertyKey, init bool) *Pointer {
@ -109,6 +118,12 @@ func (o *Object) GetPropertyAddr(key PropertyKey, init bool) *Pointer {
} }
} }
} }
// If no members was found, this is presumably a dynamic scenario. Initialize the slot to null.
if obj == nil {
obj = NewNullObject()
}
ptr = NewPointer(obj, readonly) ptr = NewPointer(obj, readonly)
o.properties[key] = ptr o.properties[key] = ptr
} }
@ -167,18 +182,31 @@ func NewPrimitiveObject(t symbols.Type, v interface{}) *Object {
return NewObject(t, v, nil) return NewObject(t, v, nil)
} }
var trueObj *Object = NewPrimitiveObject(types.Bool, true)
var falseObj *Object = NewPrimitiveObject(types.Bool, false)
// NewBoolObject creates a new primitive number object. // NewBoolObject creates a new primitive number object.
func NewBoolObject(v bool) *Object { func NewBoolObject(v bool) *Object {
return NewPrimitiveObject(types.Bool, v) if v {
return trueObj
}
return falseObj
} }
// NewNumber creates a new primitive number object. // NewNumberObject creates a new primitive number object.
func NewNumber(v float64) *Object { func NewNumberObject(v float64) *Object {
return NewPrimitiveObject(types.Number, v) return NewPrimitiveObject(types.Number, v)
} }
// NewString creates a new primitive number object. var nullObj *Object = NewPrimitiveObject(types.Null, nil)
func NewString(v string) *Object {
// NewNullObject creates a new null object; null objects are not expected to have distinct identity.
func NewNullObject() *Object {
return nullObj
}
// NewStringObject creates a new primitive number object.
func NewStringObject(v string) *Object {
return NewPrimitiveObject(types.String, v) return NewPrimitiveObject(types.String, v)
} }