Skip to content

Commit

Permalink
Merge pull request #595 from shiroyk/es2023_array
Browse files Browse the repository at this point in the history
Implemented {Array, TypedArray}.prototype.{with, toReversed, toSorted}
  • Loading branch information
dop251 authored Aug 15, 2024
2 parents 3491d4a + a52ceb6 commit 7d18bf7
Show file tree
Hide file tree
Showing 4 changed files with 303 additions and 3 deletions.
178 changes: 178 additions & 0 deletions builtin_array.go
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,177 @@ func (r *Runtime) arrayproto_flatMap(call FunctionCall) Value {
return a
}

func (r *Runtime) arrayproto_with(call FunctionCall) Value {
o := call.This.ToObject(r)
relativeIndex := call.Argument(0).ToInteger()
value := call.Argument(1)
length := toLength(o.self.getStr("length", nil))

actualIndex := int64(0)
if relativeIndex >= 0 {
actualIndex = relativeIndex
} else {
actualIndex = length + relativeIndex
}
if actualIndex >= length || actualIndex < 0 {
panic(r.newError(r.getRangeError(), "Invalid index %s", call.Argument(0).String()))
}

if src := r.checkStdArrayObj(o); src != nil {
a := make([]Value, 0, length)
for k := int64(0); k < length; k++ {
pk := valueInt(k)
var fromValue Value
if k == actualIndex {
fromValue = value
} else {
fromValue = src.values[pk]
}
a = append(a, fromValue)
}
return r.newArrayValues(a)
} else {
a := r.newArrayLength(length)
for k := int64(0); k < length; k++ {
pk := valueInt(k)
var fromValue Value
if k == actualIndex {
fromValue = value
} else {
fromValue = o.self.getIdx(pk, nil)
}
createDataPropertyOrThrow(a, pk, fromValue)
}
return a
}
}

func (r *Runtime) arrayproto_toReversed(call FunctionCall) Value {
o := call.This.ToObject(r)
length := toLength(o.self.getStr("length", nil))

if src := r.checkStdArrayObj(o); src != nil {
a := make([]Value, 0, length)
for k := int64(0); k < length; k++ {
from := valueInt(length - k - 1)
fromValue := src.values[from]
a = append(a, fromValue)
}
return r.newArrayValues(a)
} else {
a := r.newArrayLength(length)
for k := int64(0); k < length; k++ {
pk := valueInt(k)
from := valueInt(length - k - 1)
fromValue := o.self.getIdx(from, nil)
createDataPropertyOrThrow(a, pk, fromValue)
}
return a
}
}

func (r *Runtime) arrayproto_toSorted(call FunctionCall) Value {
var compareFn func(FunctionCall) Value
arg := call.Argument(0)
if arg != _undefined {
if arg, ok := arg.(*Object); ok {
compareFn, _ = arg.self.assertCallable()
}
if compareFn == nil {
panic(r.NewTypeError("The comparison function must be either a function or undefined"))
}
}

o := call.This.ToObject(r)
length := toLength(o.self.getStr("length", nil))
if length >= math.MaxUint32 {
panic(r.newError(r.getRangeError(), "Invalid array length"))
}
var a []Value

if src := r.checkStdArrayObj(o); src != nil {
a = make([]Value, length)
copy(a, src.values)
} else {
a = make([]Value, 0, length)
for i := int64(0); i < length; i++ {
idx := valueInt(i)
a = append(a, nilSafe(o.self.getIdx(idx, nil)))
}
}

ar := r.newArrayValues(a)
ctx := arraySortCtx{
obj: ar.self,
compare: compareFn,
}

sort.Stable(&ctx)
return ar
}

func (r *Runtime) arrayproto_toSpliced(call FunctionCall) Value {
o := call.This.ToObject(r)
length := toLength(o.self.getStr("length", nil))
actualStart := relToIdx(call.Argument(0).ToInteger(), length)
var actualSkipCount int64
if len(call.Arguments) == 1 {
actualSkipCount = length - actualStart
} else if len(call.Arguments) > 1 {
actualSkipCount = min(max(call.Argument(1).ToInteger(), 0), length-actualStart)
}
itemCount := max(int64(len(call.Arguments)-2), 0)
newLength := length - actualSkipCount + itemCount
if newLength >= maxInt {
panic(r.NewTypeError("Invalid array length"))
}

if src := r.checkStdArrayObj(o); src != nil {
var values []Value
if itemCount == actualSkipCount {
values = make([]Value, len(src.values))
copy(values, src.values)
} else {
values = make([]Value, newLength)
copy(values, src.values[:actualStart])
copy(values[actualStart+itemCount:], src.values[actualStart+actualSkipCount:])
}
if itemCount > 0 {
copy(values[actualStart:], call.Arguments[2:])
}
return r.newArrayValues(values)
} else {
a := r.newArrayLength(newLength)
var i int64
rl := actualStart + actualSkipCount

for i < actualStart {
pi := valueInt(i)
iValue := nilSafe(o.self.getIdx(pi, nil))
createDataPropertyOrThrow(a, pi, iValue)
i++
}

if itemCount > 0 {
for _, item := range call.Arguments[2:] {
createDataPropertyOrThrow(a, valueInt(i), nilSafe(item))
i++
}
}

for i < newLength {
pi := valueInt(i)
from := valueInt(rl)
fromValue := nilSafe(o.self.getIdx(from, nil))
createDataPropertyOrThrow(a, pi, fromValue)
i++
rl++
}

return a
}
}

func (r *Runtime) checkStdArrayObj(obj *Object) *arrayObject {
if arr, ok := obj.self.(*arrayObject); ok &&
arr.propValueCount == 0 &&
Expand Down Expand Up @@ -1442,6 +1613,10 @@ func createArrayProtoTemplate() *objectTemplate {
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("with", func(r *Runtime) Value { return r.methodProp(r.arrayproto_with, "with", 2) })
t.putStr("toReversed", func(r *Runtime) Value { return r.methodProp(r.arrayproto_toReversed, "toReversed", 0) })
t.putStr("toSorted", func(r *Runtime) Value { return r.methodProp(r.arrayproto_toSorted, "toSorted", 1) })
t.putStr("toSpliced", func(r *Runtime) Value { return r.methodProp(r.arrayproto_toSpliced, "toSpliced", 2) })
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) })
Expand All @@ -1461,6 +1636,9 @@ func createArrayProtoTemplate() *objectTemplate {
bl.setOwnStr("values", valueTrue, true)
bl.setOwnStr("groupBy", valueTrue, true)
bl.setOwnStr("groupByToMap", valueTrue, true)
bl.setOwnStr("toReversed", valueTrue, true)
bl.setOwnStr("toSorted", valueTrue, true)
bl.setOwnStr("toSpliced", valueTrue, true)

return valueProp(bl.val, false, false, true)
})
Expand Down
28 changes: 27 additions & 1 deletion builtin_arrray_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package goja

import "testing"
import (
"testing"
)

func TestArrayProtoProp(t *testing.T) {
const SCRIPT = `
Expand Down Expand Up @@ -339,3 +341,27 @@ func TestArrayProto(t *testing.T) {
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}

func TestArrayToSpliced(t *testing.T) {
const SCRIPT = `
const a = [1, 2, 3];
a.push(4)
assert(compareArray(a, [1, 2, 3, 4]));
const b = a.toSpliced(2)
assert(compareArray(a, [1, 2, 3, 4]));
assert(compareArray(b, [1, 2]));
a.push(5)
const c = a.toSpliced(1, 2);
assert(compareArray(a, [1, 2, 3, 4, 5]));
assert(compareArray(c, [1, 4, 5]));
assert(compareArray(a.toSpliced(4, 2, 'a', 'b'), [1, 2, 3, 4, 'a', 'b']));
assert(compareArray(a, [1, 2, 3, 4, 5]));
assert(compareArray(a.toSpliced(-2, 2), [1, 2, 3]));
assert(compareArray(a, [1, 2, 3, 4, 5]));
assert(compareArray(a.toSpliced(2, 10), [1, 2]));
assert(compareArray(a, [1, 2, 3, 4, 5]));
assert(compareArray(a.toSpliced(1, 0, 'a'), [1, 'a', 2, 3, 4, 5]));
assert(compareArray(a, [1, 2, 3, 4, 5]));
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
92 changes: 92 additions & 0 deletions builtin_typedarrays.go
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,95 @@ func (r *Runtime) typedArrayProto_toStringTag(call FunctionCall) Value {
return _undefined
}

func (r *Runtime) typedArrayProto_with(call FunctionCall) Value {
o := call.This.ToObject(r)
ta, ok := o.self.(*typedArrayObject)
if !ok {
panic(r.NewTypeError("%s is not a valid TypedArray", r.objectproto_toString(FunctionCall{This: call.This})))
}
length := ta.length
relativeIndex := call.Argument(0).ToInteger()
var actualIndex int

if relativeIndex >= 0 {
actualIndex = toIntStrict(relativeIndex)
} else {
actualIndex = toIntStrict(int64(length) + relativeIndex)
}
if !ta.isValidIntegerIndex(actualIndex) {
panic(r.newError(r.getRangeError(), "Invalid typed array index"))
}

// TODO BigInt
// 7. If O.[[ContentType]] is BIGINT, let numericValue be ? ToBigInt(value).
// 8. Else, let numericValue be ? ToNumber(value).
numericValue := call.Argument(1).ToNumber()

a := r.typedArrayCreate(ta.defaultCtor, intToValue(int64(length)))
for k := 0; k < length; k++ {
var fromValue Value
if k == actualIndex {
fromValue = numericValue
} else {
fromValue = ta.typedArray.get(ta.offset + k)
}
a.typedArray.set(ta.offset+k, fromValue)
}
return a.val
}

func (r *Runtime) typedArrayProto_toReversed(call FunctionCall) Value {
o := call.This.ToObject(r)
ta, ok := o.self.(*typedArrayObject)
if !ok {
panic(r.NewTypeError("%s is not a valid TypedArray", r.objectproto_toString(FunctionCall{This: call.This})))
}
length := ta.length

a := r.typedArrayCreate(ta.defaultCtor, intToValue(int64(length)))

for k := 0; k < length; k++ {
from := length - k - 1
fromValue := ta.typedArray.get(ta.offset + from)
a.typedArray.set(ta.offset+k, fromValue)
}

return a.val
}

func (r *Runtime) typedArrayProto_toSorted(call FunctionCall) Value {
o := call.This.ToObject(r)
ta, ok := o.self.(*typedArrayObject)
if !ok {
panic(r.NewTypeError("%s is not a valid TypedArray", r.objectproto_toString(FunctionCall{This: call.This})))
}

var compareFn func(FunctionCall) Value
arg := call.Argument(0)
if arg != _undefined {
if arg, ok := arg.(*Object); ok {
compareFn, _ = arg.self.assertCallable()
}
if compareFn == nil {
panic(r.NewTypeError("The comparison function must be either a function or undefined"))
}
}

length := ta.length

a := r.typedArrayCreate(ta.defaultCtor, intToValue(int64(length)))
copy(a.viewedArrayBuf.data, ta.viewedArrayBuf.data)

ctx := typedArraySortCtx{
ta: a,
compare: compareFn,
}

sort.Stable(&ctx)

return a.val
}

func (r *Runtime) newTypedArray([]Value, *Object) *Object {
panic(r.NewTypeError("Abstract class TypedArray not directly constructable"))
}
Expand Down Expand Up @@ -1543,6 +1632,9 @@ func createTypedArrayProtoTemplate() *objectTemplate {
t.putStr("sort", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_sort, "sort", 1) })
t.putStr("subarray", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_subarray, "subarray", 2) })
t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_toLocaleString, "toLocaleString", 0) })
t.putStr("with", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_with, "with", 2) })
t.putStr("toReversed", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_toReversed, "toReversed", 0) })
t.putStr("toSorted", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_toSorted, "toSorted", 1) })
t.putStr("toString", func(r *Runtime) Value { return valueProp(r.getArrayToString(), true, false, true) })
t.putStr("values", func(r *Runtime) Value { return valueProp(r.getTypedArrayValues(), true, false, true) })

Expand Down
8 changes: 6 additions & 2 deletions tc39_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,12 @@ var (
"test/language/expressions/class/cpn-class-expr-fields-methods-computed-property-name-from-integer-separators.js": true,

// BigInt
"test/built-ins/Object/seal/seal-biguint64array.js": true,
"test/built-ins/Object/seal/seal-bigint64array.js": true,
"test/built-ins/Object/seal/seal-biguint64array.js": true,
"test/built-ins/Object/seal/seal-bigint64array.js": true,
"test/built-ins/Array/prototype/toSorted/comparefn-not-a-function.js": true,
"test/built-ins/TypedArray/prototype/toReversed/this-value-invalid.js": true,
"test/built-ins/TypedArray/prototype/toSorted/comparefn-not-a-function.js": true,
"test/built-ins/TypedArray/prototype/toSorted/this-value-invalid.js": true,

// Regexp
"test/language/literals/regexp/invalid-range-negative-lookbehind.js": true,
Expand Down

0 comments on commit 7d18bf7

Please sign in to comment.