Implement static properties in the runtime
This commit is contained in:
parent
aec1dccd5d
commit
55deb13100
6 changed files with 103 additions and 47 deletions
|
@ -33,4 +33,8 @@ var (
|
|||
ErrorArgumentCountMismatch = newError(523, "Function expects %v arguments; got %v instead")
|
||||
ErrorConstructorReturnType = newError(524, "Constructor '%v' has a return type '%v'; should be nil (void)")
|
||||
ErrorConstructorNotMethod = newError(525, "Constructor '%v' is not a method; got %v instead")
|
||||
ErrorExpectedObject = newError(526,
|
||||
"Expected an object target for this instance member load operation")
|
||||
ErrorUnexpectedObject = newError(527,
|
||||
"Unexpected object target for this static or module load operation")
|
||||
)
|
||||
|
|
|
@ -13,6 +13,4 @@ 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")
|
||||
)
|
||||
|
|
|
@ -55,6 +55,7 @@ func New(ctx *binder.Context, hooks InterpreterHooks) Interpreter {
|
|||
hooks: hooks,
|
||||
alloc: NewAllocator(hooks),
|
||||
globals: make(globalMap),
|
||||
statics: make(staticMap),
|
||||
modinits: make(modinitMap),
|
||||
classinits: make(classinitMap),
|
||||
}
|
||||
|
@ -69,6 +70,7 @@ type evaluator struct {
|
|||
hooks InterpreterHooks // callbacks that hook into interpreter events.
|
||||
alloc *Allocator // the object allocator.
|
||||
globals globalMap // the object values for global variable symbols.
|
||||
statics staticMap // the object values for all static variable symbols.
|
||||
stack *rt.StackFrame // a stack of frames to keep track of calls.
|
||||
locals *localScope // local variable values scoped by the lexical structure.
|
||||
modinits modinitMap // a map of which modules have been initialized already.
|
||||
|
@ -76,6 +78,7 @@ type evaluator struct {
|
|||
}
|
||||
|
||||
type globalMap map[symbols.Variable]*rt.Pointer
|
||||
type staticMap map[*symbols.Class]*rt.ClassStatics
|
||||
type modinitMap map[*symbols.Module]bool
|
||||
type classinitMap map[*symbols.Class]bool
|
||||
|
||||
|
@ -879,15 +882,24 @@ 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.
|
||||
// Based on whether this is static or not, load either from the class statics map, or from `this`.
|
||||
key := rt.PropertyKey(sym.Name())
|
||||
if s.Static() {
|
||||
contract.Assert(this == nil)
|
||||
} else if uw := e.checkThis(node, this); uw != nil {
|
||||
return location{}, uw
|
||||
class := s.Parent
|
||||
statics, has := e.statics[class]
|
||||
if !has {
|
||||
statics = rt.NewClassStatics(class)
|
||||
e.statics[class] = statics
|
||||
}
|
||||
pv = statics.GetPropertyAddr(key, true)
|
||||
} else {
|
||||
contract.Assert(this != nil)
|
||||
if uw := e.checkThis(node, this); uw != nil {
|
||||
return location{}, uw
|
||||
}
|
||||
pv = this.GetPropertyAddr(key, true)
|
||||
}
|
||||
|
||||
// Search the class's properties and, if not present, allocate a new one.
|
||||
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.
|
||||
|
|
26
pkg/eval/rt/class.go
Normal file
26
pkg/eval/rt/class.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2016 Marapongo, Inc. All rights reserved.
|
||||
|
||||
package rt
|
||||
|
||||
import (
|
||||
"github.com/marapongo/mu/pkg/compiler/symbols"
|
||||
)
|
||||
|
||||
// ClassStatics is a holder for static variables associated with a given class.
|
||||
type ClassStatics struct {
|
||||
class *symbols.Class
|
||||
statics Properties
|
||||
}
|
||||
|
||||
func NewClassStatics(class *symbols.Class) *ClassStatics {
|
||||
return &ClassStatics{
|
||||
class: class,
|
||||
statics: make(Properties),
|
||||
}
|
||||
}
|
||||
|
||||
// GetPropertyAddr returns the reference to a class's static property, lazily initializing if 'init' is true, or
|
||||
// returning nil otherwise.
|
||||
func (c *ClassStatics) GetPropertyAddr(key PropertyKey, init bool) *Pointer {
|
||||
return c.statics.GetAddr(key, init, c.class, nil)
|
||||
}
|
|
@ -9,7 +9,6 @@ import (
|
|||
"github.com/marapongo/mu/pkg/compiler/symbols"
|
||||
"github.com/marapongo/mu/pkg/compiler/types"
|
||||
"github.com/marapongo/mu/pkg/diag"
|
||||
"github.com/marapongo/mu/pkg/tokens"
|
||||
"github.com/marapongo/mu/pkg/util/contract"
|
||||
)
|
||||
|
||||
|
@ -23,9 +22,7 @@ type Object struct {
|
|||
|
||||
var _ fmt.Stringer = (*Object)(nil)
|
||||
|
||||
type Value interface{} // a literal object value.
|
||||
type Properties map[PropertyKey]*Pointer // an object's properties.
|
||||
type PropertyKey string // property keys are strings (incl. invalid identifiers for dynamic).
|
||||
type Value interface{} // a literal object value.
|
||||
|
||||
// NewObject allocates a new object with the given type, primitive value, and properties.
|
||||
func NewObject(t symbols.Type, value Value, properties Properties) *Object {
|
||||
|
@ -93,41 +90,8 @@ func (o *Object) ExceptionValue() ExceptionInfo {
|
|||
// 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 {
|
||||
ptr, hasprop := o.properties[key]
|
||||
if !hasprop {
|
||||
// Look up the property definition (if any) in the members list, to seed a default value.
|
||||
var obj *Object
|
||||
var readonly bool
|
||||
if class, isclass := o.t.(*symbols.Class); isclass {
|
||||
if member, hasmember := class.Members[tokens.ClassMemberName(key)]; hasmember {
|
||||
switch m := member.(type) {
|
||||
case *symbols.ClassProperty:
|
||||
if m.Default() != nil {
|
||||
obj = NewConstantObject(*m.Default())
|
||||
}
|
||||
readonly = m.Readonly()
|
||||
case *symbols.ClassMethod:
|
||||
if m.Static() {
|
||||
obj = NewFunctionObject(m, nil)
|
||||
} else {
|
||||
obj = NewFunctionObject(m, o)
|
||||
}
|
||||
readonly = true // TODO[marapongo/mu#56]: consider permitting JS-style overwriting of methods.
|
||||
default:
|
||||
contract.Failf("Unexpected member type: %v", member)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
return ptr
|
||||
class, _ := o.t.(*symbols.Class)
|
||||
return o.properties.GetAddr(key, init, class, o)
|
||||
}
|
||||
|
||||
// String can be used to print the contents of an object; it tries to be smart about the display.
|
||||
|
|
52
pkg/eval/rt/property.go
Normal file
52
pkg/eval/rt/property.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
// Copyright 2016 Marapongo, Inc. All rights reserved.
|
||||
|
||||
package rt
|
||||
|
||||
import (
|
||||
"github.com/marapongo/mu/pkg/compiler/symbols"
|
||||
"github.com/marapongo/mu/pkg/tokens"
|
||||
"github.com/marapongo/mu/pkg/util/contract"
|
||||
)
|
||||
|
||||
type Properties map[PropertyKey]*Pointer // an object's properties.
|
||||
type PropertyKey string // property keys are strings (incl. invalid identifiers for dynamic).
|
||||
|
||||
// GetAddr returns a reference to a map's property. If no entry is found and `init` is true, the property will
|
||||
// be auto-initialized. If this happens and class is non-nil, it will be used to seed the default value.
|
||||
func (props Properties) GetAddr(key PropertyKey, init bool, class *symbols.Class, this *Object) *Pointer {
|
||||
ptr, hasprop := props[key]
|
||||
if !hasprop && init {
|
||||
// The property didn't already exist, but was zero-initialized to null. Look up the property definition (if
|
||||
// any) in the members list, so that we may seed a default initialization value.
|
||||
var obj *Object
|
||||
var readonly bool
|
||||
if class != nil {
|
||||
if member, hasmember := class.Members[tokens.ClassMemberName(key)]; hasmember {
|
||||
// Only use this member if it's of the expected instance/static variety.
|
||||
if member.Static() == (this == nil) {
|
||||
switch m := member.(type) {
|
||||
case *symbols.ClassProperty:
|
||||
if m.Default() != nil {
|
||||
obj = NewConstantObject(*m.Default())
|
||||
}
|
||||
readonly = m.Readonly()
|
||||
case *symbols.ClassMethod:
|
||||
obj = NewFunctionObject(m, this)
|
||||
readonly = true // TODO[marapongo/mu#56]: consider permitting JS-style overwriting of methods.
|
||||
default:
|
||||
contract.Failf("Unexpected member type: %v", member)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no entry was found, and init is true, initialize the slot to null.
|
||||
if obj == nil {
|
||||
obj = NewNullObject()
|
||||
}
|
||||
|
||||
ptr = NewPointer(obj, readonly)
|
||||
props[key] = ptr
|
||||
}
|
||||
return ptr
|
||||
}
|
Loading…
Reference in a new issue