Skip to content

Commit

Permalink
Implemented {Array, TypedArray}.prototype.{with, toReversed, toSorted} (
Browse files Browse the repository at this point in the history
#26, #27, #28, #29)

`{Array, TypedArray}.prototype.toSorted` #26
`{Array, TypedArray}.prototype.toReversed` #27
`{Array, TypedArray}.prototype.with` #28
`Array.prototype.toSpliced` #29
  • Loading branch information
shiroyk committed Jul 30, 2024
1 parent 3a280d3 commit eb01048
Show file tree
Hide file tree
Showing 3 changed files with 272 additions and 3 deletions.
170 changes: 170 additions & 0 deletions builtin_array.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down Expand Up @@ -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) })
Expand All @@ -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)
})
Expand Down
96 changes: 96 additions & 0 deletions builtin_typedarrays.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
}
Expand Down Expand Up @@ -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) })

Expand Down
9 changes: 6 additions & 3 deletions tc39_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down

0 comments on commit eb01048

Please sign in to comment.