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:
parent
1af9cd720b
commit
b1f96964ac
4 changed files with 56 additions and 11 deletions
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue