Implement dynamic loading
This change implements dynamic loads in the binder and evaluator.
This commit is contained in:
parent
93d9df1c4c
commit
a16bb714e4
|
@ -81,7 +81,7 @@ func (a *astBinder) After(node ast.Node) {
|
|||
case *ast.LoadLocationExpression:
|
||||
a.checkLoadLocationExpression(n)
|
||||
case *ast.LoadDynamicExpression:
|
||||
a.b.ctx.RegisterType(n, types.Dynamic) // register as a dynamic type.
|
||||
a.checkLoadDynamicExpression(n)
|
||||
case *ast.NewExpression:
|
||||
a.checkNewExpression(n)
|
||||
case *ast.InvokeFunctionExpression:
|
||||
|
@ -312,12 +312,25 @@ func (a *astBinder) checkLoadLocationExpression(node *ast.LoadLocationExpression
|
|||
if sym == nil {
|
||||
ty = types.Error // no symbol found, use an error type.
|
||||
} else {
|
||||
var usesThis bool
|
||||
for ty == nil {
|
||||
// Check that the this object is well-formed and extract the symbol.
|
||||
switch s := sym.(type) {
|
||||
case symbols.Function:
|
||||
ty = s.FuncType() // use the func's type.
|
||||
case symbols.Variable:
|
||||
ty = s.Type() // use the variable's type.
|
||||
case *symbols.LocalVariable:
|
||||
ty = s.Type()
|
||||
usesThis = false
|
||||
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:
|
||||
// For exports, let's keep digging until we hit something concrete.
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
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) {
|
||||
// Figure out which type we're instantiating.
|
||||
var ty symbols.Type
|
||||
|
|
|
@ -13,4 +13,6 @@ var (
|
|||
ErrorFunctionArgNotFound = newError(1006, "Function argument '%v' was not supplied")
|
||||
ErrorFunctionArgUnknown = newError(1007, "Function argument '%v' was not recognized")
|
||||
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")
|
||||
)
|
||||
|
|
|
@ -4,7 +4,6 @@ package eval
|
|||
|
||||
import (
|
||||
"github.com/marapongo/mu/pkg/compiler/symbols"
|
||||
"github.com/marapongo/mu/pkg/compiler/types"
|
||||
"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.
|
||||
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)
|
||||
return obj
|
||||
}
|
||||
|
||||
// NewBool creates a new primitive number object.
|
||||
func (a *Allocator) NewBool(v bool) *rt.Object {
|
||||
obj := rt.NewPrimitiveObject(types.Bool, v)
|
||||
obj := rt.NewBoolObject(v)
|
||||
a.onNewObject(obj)
|
||||
return obj
|
||||
}
|
||||
|
||||
// NewNumber creates a new primitive number 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)
|
||||
return obj
|
||||
}
|
||||
|
||||
// NewString creates a new primitive number object.
|
||||
func (a *Allocator) NewString(v string) *rt.Object {
|
||||
obj := rt.NewPrimitiveObject(types.String, v)
|
||||
obj := rt.NewStringObject(v)
|
||||
a.onNewObject(obj)
|
||||
return obj
|
||||
}
|
||||
|
|
181
pkg/eval/eval.go
181
pkg/eval/eval.go
|
@ -197,7 +197,7 @@ func (e *evaluator) EvaluateFunction(fnc symbols.Function, this *rt.Object, args
|
|||
|
||||
if e.Diag().Success() {
|
||||
// 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 the call had an unwind out of it, then presumably we have an unhandled exception.
|
||||
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)
|
||||
contract.Assert(len(init.Ty.Parameters) == 0)
|
||||
contract.Assert(init.Ty.Return == nil)
|
||||
ret, uw := e.evalCall(init, nil)
|
||||
ret, uw := e.evalCall(class.Tree(), init, nil)
|
||||
contract.Assert(ret == nil)
|
||||
if uw != nil {
|
||||
// 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)
|
||||
contract.Assert(len(init.Type.Parameters) == 0)
|
||||
contract.Assert(init.Type.Return == nil)
|
||||
ret, uw := e.evalCall(init, nil)
|
||||
ret, uw := e.evalCall(mod.Tree(), init, nil)
|
||||
contract.Assert(ret == nil)
|
||||
if uw != nil {
|
||||
// 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())
|
||||
var msg string
|
||||
if thrown := uw.Thrown(); thrown != nil {
|
||||
msg = thrown.StringValue()
|
||||
msg = thrown.ExceptionMessage()
|
||||
}
|
||||
if msg == "" {
|
||||
msg = "no details available"
|
||||
|
@ -313,9 +313,27 @@ func (e *evaluator) popScope() {
|
|||
|
||||
// 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))
|
||||
|
||||
// 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.
|
||||
prior := 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 this != nil {
|
||||
switch f := fnc.(type) {
|
||||
case *symbols.ClassMethod:
|
||||
contract.Assertf(!f.Static(),
|
||||
"Static methods don't have 'this' arguments, but we got a non-nil one")
|
||||
contract.Assertf(types.CanConvert(this.Type(), f.Parent),
|
||||
"'this' argument was of the wrong type")
|
||||
e.ctx.Scope.Register(f.Parent.This)
|
||||
e.locals.InitValueAddr(f.Parent.This, 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")
|
||||
}
|
||||
if thisVariable != nil {
|
||||
contract.Assert(this != nil)
|
||||
e.ctx.Scope.Register(thisVariable)
|
||||
e.locals.InitValueAddr(thisVariable, rt.NewPointer(this, true))
|
||||
}
|
||||
if superVariable != nil {
|
||||
contract.Assert(this != nil)
|
||||
e.ctx.Scope.Register(superVariable)
|
||||
e.locals.InitValueAddr(superVariable, rt.NewPointer(this, true))
|
||||
}
|
||||
|
||||
// Ensure that the arguments line up to the parameter slots and add them to the frame.
|
||||
node := fnc.FuncNode()
|
||||
params := node.GetParameters()
|
||||
fnode := fnc.FuncNode()
|
||||
params := fnode.GetParameters()
|
||||
if params == nil {
|
||||
contract.Assert(len(args) == 0)
|
||||
} 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.
|
||||
uw := e.evalBlock(node.GetBody())
|
||||
uw := e.evalBlock(fnode.GetBody())
|
||||
|
||||
// Check that the unwind is as expected. In particular:
|
||||
// 1) no breaks or continues are expected;
|
||||
|
@ -400,6 +405,10 @@ func (e *evaluator) evalCall(fnc symbols.Function, this *rt.Object, args ...*rt.
|
|||
// Statements
|
||||
|
||||
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.
|
||||
switch n := node.(type) {
|
||||
case *ast.Block:
|
||||
|
@ -609,6 +618,10 @@ func (e *evaluator) evalExpressionStatement(node *ast.ExpressionStatement) *Unwi
|
|||
// Expressions
|
||||
|
||||
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.
|
||||
switch n := node.(type) {
|
||||
case *ast.NullLiteral:
|
||||
|
@ -660,6 +673,8 @@ func (e *evaluator) evalLValueExpression(node ast.Expression) (location, *Unwind
|
|||
switch n := node.(type) {
|
||||
case *ast.LoadLocationExpression:
|
||||
return e.evalLoadLocation(n, true)
|
||||
case *ast.LoadDynamicExpression:
|
||||
return e.evalLoadDynamic(n, true)
|
||||
case *ast.UnaryOperatorExpression:
|
||||
contract.Assert(n.Operator == ast.OpDereference)
|
||||
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) {
|
||||
return e.alloc.NewPrimitive(types.Null, nil), nil
|
||||
return e.alloc.NewNull(), nil
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
|
@ -704,7 +719,7 @@ func (e *evaluator) evalArrayLiteral(node *ast.ArrayLiteral) (*rt.Object, *Unwin
|
|||
sz := int(sze.NumberValue())
|
||||
if sz < 0 {
|
||||
// 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)
|
||||
}
|
||||
|
@ -718,9 +733,7 @@ func (e *evaluator) evalArrayLiteral(node *ast.ArrayLiteral) (*rt.Object, *Unwin
|
|||
arr = make([]rt.Value, 0, len(*node.Elements))
|
||||
} else if len(*node.Elements) > *sz {
|
||||
// The element count exceeds the size; raise an error.
|
||||
return nil, NewThrowUnwind(
|
||||
e.alloc.NewException("Invalid number of array elements; expected <=%v, got %v",
|
||||
*sz, len(*node.Elements)))
|
||||
return nil, NewThrowUnwind(e.NewIncorrectArrayElementCountException(*sz, len(*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).
|
||||
}
|
||||
|
||||
// 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.
|
||||
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
|
||||
if node.Object != nil {
|
||||
var uw *Unwind
|
||||
|
@ -799,8 +812,9 @@ func (e *evaluator) evalLoadLocation(node *ast.LoadLocationExpression, lval bool
|
|||
var ty symbols.Type
|
||||
var sym symbols.Symbol
|
||||
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.
|
||||
contract.Assert(this == nil)
|
||||
loc := e.ctx.Scope.Lookup(tok.Name())
|
||||
contract.Assert(loc != nil)
|
||||
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.
|
||||
switch s := sym.(type) {
|
||||
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.
|
||||
contract.Assert(this != nil)
|
||||
pv = this.GetPropertyAddr(rt.PropertyKey(sym.Name()), true)
|
||||
ty = s.Type()
|
||||
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.
|
||||
contract.Assert(this != nil)
|
||||
// TODO[marapongo/mu#56]: consider permitting "dynamic" method overwriting.
|
||||
pv = rt.NewPointer(e.alloc.NewFunction(s, this), true)
|
||||
ty = s.Type()
|
||||
case *symbols.ModuleProperty:
|
||||
// Search the globals table and, if not present, allocate a new property.
|
||||
contract.Assert(this == nil)
|
||||
ref, has := e.globals[s]
|
||||
if !has {
|
||||
ref = rt.NewPointer(nil, s.Readonly())
|
||||
|
@ -832,12 +857,17 @@ func (e *evaluator) evalLoadLocation(node *ast.LoadLocationExpression, lval bool
|
|||
}
|
||||
pv = ref
|
||||
ty = s.Type()
|
||||
if this != nil {
|
||||
e.Diag().Errorf(errors.ErrorUnexpectedObject.At(node))
|
||||
}
|
||||
case *symbols.ModuleMethod:
|
||||
// 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.
|
||||
pv = rt.NewPointer(e.alloc.NewFunction(s, nil), true)
|
||||
ty = s.Type
|
||||
if this != nil {
|
||||
e.Diag().Errorf(errors.ErrorUnexpectedObject.At(node))
|
||||
}
|
||||
case *symbols.Export:
|
||||
// Simply chase the referent symbol until we bottom out on one of the above cases.
|
||||
sym = s.Referent
|
||||
|
@ -867,9 +897,62 @@ func (e *evaluator) evalLoadLocation(node *ast.LoadLocationExpression, lval bool
|
|||
}, 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) {
|
||||
contract.Failf("Evaluation of %v nodes not yet implemented", reflect.TypeOf(node))
|
||||
return nil, nil
|
||||
loc, uw := e.evalLoadDynamic(node, false)
|
||||
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) {
|
||||
|
@ -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.
|
||||
if _, uw := e.evalCall(ctormeth, obj, args...); uw != nil {
|
||||
if _, uw := e.evalCall(node, ctormeth, obj, args...); uw != nil {
|
||||
return nil, uw
|
||||
}
|
||||
} 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.
|
||||
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) {
|
||||
|
|
19
pkg/eval/exceptions.go
Normal file
19
pkg/eval/exceptions.go
Normal 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)
|
||||
}
|
|
@ -81,6 +81,15 @@ func (o *Object) PointerValue() *Pointer {
|
|||
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
|
||||
// returning nil otherwise.
|
||||
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)
|
||||
o.properties[key] = ptr
|
||||
}
|
||||
|
@ -167,18 +182,31 @@ func NewPrimitiveObject(t symbols.Type, v interface{}) *Object {
|
|||
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.
|
||||
func NewBoolObject(v bool) *Object {
|
||||
return NewPrimitiveObject(types.Bool, v)
|
||||
if v {
|
||||
return trueObj
|
||||
}
|
||||
return falseObj
|
||||
}
|
||||
|
||||
// NewNumber creates a new primitive number object.
|
||||
func NewNumber(v float64) *Object {
|
||||
// NewNumberObject creates a new primitive number object.
|
||||
func NewNumberObject(v float64) *Object {
|
||||
return NewPrimitiveObject(types.Number, v)
|
||||
}
|
||||
|
||||
// NewString creates a new primitive number object.
|
||||
func NewString(v string) *Object {
|
||||
var nullObj *Object = NewPrimitiveObject(types.Null, nil)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue