Browse Source

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

`{Array, TypedArray}.prototype.toSorted`
`{Array, TypedArray}.prototype.toReversed`
`{Array, TypedArray}.prototype.with`
`Array.prototype.toSpliced`
shiroyk 1 year ago
parent
commit
a52ceb6d86
4 changed files with 303 additions and 3 deletions
  1. 178 0
      builtin_array.go
  2. 27 1
      builtin_arrray_test.go
  3. 92 0
      builtin_typedarrays.go
  4. 6 2
      tc39_test.go

+ 178 - 0
builtin_array.go

@@ -1224,6 +1224,177 @@ func (r *Runtime) arrayproto_flatMap(call FunctionCall) Value {
 	return a
 	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 {
 func (r *Runtime) checkStdArrayObj(obj *Object) *arrayObject {
 	if arr, ok := obj.self.(*arrayObject); ok &&
 	if arr, ok := obj.self.(*arrayObject); ok &&
 		arr.propValueCount == 0 &&
 		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("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("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("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.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(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("values", valueTrue, true)
 		bl.setOwnStr("groupBy", valueTrue, true)
 		bl.setOwnStr("groupBy", valueTrue, true)
 		bl.setOwnStr("groupByToMap", 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)
 		return valueProp(bl.val, false, false, true)
 	})
 	})

+ 27 - 1
builtin_arrray_test.go

@@ -1,6 +1,8 @@
 package goja
 package goja
 
 
-import "testing"
+import (
+	"testing"
+)
 
 
 func TestArrayProtoProp(t *testing.T) {
 func TestArrayProtoProp(t *testing.T) {
 	const SCRIPT = `
 	const SCRIPT = `
@@ -339,3 +341,27 @@ func TestArrayProto(t *testing.T) {
 	`
 	`
 	testScriptWithTestLib(SCRIPT, _undefined, 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 - 0
builtin_typedarrays.go

@@ -1153,6 +1153,95 @@ func (r *Runtime) typedArrayProto_toStringTag(call FunctionCall) Value {
 	return _undefined
 	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 {
 func (r *Runtime) newTypedArray([]Value, *Object) *Object {
 	panic(r.NewTypeError("Abstract class TypedArray not directly constructable"))
 	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("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("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("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("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) })
 	t.putStr("values", func(r *Runtime) Value { return valueProp(r.getTypedArrayValues(), true, false, true) })
 
 

+ 6 - 2
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,
 		"test/language/expressions/class/cpn-class-expr-fields-methods-computed-property-name-from-integer-separators.js": true,
 
 
 		// BigInt
 		// 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
 		// Regexp
 		"test/language/literals/regexp/invalid-range-negative-lookbehind.js":    true,
 		"test/language/literals/regexp/invalid-range-negative-lookbehind.js":    true,