blob: bf9b74145900666b66678ec5631605ff10643ec3 [file] [log] [blame]
/*
* Copyright (C) 2014-2017 Apple Inc. All rights reserved.
* Copyright (C) 2015 Yusuke Suzuki <utatane.tea@gmail.com>.
*
* 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);
}