/* * Copyright (C) 2014-2017 Apple Inc. All rights reserved. * Copyright (C) 2015 Yusuke Suzuki . * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ @constructor @globalPrivate function ArrayIterator(iteratedObject, kind, iterationFunction) { "use strict"; @putByIdDirectPrivate(this, "iteratedObject", iteratedObject); @putByIdDirectPrivate(this, "arrayIteratorKind", kind); @putByIdDirectPrivate(this, "arrayIteratorNextIndex", 0); @putByIdDirectPrivate(this, "arrayIteratorNext", iterationFunction); @putByIdDirectPrivate(this, "arrayIteratorIsDone", false); } function values() { "use strict"; return new @ArrayIterator(@toObject(this, "Array.prototype.values requires that |this| not be null or undefined"), "value", @arrayIteratorValueNext); } function keys() { "use strict"; return new @ArrayIterator(@toObject(this, "Array.prototype.keys requires that |this| not be null or undefined"), "key", @arrayIteratorKeyNext); } function entries() { "use strict"; return new @ArrayIterator(@toObject(this, "Array.prototype.entries requires that |this| not be null or undefined"), "key+value", @arrayIteratorKeyValueNext); } function reduce(callback /*, initialValue */) { "use strict"; var array = @toObject(this, "Array.prototype.reduce requires that |this| not be null or undefined"); var length = @toLength(array.length); if (typeof callback !== "function") @throwTypeError("Array.prototype.reduce callback must be a function"); var argumentCount = @argumentCount(); if (length === 0 && argumentCount < 2) @throwTypeError("reduce of empty array with no initial value"); var accumulator, k = 0; if (argumentCount > 1) accumulator = @argument(1); else { while (k < length && !(k in array)) k += 1; if (k >= length) @throwTypeError("reduce of empty array with no initial value"); accumulator = array[k++]; } while (k < length) { if (k in array) accumulator = callback.@call(@undefined, accumulator, array[k], k, array); k += 1; } return accumulator; } function reduceRight(callback /*, initialValue */) { "use strict"; var array = @toObject(this, "Array.prototype.reduceRight requires that |this| not be null or undefined"); var length = @toLength(array.length); if (typeof callback !== "function") @throwTypeError("Array.prototype.reduceRight callback must be a function"); var argumentCount = @argumentCount(); if (length === 0 && argumentCount < 2) @throwTypeError("reduceRight of empty array with no initial value"); var accumulator, k = length - 1; if (argumentCount > 1) accumulator = @argument(1); else { while (k >= 0 && !(k in array)) k -= 1; if (k < 0) @throwTypeError("reduceRight of empty array with no initial value"); accumulator = array[k--]; } while (k >= 0) { if (k in array) accumulator = callback.@call(@undefined, accumulator, array[k], k, array); k -= 1; } return accumulator; } function every(callback /*, thisArg */) { "use strict"; var array = @toObject(this, "Array.prototype.every requires that |this| not be null or undefined"); var length = @toLength(array.length); if (typeof callback !== "function") @throwTypeError("Array.prototype.every callback must be a function"); var thisArg = @argument(1); for (var i = 0; i < length; i++) { if (!(i in array)) continue; if (!callback.@call(thisArg, array[i], i, array)) return false; } return true; } function forEach(callback /*, thisArg */) { "use strict"; var array = @toObject(this, "Array.prototype.forEach requires that |this| not be null or undefined"); var length = @toLength(array.length); if (typeof callback !== "function") @throwTypeError("Array.prototype.forEach callback must be a function"); var thisArg = @argument(1); for (var i = 0; i < length; i++) { if (i in array) callback.@call(thisArg, array[i], i, array); } } function filter(callback /*, thisArg */) { "use strict"; var array = @toObject(this, "Array.prototype.filter requires that |this| not be null or undefined"); var length = @toLength(array.length); if (typeof callback !== "function") @throwTypeError("Array.prototype.filter callback must be a function"); var thisArg = @argument(1); var result = @arraySpeciesCreate(array, 0); var nextIndex = 0; for (var i = 0; i < length; i++) { if (!(i in array)) continue; var current = array[i] if (callback.@call(thisArg, current, i, array)) { @putByValDirect(result, nextIndex, current); ++nextIndex; } } return result; } function map(callback /*, thisArg */) { "use strict"; var array = @toObject(this, "Array.prototype.map requires that |this| not be null or undefined"); var length = @toLength(array.length); if (typeof callback !== "function") @throwTypeError("Array.prototype.map callback must be a function"); var thisArg = @argument(1); var result = @arraySpeciesCreate(array, length); for (var i = 0; i < length; i++) { if (!(i in array)) continue; var mappedValue = callback.@call(thisArg, array[i], i, array); @putByValDirect(result, i, mappedValue); } return result; } function some(callback /*, thisArg */) { "use strict"; var array = @toObject(this, "Array.prototype.some requires that |this| not be null or undefined"); var length = @toLength(array.length); if (typeof callback !== "function") @throwTypeError("Array.prototype.some callback must be a function"); var thisArg = @argument(1); for (var i = 0; i < length; i++) { if (!(i in array)) continue; if (callback.@call(thisArg, array[i], i, array)) return true; } return false; } function fill(value /* [, start [, end]] */) { "use strict"; var array = @toObject(this, "Array.prototype.fill requires that |this| not be null or undefined"); var length = @toLength(array.length); var relativeStart = @toInteger(@argument(1)); var k = 0; if (relativeStart < 0) { k = length + relativeStart; if (k < 0) k = 0; } else { k = relativeStart; if (k > length) k = length; } var relativeEnd = length; var end = @argument(2); if (end !== @undefined) relativeEnd = @toInteger(end); var final = 0; if (relativeEnd < 0) { final = length + relativeEnd; if (final < 0) final = 0; } else { final = relativeEnd; if (final > length) final = length; } for (; k < final; k++) array[k] = value; return array; } function find(callback /*, thisArg */) { "use strict"; var array = @toObject(this, "Array.prototype.find requires that |this| not be null or undefined"); var length = @toLength(array.length); if (typeof callback !== "function") @throwTypeError("Array.prototype.find callback must be a function"); var thisArg = @argument(1); for (var i = 0; i < length; i++) { var kValue = array[i]; if (callback.@call(thisArg, kValue, i, array)) return kValue; } return @undefined; } function findIndex(callback /*, thisArg */) { "use strict"; var array = @toObject(this, "Array.prototype.findIndex requires that |this| not be null or undefined"); var length = @toLength(array.length); if (typeof callback !== "function") @throwTypeError("Array.prototype.findIndex callback must be a function"); var thisArg = @argument(1); for (var i = 0; i < length; i++) { if (callback.@call(thisArg, array[i], i, array)) return i; } return -1; } function includes(searchElement /*, fromIndex*/) { "use strict"; var array = @toObject(this, "Array.prototype.includes requires that |this| not be null or undefined"); var length = @toLength(array.length); if (length === 0) return false; var fromIndex = 0; var from = @argument(1); if (from !== @undefined) fromIndex = @toInteger(from); var index; if (fromIndex >= 0) index = fromIndex; else index = length + fromIndex; if (index < 0) index = 0; var currentElement; for (; index < length; ++index) { currentElement = array[index]; // Use SameValueZero comparison, rather than just StrictEquals. if (searchElement === currentElement || (searchElement !== searchElement && currentElement !== currentElement)) return true; } return false; } function sort(comparator) { "use strict"; function min(a, b) { return a < b ? a : b; } function stringComparator(a, b) { let aString = a.string; let bString = b.string; let aLength = aString.length; let bLength = bString.length; let length = min(aLength, bLength); for (let i = 0; i < length; ++i) { let aCharCode = aString.@charCodeAt(i); let bCharCode = bString.@charCodeAt(i); if (aCharCode == bCharCode) continue; return aCharCode - bCharCode; } return aLength - bLength; } // Move undefineds and holes to the end of a sparse array. Result is [values..., undefineds..., holes...]. function compactSparse(array, dst, src, length) { let values = [ ]; let seen = { }; let valueCount = 0; let undefinedCount = 0; // Clean up after the in-progress non-sparse compaction that failed. for (let i = dst; i < src; ++i) delete array[i]; for (let object = array; object; object = @Object.@getPrototypeOf(object)) { let propertyNames = @Object.@getOwnPropertyNames(object); for (let i = 0; i < propertyNames.length; ++i) { let index = propertyNames[i]; if (index < length) { // Exclude non-numeric properties and properties past length. if (seen[index]) // Exclude duplicates. continue; seen[index] = 1; let value = array[index]; delete array[index]; if (value === @undefined) { ++undefinedCount; continue; } array[valueCount++] = value; } } } for (let i = valueCount; i < valueCount + undefinedCount; ++i) array[i] = @undefined; return valueCount; } function compactSlow(array, length) { let holeCount = 0; let dst = 0; let src = 0; for (; src < length; ++src) { if (!(src in array)) { ++holeCount; if (holeCount < 256) continue; return compactSparse(array, dst, src, length); } let value = array[src]; if (value === @undefined) continue; array[dst++] = value; } let valueCount = dst; let undefinedCount = length - valueCount - holeCount; for (let i = valueCount; i < valueCount + undefinedCount; ++i) array[i] = @undefined; for (let i = valueCount + undefinedCount; i < length; ++i) delete array[i]; return valueCount; } // Move undefineds and holes to the end of an array. Result is [values..., undefineds..., holes...]. function compact(array, length) { for (let i = 0; i < array.length; ++i) { if (array[i] === @undefined) return compactSlow(array, length); } return length; } function merge(dst, src, srcIndex, srcEnd, width, comparator) { let left = srcIndex; let leftEnd = min(left + width, srcEnd); let right = leftEnd; let rightEnd = min(right + width, srcEnd); for (let dstIndex = left; dstIndex < rightEnd; ++dstIndex) { if (right < rightEnd) { if (left >= leftEnd) { dst[dstIndex] = src[right++]; continue; } let comparisonResult = comparator(src[right], src[left]); if ((typeof comparisonResult === "boolean" && !comparisonResult) || comparisonResult < 0) { dst[dstIndex] = src[right++]; continue; } } dst[dstIndex] = src[left++]; } } function mergeSort(array, valueCount, comparator) { let buffer = [ ]; buffer.length = valueCount; let dst = buffer; let src = array; for (let width = 1; width < valueCount; width *= 2) { for (let srcIndex = 0; srcIndex < valueCount; srcIndex += 2 * width) merge(dst, src, srcIndex, valueCount, width, comparator); let tmp = src; src = dst; dst = tmp; } if (src != array) { for(let i = 0; i < valueCount; i++) array[i] = src[i]; } } function bucketSort(array, dst, bucket, depth) { if (bucket.length < 32 || depth > 32) { mergeSort(bucket, bucket.length, stringComparator); for (let i = 0; i < bucket.length; ++i) array[dst++] = bucket[i].value; return dst; } let buckets = [ ]; for (let i = 0; i < bucket.length; ++i) { let entry = bucket[i]; let string = entry.string; if (string.length == depth) { array[dst++] = entry.value; continue; } let c = string.@charCodeAt(depth); if (!buckets[c]) buckets[c] = [ ]; buckets[c][buckets[c].length] = entry; } for (let i = 0; i < buckets.length; ++i) { if (!buckets[i]) continue; dst = bucketSort(array, dst, buckets[i], depth + 1); } return dst; } function comparatorSort(array, length, comparator) { let valueCount = compact(array, length); mergeSort(array, valueCount, comparator); } function stringSort(array, length) { let valueCount = compact(array, length); let strings = @newArrayWithSize(valueCount); for (let i = 0; i < valueCount; ++i) strings[i] = { string: @toString(array[i]), value: array[i] }; bucketSort(array, 0, strings, 0); } let sortFunction; if (typeof comparator == "function") sortFunction = comparatorSort; else if (comparator === @undefined) sortFunction = stringSort; else @throwTypeError("Array.prototype.sort requires the comparsion function be a function or undefined"); let array = @toObject(this, "Array.prototype.sort requires that |this| not be null or undefined"); let length = @toLength(array.length); // For compatibility with Firefox and Chrome, do nothing observable // to the target array if it has 0 or 1 sortable properties. if (length < 2) return array; sortFunction(array, length, comparator); return array; } @globalPrivate function concatSlowPath() { "use strict"; var currentElement = @toObject(this, "Array.prototype.concat requires that |this| not be null or undefined"); var argCount = arguments.length; var result = @arraySpeciesCreate(currentElement, 0); var resultIsArray = @isJSArray(result); var resultIndex = 0; var argIndex = 0; do { let spreadable = @isObject(currentElement) && currentElement.@isConcatSpreadableSymbol; if ((spreadable === @undefined && @isArray(currentElement)) || spreadable) { let length = @toLength(currentElement.length); if (length + resultIndex > @MAX_ARRAY_INDEX) @throwRangeError("Length exceeded the maximum array length"); if (resultIsArray && @isJSArray(currentElement)) { @appendMemcpy(result, currentElement, resultIndex); resultIndex += length; } else { for (var i = 0; i < length; i++) { if (i in currentElement) @putByValDirect(result, resultIndex, currentElement[i]); resultIndex++; } } } else { if (resultIndex >= @MAX_ARRAY_INDEX) @throwRangeError("Length exceeded the maximum array length"); @putByValDirect(result, resultIndex++, currentElement); } currentElement = arguments[argIndex]; } while (argIndex++ < argCount); result.length = resultIndex; return result; } function concat(first) { "use strict"; if (@argumentCount() === 1 && @isJSArray(this) && this.@isConcatSpreadableSymbol === @undefined && (!@isObject(first) || (!@isProxyObject(first) && first.@isConcatSpreadableSymbol === @undefined))) { let result = @concatMemcpy(this, first); if (result !== null) return result; } return @tailCallForwardArguments(@concatSlowPath, this); } function copyWithin(target, start /*, end */) { "use strict"; function maxWithPositives(a, b) { return (a < b) ? b : a; } function minWithMaybeNegativeZeroAndPositive(maybeNegativeZero, positive) { return (maybeNegativeZero < positive) ? maybeNegativeZero : positive; } var array = @toObject(this, "Array.prototype.copyWithin requires that |this| not be null or undefined"); var length = @toLength(array.length); var relativeTarget = @toInteger(target); var to = (relativeTarget < 0) ? maxWithPositives(length + relativeTarget, 0) : minWithMaybeNegativeZeroAndPositive(relativeTarget, length); var relativeStart = @toInteger(start); var from = (relativeStart < 0) ? maxWithPositives(length + relativeStart, 0) : minWithMaybeNegativeZeroAndPositive(relativeStart, length); var relativeEnd; var end = @argument(2); if (end === @undefined) relativeEnd = length; else relativeEnd = @toInteger(end); var finalValue = (relativeEnd < 0) ? maxWithPositives(length + relativeEnd, 0) : minWithMaybeNegativeZeroAndPositive(relativeEnd, length); var count = minWithMaybeNegativeZeroAndPositive(finalValue - from, length - to); var direction = 1; if (from < to && to < from + count) { direction = -1; from = from + count - 1; to = to + count - 1; } for (var i = 0; i < count; ++i, from += direction, to += direction) { if (from in array) array[to] = array[from]; else delete array[to]; } return array; } @globalPrivate function flatIntoArray(target, source, sourceLength, targetIndex, depth) { "use strict"; for (var sourceIndex = 0; sourceIndex < sourceLength; ++sourceIndex) { if (sourceIndex in source) { var element = source[sourceIndex]; if (depth > 0 && @isArray(element)) targetIndex = @flatIntoArray(target, element, @toLength(element.length), targetIndex, depth - 1); else { if (targetIndex >= @MAX_SAFE_INTEGER) @throwTypeError("flatten array exceeds 2**53 - 1"); @putByValDirect(target, targetIndex, element); ++targetIndex; } } } return targetIndex; } function flat() { "use strict"; var array = @toObject(this, "Array.prototype.flat requires that |this| not be null or undefined"); var length = @toLength(array.length); var depthNum = 1; var depth = @argument(0); if (depth !== @undefined) depthNum = @toInteger(depth); var result = @arraySpeciesCreate(array, 0); @flatIntoArray(result, array, length, 0, depthNum); return result; } @globalPrivate function flatIntoArrayWithCallback(target, source, sourceLength, targetIndex, callback, thisArg) { "use strict"; for (var sourceIndex = 0; sourceIndex < sourceLength; ++sourceIndex) { if (sourceIndex in source) { var element = callback.@call(thisArg, source[sourceIndex], sourceIndex, source); if (@isArray(element)) targetIndex = @flatIntoArray(target, element, @toLength(element.length), targetIndex, 0); else { if (targetIndex >= @MAX_SAFE_INTEGER) @throwTypeError("flatten array exceeds 2**53 - 1"); @putByValDirect(target, targetIndex, element); ++targetIndex; } } } return target; } function flatMap(callback) { "use strict"; var array = @toObject(this, "Array.prototype.flatMap requires that |this| not be null or undefined"); var length = @toLength(array.length); if (typeof callback !== "function") @throwTypeError("Array.prototype.flatMap callback must be a function"); var thisArg = @argument(1); var result = @arraySpeciesCreate(array, 0); return @flatIntoArrayWithCallback(result, array, length, 0, callback, thisArg); }