Implement array l-values

This commit implements array l-values (for dynamic loads only, since we do
not yet ever produce static ones).  Note that there are some ECMAScript
compliance noteworthies about this change, which are captured in comments.

Namely, we are emulating ECMAScript's ability to index into an array
anywhere (except for negative numbers, which is a problem).  This is in
contrast to the usual approach of throwing an error on out-of-bounds access,
which will crop up when we move on to other languages like Python.  And yet we
are usuing a real array as the backing store, which can cause problems with
some real ECMAScript programs that use sparse arrays and expect the "bag of
properties" approach to keep memory usage reasonable.

The work item marapongo/mu#70 tracks this among other things.
This commit is contained in:
joeduffy 2017-02-13 07:23:00 -08:00
parent 1af9cd720b
commit b1f96964ac
4 changed files with 56 additions and 11 deletions

View file

@ -117,6 +117,11 @@ func (a *astBinder) isLValue(expr ast.Expression) bool {
return true
}
// If the target is a dynamic load, permit that too.
if _, isdload := expr.(*ast.LoadDynamicExpression); isdload {
return true
}
// Otherwise, if the target is a pointer dereference, it is also okay.
if unop, isunop := expr.(*ast.UnaryOperatorExpression); isunop {
if unop.Operator == ast.OpDereference {

View file

@ -33,7 +33,7 @@ func (a *Allocator) New(t symbols.Type) *rt.Object {
}
// NewArray creates a new array object of the given element type.
func (a *Allocator) NewArray(elem symbols.Type, arr interface{}) *rt.Object {
func (a *Allocator) NewArray(elem symbols.Type, arr *[]*rt.Pointer) *rt.Object {
obj := rt.NewArrayObject(elem, arr)
a.onNewObject(obj)
return obj

View file

@ -747,7 +747,7 @@ func (e *evaluator) evalArrayLiteral(node *ast.ArrayLiteral) (*rt.Object, *Unwin
// Now create the array data.
var sz *int
var arr []rt.Value
var arr []*rt.Pointer
// If there's a node size, ensure it's a number, and initialize the array.
if node.Size != nil {
@ -761,7 +761,7 @@ func (e *evaluator) evalArrayLiteral(node *ast.ArrayLiteral) (*rt.Object, *Unwin
// If the size is less than zero, raise a new error.
return nil, NewThrowUnwind(e.NewNegativeArrayLengthException(*node.Size))
}
arr = make([]rt.Value, sz)
arr = make([]*rt.Pointer, sz)
}
// Allocate a new array object.
@ -773,7 +773,7 @@ func (e *evaluator) evalArrayLiteral(node *ast.ArrayLiteral) (*rt.Object, *Unwin
if node.Elements != nil {
if sz == nil {
// Right-size the array.
arr = make([]rt.Value, 0, len(*node.Elements))
arr = make([]*rt.Pointer, 0, len(*node.Elements))
} else if len(*node.Elements) > *sz {
// The element count exceeds the size; raise an error.
return nil, NewThrowUnwind(
@ -785,10 +785,11 @@ func (e *evaluator) evalArrayLiteral(node *ast.ArrayLiteral) (*rt.Object, *Unwin
if uw != nil {
return nil, uw
}
elemptr := rt.NewPointer(elemobj, false)
if sz == nil {
arr = append(arr, elemobj)
arr = append(arr, elemptr)
} else {
arr[i] = elemobj
arr[i] = elemptr
}
// Track all assignments.
@ -995,9 +996,38 @@ func (e *evaluator) evalLoadDynamic(node *ast.LoadDynamicExpression, lval bool)
}
// 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")
key := name.StringValue()
pv := this.GetPropertyAddr(rt.PropertyKey(key), true)
var pv *rt.Pointer
var key tokens.Name
if name.Type() == types.Number {
_, isarr := this.Type().(*symbols.ArrayType)
contract.Assertf(isarr, "Expected an array for numeric dynamic load index")
ix := int(name.NumberValue())
arrv := this.ArrayValue()
// TODO[marapongo/mu#70]: Although storing arrays as arrays is fine for many circumstances, there are two cases
// particular that could cause us troubles with ECMAScript compliance. First, negative indices are fine in
// ECMAScript. Second, sparse arrays can be represented more efficiently as a "bag of properties" than as a
// true array that needs to be resized (possibly growing to become enormous in memory usage).
// TODO[marapongo/mu#70]: We are emulating "ECMAScript-like" array accesses, where -- just like ordinary
// property accesses below -- we will permit indexes that we've never seen before. Out of bounds should
// yield `undefined`, rather than the usual case of throwing an exception, for example. And such
// assignments are to be permitted. This will cause troubles down the road when we do other languages that
// reject out of bounds accesses e.g. Python. An alternative approach would be to require ECMAScript to
// use a runtime library anytime an array is accessed, translating exceptions like this into `undefined`s.
if ix > len(*arrv) {
newarr := make([]*rt.Pointer, ix+1)
copy(*arrv, newarr)
*arrv = newarr
}
pv = (*arrv)[ix]
if pv == nil {
pv = rt.NewPointer(e.alloc.NewNull(), false)
(*arrv)[ix] = pv
}
} else {
contract.Assertf(name.Type() == types.String, "Expected dynamic load name to be a string")
key = tokens.Name(name.StringValue())
pv = this.GetPropertyAddr(rt.PropertyKey(key), 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

View file

@ -36,6 +36,16 @@ func (o *Object) Type() symbols.Type { return o.t }
func (o *Object) Value() Value { return o.value }
func (o *Object) Properties() Properties { return o.properties }
// ArrayValue asserts that the target is an array literal and returns its value.
func (o *Object) ArrayValue() *[]*Pointer {
_, isarr := o.t.(*symbols.ArrayType)
contract.Assertf(isarr, "Expected object type to be Array; got %v", o.t)
contract.Assertf(o.value != nil, "Expected Array object to carry a Value; got nil")
arr, ok := o.value.(*[]*Pointer)
contract.Assertf(ok, "Expected Array object's Value to be a *[]interface{}")
return arr
}
// BoolValue asserts that the target is a boolean literal and returns its value.
func (o *Object) BoolValue() bool {
contract.Assertf(o.t == types.Bool, "Expected object type to be Bool; got %v", o.t)
@ -146,8 +156,8 @@ func NewPrimitiveObject(t symbols.Type, v interface{}) *Object {
return NewObject(t, v, nil)
}
// NewArrayObject allocates a new pointer-like object that wraps the given reference.
func NewArrayObject(elem symbols.Type, arr interface{}) *Object {
// NewArrayObject allocates a new array object with the given array payload.
func NewArrayObject(elem symbols.Type, arr *[]*Pointer) *Object {
contract.Require(elem != nil, "elem")
arrt := symbols.NewArrayType(elem)
return NewPrimitiveObject(arrt, arr)