blob: 56f52549e6f2988c50dc8eb9345fb96894e92023 [file] [log] [blame]
Ben Murdochb0fe1622011-05-05 13:52:32 +01001// Copyright 2010 the V8 project authors. All rights reserved.
Steve Blocka7e24c12009-10-30 11:49:00 +00002// Redistribution and use in source and binary forms, with or without
3// modification, are permitted provided that the following conditions are
4// met:
5//
6// * Redistributions of source code must retain the above copyright
7// notice, this list of conditions and the following disclaimer.
8// * Redistributions in binary form must reproduce the above
9// copyright notice, this list of conditions and the following
10// disclaimer in the documentation and/or other materials provided
11// with the distribution.
12// * Neither the name of Google Inc. nor the names of its
13// contributors may be used to endorse or promote products derived
14// from this software without specific prior written permission.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28// This file relies on the fact that the following declarations have been made
29// in runtime.js:
30// const $Array = global.Array;
31
32// -------------------------------------------------------------------
33
34// Global list of arrays visited during toString, toLocaleString and
35// join invocations.
36var visited_arrays = new $Array();
37
38
39// Gets a sorted array of array keys. Useful for operations on sparse
40// arrays. Dupes have not been removed.
41function GetSortedArrayKeys(array, intervals) {
42 var length = intervals.length;
43 var keys = [];
44 for (var k = 0; k < length; k++) {
45 var key = intervals[k];
46 if (key < 0) {
47 var j = -1 - key;
48 var limit = j + intervals[++k];
49 for (; j < limit; j++) {
50 var e = array[j];
51 if (!IS_UNDEFINED(e) || j in array) {
52 keys.push(j);
53 }
54 }
55 } else {
56 // The case where key is undefined also ends here.
57 if (!IS_UNDEFINED(key)) {
58 var e = array[key];
59 if (!IS_UNDEFINED(e) || key in array) {
60 keys.push(key);
61 }
62 }
63 }
64 }
65 keys.sort(function(a, b) { return a - b; });
66 return keys;
67}
68
69
70// Optimized for sparse arrays if separator is ''.
71function SparseJoin(array, len, convert) {
72 var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
Steve Blocka7e24c12009-10-30 11:49:00 +000073 var last_key = -1;
74 var keys_length = keys.length;
Leon Clarkee46be812010-01-19 14:06:41 +000075
76 var elements = new $Array(keys_length);
77 var elements_length = 0;
78
Steve Blocka7e24c12009-10-30 11:49:00 +000079 for (var i = 0; i < keys_length; i++) {
80 var key = keys[i];
81 if (key != last_key) {
82 var e = array[key];
Leon Clarkee46be812010-01-19 14:06:41 +000083 if (!IS_STRING(e)) e = convert(e);
84 elements[elements_length++] = e;
Steve Blocka7e24c12009-10-30 11:49:00 +000085 last_key = key;
86 }
87 }
Leon Clarkee46be812010-01-19 14:06:41 +000088 return %StringBuilderConcat(elements, elements_length, '');
Steve Blocka7e24c12009-10-30 11:49:00 +000089}
90
91
92function UseSparseVariant(object, length, is_array) {
93 return is_array &&
94 length > 1000 &&
95 (!%_IsSmi(length) ||
96 %EstimateNumberOfElements(object) < (length >> 2));
97}
98
99
100function Join(array, length, separator, convert) {
101 if (length == 0) return '';
102
103 var is_array = IS_ARRAY(array);
104
105 if (is_array) {
106 // If the array is cyclic, return the empty string for already
107 // visited arrays.
108 if (!%PushIfAbsent(visited_arrays, array)) return '';
109 }
110
111 // Attempt to convert the elements.
112 try {
Leon Clarkee46be812010-01-19 14:06:41 +0000113 if (UseSparseVariant(array, length, is_array) && (separator.length == 0)) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000114 return SparseJoin(array, length, convert);
115 }
116
117 // Fast case for one-element arrays.
118 if (length == 1) {
119 var e = array[0];
120 if (!IS_UNDEFINED(e) || (0 in array)) {
Leon Clarkee46be812010-01-19 14:06:41 +0000121 if (IS_STRING(e)) return e;
Steve Blocka7e24c12009-10-30 11:49:00 +0000122 return convert(e);
123 }
Ben Murdoch086aeea2011-05-13 15:57:08 +0100124 return '';
Steve Blocka7e24c12009-10-30 11:49:00 +0000125 }
126
Leon Clarkee46be812010-01-19 14:06:41 +0000127 // Construct an array for the elements.
Ben Murdoch086aeea2011-05-13 15:57:08 +0100128 var elements = new $Array(length);
Leon Clarkee46be812010-01-19 14:06:41 +0000129 var elements_length = 0;
Steve Blocka7e24c12009-10-30 11:49:00 +0000130
Steve Blockd0582a62009-12-15 09:54:21 +0000131 // We pull the empty separator check outside the loop for speed!
132 if (separator.length == 0) {
133 for (var i = 0; i < length; i++) {
134 var e = array[i];
Ben Murdoch086aeea2011-05-13 15:57:08 +0100135 if (!IS_UNDEFINED(e)) {
Leon Clarkee46be812010-01-19 14:06:41 +0000136 if (!IS_STRING(e)) e = convert(e);
137 elements[elements_length++] = e;
Steve Blockd0582a62009-12-15 09:54:21 +0000138 }
139 }
Ben Murdoch086aeea2011-05-13 15:57:08 +0100140 elements.length = elements_length;
141 var result = %_FastAsciiArrayJoin(elements, '');
142 if (!IS_UNDEFINED(result)) return result;
143 return %StringBuilderConcat(elements, elements_length, '');
144 }
145 // Non-empty separator.
146 for (var i = 0; i < length; i++) {
147 var e = array[i];
148 if (!IS_UNDEFINED(e)) {
149 if (!IS_STRING(e)) e = convert(e);
150 elements[i] = e;
151 } else {
152 elements[i] = '';
Steve Blocka7e24c12009-10-30 11:49:00 +0000153 }
154 }
Ben Murdoch086aeea2011-05-13 15:57:08 +0100155 var result = %_FastAsciiArrayJoin(elements, separator);
156 if (!IS_UNDEFINED(result)) return result;
157
158 var length2 = (length << 1) - 1;
159 var j = length2;
160 var i = length;
161 elements[--j] = elements[--i];
162 while (i > 0) {
163 elements[--j] = separator;
164 elements[--j] = elements[--i];
165 }
166 return %StringBuilderConcat(elements, length2, '');
Steve Blocka7e24c12009-10-30 11:49:00 +0000167 } finally {
168 // Make sure to pop the visited array no matter what happens.
169 if (is_array) visited_arrays.pop();
170 }
171}
172
173
Ben Murdochb0fe1622011-05-05 13:52:32 +0100174function ConvertToString(x) {
Ben Murdoch086aeea2011-05-13 15:57:08 +0100175 // Assumes x is a non-string.
Ben Murdochb0fe1622011-05-05 13:52:32 +0100176 if (IS_NUMBER(x)) return %_NumberToString(x);
177 if (IS_BOOLEAN(x)) return x ? 'true' : 'false';
178 return (IS_NULL_OR_UNDEFINED(x)) ? '' : %ToString(%DefaultString(x));
Steve Blocka7e24c12009-10-30 11:49:00 +0000179}
180
181
182function ConvertToLocaleString(e) {
Leon Clarkee46be812010-01-19 14:06:41 +0000183 if (e == null) {
184 return '';
185 } else {
Steve Blocka7e24c12009-10-30 11:49:00 +0000186 // e_obj's toLocaleString might be overwritten, check if it is a function.
187 // Call ToString if toLocaleString is not a function.
188 // See issue 877615.
189 var e_obj = ToObject(e);
190 if (IS_FUNCTION(e_obj.toLocaleString))
Steve Blockd0582a62009-12-15 09:54:21 +0000191 return ToString(e_obj.toLocaleString());
Steve Blocka7e24c12009-10-30 11:49:00 +0000192 else
193 return ToString(e);
194 }
195}
196
197
198// This function implements the optimized splice implementation that can use
199// special array operations to handle sparse arrays in a sensible fashion.
200function SmartSlice(array, start_i, del_count, len, deleted_elements) {
201 // Move deleted elements to a new array (the return value from splice).
202 // Intervals array can contain keys and intervals. See comment in Concat.
203 var intervals = %GetArrayKeys(array, start_i + del_count);
204 var length = intervals.length;
205 for (var k = 0; k < length; k++) {
206 var key = intervals[k];
207 if (key < 0) {
208 var j = -1 - key;
209 var interval_limit = j + intervals[++k];
210 if (j < start_i) {
211 j = start_i;
212 }
213 for (; j < interval_limit; j++) {
214 // ECMA-262 15.4.4.12 line 10. The spec could also be
215 // interpreted such that %HasLocalProperty would be the
216 // appropriate test. We follow KJS in consulting the
217 // prototype.
218 var current = array[j];
219 if (!IS_UNDEFINED(current) || j in array) {
220 deleted_elements[j - start_i] = current;
221 }
222 }
223 } else {
224 if (!IS_UNDEFINED(key)) {
225 if (key >= start_i) {
226 // ECMA-262 15.4.4.12 line 10. The spec could also be
227 // interpreted such that %HasLocalProperty would be the
228 // appropriate test. We follow KJS in consulting the
229 // prototype.
230 var current = array[key];
231 if (!IS_UNDEFINED(current) || key in array) {
232 deleted_elements[key - start_i] = current;
233 }
234 }
235 }
236 }
237 }
238}
239
240
241// This function implements the optimized splice implementation that can use
242// special array operations to handle sparse arrays in a sensible fashion.
243function SmartMove(array, start_i, del_count, len, num_additional_args) {
244 // Move data to new array.
245 var new_array = new $Array(len - del_count + num_additional_args);
246 var intervals = %GetArrayKeys(array, len);
247 var length = intervals.length;
248 for (var k = 0; k < length; k++) {
249 var key = intervals[k];
250 if (key < 0) {
251 var j = -1 - key;
252 var interval_limit = j + intervals[++k];
253 while (j < start_i && j < interval_limit) {
254 // The spec could also be interpreted such that
255 // %HasLocalProperty would be the appropriate test. We follow
256 // KJS in consulting the prototype.
257 var current = array[j];
258 if (!IS_UNDEFINED(current) || j in array) {
259 new_array[j] = current;
260 }
261 j++;
262 }
263 j = start_i + del_count;
264 while (j < interval_limit) {
265 // ECMA-262 15.4.4.12 lines 24 and 41. The spec could also be
266 // interpreted such that %HasLocalProperty would be the
267 // appropriate test. We follow KJS in consulting the
268 // prototype.
269 var current = array[j];
270 if (!IS_UNDEFINED(current) || j in array) {
271 new_array[j - del_count + num_additional_args] = current;
272 }
273 j++;
274 }
275 } else {
276 if (!IS_UNDEFINED(key)) {
277 if (key < start_i) {
278 // The spec could also be interpreted such that
279 // %HasLocalProperty would be the appropriate test. We follow
280 // KJS in consulting the prototype.
281 var current = array[key];
282 if (!IS_UNDEFINED(current) || key in array) {
283 new_array[key] = current;
284 }
285 } else if (key >= start_i + del_count) {
286 // ECMA-262 15.4.4.12 lines 24 and 41. The spec could also
287 // be interpreted such that %HasLocalProperty would be the
288 // appropriate test. We follow KJS in consulting the
289 // prototype.
290 var current = array[key];
291 if (!IS_UNDEFINED(current) || key in array) {
292 new_array[key - del_count + num_additional_args] = current;
293 }
294 }
295 }
296 }
297 }
298 // Move contents of new_array into this array
299 %MoveArrayContents(new_array, array);
300}
301
302
303// This is part of the old simple-minded splice. We are using it either
304// because the receiver is not an array (so we have no choice) or because we
305// know we are not deleting or moving a lot of elements.
306function SimpleSlice(array, start_i, del_count, len, deleted_elements) {
307 for (var i = 0; i < del_count; i++) {
308 var index = start_i + i;
309 // The spec could also be interpreted such that %HasLocalProperty
310 // would be the appropriate test. We follow KJS in consulting the
311 // prototype.
312 var current = array[index];
313 if (!IS_UNDEFINED(current) || index in array)
314 deleted_elements[i] = current;
315 }
316}
317
318
319function SimpleMove(array, start_i, del_count, len, num_additional_args) {
320 if (num_additional_args !== del_count) {
321 // Move the existing elements after the elements to be deleted
322 // to the right position in the resulting array.
323 if (num_additional_args > del_count) {
324 for (var i = len - del_count; i > start_i; i--) {
325 var from_index = i + del_count - 1;
326 var to_index = i + num_additional_args - 1;
327 // The spec could also be interpreted such that
328 // %HasLocalProperty would be the appropriate test. We follow
329 // KJS in consulting the prototype.
330 var current = array[from_index];
331 if (!IS_UNDEFINED(current) || from_index in array) {
332 array[to_index] = current;
333 } else {
334 delete array[to_index];
335 }
336 }
337 } else {
338 for (var i = start_i; i < len - del_count; i++) {
339 var from_index = i + del_count;
340 var to_index = i + num_additional_args;
341 // The spec could also be interpreted such that
342 // %HasLocalProperty would be the appropriate test. We follow
343 // KJS in consulting the prototype.
344 var current = array[from_index];
345 if (!IS_UNDEFINED(current) || from_index in array) {
346 array[to_index] = current;
347 } else {
348 delete array[to_index];
349 }
350 }
351 for (var i = len; i > len - del_count + num_additional_args; i--) {
352 delete array[i - 1];
353 }
354 }
355 }
356}
357
358
359// -------------------------------------------------------------------
360
361
362function ArrayToString() {
363 if (!IS_ARRAY(this)) {
364 throw new $TypeError('Array.prototype.toString is not generic');
365 }
366 return Join(this, this.length, ',', ConvertToString);
367}
368
369
370function ArrayToLocaleString() {
371 if (!IS_ARRAY(this)) {
372 throw new $TypeError('Array.prototype.toString is not generic');
373 }
374 return Join(this, this.length, ',', ConvertToLocaleString);
375}
376
377
378function ArrayJoin(separator) {
Leon Clarkee46be812010-01-19 14:06:41 +0000379 if (IS_UNDEFINED(separator)) {
380 separator = ',';
381 } else if (!IS_STRING(separator)) {
Ben Murdochb0fe1622011-05-05 13:52:32 +0100382 separator = NonStringToString(separator);
Leon Clarkee46be812010-01-19 14:06:41 +0000383 }
Shimeng (Simon) Wang8a31eba2010-12-06 19:01:33 -0800384
385 var result = %_FastAsciiArrayJoin(this, separator);
Ben Murdochb0fe1622011-05-05 13:52:32 +0100386 if (!IS_UNDEFINED(result)) return result;
Shimeng (Simon) Wang8a31eba2010-12-06 19:01:33 -0800387
Ben Murdochb0fe1622011-05-05 13:52:32 +0100388 return Join(this, TO_UINT32(this.length), separator, ConvertToString);
Steve Blocka7e24c12009-10-30 11:49:00 +0000389}
390
391
392// Removes the last element from the array and returns it. See
393// ECMA-262, section 15.4.4.6.
394function ArrayPop() {
Leon Clarkee46be812010-01-19 14:06:41 +0000395 var n = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000396 if (n == 0) {
397 this.length = n;
398 return;
399 }
400 n--;
401 var value = this[n];
402 this.length = n;
403 delete this[n];
404 return value;
405}
406
407
408// Appends the arguments to the end of the array and returns the new
409// length of the array. See ECMA-262, section 15.4.4.7.
410function ArrayPush() {
Leon Clarkee46be812010-01-19 14:06:41 +0000411 var n = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000412 var m = %_ArgumentsLength();
413 for (var i = 0; i < m; i++) {
414 this[i+n] = %_Arguments(i);
415 }
416 this.length = n + m;
417 return this.length;
418}
419
420
421function ArrayConcat(arg1) { // length == 1
422 // TODO: can we just use arguments?
423 var arg_count = %_ArgumentsLength();
424 var arrays = new $Array(1 + arg_count);
425 arrays[0] = this;
426 for (var i = 0; i < arg_count; i++) {
427 arrays[i + 1] = %_Arguments(i);
428 }
429
430 return %ArrayConcat(arrays);
431}
432
433
434// For implementing reverse() on large, sparse arrays.
435function SparseReverse(array, len) {
436 var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
437 var high_counter = keys.length - 1;
438 var low_counter = 0;
439 while (low_counter <= high_counter) {
440 var i = keys[low_counter];
441 var j = keys[high_counter];
442
443 var j_complement = len - j - 1;
444 var low, high;
445
446 if (j_complement <= i) {
447 high = j;
448 while (keys[--high_counter] == j);
449 low = j_complement;
450 }
451 if (j_complement >= i) {
452 low = i;
453 while (keys[++low_counter] == i);
454 high = len - i - 1;
455 }
456
457 var current_i = array[low];
458 if (!IS_UNDEFINED(current_i) || low in array) {
459 var current_j = array[high];
460 if (!IS_UNDEFINED(current_j) || high in array) {
461 array[low] = current_j;
462 array[high] = current_i;
463 } else {
464 array[high] = current_i;
465 delete array[low];
466 }
467 } else {
468 var current_j = array[high];
469 if (!IS_UNDEFINED(current_j) || high in array) {
470 array[low] = current_j;
471 delete array[high];
472 }
473 }
474 }
475}
476
477
478function ArrayReverse() {
Leon Clarkee46be812010-01-19 14:06:41 +0000479 var j = TO_UINT32(this.length) - 1;
Steve Blocka7e24c12009-10-30 11:49:00 +0000480
481 if (UseSparseVariant(this, j, IS_ARRAY(this))) {
482 SparseReverse(this, j+1);
483 return this;
484 }
485
486 for (var i = 0; i < j; i++, j--) {
487 var current_i = this[i];
488 if (!IS_UNDEFINED(current_i) || i in this) {
489 var current_j = this[j];
490 if (!IS_UNDEFINED(current_j) || j in this) {
491 this[i] = current_j;
492 this[j] = current_i;
493 } else {
494 this[j] = current_i;
495 delete this[i];
496 }
497 } else {
498 var current_j = this[j];
499 if (!IS_UNDEFINED(current_j) || j in this) {
500 this[i] = current_j;
501 delete this[j];
502 }
503 }
504 }
505 return this;
506}
507
508
509function ArrayShift() {
Leon Clarkee46be812010-01-19 14:06:41 +0000510 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000511
512 if (len === 0) {
513 this.length = 0;
514 return;
515 }
516
517 var first = this[0];
518
519 if (IS_ARRAY(this))
520 SmartMove(this, 0, 1, len, 0);
521 else
522 SimpleMove(this, 0, 1, len, 0);
523
524 this.length = len - 1;
525
526 return first;
527}
528
529
530function ArrayUnshift(arg1) { // length == 1
Leon Clarkee46be812010-01-19 14:06:41 +0000531 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000532 var num_arguments = %_ArgumentsLength();
533
534 if (IS_ARRAY(this))
535 SmartMove(this, 0, 0, len, num_arguments);
536 else
537 SimpleMove(this, 0, 0, len, num_arguments);
538
539 for (var i = 0; i < num_arguments; i++) {
540 this[i] = %_Arguments(i);
541 }
542
543 this.length = len + num_arguments;
544
545 return len + num_arguments;
546}
547
548
549function ArraySlice(start, end) {
Leon Clarkee46be812010-01-19 14:06:41 +0000550 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000551 var start_i = TO_INTEGER(start);
552 var end_i = len;
553
554 if (end !== void 0) end_i = TO_INTEGER(end);
555
556 if (start_i < 0) {
557 start_i += len;
558 if (start_i < 0) start_i = 0;
559 } else {
560 if (start_i > len) start_i = len;
561 }
562
563 if (end_i < 0) {
564 end_i += len;
565 if (end_i < 0) end_i = 0;
566 } else {
567 if (end_i > len) end_i = len;
568 }
569
570 var result = [];
571
572 if (end_i < start_i) return result;
573
574 if (IS_ARRAY(this)) {
575 SmartSlice(this, start_i, end_i - start_i, len, result);
576 } else {
577 SimpleSlice(this, start_i, end_i - start_i, len, result);
578 }
579
580 result.length = end_i - start_i;
581
582 return result;
583}
584
585
586function ArraySplice(start, delete_count) {
587 var num_arguments = %_ArgumentsLength();
588
Leon Clarkee46be812010-01-19 14:06:41 +0000589 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000590 var start_i = TO_INTEGER(start);
591
592 if (start_i < 0) {
593 start_i += len;
594 if (start_i < 0) start_i = 0;
595 } else {
596 if (start_i > len) start_i = len;
597 }
598
Andrei Popescu402d9372010-02-26 13:31:12 +0000599 // SpiderMonkey, TraceMonkey and JSC treat the case where no delete count is
Steve Blocka7e24c12009-10-30 11:49:00 +0000600 // given differently from when an undefined delete count is given.
601 // This does not follow ECMA-262, but we do the same for
602 // compatibility.
603 var del_count = 0;
604 if (num_arguments > 1) {
605 del_count = TO_INTEGER(delete_count);
606 if (del_count < 0) del_count = 0;
607 if (del_count > len - start_i) del_count = len - start_i;
608 } else {
609 del_count = len - start_i;
610 }
611
612 var deleted_elements = [];
613 deleted_elements.length = del_count;
614
615 // Number of elements to add.
616 var num_additional_args = 0;
617 if (num_arguments > 2) {
618 num_additional_args = num_arguments - 2;
619 }
620
621 var use_simple_splice = true;
622
623 if (IS_ARRAY(this) && num_additional_args !== del_count) {
624 // If we are only deleting/moving a few things near the end of the
625 // array then the simple version is going to be faster, because it
626 // doesn't touch most of the array.
627 var estimated_non_hole_elements = %EstimateNumberOfElements(this);
628 if (len > 20 && (estimated_non_hole_elements >> 2) < (len - start_i)) {
629 use_simple_splice = false;
630 }
631 }
632
633 if (use_simple_splice) {
634 SimpleSlice(this, start_i, del_count, len, deleted_elements);
635 SimpleMove(this, start_i, del_count, len, num_additional_args);
636 } else {
637 SmartSlice(this, start_i, del_count, len, deleted_elements);
638 SmartMove(this, start_i, del_count, len, num_additional_args);
639 }
640
641 // Insert the arguments into the resulting array in
642 // place of the deleted elements.
643 var i = start_i;
644 var arguments_index = 2;
645 var arguments_length = %_ArgumentsLength();
646 while (arguments_index < arguments_length) {
647 this[i++] = %_Arguments(arguments_index++);
648 }
649 this.length = len - del_count + num_additional_args;
650
651 // Return the deleted elements.
652 return deleted_elements;
653}
654
655
656function ArraySort(comparefn) {
657 // In-place QuickSort algorithm.
658 // For short (length <= 22) arrays, insertion sort is used for efficiency.
659
Steve Block6ded16b2010-05-10 14:33:55 +0100660 if (!IS_FUNCTION(comparefn)) {
661 comparefn = function (x, y) {
662 if (x === y) return 0;
663 if (%_IsSmi(x) && %_IsSmi(y)) {
664 return %SmiLexicographicCompare(x, y);
665 }
666 x = ToString(x);
667 y = ToString(y);
668 if (x == y) return 0;
669 else return x < y ? -1 : 1;
670 };
671 }
672 var global_receiver = %GetGlobalReceiver();
Steve Blocka7e24c12009-10-30 11:49:00 +0000673
674 function InsertionSort(a, from, to) {
675 for (var i = from + 1; i < to; i++) {
676 var element = a[i];
Steve Block6ded16b2010-05-10 14:33:55 +0100677 for (var j = i - 1; j >= from; j--) {
678 var tmp = a[j];
679 var order = %_CallFunction(global_receiver, tmp, element, comparefn);
680 if (order > 0) {
681 a[j + 1] = tmp;
682 } else {
Steve Blocka7e24c12009-10-30 11:49:00 +0000683 break;
684 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000685 }
Steve Block6ded16b2010-05-10 14:33:55 +0100686 a[j + 1] = element;
Steve Blocka7e24c12009-10-30 11:49:00 +0000687 }
688 }
689
690 function QuickSort(a, from, to) {
691 // Insertion sort is faster for short arrays.
Ben Murdochb0fe1622011-05-05 13:52:32 +0100692 if (to - from <= 10) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000693 InsertionSort(a, from, to);
694 return;
695 }
Ben Murdochb0fe1622011-05-05 13:52:32 +0100696 // Find a pivot as the median of first, last and middle element.
697 var v0 = a[from];
698 var v1 = a[to - 1];
699 var middle_index = from + ((to - from) >> 1);
700 var v2 = a[middle_index];
701 var c01 = %_CallFunction(global_receiver, v0, v1, comparefn);
702 if (c01 > 0) {
703 // v1 < v0, so swap them.
704 var tmp = v0;
705 v0 = v1;
706 v1 = tmp;
707 } // v0 <= v1.
708 var c02 = %_CallFunction(global_receiver, v0, v2, comparefn);
709 if (c02 >= 0) {
710 // v2 <= v0 <= v1.
711 var tmp = v0;
712 v0 = v2;
713 v2 = v1;
714 v1 = tmp;
715 } else {
716 // v0 <= v1 && v0 < v2
717 var c12 = %_CallFunction(global_receiver, v1, v2, comparefn);
718 if (c12 > 0) {
719 // v0 <= v2 < v1
720 var tmp = v1;
721 v1 = v2;
722 v2 = tmp;
723 }
724 }
725 // v0 <= v1 <= v2
726 a[from] = v0;
727 a[to - 1] = v2;
728 var pivot = v1;
729 var low_end = from + 1; // Upper bound of elements lower than pivot.
730 var high_start = to - 1; // Lower bound of elements greater than pivot.
731 a[middle_index] = a[low_end];
732 a[low_end] = pivot;
733
Steve Blocka7e24c12009-10-30 11:49:00 +0000734 // From low_end to i are elements equal to pivot.
735 // From i to high_start are elements that haven't been compared yet.
Ben Murdochb0fe1622011-05-05 13:52:32 +0100736 partition: for (var i = low_end + 1; i < high_start; i++) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000737 var element = a[i];
Steve Block6ded16b2010-05-10 14:33:55 +0100738 var order = %_CallFunction(global_receiver, element, pivot, comparefn);
Steve Blocka7e24c12009-10-30 11:49:00 +0000739 if (order < 0) {
Steve Block6ded16b2010-05-10 14:33:55 +0100740 %_SwapElements(a, i, low_end);
Steve Blocka7e24c12009-10-30 11:49:00 +0000741 low_end++;
742 } else if (order > 0) {
Ben Murdochb0fe1622011-05-05 13:52:32 +0100743 do {
744 high_start--;
745 if (high_start == i) break partition;
746 var top_elem = a[high_start];
747 order = %_CallFunction(global_receiver, top_elem, pivot, comparefn);
748 } while (order > 0);
Steve Block6ded16b2010-05-10 14:33:55 +0100749 %_SwapElements(a, i, high_start);
Ben Murdochb0fe1622011-05-05 13:52:32 +0100750 if (order < 0) {
751 %_SwapElements(a, i, low_end);
752 low_end++;
753 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000754 }
755 }
756 QuickSort(a, from, low_end);
757 QuickSort(a, high_start, to);
758 }
759
Ben Murdochb0fe1622011-05-05 13:52:32 +0100760 // Copy elements in the range 0..length from obj's prototype chain
761 // to obj itself, if obj has holes. Return one more than the maximal index
Steve Blocka7e24c12009-10-30 11:49:00 +0000762 // of a prototype property.
763 function CopyFromPrototype(obj, length) {
764 var max = 0;
765 for (var proto = obj.__proto__; proto; proto = proto.__proto__) {
766 var indices = %GetArrayKeys(proto, length);
767 if (indices.length > 0) {
768 if (indices[0] == -1) {
769 // It's an interval.
770 var proto_length = indices[1];
771 for (var i = 0; i < proto_length; i++) {
772 if (!obj.hasOwnProperty(i) && proto.hasOwnProperty(i)) {
773 obj[i] = proto[i];
774 if (i >= max) { max = i + 1; }
775 }
776 }
777 } else {
778 for (var i = 0; i < indices.length; i++) {
779 var index = indices[i];
780 if (!IS_UNDEFINED(index) &&
781 !obj.hasOwnProperty(index) && proto.hasOwnProperty(index)) {
782 obj[index] = proto[index];
783 if (index >= max) { max = index + 1; }
784 }
785 }
786 }
787 }
788 }
789 return max;
790 }
791
792 // Set a value of "undefined" on all indices in the range from..to
793 // where a prototype of obj has an element. I.e., shadow all prototype
794 // elements in that range.
795 function ShadowPrototypeElements(obj, from, to) {
796 for (var proto = obj.__proto__; proto; proto = proto.__proto__) {
797 var indices = %GetArrayKeys(proto, to);
798 if (indices.length > 0) {
799 if (indices[0] == -1) {
800 // It's an interval.
801 var proto_length = indices[1];
802 for (var i = from; i < proto_length; i++) {
803 if (proto.hasOwnProperty(i)) {
804 obj[i] = void 0;
805 }
806 }
807 } else {
808 for (var i = 0; i < indices.length; i++) {
809 var index = indices[i];
810 if (!IS_UNDEFINED(index) && from <= index &&
811 proto.hasOwnProperty(index)) {
812 obj[index] = void 0;
813 }
814 }
815 }
816 }
817 }
818 }
819
820 function SafeRemoveArrayHoles(obj) {
821 // Copy defined elements from the end to fill in all holes and undefineds
822 // in the beginning of the array. Write undefineds and holes at the end
823 // after loop is finished.
824 var first_undefined = 0;
825 var last_defined = length - 1;
826 var num_holes = 0;
827 while (first_undefined < last_defined) {
828 // Find first undefined element.
829 while (first_undefined < last_defined &&
830 !IS_UNDEFINED(obj[first_undefined])) {
831 first_undefined++;
832 }
833 // Maintain the invariant num_holes = the number of holes in the original
834 // array with indices <= first_undefined or > last_defined.
835 if (!obj.hasOwnProperty(first_undefined)) {
836 num_holes++;
837 }
838
839 // Find last defined element.
840 while (first_undefined < last_defined &&
841 IS_UNDEFINED(obj[last_defined])) {
842 if (!obj.hasOwnProperty(last_defined)) {
843 num_holes++;
844 }
845 last_defined--;
846 }
847 if (first_undefined < last_defined) {
848 // Fill in hole or undefined.
849 obj[first_undefined] = obj[last_defined];
850 obj[last_defined] = void 0;
851 }
852 }
853 // If there were any undefineds in the entire array, first_undefined
854 // points to one past the last defined element. Make this true if
855 // there were no undefineds, as well, so that first_undefined == number
856 // of defined elements.
857 if (!IS_UNDEFINED(obj[first_undefined])) first_undefined++;
858 // Fill in the undefineds and the holes. There may be a hole where
859 // an undefined should be and vice versa.
860 var i;
861 for (i = first_undefined; i < length - num_holes; i++) {
862 obj[i] = void 0;
863 }
864 for (i = length - num_holes; i < length; i++) {
865 // For compatability with Webkit, do not expose elements in the prototype.
866 if (i in obj.__proto__) {
867 obj[i] = void 0;
868 } else {
869 delete obj[i];
870 }
871 }
872
873 // Return the number of defined elements.
874 return first_undefined;
875 }
876
Steve Block6ded16b2010-05-10 14:33:55 +0100877 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000878 if (length < 2) return this;
879
880 var is_array = IS_ARRAY(this);
881 var max_prototype_element;
882 if (!is_array) {
883 // For compatibility with JSC, we also sort elements inherited from
884 // the prototype chain on non-Array objects.
885 // We do this by copying them to this object and sorting only
886 // local elements. This is not very efficient, but sorting with
887 // inherited elements happens very, very rarely, if at all.
888 // The specification allows "implementation dependent" behavior
889 // if an element on the prototype chain has an element that
890 // might interact with sorting.
891 max_prototype_element = CopyFromPrototype(this, length);
892 }
893
894 var num_non_undefined = %RemoveArrayHoles(this, length);
895 if (num_non_undefined == -1) {
896 // There were indexed accessors in the array. Move array holes and
897 // undefineds to the end using a Javascript function that is safe
898 // in the presence of accessors.
899 num_non_undefined = SafeRemoveArrayHoles(this);
900 }
901
902 QuickSort(this, 0, num_non_undefined);
903
904 if (!is_array && (num_non_undefined + 1 < max_prototype_element)) {
905 // For compatibility with JSC, we shadow any elements in the prototype
906 // chain that has become exposed by sort moving a hole to its position.
907 ShadowPrototypeElements(this, num_non_undefined, max_prototype_element);
908 }
909
910 return this;
911}
912
913
914// The following functions cannot be made efficient on sparse arrays while
915// preserving the semantics, since the calls to the receiver function can add
916// or delete elements from the array.
917function ArrayFilter(f, receiver) {
918 if (!IS_FUNCTION(f)) {
919 throw MakeTypeError('called_non_callable', [ f ]);
920 }
921 // Pull out the length so that modifications to the length in the
922 // loop will not affect the looping.
923 var length = this.length;
924 var result = [];
925 var result_length = 0;
926 for (var i = 0; i < length; i++) {
927 var current = this[i];
928 if (!IS_UNDEFINED(current) || i in this) {
929 if (f.call(receiver, current, i, this)) result[result_length++] = current;
930 }
931 }
932 return result;
933}
934
935
936function ArrayForEach(f, receiver) {
937 if (!IS_FUNCTION(f)) {
938 throw MakeTypeError('called_non_callable', [ f ]);
939 }
940 // Pull out the length so that modifications to the length in the
941 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000942 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000943 for (var i = 0; i < length; i++) {
944 var current = this[i];
945 if (!IS_UNDEFINED(current) || i in this) {
946 f.call(receiver, current, i, this);
947 }
948 }
949}
950
951
952// Executes the function once for each element present in the
953// array until it finds one where callback returns true.
954function ArraySome(f, receiver) {
955 if (!IS_FUNCTION(f)) {
956 throw MakeTypeError('called_non_callable', [ f ]);
957 }
958 // Pull out the length so that modifications to the length in the
959 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000960 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000961 for (var i = 0; i < length; i++) {
962 var current = this[i];
963 if (!IS_UNDEFINED(current) || i in this) {
964 if (f.call(receiver, current, i, this)) return true;
965 }
966 }
967 return false;
968}
969
970
971function ArrayEvery(f, receiver) {
972 if (!IS_FUNCTION(f)) {
973 throw MakeTypeError('called_non_callable', [ f ]);
974 }
975 // Pull out the length so that modifications to the length in the
976 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000977 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000978 for (var i = 0; i < length; i++) {
979 var current = this[i];
980 if (!IS_UNDEFINED(current) || i in this) {
981 if (!f.call(receiver, current, i, this)) return false;
982 }
983 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000984 return true;
985}
986
Steve Blocka7e24c12009-10-30 11:49:00 +0000987function ArrayMap(f, receiver) {
988 if (!IS_FUNCTION(f)) {
989 throw MakeTypeError('called_non_callable', [ f ]);
990 }
991 // Pull out the length so that modifications to the length in the
992 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000993 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000994 var result = new $Array(length);
995 for (var i = 0; i < length; i++) {
996 var current = this[i];
997 if (!IS_UNDEFINED(current) || i in this) {
998 result[i] = f.call(receiver, current, i, this);
999 }
1000 }
1001 return result;
1002}
1003
1004
1005function ArrayIndexOf(element, index) {
Kristian Monsen80d68ea2010-09-08 11:05:35 +01001006 var length = TO_UINT32(this.length);
1007 if (length == 0) return -1;
Steve Block8defd9f2010-07-08 12:39:36 +01001008 if (IS_UNDEFINED(index)) {
Steve Blocka7e24c12009-10-30 11:49:00 +00001009 index = 0;
1010 } else {
1011 index = TO_INTEGER(index);
1012 // If index is negative, index from the end of the array.
1013 if (index < 0) index = length + index;
1014 // If index is still negative, search the entire array.
1015 if (index < 0) index = 0;
1016 }
Steve Block59151502010-09-22 15:07:15 +01001017 var min = index;
1018 var max = length;
1019 if (UseSparseVariant(this, length, true)) {
1020 var intervals = %GetArrayKeys(this, length);
1021 if (intervals.length == 2 && intervals[0] < 0) {
1022 // A single interval.
1023 var intervalMin = -(intervals[0] + 1);
1024 var intervalMax = intervalMin + intervals[1];
1025 min = MAX(min, intervalMin);
1026 max = intervalMax; // Capped by length already.
1027 // Fall through to loop below.
1028 } else {
1029 if (intervals.length == 0) return -1;
1030 // Get all the keys in sorted order.
1031 var sortedKeys = GetSortedArrayKeys(this, intervals);
1032 var n = sortedKeys.length;
1033 var i = 0;
1034 while (i < n && sortedKeys[i] < index) i++;
1035 while (i < n) {
1036 var key = sortedKeys[i];
1037 if (!IS_UNDEFINED(key) && this[key] === element) return key;
1038 i++;
1039 }
1040 return -1;
1041 }
1042 }
Kristian Monsen80d68ea2010-09-08 11:05:35 +01001043 // Lookup through the array.
Steve Block6ded16b2010-05-10 14:33:55 +01001044 if (!IS_UNDEFINED(element)) {
Steve Block59151502010-09-22 15:07:15 +01001045 for (var i = min; i < max; i++) {
Steve Block6ded16b2010-05-10 14:33:55 +01001046 if (this[i] === element) return i;
1047 }
1048 return -1;
1049 }
Steve Block59151502010-09-22 15:07:15 +01001050 // Lookup through the array.
1051 for (var i = min; i < max; i++) {
Steve Block6ded16b2010-05-10 14:33:55 +01001052 if (IS_UNDEFINED(this[i]) && i in this) {
1053 return i;
Steve Blocka7e24c12009-10-30 11:49:00 +00001054 }
1055 }
1056 return -1;
1057}
1058
1059
1060function ArrayLastIndexOf(element, index) {
Kristian Monsen80d68ea2010-09-08 11:05:35 +01001061 var length = TO_UINT32(this.length);
1062 if (length == 0) return -1;
Steve Block8defd9f2010-07-08 12:39:36 +01001063 if (%_ArgumentsLength() < 2) {
Steve Blocka7e24c12009-10-30 11:49:00 +00001064 index = length - 1;
1065 } else {
1066 index = TO_INTEGER(index);
1067 // If index is negative, index from end of the array.
Steve Block59151502010-09-22 15:07:15 +01001068 if (index < 0) index += length;
Steve Blocka7e24c12009-10-30 11:49:00 +00001069 // If index is still negative, do not search the array.
Steve Block59151502010-09-22 15:07:15 +01001070 if (index < 0) return -1;
Steve Blocka7e24c12009-10-30 11:49:00 +00001071 else if (index >= length) index = length - 1;
1072 }
Steve Block59151502010-09-22 15:07:15 +01001073 var min = 0;
1074 var max = index;
1075 if (UseSparseVariant(this, length, true)) {
1076 var intervals = %GetArrayKeys(this, index + 1);
1077 if (intervals.length == 2 && intervals[0] < 0) {
1078 // A single interval.
1079 var intervalMin = -(intervals[0] + 1);
1080 var intervalMax = intervalMin + intervals[1];
1081 min = MAX(min, intervalMin);
1082 max = intervalMax; // Capped by index already.
1083 // Fall through to loop below.
1084 } else {
1085 if (intervals.length == 0) return -1;
1086 // Get all the keys in sorted order.
1087 var sortedKeys = GetSortedArrayKeys(this, intervals);
1088 var i = sortedKeys.length - 1;
1089 while (i >= 0) {
1090 var key = sortedKeys[i];
1091 if (!IS_UNDEFINED(key) && this[key] === element) return key;
1092 i--;
1093 }
1094 return -1;
1095 }
1096 }
Steve Blocka7e24c12009-10-30 11:49:00 +00001097 // Lookup through the array.
Steve Block6ded16b2010-05-10 14:33:55 +01001098 if (!IS_UNDEFINED(element)) {
Steve Block59151502010-09-22 15:07:15 +01001099 for (var i = max; i >= min; i--) {
Steve Block6ded16b2010-05-10 14:33:55 +01001100 if (this[i] === element) return i;
1101 }
1102 return -1;
1103 }
Steve Block59151502010-09-22 15:07:15 +01001104 for (var i = max; i >= min; i--) {
Steve Block6ded16b2010-05-10 14:33:55 +01001105 if (IS_UNDEFINED(this[i]) && i in this) {
1106 return i;
Steve Blocka7e24c12009-10-30 11:49:00 +00001107 }
1108 }
1109 return -1;
1110}
1111
1112
1113function ArrayReduce(callback, current) {
1114 if (!IS_FUNCTION(callback)) {
1115 throw MakeTypeError('called_non_callable', [callback]);
1116 }
1117 // Pull out the length so that modifications to the length in the
1118 // loop will not affect the looping.
1119 var length = this.length;
1120 var i = 0;
1121
1122 find_initial: if (%_ArgumentsLength() < 2) {
1123 for (; i < length; i++) {
1124 current = this[i];
1125 if (!IS_UNDEFINED(current) || i in this) {
1126 i++;
1127 break find_initial;
1128 }
1129 }
1130 throw MakeTypeError('reduce_no_initial', []);
1131 }
1132
1133 for (; i < length; i++) {
1134 var element = this[i];
1135 if (!IS_UNDEFINED(element) || i in this) {
1136 current = callback.call(null, current, element, i, this);
1137 }
1138 }
1139 return current;
1140}
1141
1142function ArrayReduceRight(callback, current) {
1143 if (!IS_FUNCTION(callback)) {
1144 throw MakeTypeError('called_non_callable', [callback]);
1145 }
1146 var i = this.length - 1;
1147
1148 find_initial: if (%_ArgumentsLength() < 2) {
1149 for (; i >= 0; i--) {
1150 current = this[i];
1151 if (!IS_UNDEFINED(current) || i in this) {
1152 i--;
1153 break find_initial;
1154 }
1155 }
1156 throw MakeTypeError('reduce_no_initial', []);
1157 }
1158
1159 for (; i >= 0; i--) {
1160 var element = this[i];
1161 if (!IS_UNDEFINED(element) || i in this) {
1162 current = callback.call(null, current, element, i, this);
1163 }
1164 }
1165 return current;
1166}
1167
Steve Block3ce2e202009-11-05 08:53:23 +00001168// ES5, 15.4.3.2
1169function ArrayIsArray(obj) {
1170 return IS_ARRAY(obj);
1171}
Steve Blocka7e24c12009-10-30 11:49:00 +00001172
Steve Blocka7e24c12009-10-30 11:49:00 +00001173
1174// -------------------------------------------------------------------
1175function SetupArray() {
1176 // Setup non-enumerable constructor property on the Array.prototype
1177 // object.
1178 %SetProperty($Array.prototype, "constructor", $Array, DONT_ENUM);
1179
Steve Block3ce2e202009-11-05 08:53:23 +00001180 // Setup non-enumerable functions on the Array object.
1181 InstallFunctions($Array, DONT_ENUM, $Array(
1182 "isArray", ArrayIsArray
1183 ));
1184
Steve Block6ded16b2010-05-10 14:33:55 +01001185 var specialFunctions = %SpecialArrayFunctions({});
1186
1187 function getFunction(name, jsBuiltin, len) {
1188 var f = jsBuiltin;
1189 if (specialFunctions.hasOwnProperty(name)) {
1190 f = specialFunctions[name];
1191 }
1192 if (!IS_UNDEFINED(len)) {
1193 %FunctionSetLength(f, len);
1194 }
1195 return f;
1196 }
1197
Steve Blocka7e24c12009-10-30 11:49:00 +00001198 // Setup non-enumerable functions of the Array.prototype object and
1199 // set their names.
Steve Blocka7e24c12009-10-30 11:49:00 +00001200 // Manipulate the length of some of the functions to meet
1201 // expectations set by ECMA-262 or Mozilla.
Steve Block6ded16b2010-05-10 14:33:55 +01001202 InstallFunctionsOnHiddenPrototype($Array.prototype, DONT_ENUM, $Array(
1203 "toString", getFunction("toString", ArrayToString),
1204 "toLocaleString", getFunction("toLocaleString", ArrayToLocaleString),
1205 "join", getFunction("join", ArrayJoin),
1206 "pop", getFunction("pop", ArrayPop),
1207 "push", getFunction("push", ArrayPush, 1),
1208 "concat", getFunction("concat", ArrayConcat, 1),
1209 "reverse", getFunction("reverse", ArrayReverse),
1210 "shift", getFunction("shift", ArrayShift),
1211 "unshift", getFunction("unshift", ArrayUnshift, 1),
1212 "slice", getFunction("slice", ArraySlice, 2),
1213 "splice", getFunction("splice", ArraySplice, 2),
1214 "sort", getFunction("sort", ArraySort),
1215 "filter", getFunction("filter", ArrayFilter, 1),
1216 "forEach", getFunction("forEach", ArrayForEach, 1),
1217 "some", getFunction("some", ArraySome, 1),
1218 "every", getFunction("every", ArrayEvery, 1),
1219 "map", getFunction("map", ArrayMap, 1),
1220 "indexOf", getFunction("indexOf", ArrayIndexOf, 1),
1221 "lastIndexOf", getFunction("lastIndexOf", ArrayLastIndexOf, 1),
1222 "reduce", getFunction("reduce", ArrayReduce, 1),
1223 "reduceRight", getFunction("reduceRight", ArrayReduceRight, 1)
1224 ));
1225
1226 %FinishArrayPrototypeSetup($Array.prototype);
Steve Blocka7e24c12009-10-30 11:49:00 +00001227}
1228
1229
1230SetupArray();