package goja import ( "bytes" "sort" "strings" ) func (r *Runtime) newArray(prototype *Object) (a *arrayObject) { v := &Object{runtime: r} a = &arrayObject{} a.class = classArray a.val = v a.extensible = true v.self = a a.prototype = prototype a.init() return } func (r *Runtime) newArrayObject() *arrayObject { return r.newArray(r.global.ArrayPrototype) } func setArrayValues(a *arrayObject, values []Value) *arrayObject { a.values = values a.length = int64(len(values)) a.objCount = a.length return a } func setArrayLength(a *arrayObject, l int64) *arrayObject { a.putStr("length", intToValue(l), true) return a } func relToIdx(rel, l int64) int64 { if rel >= 0 { return min(rel, l) } return max(l+rel, 0) } func (r *Runtime) newArrayValues(values []Value) *Object { return setArrayValues(r.newArrayObject(), values).val } func (r *Runtime) newArrayLength(l int64) *Object { return setArrayLength(r.newArrayObject(), l).val } func (r *Runtime) builtin_newArray(args []Value, proto *Object) *Object { l := len(args) if l == 1 { if al, ok := args[0].assertInt(); ok { return setArrayLength(r.newArray(proto), al).val } else if f, ok := args[0].assertFloat(); ok { al := int64(f) if float64(al) == f { return r.newArrayLength(al) } else { panic(r.newError(r.global.RangeError, "Invalid array length")) } } return setArrayValues(r.newArray(proto), []Value{args[0]}).val } else { argsCopy := make([]Value, l) copy(argsCopy, args) return setArrayValues(r.newArray(proto), argsCopy).val } } func (r *Runtime) generic_push(obj *Object, call FunctionCall) Value { l := toLength(obj.self.getStr("length")) nl := l + int64(len(call.Arguments)) if nl >= maxInt { r.typeErrorResult(true, "Invalid array length") panic("unreachable") } for i, arg := range call.Arguments { obj.self.put(intToValue(l+int64(i)), arg, true) } n := intToValue(nl) obj.self.putStr("length", n, true) return n } func (r *Runtime) arrayproto_push(call FunctionCall) Value { obj := call.This.ToObject(r) return r.generic_push(obj, call) } func (r *Runtime) arrayproto_pop_generic(obj *Object) Value { l := toLength(obj.self.getStr("length")) if l == 0 { obj.self.putStr("length", intToValue(0), true) return _undefined } idx := intToValue(l - 1) val := obj.self.get(idx) obj.self.delete(idx, true) obj.self.putStr("length", idx, true) return val } func (r *Runtime) arrayproto_pop(call FunctionCall) Value { obj := call.This.ToObject(r) if a, ok := obj.self.(*arrayObject); ok { l := a.length if l > 0 { var val Value l-- if l < int64(len(a.values)) { val = a.values[l] } if val == nil { // optimisation bail-out return r.arrayproto_pop_generic(obj) } if _, ok := val.(*valueProperty); ok { // optimisation bail-out return r.arrayproto_pop_generic(obj) } //a._setLengthInt(l, false) a.values[l] = nil a.values = a.values[:l] a.length = l return val } return _undefined } else { return r.arrayproto_pop_generic(obj) } } func (r *Runtime) arrayproto_join(call FunctionCall) Value { o := call.This.ToObject(r) l := int(toLength(o.self.getStr("length"))) sep := "" if s := call.Argument(0); s != _undefined { sep = s.String() } else { sep = "," } if l == 0 { return stringEmpty } var buf bytes.Buffer element0 := o.self.get(intToValue(0)) if element0 != nil && element0 != _undefined && element0 != _null { buf.WriteString(element0.String()) } for i := 1; i < l; i++ { buf.WriteString(sep) element := o.self.get(intToValue(int64(i))) if element != nil && element != _undefined && element != _null { buf.WriteString(element.String()) } } return newStringValue(buf.String()) } func (r *Runtime) arrayproto_toString(call FunctionCall) Value { array := call.This.ToObject(r) f := array.self.getStr("join") if fObj, ok := f.(*Object); ok { if fcall, ok := fObj.self.assertCallable(); ok { return fcall(FunctionCall{ This: array, }) } } return r.objectproto_toString(FunctionCall{ This: array, }) } func (r *Runtime) writeItemLocaleString(item Value, buf *bytes.Buffer) { if item != nil && item != _undefined && item != _null { itemObj := item.ToObject(r) if f, ok := itemObj.self.getStr("toLocaleString").(*Object); ok { if c, ok := f.self.assertCallable(); ok { strVal := c(FunctionCall{ This: itemObj, }) buf.WriteString(strVal.String()) return } } r.typeErrorResult(true, "Property 'toLocaleString' of object %s is not a function", itemObj) } } func (r *Runtime) arrayproto_toLocaleString_generic(obj *Object, start int64, buf *bytes.Buffer) Value { length := toLength(obj.self.getStr("length")) for i := int64(start); i < length; i++ { if i > 0 { buf.WriteByte(',') } item := obj.self.get(intToValue(i)) r.writeItemLocaleString(item, buf) } return newStringValue(buf.String()) } func (r *Runtime) arrayproto_toLocaleString(call FunctionCall) Value { array := call.This.ToObject(r) if a, ok := array.self.(*arrayObject); ok { var buf bytes.Buffer for i := int64(0); i < a.length; i++ { var item Value if i < int64(len(a.values)) { item = a.values[i] } if item == nil { return r.arrayproto_toLocaleString_generic(array, i, &buf) } if prop, ok := item.(*valueProperty); ok { item = prop.get(array) } if i > 0 { buf.WriteByte(',') } r.writeItemLocaleString(item, &buf) } return newStringValue(buf.String()) } else { return r.arrayproto_toLocaleString_generic(array, 0, bytes.NewBuffer(nil)) } } func isConcatSpreadable(obj *Object) bool { spreadable := obj.self.get(symIsConcatSpreadable) if spreadable != nil && spreadable != _undefined { return spreadable.ToBoolean() } return isArray(obj) } func (r *Runtime) arrayproto_concat_append(a *Object, item Value) { aLength := toLength(a.self.getStr("length")) if obj, ok := item.(*Object); ok && isConcatSpreadable(obj) { length := toLength(obj.self.getStr("length")) for i := int64(0); i < length; i++ { v := obj.self.get(intToValue(i)) if v != nil { defineDataPropertyOrThrow(a, intToValue(aLength), v) } aLength++ } } else { defineDataPropertyOrThrow(a, intToValue(aLength), item) aLength++ } a.self.putStr("length", intToValue(aLength), true) } func arraySpeciesCreate(obj *Object, size int) *Object { if isArray(obj) { v := obj.self.getStr("constructor") if constructObj, ok := v.(*Object); ok { species := constructObj.self.get(symSpecies) if species != nil && !IsUndefined(species) && !IsNull(species) { constructObj, _ = species.(*Object) if constructObj != nil { constructor := getConstructor(constructObj) if constructor != nil { return constructor([]Value{intToValue(int64(size))}) } } panic(obj.runtime.NewTypeError()) } } } return obj.runtime.newArrayValues(nil) } func (r *Runtime) arrayproto_concat(call FunctionCall) Value { obj := call.This.ToObject(r) a := arraySpeciesCreate(obj, 0) r.arrayproto_concat_append(a, call.This.ToObject(r)) for _, item := range call.Arguments { r.arrayproto_concat_append(a, item) } return a } func max(a, b int64) int64 { if a > b { return a } return b } func min(a, b int64) int64 { if a < b { return a } return b } func (r *Runtime) arrayproto_slice(call FunctionCall) Value { o := call.This.ToObject(r) length := toLength(o.self.getStr("length")) start := relToIdx(call.Argument(0).ToInteger(), length) var end int64 if endArg := call.Argument(1); endArg != _undefined { end = endArg.ToInteger() } else { end = length } end = relToIdx(end, length) count := end - start if count < 0 { count = 0 } if arr := r.checkStdArrayObj(o); arr != nil { values := make([]Value, count) copy(values, arr.values[start:]) return r.newArrayValues(values) } a := r.newArrayLength(count) n := int64(0) for start < end { p := o.self.get(intToValue(start)) if p != nil { defineDataPropertyOrThrow(a, intToValue(n), p) } start++ n++ } return a } func (r *Runtime) arrayproto_sort(call FunctionCall) Value { o := call.This.ToObject(r) var compareFn func(FunctionCall) Value if arg, ok := call.Argument(0).(*Object); ok { compareFn, _ = arg.self.assertCallable() } ctx := arraySortCtx{ obj: o.self, compare: compareFn, } sort.Sort(&ctx) return o } func (r *Runtime) arrayproto_splice(call FunctionCall) Value { o := call.This.ToObject(r) a := r.newArrayValues(nil) length := toLength(o.self.getStr("length")) actualStart := relToIdx(call.Argument(0).ToInteger(), length) actualDeleteCount := min(max(call.Argument(1).ToInteger(), 0), length-actualStart) for k := int64(0); k < actualDeleteCount; k++ { from := intToValue(k + actualStart) if o.self.hasProperty(from) { a.self.put(intToValue(k), o.self.get(from), false) } } itemCount := max(int64(len(call.Arguments)-2), 0) if itemCount < actualDeleteCount { for k := actualStart; k < length-actualDeleteCount; k++ { from := intToValue(k + actualDeleteCount) to := intToValue(k + itemCount) if o.self.hasProperty(from) { o.self.put(to, o.self.get(from), true) } else { o.self.delete(to, true) } } for k := length; k > length-actualDeleteCount+itemCount; k-- { o.self.delete(intToValue(k-1), true) } } else if itemCount > actualDeleteCount { for k := length - actualDeleteCount; k > actualStart; k-- { from := intToValue(k + actualDeleteCount - 1) to := intToValue(k + itemCount - 1) if o.self.hasProperty(from) { o.self.put(to, o.self.get(from), true) } else { o.self.delete(to, true) } } } if itemCount > 0 { for i, item := range call.Arguments[2:] { o.self.put(intToValue(actualStart+int64(i)), item, true) } } o.self.putStr("length", intToValue(length-actualDeleteCount+itemCount), true) return a } func (r *Runtime) arrayproto_unshift(call FunctionCall) Value { o := call.This.ToObject(r) length := toLength(o.self.getStr("length")) argCount := int64(len(call.Arguments)) for k := length - 1; k >= 0; k-- { from := intToValue(k) to := intToValue(k + argCount) if o.self.hasProperty(from) { o.self.put(to, o.self.get(from), true) } else { o.self.delete(to, true) } } for k, arg := range call.Arguments { o.self.put(intToValue(int64(k)), arg, true) } newLen := intToValue(length + argCount) o.self.putStr("length", newLen, true) return newLen } func (r *Runtime) arrayproto_indexOf(call FunctionCall) Value { o := call.This.ToObject(r) length := toLength(o.self.getStr("length")) if length == 0 { return intToValue(-1) } n := call.Argument(1).ToInteger() if n >= length { return intToValue(-1) } if n < 0 { n = max(length+n, 0) } searchElement := call.Argument(0) if arr := r.checkStdArrayObj(o); arr != nil { for i, val := range arr.values[n:] { if searchElement.StrictEquals(val) { return intToValue(n + int64(i)) } } return intToValue(-1) } for ; n < length; n++ { idx := intToValue(n) if val := o.self.get(idx); val != nil { if searchElement.StrictEquals(val) { return idx } } } return intToValue(-1) } func (r *Runtime) arrayproto_lastIndexOf(call FunctionCall) Value { o := call.This.ToObject(r) length := toLength(o.self.getStr("length")) if length == 0 { return intToValue(-1) } var fromIndex int64 if len(call.Arguments) < 2 { fromIndex = length - 1 } else { fromIndex = call.Argument(1).ToInteger() if fromIndex >= 0 { fromIndex = min(fromIndex, length-1) } else { fromIndex += length } } searchElement := call.Argument(0) if arr := r.checkStdArrayObj(o); arr != nil { vals := arr.values for k := fromIndex; k >= 0; k-- { if v := vals[k]; v != nil && searchElement.StrictEquals(v) { return intToValue(k) } } return intToValue(-1) } for k := fromIndex; k >= 0; k-- { idx := intToValue(k) if val := o.self.get(idx); val != nil { if searchElement.StrictEquals(val) { return idx } } } return intToValue(-1) } func (r *Runtime) arrayproto_every(call FunctionCall) Value { o := call.This.ToObject(r) length := toLength(o.self.getStr("length")) callbackFn := call.Argument(0).ToObject(r) if callbackFn, ok := callbackFn.self.assertCallable(); ok { fc := FunctionCall{ This: call.Argument(1), Arguments: []Value{nil, nil, o}, } for k := int64(0); k < length; k++ { idx := intToValue(k) if val := o.self.get(idx); val != nil { fc.Arguments[0] = val fc.Arguments[1] = idx if !callbackFn(fc).ToBoolean() { return valueFalse } } } } else { r.typeErrorResult(true, "%s is not a function", call.Argument(0)) } return valueTrue } func (r *Runtime) arrayproto_some(call FunctionCall) Value { o := call.This.ToObject(r) length := toLength(o.self.getStr("length")) callbackFn := call.Argument(0).ToObject(r) if callbackFn, ok := callbackFn.self.assertCallable(); ok { fc := FunctionCall{ This: call.Argument(1), Arguments: []Value{nil, nil, o}, } for k := int64(0); k < length; k++ { idx := intToValue(k) if val := o.self.get(idx); val != nil { fc.Arguments[0] = val fc.Arguments[1] = idx if callbackFn(fc).ToBoolean() { return valueTrue } } } } else { r.typeErrorResult(true, "%s is not a function", call.Argument(0)) } return valueFalse } func (r *Runtime) arrayproto_forEach(call FunctionCall) Value { o := call.This.ToObject(r) length := toLength(o.self.getStr("length")) callbackFn := call.Argument(0).ToObject(r) if callbackFn, ok := callbackFn.self.assertCallable(); ok { fc := FunctionCall{ This: call.Argument(1), Arguments: []Value{nil, nil, o}, } for k := int64(0); k < length; k++ { idx := intToValue(k) if val := o.self.get(idx); val != nil { fc.Arguments[0] = val fc.Arguments[1] = idx callbackFn(fc) } } } else { r.typeErrorResult(true, "%s is not a function", call.Argument(0)) } return _undefined } func (r *Runtime) arrayproto_map(call FunctionCall) Value { o := call.This.ToObject(r) length := toLength(o.self.getStr("length")) callbackFn := call.Argument(0).ToObject(r) if callbackFn, ok := callbackFn.self.assertCallable(); ok { fc := FunctionCall{ This: call.Argument(1), Arguments: []Value{nil, nil, o}, } a := r.newArrayObject() a._setLengthInt(length, true) a.values = make([]Value, length) for k := int64(0); k < length; k++ { idx := intToValue(k) if val := o.self.get(idx); val != nil { fc.Arguments[0] = val fc.Arguments[1] = idx a.values[k] = callbackFn(fc) a.objCount++ } } return a.val } else { r.typeErrorResult(true, "%s is not a function", call.Argument(0)) } panic("unreachable") } func (r *Runtime) arrayproto_filter(call FunctionCall) Value { o := call.This.ToObject(r) length := toLength(o.self.getStr("length")) callbackFn := call.Argument(0).ToObject(r) if callbackFn, ok := callbackFn.self.assertCallable(); ok { a := r.newArrayObject() fc := FunctionCall{ This: call.Argument(1), Arguments: []Value{nil, nil, o}, } for k := int64(0); k < length; k++ { idx := intToValue(k) if val := o.self.get(idx); val != nil { fc.Arguments[0] = val fc.Arguments[1] = idx if callbackFn(fc).ToBoolean() { a.values = append(a.values, val) } } } a.length = int64(len(a.values)) a.objCount = a.length return a.val } else { r.typeErrorResult(true, "%s is not a function", call.Argument(0)) } panic("unreachable") } func (r *Runtime) arrayproto_reduce(call FunctionCall) Value { o := call.This.ToObject(r) length := toLength(o.self.getStr("length")) callbackFn := call.Argument(0).ToObject(r) if callbackFn, ok := callbackFn.self.assertCallable(); ok { fc := FunctionCall{ This: _undefined, Arguments: []Value{nil, nil, nil, o}, } var k int64 if len(call.Arguments) >= 2 { fc.Arguments[0] = call.Argument(1) } else { for ; k < length; k++ { idx := intToValue(k) if val := o.self.get(idx); val != nil { fc.Arguments[0] = val break } } if fc.Arguments[0] == nil { r.typeErrorResult(true, "No initial value") panic("unreachable") } k++ } for ; k < length; k++ { idx := intToValue(k) if val := o.self.get(idx); val != nil { fc.Arguments[1] = val fc.Arguments[2] = idx fc.Arguments[0] = callbackFn(fc) } } return fc.Arguments[0] } else { r.typeErrorResult(true, "%s is not a function", call.Argument(0)) } panic("unreachable") } func (r *Runtime) arrayproto_reduceRight(call FunctionCall) Value { o := call.This.ToObject(r) length := toLength(o.self.getStr("length")) callbackFn := call.Argument(0).ToObject(r) if callbackFn, ok := callbackFn.self.assertCallable(); ok { fc := FunctionCall{ This: _undefined, Arguments: []Value{nil, nil, nil, o}, } k := length - 1 if len(call.Arguments) >= 2 { fc.Arguments[0] = call.Argument(1) } else { for ; k >= 0; k-- { idx := intToValue(k) if val := o.self.get(idx); val != nil { fc.Arguments[0] = val break } } if fc.Arguments[0] == nil { r.typeErrorResult(true, "No initial value") panic("unreachable") } k-- } for ; k >= 0; k-- { idx := intToValue(k) if val := o.self.get(idx); val != nil { fc.Arguments[1] = val fc.Arguments[2] = idx fc.Arguments[0] = callbackFn(fc) } } return fc.Arguments[0] } else { r.typeErrorResult(true, "%s is not a function", call.Argument(0)) } panic("unreachable") } func arrayproto_reverse_generic_step(o *Object, lower, upper int64) { lowerP := intToValue(lower) upperP := intToValue(upper) lowerValue := o.self.get(lowerP) upperValue := o.self.get(upperP) if lowerValue != nil && upperValue != nil { o.self.put(lowerP, upperValue, true) o.self.put(upperP, lowerValue, true) } else if lowerValue == nil && upperValue != nil { o.self.put(lowerP, upperValue, true) o.self.delete(upperP, true) } else if lowerValue != nil && upperValue == nil { o.self.delete(lowerP, true) o.self.put(upperP, lowerValue, true) } } func (r *Runtime) arrayproto_reverse_generic(o *Object, start int64) { l := toLength(o.self.getStr("length")) middle := l / 2 for lower := start; lower != middle; lower++ { arrayproto_reverse_generic_step(o, lower, l-lower-1) } } func (r *Runtime) arrayproto_reverse(call FunctionCall) Value { o := call.This.ToObject(r) if a, ok := o.self.(*arrayObject); ok { l := a.length middle := l / 2 al := int64(len(a.values)) for lower := int64(0); lower != middle; lower++ { upper := l - lower - 1 var lowerValue, upperValue Value if upper >= al || lower >= al { goto bailout } lowerValue = a.values[lower] if lowerValue == nil { goto bailout } if _, ok := lowerValue.(*valueProperty); ok { goto bailout } upperValue = a.values[upper] if upperValue == nil { goto bailout } if _, ok := upperValue.(*valueProperty); ok { goto bailout } a.values[lower], a.values[upper] = upperValue, lowerValue continue bailout: arrayproto_reverse_generic_step(o, lower, upper) } //TODO: go arrays } else { r.arrayproto_reverse_generic(o, 0) } return o } func (r *Runtime) arrayproto_shift(call FunctionCall) Value { o := call.This.ToObject(r) length := toLength(o.self.getStr("length")) if length == 0 { o.self.putStr("length", intToValue(0), true) return _undefined } first := o.self.get(intToValue(0)) for i := int64(1); i < length; i++ { v := o.self.get(intToValue(i)) if v != nil && v != _undefined { o.self.put(intToValue(i-1), v, true) } else { o.self.delete(intToValue(i-1), true) } } lv := intToValue(length - 1) o.self.delete(lv, true) o.self.putStr("length", lv, true) return first } func (r *Runtime) arrayproto_values(call FunctionCall) Value { return r.createArrayIterator(call.This.ToObject(r), iterationKindValue) } func (r *Runtime) arrayproto_copyWithin(call FunctionCall) Value { o := call.This.ToObject(r) l := toLength(o.self.getStr("length")) var relEnd, dir int64 to := relToIdx(call.Argument(0).ToInteger(), l) from := relToIdx(call.Argument(1).ToInteger(), l) if end := call.Argument(2); end != _undefined { relEnd = end.ToInteger() } else { relEnd = l } final := relToIdx(relEnd, l) count := min(final-from, l-to) if arr := r.checkStdArrayObj(o); arr != nil { if count > 0 { copy(arr.values[to:to+count], arr.values[from:from+count]) } return o } if from < to && to < from+count { dir = -1 from = from + count - 1 to = to + count - 1 } else { dir = 1 } for count > 0 { if p := o.self.get(intToValue(from)); p != nil { o.self.put(intToValue(to), p, true) } else { o.self.delete(intToValue(to), true) } from += dir to += dir count-- } return o } func (r *Runtime) arrayproto_entries(call FunctionCall) Value { return r.createArrayIterator(call.This.ToObject(r), iterationKindKeyValue) } func (r *Runtime) arrayproto_fill(call FunctionCall) Value { o := call.This.ToObject(r) l := toLength(o.self.getStr("length")) k := relToIdx(call.Argument(1).ToInteger(), l) var relEnd int64 if endArg := call.Argument(2); endArg != _undefined { relEnd = endArg.ToInteger() } else { relEnd = l } final := relToIdx(relEnd, l) value := call.Argument(0) if arr := r.checkStdArrayObj(o); arr != nil { for ; k < final; k++ { arr.values[k] = value } } else { for ; k < final; k++ { o.self.put(intToValue(k), value, true) } } return o } func (r *Runtime) checkStdArrayObj(obj *Object) *arrayObject { if arr, ok := obj.self.(*arrayObject); ok && arr.propValueCount == 0 && arr.length == int64(len(arr.values)) && arr.objCount == arr.length { return arr } return nil } func (r *Runtime) checkStdArray(v Value) *arrayObject { if obj, ok := v.(*Object); ok { return r.checkStdArrayObj(obj) } return nil } func (r *Runtime) checkStdArrayIter(v Value) *arrayObject { if arr := r.checkStdArray(v); arr != nil && arr.getSym(symIterator) == r.global.arrayValues { return arr } return nil } func (r *Runtime) array_from(call FunctionCall) Value { var mapFn func(FunctionCall) Value if mapFnArg := call.Argument(1); mapFnArg != _undefined { if mapFnObj, ok := mapFnArg.(*Object); ok { if fn, ok := mapFnObj.self.assertCallable(); ok { mapFn = fn } } if mapFn == nil { panic(r.NewTypeError("%s is not a function", mapFnArg)) } } t := call.Argument(2) items := call.Argument(0) if mapFn == nil && call.This == r.global.Array { // mapFn may mutate the array if arr := r.checkStdArrayIter(items); arr != nil { items := make([]Value, len(arr.values)) copy(items, arr.values) return r.newArrayValues(items) } } var ctor func(args []Value) *Object if call.This != r.global.Array { if o, ok := call.This.(*Object); ok { if c := getConstructor(o); c != nil { ctor = c } } } var arr *Object if usingIterator := toMethod(r.getV(items, symIterator)); usingIterator != nil { if ctor != nil { arr = ctor([]Value{}) } else { arr = r.newArrayValues(nil) } iter := r.getIterator(items, usingIterator) if mapFn == nil { if a := r.checkStdArrayObj(arr); a != nil { var values []Value r.iterate(iter, func(val Value) { values = append(values, val) }) setArrayValues(a, values) return arr } } k := int64(0) r.iterate(iter, func(val Value) { if mapFn != nil { val = mapFn(FunctionCall{This: t, Arguments: []Value{val, intToValue(k)}}) } defineDataPropertyOrThrow(arr, intToValue(k), val) k++ }) arr.self.putStr("length", intToValue(k), true) } else { arrayLike := items.ToObject(r) l := toLength(arrayLike.self.getStr("length")) if ctor != nil { arr = ctor([]Value{intToValue(l)}) } else { arr = r.newArrayValues(nil) } if mapFn == nil { if a := r.checkStdArrayObj(arr); a != nil { values := make([]Value, l) for k := int64(0); k < l; k++ { values[k] = nilSafe(arrayLike.self.get(intToValue(k))) } setArrayValues(a, values) return arr } } for k := int64(0); k < l; k++ { idx := intToValue(k) item := arrayLike.self.get(idx) if mapFn != nil { item = mapFn(FunctionCall{This: t, Arguments: []Value{item, idx}}) } else { item = nilSafe(item) } defineDataPropertyOrThrow(arr, idx, item) } arr.self.putStr("length", intToValue(l), true) } return arr } func (r *Runtime) array_isArray(call FunctionCall) Value { if o, ok := call.Argument(0).(*Object); ok { if isArray(o) { return valueTrue } } return valueFalse } func (r *Runtime) array_of(call FunctionCall) Value { var ctor func(args []Value) *Object if call.This != r.global.Array { if o, ok := call.This.(*Object); ok { if c := getConstructor(o); c != nil { ctor = c } } } if ctor == nil { values := make([]Value, len(call.Arguments)) copy(values, call.Arguments) return r.newArrayValues(values) } l := intToValue(int64(len(call.Arguments))) arr := ctor([]Value{l}) for i, val := range call.Arguments { defineDataPropertyOrThrow(arr, intToValue(int64(i)), val) } arr.self.putStr("length", l, true) return arr } func (r *Runtime) arrayIterProto_next(call FunctionCall) Value { thisObj := r.toObject(call.This) if iter, ok := thisObj.self.(*arrayIterObject); ok { return iter.next() } panic(r.NewTypeError("Method Array Iterator.prototype.next called on incompatible receiver %s", thisObj.String())) } func (r *Runtime) createArrayProto(val *Object) objectImpl { o := &arrayObject{ baseObject: baseObject{ class: classArray, val: val, extensible: true, prototype: r.global.ObjectPrototype, }, } o.init() o._putProp("constructor", r.global.Array, true, false, true) o._putProp("copyWithin", r.newNativeFunc(r.arrayproto_copyWithin, nil, "copyWithin", nil, 2), true, false, true) o._putProp("entries", r.newNativeFunc(r.arrayproto_entries, nil, "entries", nil, 0), true, false, true) o._putProp("fill", r.newNativeFunc(r.arrayproto_fill, nil, "fill", nil, 1), true, false, true) o._putProp("pop", r.newNativeFunc(r.arrayproto_pop, nil, "pop", nil, 0), true, false, true) o._putProp("push", r.newNativeFunc(r.arrayproto_push, nil, "push", nil, 1), true, false, true) o._putProp("join", r.newNativeFunc(r.arrayproto_join, nil, "join", nil, 1), true, false, true) o._putProp("toString", r.newNativeFunc(r.arrayproto_toString, nil, "toString", nil, 0), true, false, true) o._putProp("toLocaleString", r.newNativeFunc(r.arrayproto_toLocaleString, nil, "toLocaleString", nil, 0), true, false, true) o._putProp("concat", r.newNativeFunc(r.arrayproto_concat, nil, "concat", nil, 1), true, false, true) o._putProp("reverse", r.newNativeFunc(r.arrayproto_reverse, nil, "reverse", nil, 0), true, false, true) o._putProp("shift", r.newNativeFunc(r.arrayproto_shift, nil, "shift", nil, 0), true, false, true) o._putProp("slice", r.newNativeFunc(r.arrayproto_slice, nil, "slice", nil, 2), true, false, true) o._putProp("sort", r.newNativeFunc(r.arrayproto_sort, nil, "sort", nil, 1), true, false, true) o._putProp("splice", r.newNativeFunc(r.arrayproto_splice, nil, "splice", nil, 2), true, false, true) o._putProp("unshift", r.newNativeFunc(r.arrayproto_unshift, nil, "unshift", nil, 1), true, false, true) o._putProp("indexOf", r.newNativeFunc(r.arrayproto_indexOf, nil, "indexOf", nil, 1), true, false, true) o._putProp("lastIndexOf", r.newNativeFunc(r.arrayproto_lastIndexOf, nil, "lastIndexOf", nil, 1), true, false, true) o._putProp("every", r.newNativeFunc(r.arrayproto_every, nil, "every", nil, 1), true, false, true) o._putProp("some", r.newNativeFunc(r.arrayproto_some, nil, "some", nil, 1), true, false, true) o._putProp("forEach", r.newNativeFunc(r.arrayproto_forEach, nil, "forEach", nil, 1), true, false, true) o._putProp("map", r.newNativeFunc(r.arrayproto_map, nil, "map", nil, 1), true, false, true) o._putProp("filter", r.newNativeFunc(r.arrayproto_filter, nil, "filter", nil, 1), true, false, true) o._putProp("reduce", r.newNativeFunc(r.arrayproto_reduce, nil, "reduce", nil, 1), true, false, true) o._putProp("reduceRight", r.newNativeFunc(r.arrayproto_reduceRight, nil, "reduceRight", nil, 1), true, false, true) valuesFunc := r.newNativeFunc(r.arrayproto_values, nil, "values", nil, 0) o._putProp("values", valuesFunc, true, false, true) o.put(symIterator, valueProp(valuesFunc, true, false, true), true) r.global.arrayValues = valuesFunc return o } func (r *Runtime) createArray(val *Object) objectImpl { o := r.newNativeFuncConstructObj(val, r.builtin_newArray, "Array", r.global.ArrayPrototype, 1) o._putProp("from", r.newNativeFunc(r.array_from, nil, "from", nil, 1), true, false, true) o._putProp("isArray", r.newNativeFunc(r.array_isArray, nil, "isArray", nil, 1), true, false, true) o._putProp("of", r.newNativeFunc(r.array_of, nil, "of", nil, 0), true, false, true) o.putSym(symSpecies, &valueProperty{ getterFunc: r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0), accessor: true, configurable: true, }, true) return o } func (r *Runtime) createArrayIterProto(val *Object) objectImpl { o := newBaseObjectObj(val, r.global.IteratorPrototype, classObject) o._putProp("next", r.newNativeFunc(r.arrayIterProto_next, nil, "next", nil, 0), true, false, true) o.put(symToStringTag, valueProp(asciiString(classArrayIterator), false, false, true), true) return o } func (r *Runtime) initArray() { r.global.ArrayIteratorPrototype = r.newLazyObject(r.createArrayIterProto) //r.global.ArrayPrototype = r.newArray(r.global.ObjectPrototype).val //o := r.global.ArrayPrototype.self r.global.ArrayPrototype = r.newLazyObject(r.createArrayProto) //r.global.Array = r.newNativeFuncConstruct(r.builtin_newArray, "Array", r.global.ArrayPrototype, 1) //o = r.global.Array.self //o._putProp("isArray", r.newNativeFunc(r.array_isArray, nil, "isArray", nil, 1), true, false, true) r.global.Array = r.newLazyObject(r.createArray) r.addToGlobal("Array", r.global.Array) } type sortable interface { sortLen() int64 sortGet(int64) Value swap(int64, int64) } type arraySortCtx struct { obj sortable compare func(FunctionCall) Value } func (ctx *arraySortCtx) sortCompare(x, y Value) int { if x == nil && y == nil { return 0 } if x == nil { return 1 } if y == nil { return -1 } if x == _undefined && y == _undefined { return 0 } if x == _undefined { return 1 } if y == _undefined { return -1 } if ctx.compare != nil { return int(ctx.compare(FunctionCall{ This: _undefined, Arguments: []Value{x, y}, }).ToInteger()) } return strings.Compare(x.String(), y.String()) } // sort.Interface func (a *arraySortCtx) Len() int { return int(a.obj.sortLen()) } func (a *arraySortCtx) Less(j, k int) bool { return a.sortCompare(a.obj.sortGet(int64(j)), a.obj.sortGet(int64(k))) < 0 } func (a *arraySortCtx) Swap(j, k int) { a.obj.swap(int64(j), int64(k)) }