From a52ceb6d86b07d03957418609e65263ac4c51085 Mon Sep 17 00:00:00 2001 From: shiroyk Date: Wed, 14 Aug 2024 17:30:46 +0800 Subject: [PATCH] Implemented {Array, TypedArray}.prototype.{with, toReversed, toSorted} `{Array, TypedArray}.prototype.toSorted` `{Array, TypedArray}.prototype.toReversed` `{Array, TypedArray}.prototype.with` `Array.prototype.toSpliced` --- builtin_array.go | 178 +++++++++++++++++++++++++++++++++++++++++ builtin_arrray_test.go | 28 ++++++- builtin_typedarrays.go | 92 +++++++++++++++++++++ tc39_test.go | 8 +- 4 files changed, 303 insertions(+), 3 deletions(-) diff --git a/builtin_array.go b/builtin_array.go index 6ba8802c..147bf4b8 100644 --- a/builtin_array.go +++ b/builtin_array.go @@ -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 && @@ -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) }) @@ -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) }) diff --git a/builtin_arrray_test.go b/builtin_arrray_test.go index a190f1f4..2fcc15e8 100644 --- a/builtin_arrray_test.go +++ b/builtin_arrray_test.go @@ -1,6 +1,8 @@ package goja -import "testing" +import ( + "testing" +) func TestArrayProtoProp(t *testing.T) { const SCRIPT = ` @@ -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) +} diff --git a/builtin_typedarrays.go b/builtin_typedarrays.go index 1fd672c1..14431342 100644 --- a/builtin_typedarrays.go +++ b/builtin_typedarrays.go @@ -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")) } @@ -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) }) diff --git a/tc39_test.go b/tc39_test.go index 964e2b57..841feae1 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -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,