Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented {Array, TypedArray}.prototype.{with, toReversed, toSorted} #595

Merged
merged 1 commit into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading