Implement static properties in the runtime

This commit is contained in:
joeduffy 2017-02-13 05:45:28 -08:00
parent aec1dccd5d
commit 55deb13100
6 changed files with 103 additions and 47 deletions

View file

@ -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")
)

View file

@ -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")
)

View file

@ -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
View 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)
}

View file

@ -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
View 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
}