package otto import ( "strconv" "strings" ) // Array func builtinArray(call FunctionCall) Value { return toValue_object(builtinNewArrayNative(call.runtime, call.ArgumentList)) } func builtinNewArray(self *_object, argumentList []Value) Value { return toValue_object(builtinNewArrayNative(self.runtime, argumentList)) } func builtinNewArrayNative(runtime *_runtime, argumentList []Value) *_object { if len(argumentList) == 1 { firstArgument := argumentList[0] if firstArgument.IsNumber() { return runtime.newArray(arrayUint32(runtime, firstArgument)) } } return runtime.newArrayOf(argumentList) } func builtinArray_toString(call FunctionCall) Value { thisObject := call.thisObject() join := thisObject.get("join") if join.isCallable() { join := join._object() return join.call(call.This, call.ArgumentList, false, nativeFrame) } return builtinObject_toString(call) } func builtinArray_toLocaleString(call FunctionCall) Value { separator := "," thisObject := call.thisObject() length := int64(toUint32(thisObject.get("length"))) if length == 0 { return toValue_string("") } stringList := make([]string, 0, length) for index := int64(0); index < length; index += 1 { value := thisObject.get(arrayIndexToString(index)) stringValue := "" switch value.kind { case valueEmpty, valueUndefined, valueNull: default: object := call.runtime.toObject(value) toLocaleString := object.get("toLocaleString") if !toLocaleString.isCallable() { panic(call.runtime.panicTypeError()) } stringValue = toLocaleString.call(call.runtime, toValue_object(object)).string() } stringList = append(stringList, stringValue) } return toValue_string(strings.Join(stringList, separator)) } func builtinArray_concat(call FunctionCall) Value { thisObject := call.thisObject() valueArray := []Value{} source := append([]Value{toValue_object(thisObject)}, call.ArgumentList...) for _, item := range source { switch item.kind { case valueObject: object := item._object() if isArray(object) { length := object.get("length").number().int64 for index := int64(0); index < length; index += 1 { name := strconv.FormatInt(index, 10) if object.hasProperty(name) { valueArray = append(valueArray, object.get(name)) } else { valueArray = append(valueArray, Value{}) } } continue } fallthrough default: valueArray = append(valueArray, item) } } return toValue_object(call.runtime.newArrayOf(valueArray)) } func builtinArray_shift(call FunctionCall) Value { thisObject := call.thisObject() length := int64(toUint32(thisObject.get("length"))) if 0 == length { thisObject.put("length", toValue_int64(0), true) return Value{} } first := thisObject.get("0") for index := int64(1); index < length; index++ { from := arrayIndexToString(index) to := arrayIndexToString(index - 1) if thisObject.hasProperty(from) { thisObject.put(to, thisObject.get(from), true) } else { thisObject.delete(to, true) } } thisObject.delete(arrayIndexToString(length-1), true) thisObject.put("length", toValue_int64(length-1), true) return first } func builtinArray_push(call FunctionCall) Value { thisObject := call.thisObject() itemList := call.ArgumentList index := int64(toUint32(thisObject.get("length"))) for len(itemList) > 0 { thisObject.put(arrayIndexToString(index), itemList[0], true) itemList = itemList[1:] index += 1 } length := toValue_int64(index) thisObject.put("length", length, true) return length } func builtinArray_pop(call FunctionCall) Value { thisObject := call.thisObject() length := int64(toUint32(thisObject.get("length"))) if 0 == length { thisObject.put("length", toValue_uint32(0), true) return Value{} } last := thisObject.get(arrayIndexToString(length - 1)) thisObject.delete(arrayIndexToString(length-1), true) thisObject.put("length", toValue_int64(length-1), true) return last } func builtinArray_join(call FunctionCall) Value { separator := "," { argument := call.Argument(0) if argument.IsDefined() { separator = argument.string() } } thisObject := call.thisObject() length := int64(toUint32(thisObject.get("length"))) if length == 0 { return toValue_string("") } stringList := make([]string, 0, length) for index := int64(0); index < length; index += 1 { value := thisObject.get(arrayIndexToString(index)) stringValue := "" switch value.kind { case valueEmpty, valueUndefined, valueNull: default: stringValue = value.string() } stringList = append(stringList, stringValue) } return toValue_string(strings.Join(stringList, separator)) } func builtinArray_splice(call FunctionCall) Value { thisObject := call.thisObject() length := int64(toUint32(thisObject.get("length"))) start := valueToRangeIndex(call.Argument(0), length, false) deleteCount := valueToRangeIndex(call.Argument(1), int64(length)-start, true) valueArray := make([]Value, deleteCount) for index := int64(0); index < deleteCount; index++ { indexString := arrayIndexToString(int64(start + index)) if thisObject.hasProperty(indexString) { valueArray[index] = thisObject.get(indexString) } } // 0, <1, 2, 3, 4>, 5, 6, 7 // a, b // length 8 - delete 4 @ start 1 itemList := []Value{} itemCount := int64(len(call.ArgumentList)) if itemCount > 2 { itemCount -= 2 // Less the first two arguments itemList = call.ArgumentList[2:] } else { itemCount = 0 } if itemCount < deleteCount { // The Object/Array is shrinking stop := int64(length) - deleteCount // The new length of the Object/Array before // appending the itemList remainder // Stopping at the lower bound of the insertion: // Move an item from the after the deleted portion // to a position after the inserted portion for index := start; index < stop; index++ { from := arrayIndexToString(index + deleteCount) // Position just after deletion to := arrayIndexToString(index + itemCount) // Position just after splice (insertion) if thisObject.hasProperty(from) { thisObject.put(to, thisObject.get(from), true) } else { thisObject.delete(to, true) } } // Delete off the end // We don't bother to delete below (if any) since those // will be overwritten anyway for index := int64(length); index > (stop + itemCount); index-- { thisObject.delete(arrayIndexToString(index-1), true) } } else if itemCount > deleteCount { // The Object/Array is growing // The itemCount is greater than the deleteCount, so we do // not have to worry about overwriting what we should be moving // --- // Starting from the upper bound of the deletion: // Move an item from the after the deleted portion // to a position after the inserted portion for index := int64(length) - deleteCount; index > start; index-- { from := arrayIndexToString(index + deleteCount - 1) to := arrayIndexToString(index + itemCount - 1) if thisObject.hasProperty(from) { thisObject.put(to, thisObject.get(from), true) } else { thisObject.delete(to, true) } } } for index := int64(0); index < itemCount; index++ { thisObject.put(arrayIndexToString(index+start), itemList[index], true) } thisObject.put("length", toValue_int64(int64(length)+itemCount-deleteCount), true) return toValue_object(call.runtime.newArrayOf(valueArray)) } func builtinArray_slice(call FunctionCall) Value { thisObject := call.thisObject() length := int64(toUint32(thisObject.get("length"))) start, end := rangeStartEnd(call.ArgumentList, length, false) if start >= end { // Always an empty array return toValue_object(call.runtime.newArray(0)) } sliceLength := end - start sliceValueArray := make([]Value, sliceLength) for index := int64(0); index < sliceLength; index++ { from := arrayIndexToString(index + start) if thisObject.hasProperty(from) { sliceValueArray[index] = thisObject.get(from) } } return toValue_object(call.runtime.newArrayOf(sliceValueArray)) } func builtinArray_unshift(call FunctionCall) Value { thisObject := call.thisObject() length := int64(toUint32(thisObject.get("length"))) itemList := call.ArgumentList itemCount := int64(len(itemList)) for index := length; index > 0; index-- { from := arrayIndexToString(index - 1) to := arrayIndexToString(index + itemCount - 1) if thisObject.hasProperty(from) { thisObject.put(to, thisObject.get(from), true) } else { thisObject.delete(to, true) } } for index := int64(0); index < itemCount; index++ { thisObject.put(arrayIndexToString(index), itemList[index], true) } newLength := toValue_int64(length + itemCount) thisObject.put("length", newLength, true) return newLength } func builtinArray_reverse(call FunctionCall) Value { thisObject := call.thisObject() length := int64(toUint32(thisObject.get("length"))) lower := struct { name string index int64 exists bool }{} upper := lower lower.index = 0 middle := length / 2 // Division will floor for lower.index != middle { lower.name = arrayIndexToString(lower.index) upper.index = length - lower.index - 1 upper.name = arrayIndexToString(upper.index) lower.exists = thisObject.hasProperty(lower.name) upper.exists = thisObject.hasProperty(upper.name) if lower.exists && upper.exists { lowerValue := thisObject.get(lower.name) upperValue := thisObject.get(upper.name) thisObject.put(lower.name, upperValue, true) thisObject.put(upper.name, lowerValue, true) } else if !lower.exists && upper.exists { value := thisObject.get(upper.name) thisObject.delete(upper.name, true) thisObject.put(lower.name, value, true) } else if lower.exists && !upper.exists { value := thisObject.get(lower.name) thisObject.delete(lower.name, true) thisObject.put(upper.name, value, true) } else { // Nothing happens. } lower.index += 1 } return call.This } func sortCompare(thisObject *_object, index0, index1 uint, compare *_object) int { j := struct { name string exists bool defined bool value string }{} k := j j.name = arrayIndexToString(int64(index0)) j.exists = thisObject.hasProperty(j.name) k.name = arrayIndexToString(int64(index1)) k.exists = thisObject.hasProperty(k.name) if !j.exists && !k.exists { return 0 } else if !j.exists { return 1 } else if !k.exists { return -1 } x := thisObject.get(j.name) y := thisObject.get(k.name) j.defined = x.IsDefined() k.defined = y.IsDefined() if !j.defined && !k.defined { return 0 } else if !j.defined { return 1 } else if !k.defined { return -1 } if compare == nil { j.value = x.string() k.value = y.string() if j.value == k.value { return 0 } else if j.value < k.value { return -1 } return 1 } return int(toInt32(compare.call(Value{}, []Value{x, y}, false, nativeFrame))) } func arraySortSwap(thisObject *_object, index0, index1 uint) { j := struct { name string exists bool }{} k := j j.name = arrayIndexToString(int64(index0)) j.exists = thisObject.hasProperty(j.name) k.name = arrayIndexToString(int64(index1)) k.exists = thisObject.hasProperty(k.name) if j.exists && k.exists { jValue := thisObject.get(j.name) kValue := thisObject.get(k.name) thisObject.put(j.name, kValue, true) thisObject.put(k.name, jValue, true) } else if !j.exists && k.exists { value := thisObject.get(k.name) thisObject.delete(k.name, true) thisObject.put(j.name, value, true) } else if j.exists && !k.exists { value := thisObject.get(j.name) thisObject.delete(j.name, true) thisObject.put(k.name, value, true) } else { // Nothing happens. } } func arraySortQuickPartition(thisObject *_object, left, right, pivot uint, compare *_object) (uint, uint) { arraySortSwap(thisObject, pivot, right) // Right is now the pivot value cursor := left cursor2 := left for index := left; index < right; index++ { comparison := sortCompare(thisObject, index, right, compare) // Compare to the pivot value if comparison < 0 { arraySortSwap(thisObject, index, cursor) if cursor < cursor2 { arraySortSwap(thisObject, index, cursor2) } cursor += 1 cursor2 += 1 } else if comparison == 0 { arraySortSwap(thisObject, index, cursor2) cursor2 += 1 } } arraySortSwap(thisObject, cursor2, right) return cursor, cursor2 } func arraySortQuickSort(thisObject *_object, left, right uint, compare *_object) { if left < right { middle := left + (right-left)/2 pivot, pivot2 := arraySortQuickPartition(thisObject, left, right, middle, compare) if pivot > 0 { arraySortQuickSort(thisObject, left, pivot-1, compare) } arraySortQuickSort(thisObject, pivot2+1, right, compare) } } func builtinArray_sort(call FunctionCall) Value { thisObject := call.thisObject() length := uint(toUint32(thisObject.get("length"))) compareValue := call.Argument(0) compare := compareValue._object() if compareValue.IsUndefined() { } else if !compareValue.isCallable() { panic(call.runtime.panicTypeError()) } if length > 1 { arraySortQuickSort(thisObject, 0, length-1, compare) } return call.This } func builtinArray_isArray(call FunctionCall) Value { return toValue_bool(isArray(call.Argument(0)._object())) } func builtinArray_indexOf(call FunctionCall) Value { thisObject, matchValue := call.thisObject(), call.Argument(0) if length := int64(toUint32(thisObject.get("length"))); length > 0 { index := int64(0) if len(call.ArgumentList) > 1 { index = call.Argument(1).number().int64 } if index < 0 { if index += length; index < 0 { index = 0 } } else if index >= length { index = -1 } for ; index >= 0 && index < length; index++ { name := arrayIndexToString(int64(index)) if !thisObject.hasProperty(name) { continue } value := thisObject.get(name) if strictEqualityComparison(matchValue, value) { return toValue_uint32(uint32(index)) } } } return toValue_int(-1) } func builtinArray_lastIndexOf(call FunctionCall) Value { thisObject, matchValue := call.thisObject(), call.Argument(0) length := int64(toUint32(thisObject.get("length"))) index := length - 1 if len(call.ArgumentList) > 1 { index = call.Argument(1).number().int64 } if 0 > index { index += length } if index > length { index = length - 1 } else if 0 > index { return toValue_int(-1) } for ; index >= 0; index-- { name := arrayIndexToString(int64(index)) if !thisObject.hasProperty(name) { continue } value := thisObject.get(name) if strictEqualityComparison(matchValue, value) { return toValue_uint32(uint32(index)) } } return toValue_int(-1) } func builtinArray_every(call FunctionCall) Value { thisObject := call.thisObject() this := toValue_object(thisObject) if iterator := call.Argument(0); iterator.isCallable() { length := int64(toUint32(thisObject.get("length"))) callThis := call.Argument(1) for index := int64(0); index < length; index++ { if key := arrayIndexToString(index); thisObject.hasProperty(key) { if value := thisObject.get(key); iterator.call(call.runtime, callThis, value, toValue_int64(index), this).bool() { continue } return falseValue } } return trueValue } panic(call.runtime.panicTypeError()) } func builtinArray_some(call FunctionCall) Value { thisObject := call.thisObject() this := toValue_object(thisObject) if iterator := call.Argument(0); iterator.isCallable() { length := int64(toUint32(thisObject.get("length"))) callThis := call.Argument(1) for index := int64(0); index < length; index++ { if key := arrayIndexToString(index); thisObject.hasProperty(key) { if value := thisObject.get(key); iterator.call(call.runtime, callThis, value, toValue_int64(index), this).bool() { return trueValue } } } return falseValue } panic(call.runtime.panicTypeError()) } func builtinArray_forEach(call FunctionCall) Value { thisObject := call.thisObject() this := toValue_object(thisObject) if iterator := call.Argument(0); iterator.isCallable() { length := int64(toUint32(thisObject.get("length"))) callThis := call.Argument(1) for index := int64(0); index < length; index++ { if key := arrayIndexToString(index); thisObject.hasProperty(key) { iterator.call(call.runtime, callThis, thisObject.get(key), toValue_int64(index), this) } } return Value{} } panic(call.runtime.panicTypeError()) } func builtinArray_map(call FunctionCall) Value { thisObject := call.thisObject() this := toValue_object(thisObject) if iterator := call.Argument(0); iterator.isCallable() { length := int64(toUint32(thisObject.get("length"))) callThis := call.Argument(1) values := make([]Value, length) for index := int64(0); index < length; index++ { if key := arrayIndexToString(index); thisObject.hasProperty(key) { values[index] = iterator.call(call.runtime, callThis, thisObject.get(key), index, this) } else { values[index] = Value{} } } return toValue_object(call.runtime.newArrayOf(values)) } panic(call.runtime.panicTypeError()) } func builtinArray_filter(call FunctionCall) Value { thisObject := call.thisObject() this := toValue_object(thisObject) if iterator := call.Argument(0); iterator.isCallable() { length := int64(toUint32(thisObject.get("length"))) callThis := call.Argument(1) values := make([]Value, 0) for index := int64(0); index < length; index++ { if key := arrayIndexToString(index); thisObject.hasProperty(key) { value := thisObject.get(key) if iterator.call(call.runtime, callThis, value, index, this).bool() { values = append(values, value) } } } return toValue_object(call.runtime.newArrayOf(values)) } panic(call.runtime.panicTypeError()) } func builtinArray_reduce(call FunctionCall) Value { thisObject := call.thisObject() this := toValue_object(thisObject) if iterator := call.Argument(0); iterator.isCallable() { initial := len(call.ArgumentList) > 1 start := call.Argument(1) length := int64(toUint32(thisObject.get("length"))) index := int64(0) if length > 0 || initial { var accumulator Value if !initial { for ; index < length; index++ { if key := arrayIndexToString(index); thisObject.hasProperty(key) { accumulator = thisObject.get(key) index++ break } } } else { accumulator = start } for ; index < length; index++ { if key := arrayIndexToString(index); thisObject.hasProperty(key) { accumulator = iterator.call(call.runtime, Value{}, accumulator, thisObject.get(key), key, this) } } return accumulator } } panic(call.runtime.panicTypeError()) } func builtinArray_reduceRight(call FunctionCall) Value { thisObject := call.thisObject() this := toValue_object(thisObject) if iterator := call.Argument(0); iterator.isCallable() { initial := len(call.ArgumentList) > 1 start := call.Argument(1) length := int64(toUint32(thisObject.get("length"))) if length > 0 || initial { index := length - 1 var accumulator Value if !initial { for ; index >= 0; index-- { if key := arrayIndexToString(index); thisObject.hasProperty(key) { accumulator = thisObject.get(key) index-- break } } } else { accumulator = start } for ; index >= 0; index-- { if key := arrayIndexToString(index); thisObject.hasProperty(key) { accumulator = iterator.call(call.runtime, Value{}, accumulator, thisObject.get(key), key, this) } } return accumulator } } panic(call.runtime.panicTypeError()) }