package otto import ( "strings" ) // Array func builtinArray(call FunctionCall) Value { return toValue(builtinNewArrayNative(call.runtime, call.ArgumentList)) } func builtinNewArray(self *_object, _ Value, argumentList []Value) Value { return toValue(builtinNewArrayNative(self.runtime, argumentList)) } func builtinNewArrayNative(runtime *_runtime, argumentList []Value) *_object { valueArray := argumentList if len(argumentList) == 1 { value := argumentList[0] if value.IsNumber() { numberValue := uint(toUint32(value)) if float64(numberValue) == toFloat(value) { valueArray = make([]Value, numberValue) } else { panic(newRangeError()) } } } return runtime.newArray(valueArray) } func builtinArray_concat(call FunctionCall) Value { thisObject := call.thisObject() valueArray := []Value{} itemList := append([]Value{toValue(thisObject)}, call.ArgumentList...) for len(itemList) > 0 { item := itemList[0] itemList = itemList[1:] switch item._valueType { case valueObject: value := item._object() if value.class == "Array" { itemValueArray := value.stash.(*_arrayStash).valueArray for _, item := range itemValueArray { if item._valueType == valueEmpty { continue } valueArray = append(valueArray, item) } continue } fallthrough default: valueArray = append(valueArray, item) } } return toValue(call.runtime.newArray(valueArray)) } func builtinArray_shift(call FunctionCall) Value { thisObject := call.thisObject() length := uint(toUint32(thisObject.get("length"))) if 0 == length { thisObject.put("length", toValue(length), true) return UndefinedValue() } first := thisObject.get("0") for index := uint(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(length-1), true) return first } func builtinArray_push(call FunctionCall) Value { thisObject := call.thisObject() itemList := call.ArgumentList index := uint(toUint32(thisObject.get("length"))) for len(itemList) > 0 { thisObject.put(arrayIndexToString(index), itemList[0], true) itemList = itemList[1:] index += 1 } length := toValue(index) thisObject.put("length", length, true) return length } func builtinArray_pop(call FunctionCall) Value { thisObject := call.thisObject() length := uint(toUint32(thisObject.get("length"))) if 0 == length { thisObject.put("length", toValue(length), true) return UndefinedValue() } last := thisObject.get(arrayIndexToString(length - 1)) thisObject.delete(arrayIndexToString(length-1), true) thisObject.put("length", toValue(length-1), true) return last } func builtinArray_join(call FunctionCall) Value { separator := "," { argument := call.Argument(0) if argument.IsDefined() { separator = toString(argument) } } thisObject := call.thisObject() if stash, isArray := thisObject.stash.(*_arrayStash); isArray { return toValue(builtinArray_joinNative(stash.valueArray, separator)) } // Generic .join length := uint(toUint32(thisObject.get("length"))) if length == 0 { return toValue("") } stringList := make([]string, 0, length) for index := uint(0); index < length; index += 1 { value := thisObject.get(arrayIndexToString(index)) stringValue := "" switch value._valueType { case valueEmpty, valueUndefined, valueNull: default: stringValue = toString(value) } stringList = append(stringList, stringValue) } return toValue(strings.Join(stringList, ",")) } func builtinArray_joinNative(valueArray []Value, separator string) string { length := len(valueArray) if length == 0 { return "" } stringList := make([]string, 0, length) for index := 0; index < length; index++ { value := valueArray[index] stringValue := "" switch value._valueType { case valueEmpty, valueUndefined, valueNull: default: stringValue = toString(value) } stringList = append(stringList, stringValue) } return strings.Join(stringList, separator) } func builtinArray_splice(call FunctionCall) Value { thisObject := call.thisObject() length := uint(toUint32(thisObject.get("length"))) start := valueToRangeIndex(call.Argument(0), length, false) deleteCount := valueToRangeIndex(call.Argument(1), length-start, true) valueArray := make([]Value, deleteCount) for index := uint(0); index < deleteCount; index++ { indexString := arrayIndexToString(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 := uint(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 := 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 := 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 := 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 := uint(0); index < itemCount; index++ { thisObject.put(arrayIndexToString(index+start), itemList[index], true) } thisObject.put("length", toValue(length+itemCount-deleteCount), true) return toValue(call.runtime.newArray(valueArray)) } func builtinArray_slice(call FunctionCall) Value { thisObject := call.thisObject() length := uint(toUint32(thisObject.get("length"))) start, end := rangeStartEnd(call.ArgumentList, length, false) if start >= end { // Always an empty array return toValue(call.runtime.newArray([]Value{})) } sliceLength := end - start sliceValueArray := make([]Value, sliceLength) // Native slicing if a "real" array if _arrayStash, ok := thisObject.stash.(*_arrayStash); ok { copy(sliceValueArray, _arrayStash.valueArray[start:start+sliceLength]) } else { for index := uint(0); index < sliceLength; index++ { from := arrayIndexToString(index + start) if thisObject.hasProperty(from) { sliceValueArray[index] = thisObject.get(from) } } } return toValue(call.runtime.newArray(sliceValueArray)) } func builtinArray_unshift(call FunctionCall) Value { thisObject := call.thisObject() length := uint(toUint32(thisObject.get("length"))) itemList := call.ArgumentList itemCount := uint(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 := uint(0); index < itemCount; index++ { thisObject.put(arrayIndexToString(index), itemList[index], true) } newLength := toValue(length + itemCount) thisObject.put("length", newLength, true) return newLength } func builtinArray_reverse(call FunctionCall) Value { thisObject := call.thisObject() length := uint(toUint32(thisObject.get("length"))) lower := struct { name string index uint 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(index0) j.exists = thisObject.hasProperty(j.name) k.name = arrayIndexToString(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 = toString(x) k.value = toString(y) if j.value == k.value { return 0 } else if j.value < k.value { return -1 } return 1 } return int(toInt32(compare.Call(UndefinedValue(), []Value{x, y}))) } func arraySortSwap(thisObject *_object, index0, index1 uint) { j := struct { name string exists bool }{} k := j j.name = arrayIndexToString(index0) j.exists = thisObject.hasProperty(j.name) k.name = arrayIndexToString(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 { arraySortSwap(thisObject, pivot, right) // Right is now the pivot value cursor := left for index := left; index < right; index++ { if sortCompare(thisObject, index, right, compare) == -1 { // Compare to the pivot value arraySortSwap(thisObject, index, cursor) cursor += 1 } } arraySortSwap(thisObject, cursor, right) return cursor } func arraySortQuickSort(thisObject *_object, left, right uint, compare *_object) { if left < right { pivot := left + (right-left)/2 pivot = arraySortQuickPartition(thisObject, left, right, pivot, compare) if pivot > 0 { arraySortQuickSort(thisObject, left, pivot-1, compare) } arraySortQuickSort(thisObject, pivot+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(newTypeError()) } if length > 1 { arraySortQuickSort(thisObject, 0, length-1, compare) } return call.This }