Skip to content

Commit

Permalink
Implemented template-backed objects and used them for most of the bui…
Browse files Browse the repository at this point in the history
…lt-ins. Closes #524, closes #459.
  • Loading branch information
dop251 committed Sep 6, 2023
1 parent 3dbe69d commit 9410bca
Show file tree
Hide file tree
Showing 40 changed files with 2,218 additions and 1,309 deletions.
14 changes: 13 additions & 1 deletion array.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,18 @@ func (a *arrayObject) hasOwnPropertyIdx(idx valueInt) bool {
return a.baseObject.hasOwnPropertyStr(idx.string())
}

func (a *arrayObject) hasPropertyIdx(idx valueInt) bool {
if a.hasOwnPropertyIdx(idx) {
return true
}

if a.prototype != nil {
return a.prototype.self.hasPropertyIdx(idx)
}

return false
}

func (a *arrayObject) expand(idx uint32) bool {
targetLen := idx + 1
if targetLen > uint32(len(a.values)) {
Expand Down Expand Up @@ -509,7 +521,7 @@ func (a *arrayObject) exportType() reflect.Type {

func (a *arrayObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
r := a.val.runtime
if iter := a.getSym(SymIterator, nil); iter == r.global.arrayValues || iter == nil {
if iter := a.getSym(SymIterator, nil); iter == r.getArrayValues() || iter == nil {
l := toIntStrict(int64(a.length))
if typ.Kind() == reflect.Array {
if dst.Len() != l {
Expand Down
14 changes: 13 additions & 1 deletion array_sparse.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,18 @@ func (a *sparseArrayObject) hasOwnPropertyIdx(idx valueInt) bool {
return a.baseObject.hasOwnPropertyStr(idx.string())
}

func (a *sparseArrayObject) hasPropertyIdx(idx valueInt) bool {
if a.hasOwnPropertyIdx(idx) {
return true
}

if a.prototype != nil {
return a.prototype.self.hasPropertyIdx(idx)
}

return false
}

func (a *sparseArrayObject) expand(idx uint32) bool {
if l := len(a.items); l >= 1024 {
if ii := a.items[l-1].idx; ii > idx {
Expand Down Expand Up @@ -458,7 +470,7 @@ func (a *sparseArrayObject) exportType() reflect.Type {

func (a *sparseArrayObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
r := a.val.runtime
if iter := a.getSym(SymIterator, nil); iter == r.global.arrayValues || iter == nil {
if iter := a.getSym(SymIterator, nil); iter == r.getArrayValues() || iter == nil {
l := toIntStrict(int64(a.length))
if typ.Kind() == reflect.Array {
if dst.Len() != l {
Expand Down
201 changes: 120 additions & 81 deletions builtin_array.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package goja
import (
"math"
"sort"
"sync"
)

func (r *Runtime) newArray(prototype *Object) (a *arrayObject) {
Expand All @@ -19,7 +20,7 @@ func (r *Runtime) newArray(prototype *Object) (a *arrayObject) {
}

func (r *Runtime) newArrayObject() *arrayObject {
return r.newArray(r.global.ArrayPrototype)
return r.newArray(r.getArrayPrototype())
}

func setArrayValues(a *arrayObject, values []Value) *arrayObject {
Expand Down Expand Up @@ -96,7 +97,7 @@ func (r *Runtime) builtin_newArray(args []Value, proto *Object) *Object {
if float64(al) == float64(f) {
return r.newArrayLength(al)
} else {
panic(r.newError(r.global.RangeError, "Invalid array length"))
panic(r.newError(r.getRangeError(), "Invalid array length"))
}
}
return setArrayValues(r.newArray(proto), []Value{args[0]}).val
Expand Down Expand Up @@ -1259,7 +1260,7 @@ func (r *Runtime) checkStdArray(v Value) *arrayObject {

func (r *Runtime) checkStdArrayIter(v Value) *arrayObject {
if arr := r.checkStdArray(v); arr != nil &&
arr.getSym(SymIterator, nil) == r.global.arrayValues {
arr.getSym(SymIterator, nil) == r.getArrayValues() {

return arr
}
Expand Down Expand Up @@ -1398,80 +1399,110 @@ func (r *Runtime) arrayIterProto_next(call FunctionCall) Value {
panic(r.NewTypeError("Method Array Iterator.prototype.next called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}

func (r *Runtime) createArrayProto(val *Object) objectImpl {
o := &arrayObject{
baseObject: baseObject{
class: classArray,
val: val,
extensible: true,
prototype: r.global.ObjectPrototype,
},
}
o.init()

o._putProp("at", r.newNativeFunc(r.arrayproto_at, nil, "at", nil, 1), true, false, true)
o._putProp("constructor", r.global.Array, true, false, true)
o._putProp("concat", r.newNativeFunc(r.arrayproto_concat, nil, "concat", nil, 1), true, false, true)
o._putProp("copyWithin", r.newNativeFunc(r.arrayproto_copyWithin, nil, "copyWithin", nil, 2), true, false, true)
o._putProp("entries", r.newNativeFunc(r.arrayproto_entries, nil, "entries", nil, 0), true, false, true)
o._putProp("every", r.newNativeFunc(r.arrayproto_every, nil, "every", nil, 1), true, false, true)
o._putProp("fill", r.newNativeFunc(r.arrayproto_fill, nil, "fill", nil, 1), true, false, true)
o._putProp("filter", r.newNativeFunc(r.arrayproto_filter, nil, "filter", nil, 1), true, false, true)
o._putProp("find", r.newNativeFunc(r.arrayproto_find, nil, "find", nil, 1), true, false, true)
o._putProp("findIndex", r.newNativeFunc(r.arrayproto_findIndex, nil, "findIndex", nil, 1), true, false, true)
o._putProp("findLast", r.newNativeFunc(r.arrayproto_findLast, nil, "findLast", nil, 1), true, false, true)
o._putProp("findLastIndex", r.newNativeFunc(r.arrayproto_findLastIndex, nil, "findLastIndex", nil, 1), true, false, true)
o._putProp("flat", r.newNativeFunc(r.arrayproto_flat, nil, "flat", nil, 0), true, false, true)
o._putProp("flatMap", r.newNativeFunc(r.arrayproto_flatMap, nil, "flatMap", nil, 1), true, false, true)
o._putProp("forEach", r.newNativeFunc(r.arrayproto_forEach, nil, "forEach", nil, 1), true, false, true)
o._putProp("includes", r.newNativeFunc(r.arrayproto_includes, nil, "includes", nil, 1), true, false, true)
o._putProp("indexOf", r.newNativeFunc(r.arrayproto_indexOf, nil, "indexOf", nil, 1), true, false, true)
o._putProp("join", r.newNativeFunc(r.arrayproto_join, nil, "join", nil, 1), true, false, true)
o._putProp("keys", r.newNativeFunc(r.arrayproto_keys, nil, "keys", nil, 0), true, false, true)
o._putProp("lastIndexOf", r.newNativeFunc(r.arrayproto_lastIndexOf, nil, "lastIndexOf", nil, 1), true, false, true)
o._putProp("map", r.newNativeFunc(r.arrayproto_map, nil, "map", nil, 1), true, false, true)
o._putProp("pop", r.newNativeFunc(r.arrayproto_pop, nil, "pop", nil, 0), true, false, true)
o._putProp("push", r.newNativeFunc(r.arrayproto_push, nil, "push", nil, 1), true, false, true)
o._putProp("reduce", r.newNativeFunc(r.arrayproto_reduce, nil, "reduce", nil, 1), true, false, true)
o._putProp("reduceRight", r.newNativeFunc(r.arrayproto_reduceRight, nil, "reduceRight", nil, 1), true, false, true)
o._putProp("reverse", r.newNativeFunc(r.arrayproto_reverse, nil, "reverse", nil, 0), true, false, true)
o._putProp("shift", r.newNativeFunc(r.arrayproto_shift, nil, "shift", nil, 0), true, false, true)
o._putProp("slice", r.newNativeFunc(r.arrayproto_slice, nil, "slice", nil, 2), true, false, true)
o._putProp("some", r.newNativeFunc(r.arrayproto_some, nil, "some", nil, 1), true, false, true)
o._putProp("sort", r.newNativeFunc(r.arrayproto_sort, nil, "sort", nil, 1), true, false, true)
o._putProp("splice", r.newNativeFunc(r.arrayproto_splice, nil, "splice", nil, 2), true, false, true)
o._putProp("toLocaleString", r.newNativeFunc(r.arrayproto_toLocaleString, nil, "toLocaleString", nil, 0), true, false, true)
o._putProp("toString", r.global.arrayToString, true, false, true)
o._putProp("unshift", r.newNativeFunc(r.arrayproto_unshift, nil, "unshift", nil, 1), true, false, true)
o._putProp("values", r.global.arrayValues, true, false, true)

o._putSym(SymIterator, valueProp(r.global.arrayValues, true, false, true))

bl := r.newBaseObject(nil, classObject)
bl.setOwnStr("copyWithin", valueTrue, true)
bl.setOwnStr("entries", valueTrue, true)
bl.setOwnStr("fill", valueTrue, true)
bl.setOwnStr("find", valueTrue, true)
bl.setOwnStr("findIndex", valueTrue, true)
bl.setOwnStr("findLast", valueTrue, true)
bl.setOwnStr("findLastIndex", valueTrue, true)
bl.setOwnStr("flat", valueTrue, true)
bl.setOwnStr("flatMap", valueTrue, true)
bl.setOwnStr("includes", valueTrue, true)
bl.setOwnStr("keys", valueTrue, true)
bl.setOwnStr("values", valueTrue, true)
bl.setOwnStr("groupBy", valueTrue, true)
bl.setOwnStr("groupByToMap", valueTrue, true)
o._putSym(SymUnscopables, valueProp(bl.val, false, false, true))
func createArrayProtoTemplate() *objectTemplate {
t := newObjectTemplate()
t.protoFactory = func(r *Runtime) *Object {
return r.global.ObjectPrototype
}

t.putStr("length", func(r *Runtime) Value { return valueProp(_positiveZero, true, false, false) })

t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getArray(), true, false, true) })

t.putStr("at", func(r *Runtime) Value { return r.methodProp(r.arrayproto_at, "at", 1) })
t.putStr("concat", func(r *Runtime) Value { return r.methodProp(r.arrayproto_concat, "concat", 1) })
t.putStr("copyWithin", func(r *Runtime) Value { return r.methodProp(r.arrayproto_copyWithin, "copyWithin", 2) })
t.putStr("entries", func(r *Runtime) Value { return r.methodProp(r.arrayproto_entries, "entries", 0) })
t.putStr("every", func(r *Runtime) Value { return r.methodProp(r.arrayproto_every, "every", 1) })
t.putStr("fill", func(r *Runtime) Value { return r.methodProp(r.arrayproto_fill, "fill", 1) })
t.putStr("filter", func(r *Runtime) Value { return r.methodProp(r.arrayproto_filter, "filter", 1) })
t.putStr("find", func(r *Runtime) Value { return r.methodProp(r.arrayproto_find, "find", 1) })
t.putStr("findIndex", func(r *Runtime) Value { return r.methodProp(r.arrayproto_findIndex, "findIndex", 1) })
t.putStr("findLast", func(r *Runtime) Value { return r.methodProp(r.arrayproto_findLast, "findLast", 1) })
t.putStr("findLastIndex", func(r *Runtime) Value { return r.methodProp(r.arrayproto_findLastIndex, "findLastIndex", 1) })
t.putStr("flat", func(r *Runtime) Value { return r.methodProp(r.arrayproto_flat, "flat", 0) })
t.putStr("flatMap", func(r *Runtime) Value { return r.methodProp(r.arrayproto_flatMap, "flatMap", 1) })
t.putStr("forEach", func(r *Runtime) Value { return r.methodProp(r.arrayproto_forEach, "forEach", 1) })
t.putStr("includes", func(r *Runtime) Value { return r.methodProp(r.arrayproto_includes, "includes", 1) })
t.putStr("indexOf", func(r *Runtime) Value { return r.methodProp(r.arrayproto_indexOf, "indexOf", 1) })
t.putStr("join", func(r *Runtime) Value { return r.methodProp(r.arrayproto_join, "join", 1) })
t.putStr("keys", func(r *Runtime) Value { return r.methodProp(r.arrayproto_keys, "keys", 0) })
t.putStr("lastIndexOf", func(r *Runtime) Value { return r.methodProp(r.arrayproto_lastIndexOf, "lastIndexOf", 1) })
t.putStr("map", func(r *Runtime) Value { return r.methodProp(r.arrayproto_map, "map", 1) })
t.putStr("pop", func(r *Runtime) Value { return r.methodProp(r.arrayproto_pop, "pop", 0) })
t.putStr("push", func(r *Runtime) Value { return r.methodProp(r.arrayproto_push, "push", 1) })
t.putStr("reduce", func(r *Runtime) Value { return r.methodProp(r.arrayproto_reduce, "reduce", 1) })
t.putStr("reduceRight", func(r *Runtime) Value { return r.methodProp(r.arrayproto_reduceRight, "reduceRight", 1) })
t.putStr("reverse", func(r *Runtime) Value { return r.methodProp(r.arrayproto_reverse, "reverse", 0) })
t.putStr("shift", func(r *Runtime) Value { return r.methodProp(r.arrayproto_shift, "shift", 0) })
t.putStr("slice", func(r *Runtime) Value { return r.methodProp(r.arrayproto_slice, "slice", 2) })
t.putStr("some", func(r *Runtime) Value { return r.methodProp(r.arrayproto_some, "some", 1) })
t.putStr("sort", func(r *Runtime) Value { return r.methodProp(r.arrayproto_sort, "sort", 1) })
t.putStr("splice", func(r *Runtime) Value { return r.methodProp(r.arrayproto_splice, "splice", 2) })
t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.arrayproto_toLocaleString, "toLocaleString", 0) })
t.putStr("toString", func(r *Runtime) Value { return valueProp(r.getArrayToString(), true, false, true) })
t.putStr("unshift", func(r *Runtime) Value { return r.methodProp(r.arrayproto_unshift, "unshift", 1) })
t.putStr("values", func(r *Runtime) Value { return valueProp(r.getArrayValues(), true, false, true) })

t.putSym(SymIterator, func(r *Runtime) Value { return valueProp(r.getArrayValues(), true, false, true) })
t.putSym(SymUnscopables, func(r *Runtime) Value {
bl := r.newBaseObject(nil, classObject)
bl.setOwnStr("copyWithin", valueTrue, true)
bl.setOwnStr("entries", valueTrue, true)
bl.setOwnStr("fill", valueTrue, true)
bl.setOwnStr("find", valueTrue, true)
bl.setOwnStr("findIndex", valueTrue, true)
bl.setOwnStr("findLast", valueTrue, true)
bl.setOwnStr("findLastIndex", valueTrue, true)
bl.setOwnStr("flat", valueTrue, true)
bl.setOwnStr("flatMap", valueTrue, true)
bl.setOwnStr("includes", valueTrue, true)
bl.setOwnStr("keys", valueTrue, true)
bl.setOwnStr("values", valueTrue, true)
bl.setOwnStr("groupBy", valueTrue, true)
bl.setOwnStr("groupByToMap", valueTrue, true)

return valueProp(bl.val, false, false, true)
})

return o
return t
}

var arrayProtoTemplate *objectTemplate
var arrayProtoTemplateOnce sync.Once

func getArrayProtoTemplate() *objectTemplate {
arrayProtoTemplateOnce.Do(func() {
arrayProtoTemplate = createArrayProtoTemplate()
})
return arrayProtoTemplate
}

func (r *Runtime) getArrayPrototype() *Object {
ret := r.global.ArrayPrototype
if ret == nil {
ret = &Object{runtime: r}
r.global.ArrayPrototype = ret
r.newTemplatedArrayObject(getArrayProtoTemplate(), ret)
}
return ret
}

func (r *Runtime) getArray() *Object {
ret := r.global.Array
if ret == nil {
ret = &Object{runtime: r}
ret.self = r.createArray(ret)
r.global.Array = ret
}
return ret
}

func (r *Runtime) createArray(val *Object) objectImpl {
o := r.newNativeFuncConstructObj(val, r.builtin_newArray, "Array", r.global.ArrayPrototype, 1)
o._putProp("from", r.newNativeFunc(r.array_from, nil, "from", nil, 1), true, false, true)
o._putProp("isArray", r.newNativeFunc(r.array_isArray, nil, "isArray", nil, 1), true, false, true)
o._putProp("of", r.newNativeFunc(r.array_of, nil, "of", nil, 0), true, false, true)
o := r.newNativeFuncConstructObj(val, r.builtin_newArray, "Array", r.getArrayPrototype(), 1)
o._putProp("from", r.newNativeFunc(r.array_from, "from", 1), true, false, true)
o._putProp("isArray", r.newNativeFunc(r.array_isArray, "isArray", 1), true, false, true)
o._putProp("of", r.newNativeFunc(r.array_of, "of", 0), true, false, true)
r.putSpeciesReturnThis(o)

return o
Expand All @@ -1480,20 +1511,28 @@ func (r *Runtime) createArray(val *Object) objectImpl {
func (r *Runtime) createArrayIterProto(val *Object) objectImpl {
o := newBaseObjectObj(val, r.getIteratorPrototype(), classObject)

o._putProp("next", r.newNativeFunc(r.arrayIterProto_next, nil, "next", nil, 0), true, false, true)
o._putProp("next", r.newNativeFunc(r.arrayIterProto_next, "next", 0), true, false, true)
o._putSym(SymToStringTag, valueProp(asciiString(classArrayIterator), false, false, true))

return o
}

func (r *Runtime) initArray() {
r.global.arrayValues = r.newNativeFunc(r.arrayproto_values, nil, "values", nil, 0)
r.global.arrayToString = r.newNativeFunc(r.arrayproto_toString, nil, "toString", nil, 0)

r.global.ArrayPrototype = r.newLazyObject(r.createArrayProto)
func (r *Runtime) getArrayValues() *Object {
ret := r.global.arrayValues
if ret == nil {
ret = r.newNativeFunc(r.arrayproto_values, "values", 0)
r.global.arrayValues = ret
}
return ret
}

r.global.Array = r.newLazyObject(r.createArray)
r.addToGlobal("Array", r.global.Array)
func (r *Runtime) getArrayToString() *Object {
ret := r.global.arrayToString
if ret == nil {
ret = r.newNativeFunc(r.arrayproto_toString, "toString", 0)
r.global.arrayToString = ret
}
return ret
}

func (r *Runtime) getArrayIteratorPrototype() *Object {
Expand Down
16 changes: 16 additions & 0 deletions builtin_arrray_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,3 +323,19 @@ func TestArrayFlatMap(t *testing.T) {
`
testScriptWithTestLibX(SCRIPT, _undefined, t)
}

func TestArrayProto(t *testing.T) {
const SCRIPT = `
const a = Array.prototype;
a.push(1, 2, 3, 4, 5);
assert.sameValue(a.length, 5);
assert.sameValue(a[0], 1);
a.length = 3;
assert.sameValue(a.length, 3);
assert(compareArray(a, [1, 2, 3]));
a.shift();
assert.sameValue(a.length, 2);
assert(compareArray(a, [2, 3]));
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
29 changes: 22 additions & 7 deletions builtin_boolean.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,27 @@ func (r *Runtime) booleanproto_valueOf(call FunctionCall) Value {
return nil
}

func (r *Runtime) initBoolean() {
r.global.BooleanPrototype = r.newPrimitiveObject(valueFalse, r.global.ObjectPrototype, classBoolean)
o := r.global.BooleanPrototype.self
o._putProp("toString", r.newNativeFunc(r.booleanproto_toString, nil, "toString", nil, 0), true, false, true)
o._putProp("valueOf", r.newNativeFunc(r.booleanproto_valueOf, nil, "valueOf", nil, 0), true, false, true)
func (r *Runtime) getBooleanPrototype() *Object {
ret := r.global.BooleanPrototype
if ret == nil {
ret = r.newPrimitiveObject(valueFalse, r.global.ObjectPrototype, classBoolean)
r.global.BooleanPrototype = ret
o := ret.self
o._putProp("toString", r.newNativeFunc(r.booleanproto_toString, "toString", 0), true, false, true)
o._putProp("valueOf", r.newNativeFunc(r.booleanproto_valueOf, "valueOf", 0), true, false, true)
o._putProp("constructor", r.getBoolean(), true, false, true)
}
return ret
}

r.global.Boolean = r.newNativeFunc(r.builtin_Boolean, r.builtin_newBoolean, "Boolean", r.global.BooleanPrototype, 1)
r.addToGlobal("Boolean", r.global.Boolean)
func (r *Runtime) getBoolean() *Object {
ret := r.global.Boolean
if ret == nil {
ret = &Object{runtime: r}
r.global.Boolean = ret
proto := r.getBooleanPrototype()
r.newNativeFuncAndConstruct(ret, r.builtin_Boolean,
r.wrapNativeConstruct(r.builtin_newBoolean, ret, proto), proto, "Boolean", intToValue(1))
}
return ret
}
Loading

1 comment on commit 9410bca

@Calvinnix
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic addition! Great work on this!

Please sign in to comment.