Chromium Code Reviews| Index: src/js/array.js | 
| diff --git a/src/js/array.js b/src/js/array.js | 
| index 1468d57c6cae52f3f4c307609d1c58a9517382f5..ce92b82b482f2e1e6e8e653dc48667e1cde8b461 100644 | 
| --- a/src/js/array.js | 
| +++ b/src/js/array.js | 
| @@ -73,17 +73,13 @@ function DefineIndexedProperty(array, i, value) { | 
| } | 
| } | 
| +function KeySortCompare(a, b) { | 
| + return a - b; | 
| +} | 
| -// Global list of arrays visited during toString, toLocaleString and | 
| -// join invocations. | 
| -var visited_arrays = new InternalArray(); | 
| - | 
| - | 
| -// Gets a sorted array of array keys. Useful for operations on sparse | 
| -// arrays. Dupes have not been removed. | 
| function GetSortedArrayKeys(array, indices) { | 
| - var keys = new InternalArray(); | 
| if (IS_NUMBER(indices)) { | 
| + var keys = new InternalArray(); | 
| // It's an interval | 
| var limit = indices; | 
| for (var i = 0; i < limit; ++i) { | 
| @@ -92,61 +88,33 @@ function GetSortedArrayKeys(array, indices) { | 
| keys.push(i); | 
| } | 
| } | 
| - } else { | 
| - var length = indices.length; | 
| - for (var k = 0; k < length; ++k) { | 
| - var key = indices[k]; | 
| - if (!IS_UNDEFINED(key)) { | 
| - var e = array[key]; | 
| - if (!IS_UNDEFINED(e) || key in array) { | 
| - keys.push(key); | 
| - } | 
| - } | 
| - } | 
| - keys.sort(function(a, b) { return a - b; }); | 
| } | 
| - return keys; | 
| + return InnerArraySort(indices, indices.length, KeySortCompare); | 
| } | 
| -function SparseJoinWithSeparatorJS(array, len, convert, separator) { | 
| - var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len)); | 
| - var totalLength = 0; | 
| - var elements = new InternalArray(keys.length * 2); | 
| - var previousKey = -1; | 
| - for (var i = 0; i < keys.length; i++) { | 
| +function SparseJoinWithSeparatorJS(array, keys, length, convert, separator) { | 
| + var keys_length = keys.length; | 
| + var elements = new InternalArray(keys_length * 2); | 
| + for (var i = 0; i < keys_length; i++) { | 
| var key = keys[i]; | 
| - if (key != previousKey) { // keys may contain duplicates. | 
| - var e = array[key]; | 
| - if (!IS_STRING(e)) e = convert(e); | 
| - elements[i * 2] = key; | 
| - elements[i * 2 + 1] = e; | 
| - previousKey = key; | 
| - } | 
| + var e = array[key]; | 
| + elements[i * 2] = key; | 
| + elements[i * 2 + 1] = IS_STRING(e) ? e : convert(e); | 
| } | 
| - return %SparseJoinWithSeparator(elements, len, separator); | 
| + return %SparseJoinWithSeparator(elements, length, separator); | 
| } | 
| // Optimized for sparse arrays if separator is ''. | 
| -function SparseJoin(array, len, convert) { | 
| - var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len)); | 
| - var last_key = -1; | 
| +function SparseJoin(array, keys, convert) { | 
| var keys_length = keys.length; | 
| - | 
| var elements = new InternalArray(keys_length); | 
| - var elements_length = 0; | 
| - | 
| for (var i = 0; i < keys_length; i++) { | 
| - var key = keys[i]; | 
| - if (key != last_key) { | 
| - var e = array[key]; | 
| - if (!IS_STRING(e)) e = convert(e); | 
| - elements[elements_length++] = e; | 
| - last_key = key; | 
| - } | 
| + var e = array[keys[i]]; | 
| + elements[i] = IS_STRING(e) ? e : convert(e); | 
| } | 
| - return %StringBuilderConcat(elements, elements_length, ''); | 
| + return %StringBuilderConcat(elements, keys_length, ''); | 
| } | 
| @@ -167,98 +135,122 @@ function UseSparseVariant(array, length, is_array, touched) { | 
| (touched > estimated_elements * 4); | 
| } | 
| +function Stack() { | 
| + this.length = 0; | 
| + this.values = new InternalArray(); | 
| +} | 
| -function Join(array, length, separator, convert) { | 
| - if (length == 0) return ''; | 
| +// Predeclare the instance variables on the prototype. Otherwise setting them in | 
| +// the constructor will leak the instance through settings on Object.prototype. | 
| +Stack.prototype.length = null; | 
| +Stack.prototype.values = null; | 
| - var is_array = IS_ARRAY(array); | 
| +function StackPush(stack, value) { | 
| + stack.values[stack.length++] = value; | 
| +} | 
| - if (is_array) { | 
| - // If the array is cyclic, return the empty string for already | 
| - // visited arrays. | 
| - if (!%PushIfAbsent(visited_arrays, array)) return ''; | 
| +function StackPop(stack) { | 
| + stack.values[--stack.length] = null | 
| +} | 
| + | 
| +function StackHas(stack, v) { | 
| + var length = stack.length; | 
| + var values = stack.values; | 
| + for (var i = 0; i < length; i++) { | 
| + if (values[i] === v) return true; | 
| } | 
| + return false; | 
| +} | 
| - // Attempt to convert the elements. | 
| - try { | 
| - if (UseSparseVariant(array, length, is_array, length)) { | 
| - %NormalizeElements(array); | 
| - if (separator.length == 0) { | 
| - return SparseJoin(array, length, convert); | 
| - } else { | 
| - return SparseJoinWithSeparatorJS(array, length, convert, separator); | 
| - } | 
| - } | 
| +// Global list of arrays visited during toString, toLocaleString and | 
| +// join invocations. | 
| +var visited_arrays = new Stack(); | 
| - // Fast case for one-element arrays. | 
| - if (length == 1) { | 
| - var e = array[0]; | 
| - if (IS_STRING(e)) return e; | 
| - return convert(e); | 
| +function DoJoin(array, length, is_array, separator, convert) { | 
| + if (UseSparseVariant(array, length, is_array, length)) { | 
| + %NormalizeElements(array); | 
| + var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, length)); | 
| + if (separator === '') { | 
| + if (keys.length === 0) return ''; | 
| + return SparseJoin(array, keys, convert); | 
| + } else { | 
| + return SparseJoinWithSeparatorJS(array, keys, length, convert, separator); | 
| } | 
| + } | 
| - // Construct an array for the elements. | 
| - var elements = new InternalArray(length); | 
| + // Fast case for one-element arrays. | 
| + if (length === 1) { | 
| + var e = array[0]; | 
| + return IS_STRING(e) ? e : convert(e); | 
| + } | 
| - // We pull the empty separator check outside the loop for speed! | 
| - if (separator.length == 0) { | 
| - var elements_length = 0; | 
| - for (var i = 0; i < length; i++) { | 
| - var e = array[i]; | 
| - if (!IS_STRING(e)) e = convert(e); | 
| - elements[elements_length++] = e; | 
| - } | 
| - elements.length = elements_length; | 
| - return %StringBuilderConcat(elements, elements_length, ''); | 
| + // Construct an array for the elements. | 
| + var elements = new InternalArray(length); | 
| + | 
| + // We pull the empty separator check outside the loop for speed! | 
| + if (separator === '') { | 
| + for (var i = 0; i < length; i++) { | 
| + var e = array[i]; | 
| + elements[i] = IS_STRING(e) ? e : convert(e); | 
| } | 
| - // Non-empty separator case. | 
| - // If the first element is a number then use the heuristic that the | 
| - // remaining elements are also likely to be numbers. | 
| - var e = array[0]; | 
| - if (!IS_NUMBER(e)) { | 
| - if (!IS_STRING(e)) e = convert(e); | 
| - elements[0] = e; | 
| - for (var i = 1; i < length; i++) { | 
| - e = array[i]; | 
| - if (!IS_STRING(e)) e = convert(e); | 
| - elements[i] = e; | 
| - } | 
| - } else { | 
| - elements[0] = %_NumberToString(e); | 
| - for (var i = 1; i < length; i++) { | 
| - e = array[i]; | 
| - if (IS_NUMBER(e)) { | 
| - e = %_NumberToString(e); | 
| - } else if (!IS_STRING(e)) { | 
| - e = convert(e); | 
| - } | 
| - elements[i] = e; | 
| + return %StringBuilderConcat(elements, length, ''); | 
| 
 
Camillo Bruni
2016/03/14 10:09:52
As you said, you could probably still create a sil
 
 | 
| + } | 
| + // Non-empty separator case. | 
| + // If the first element is a number then use the heuristic that the | 
| + // remaining elements are also likely to be numbers. | 
| + var e = array[0]; | 
| + if (IS_NUMBER(e)) { | 
| + elements[0] = %_NumberToString(e); | 
| + for (var i = 1; i < length; i++) { | 
| + e = array[i]; | 
| + if (IS_NUMBER(e)) { | 
| + elements[i] = %_NumberToString(e); | 
| + } else { | 
| + elements[i] = IS_STRING(e) ? e : convert(e); | 
| } | 
| } | 
| - return %StringBuilderJoin(elements, length, separator); | 
| + } else { | 
| + elements[0] = IS_STRING(e) ? e : convert(e); | 
| + for (var i = 1; i < length; i++) { | 
| + e = array[i]; | 
| + elements[i] = IS_STRING(e) ? e : convert(e); | 
| + } | 
| + } | 
| + return %StringBuilderJoin(elements, length, separator); | 
| +} | 
| + | 
| +function Join(array, length, separator, convert) { | 
| + if (length === 0) return ''; | 
| + | 
| + var is_array = IS_ARRAY(array); | 
| + | 
| + if (is_array) { | 
| + // If the array is cyclic, return the empty string for already | 
| + // visited arrays. | 
| + if (StackHas(visited_arrays, array)) return ''; | 
| + StackPush(visited_arrays, array); | 
| + } | 
| + | 
| + // Attempt to convert the elements. | 
| + try { | 
| + return DoJoin(array, length, is_array, separator, convert); | 
| } finally { | 
| // Make sure to remove the last element of the visited array no | 
| // matter what happens. | 
| - if (is_array) visited_arrays.length = visited_arrays.length - 1; | 
| + if (is_array) StackPop(visited_arrays); | 
| } | 
| } | 
| function ConvertToString(x) { | 
| - if (IS_NULL_OR_UNDEFINED(x)) { | 
| - return ''; | 
| - } else { | 
| - return TO_STRING(x); | 
| - } | 
| + if (IS_NULL_OR_UNDEFINED(x)) return ''; | 
| + return TO_STRING(x); | 
| } | 
| function ConvertToLocaleString(e) { | 
| - if (IS_NULL_OR_UNDEFINED(e)) { | 
| - return ''; | 
| - } else { | 
| - return TO_STRING(e.toLocaleString()); | 
| - } | 
| + if (IS_NULL_OR_UNDEFINED(e)) return ''; | 
| + return TO_STRING(e.toLocaleString()); | 
| } | 
| @@ -1959,6 +1951,10 @@ utils.Export(function(to) { | 
| to.InnerArraySort = InnerArraySort; | 
| to.InnerArrayToLocaleString = InnerArrayToLocaleString; | 
| to.PackedArrayReverse = PackedArrayReverse; | 
| + to.Stack = Stack; | 
| + to.StackHas = StackHas; | 
| + to.StackPush = StackPush; | 
| + to.StackPop = StackPop; | 
| }); | 
| %InstallToContext([ |