From eb0104882d9d37f8507829139fe0b53f5c2445c0 Mon Sep 17 00:00:00 2001 From: shiroyk Date: Tue, 30 Jul 2024 16:45:52 +0800 Subject: [PATCH] Implemented {Array, TypedArray}.prototype.{with, toReversed, toSorted} (#26, #27, #28, #29) `{Array, TypedArray}.prototype.toSorted` #26 `{Array, TypedArray}.prototype.toReversed` #27 `{Array, TypedArray}.prototype.with` #28 `Array.prototype.toSpliced` #29 --- builtin_array.go | 170 +++++++++++++++++++++++++++++++++++++++++ builtin_typedarrays.go | 96 +++++++++++++++++++++++ tc39_test.go | 9 ++- 3 files changed, 272 insertions(+), 3 deletions(-) diff --git a/builtin_array.go b/builtin_array.go index 7121bfa..853a7a6 100644 --- a/builtin_array.go +++ b/builtin_array.go @@ -1224,6 +1224,169 @@ 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())) + } + 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)) + a := r.newArrayLength(length) + + if src := r.checkStdArrayObj(o); src != nil { + for k := int64(0); k < length; k++ { + pk := valueInt(k) + from := valueInt(length - k - 1) + fromValue := src.values[from] + createDataPropertyOrThrow(a, pk, fromValue) + } + } else { + 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")) + } + 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 + switch len(call.Arguments) { + case 0: + case 1: + actualSkipCount = length - actualStart + default: + 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 = src.values + copy(values[actualStart+itemCount:], values[actualStart+actualSkipCount:]) + tail := values[newLength:] + for k := range tail { + tail[k] = nil + } + values = values[:newLength] + } else if itemCount > actualSkipCount { + if int64(cap(src.values)) >= newLength { + values = src.values[:newLength] + copy(values[actualStart+itemCount:], values[actualStart+actualSkipCount:length]) + } else { + values = make([]Value, newLength) + copy(values, src.values[:actualStart]) + copy(values[actualStart+itemCount:], src.values[actualStart+actualSkipCount:]) + } + } else { + values = src.values + } + 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 +1605,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 +1628,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_typedarrays.go b/builtin_typedarrays.go index 7ad737f..3b7e155 100644 --- a/builtin_typedarrays.go +++ b/builtin_typedarrays.go @@ -1153,6 +1153,99 @@ 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")) + } + ta.viewedArrayBuf.ensureNotDetached(true) + + // 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.toConstructor(ta.defaultCtor)([]Value{intToValue(int64(length))}, ta.defaultCtor)).self.(*typedArrayObject) + 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.toConstructor(ta.defaultCtor)([]Value{intToValue(int64(length))}, ta.defaultCtor)).self.(*typedArrayObject) + ta.viewedArrayBuf.ensureNotDetached(true) + + 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.toConstructor(ta.defaultCtor)([]Value{intToValue(int64(length))}, ta.defaultCtor)).self.(*typedArrayObject) + ta.viewedArrayBuf.ensureNotDetached(true) + + 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 +1636,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 0aecb5c..661da1e 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -178,8 +178,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, @@ -277,7 +281,6 @@ var ( "array-grouping", "String.prototype.toWellFormed", "String.prototype.isWellFormed", - "change-array-by-copy", "arraybuffer-transfer", "symbols-as-weakmap-keys", "set-methods",