blob: 0f1e969f9871f2e7bee7b0f21e0d743facd8ac94 [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 }
124 }
125
Leon Clarkee46be812010-01-19 14:06:41 +0000126 // Construct an array for the elements.
127 var elements;
128 var elements_length = 0;
Steve Blocka7e24c12009-10-30 11:49:00 +0000129
Steve Blockd0582a62009-12-15 09:54:21 +0000130 // We pull the empty separator check outside the loop for speed!
131 if (separator.length == 0) {
Leon Clarkee46be812010-01-19 14:06:41 +0000132 elements = new $Array(length);
Steve Blockd0582a62009-12-15 09:54:21 +0000133 for (var i = 0; i < length; i++) {
134 var e = array[i];
135 if (!IS_UNDEFINED(e) || (i in array)) {
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 }
140 } else {
Leon Clarkee46be812010-01-19 14:06:41 +0000141 elements = new $Array(length << 1);
Steve Blockd0582a62009-12-15 09:54:21 +0000142 for (var i = 0; i < length; i++) {
143 var e = array[i];
Leon Clarkee46be812010-01-19 14:06:41 +0000144 if (i != 0) elements[elements_length++] = separator;
Steve Blockd0582a62009-12-15 09:54:21 +0000145 if (!IS_UNDEFINED(e) || (i in array)) {
Leon Clarkee46be812010-01-19 14:06:41 +0000146 if (!IS_STRING(e)) e = convert(e);
147 elements[elements_length++] = e;
Steve Blockd0582a62009-12-15 09:54:21 +0000148 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000149 }
150 }
Ben Murdochb0fe1622011-05-05 13:52:32 +0100151 elements.length = elements_length;
152 var result = %_FastAsciiArrayJoin(elements, "");
153 if (!IS_UNDEFINED(result)) return result;
Leon Clarkee46be812010-01-19 14:06:41 +0000154 return %StringBuilderConcat(elements, elements_length, '');
Steve Blocka7e24c12009-10-30 11:49:00 +0000155 } finally {
156 // Make sure to pop the visited array no matter what happens.
157 if (is_array) visited_arrays.pop();
158 }
159}
160
161
Ben Murdochb0fe1622011-05-05 13:52:32 +0100162function ConvertToString(x) {
163 if (IS_STRING(x)) return x;
164 if (IS_NUMBER(x)) return %_NumberToString(x);
165 if (IS_BOOLEAN(x)) return x ? 'true' : 'false';
166 return (IS_NULL_OR_UNDEFINED(x)) ? '' : %ToString(%DefaultString(x));
Steve Blocka7e24c12009-10-30 11:49:00 +0000167}
168
169
170function ConvertToLocaleString(e) {
Leon Clarkee46be812010-01-19 14:06:41 +0000171 if (e == null) {
172 return '';
173 } else {
Steve Blocka7e24c12009-10-30 11:49:00 +0000174 // e_obj's toLocaleString might be overwritten, check if it is a function.
175 // Call ToString if toLocaleString is not a function.
176 // See issue 877615.
177 var e_obj = ToObject(e);
178 if (IS_FUNCTION(e_obj.toLocaleString))
Steve Blockd0582a62009-12-15 09:54:21 +0000179 return ToString(e_obj.toLocaleString());
Steve Blocka7e24c12009-10-30 11:49:00 +0000180 else
181 return ToString(e);
182 }
183}
184
185
186// This function implements the optimized splice implementation that can use
187// special array operations to handle sparse arrays in a sensible fashion.
188function SmartSlice(array, start_i, del_count, len, deleted_elements) {
189 // Move deleted elements to a new array (the return value from splice).
190 // Intervals array can contain keys and intervals. See comment in Concat.
191 var intervals = %GetArrayKeys(array, start_i + del_count);
192 var length = intervals.length;
193 for (var k = 0; k < length; k++) {
194 var key = intervals[k];
195 if (key < 0) {
196 var j = -1 - key;
197 var interval_limit = j + intervals[++k];
198 if (j < start_i) {
199 j = start_i;
200 }
201 for (; j < interval_limit; j++) {
202 // ECMA-262 15.4.4.12 line 10. The spec could also be
203 // interpreted such that %HasLocalProperty would be the
204 // appropriate test. We follow KJS in consulting the
205 // prototype.
206 var current = array[j];
207 if (!IS_UNDEFINED(current) || j in array) {
208 deleted_elements[j - start_i] = current;
209 }
210 }
211 } else {
212 if (!IS_UNDEFINED(key)) {
213 if (key >= start_i) {
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[key];
219 if (!IS_UNDEFINED(current) || key in array) {
220 deleted_elements[key - start_i] = current;
221 }
222 }
223 }
224 }
225 }
226}
227
228
229// This function implements the optimized splice implementation that can use
230// special array operations to handle sparse arrays in a sensible fashion.
231function SmartMove(array, start_i, del_count, len, num_additional_args) {
232 // Move data to new array.
233 var new_array = new $Array(len - del_count + num_additional_args);
234 var intervals = %GetArrayKeys(array, len);
235 var length = intervals.length;
236 for (var k = 0; k < length; k++) {
237 var key = intervals[k];
238 if (key < 0) {
239 var j = -1 - key;
240 var interval_limit = j + intervals[++k];
241 while (j < start_i && j < interval_limit) {
242 // The spec could also be interpreted such that
243 // %HasLocalProperty would be the appropriate test. We follow
244 // KJS in consulting the prototype.
245 var current = array[j];
246 if (!IS_UNDEFINED(current) || j in array) {
247 new_array[j] = current;
248 }
249 j++;
250 }
251 j = start_i + del_count;
252 while (j < interval_limit) {
253 // ECMA-262 15.4.4.12 lines 24 and 41. The spec could also be
254 // interpreted such that %HasLocalProperty would be the
255 // appropriate test. We follow KJS in consulting the
256 // prototype.
257 var current = array[j];
258 if (!IS_UNDEFINED(current) || j in array) {
259 new_array[j - del_count + num_additional_args] = current;
260 }
261 j++;
262 }
263 } else {
264 if (!IS_UNDEFINED(key)) {
265 if (key < start_i) {
266 // The spec could also be interpreted such that
267 // %HasLocalProperty would be the appropriate test. We follow
268 // KJS in consulting the prototype.
269 var current = array[key];
270 if (!IS_UNDEFINED(current) || key in array) {
271 new_array[key] = current;
272 }
273 } else if (key >= start_i + del_count) {
274 // ECMA-262 15.4.4.12 lines 24 and 41. The spec could also
275 // be interpreted such that %HasLocalProperty would be the
276 // appropriate test. We follow KJS in consulting the
277 // prototype.
278 var current = array[key];
279 if (!IS_UNDEFINED(current) || key in array) {
280 new_array[key - del_count + num_additional_args] = current;
281 }
282 }
283 }
284 }
285 }
286 // Move contents of new_array into this array
287 %MoveArrayContents(new_array, array);
288}
289
290
291// This is part of the old simple-minded splice. We are using it either
292// because the receiver is not an array (so we have no choice) or because we
293// know we are not deleting or moving a lot of elements.
294function SimpleSlice(array, start_i, del_count, len, deleted_elements) {
295 for (var i = 0; i < del_count; i++) {
296 var index = start_i + i;
297 // The spec could also be interpreted such that %HasLocalProperty
298 // would be the appropriate test. We follow KJS in consulting the
299 // prototype.
300 var current = array[index];
301 if (!IS_UNDEFINED(current) || index in array)
302 deleted_elements[i] = current;
303 }
304}
305
306
307function SimpleMove(array, start_i, del_count, len, num_additional_args) {
308 if (num_additional_args !== del_count) {
309 // Move the existing elements after the elements to be deleted
310 // to the right position in the resulting array.
311 if (num_additional_args > del_count) {
312 for (var i = len - del_count; i > start_i; i--) {
313 var from_index = i + del_count - 1;
314 var to_index = i + num_additional_args - 1;
315 // The spec could also be interpreted such that
316 // %HasLocalProperty would be the appropriate test. We follow
317 // KJS in consulting the prototype.
318 var current = array[from_index];
319 if (!IS_UNDEFINED(current) || from_index in array) {
320 array[to_index] = current;
321 } else {
322 delete array[to_index];
323 }
324 }
325 } else {
326 for (var i = start_i; i < len - del_count; i++) {
327 var from_index = i + del_count;
328 var to_index = i + num_additional_args;
329 // The spec could also be interpreted such that
330 // %HasLocalProperty would be the appropriate test. We follow
331 // KJS in consulting the prototype.
332 var current = array[from_index];
333 if (!IS_UNDEFINED(current) || from_index in array) {
334 array[to_index] = current;
335 } else {
336 delete array[to_index];
337 }
338 }
339 for (var i = len; i > len - del_count + num_additional_args; i--) {
340 delete array[i - 1];
341 }
342 }
343 }
344}
345
346
347// -------------------------------------------------------------------
348
349
350function ArrayToString() {
351 if (!IS_ARRAY(this)) {
352 throw new $TypeError('Array.prototype.toString is not generic');
353 }
354 return Join(this, this.length, ',', ConvertToString);
355}
356
357
358function ArrayToLocaleString() {
359 if (!IS_ARRAY(this)) {
360 throw new $TypeError('Array.prototype.toString is not generic');
361 }
362 return Join(this, this.length, ',', ConvertToLocaleString);
363}
364
365
366function ArrayJoin(separator) {
Leon Clarkee46be812010-01-19 14:06:41 +0000367 if (IS_UNDEFINED(separator)) {
368 separator = ',';
369 } else if (!IS_STRING(separator)) {
Ben Murdochb0fe1622011-05-05 13:52:32 +0100370 separator = NonStringToString(separator);
Leon Clarkee46be812010-01-19 14:06:41 +0000371 }
Shimeng (Simon) Wang8a31eba2010-12-06 19:01:33 -0800372
373 var result = %_FastAsciiArrayJoin(this, separator);
Ben Murdochb0fe1622011-05-05 13:52:32 +0100374 if (!IS_UNDEFINED(result)) return result;
Shimeng (Simon) Wang8a31eba2010-12-06 19:01:33 -0800375
Ben Murdochb0fe1622011-05-05 13:52:32 +0100376 return Join(this, TO_UINT32(this.length), separator, ConvertToString);
Steve Blocka7e24c12009-10-30 11:49:00 +0000377}
378
379
380// Removes the last element from the array and returns it. See
381// ECMA-262, section 15.4.4.6.
382function ArrayPop() {
Leon Clarkee46be812010-01-19 14:06:41 +0000383 var n = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000384 if (n == 0) {
385 this.length = n;
386 return;
387 }
388 n--;
389 var value = this[n];
390 this.length = n;
391 delete this[n];
392 return value;
393}
394
395
396// Appends the arguments to the end of the array and returns the new
397// length of the array. See ECMA-262, section 15.4.4.7.
398function ArrayPush() {
Leon Clarkee46be812010-01-19 14:06:41 +0000399 var n = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000400 var m = %_ArgumentsLength();
401 for (var i = 0; i < m; i++) {
402 this[i+n] = %_Arguments(i);
403 }
404 this.length = n + m;
405 return this.length;
406}
407
408
409function ArrayConcat(arg1) { // length == 1
410 // TODO: can we just use arguments?
411 var arg_count = %_ArgumentsLength();
412 var arrays = new $Array(1 + arg_count);
413 arrays[0] = this;
414 for (var i = 0; i < arg_count; i++) {
415 arrays[i + 1] = %_Arguments(i);
416 }
417
418 return %ArrayConcat(arrays);
419}
420
421
422// For implementing reverse() on large, sparse arrays.
423function SparseReverse(array, len) {
424 var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
425 var high_counter = keys.length - 1;
426 var low_counter = 0;
427 while (low_counter <= high_counter) {
428 var i = keys[low_counter];
429 var j = keys[high_counter];
430
431 var j_complement = len - j - 1;
432 var low, high;
433
434 if (j_complement <= i) {
435 high = j;
436 while (keys[--high_counter] == j);
437 low = j_complement;
438 }
439 if (j_complement >= i) {
440 low = i;
441 while (keys[++low_counter] == i);
442 high = len - i - 1;
443 }
444
445 var current_i = array[low];
446 if (!IS_UNDEFINED(current_i) || low in array) {
447 var current_j = array[high];
448 if (!IS_UNDEFINED(current_j) || high in array) {
449 array[low] = current_j;
450 array[high] = current_i;
451 } else {
452 array[high] = current_i;
453 delete array[low];
454 }
455 } else {
456 var current_j = array[high];
457 if (!IS_UNDEFINED(current_j) || high in array) {
458 array[low] = current_j;
459 delete array[high];
460 }
461 }
462 }
463}
464
465
466function ArrayReverse() {
Leon Clarkee46be812010-01-19 14:06:41 +0000467 var j = TO_UINT32(this.length) - 1;
Steve Blocka7e24c12009-10-30 11:49:00 +0000468
469 if (UseSparseVariant(this, j, IS_ARRAY(this))) {
470 SparseReverse(this, j+1);
471 return this;
472 }
473
474 for (var i = 0; i < j; i++, j--) {
475 var current_i = this[i];
476 if (!IS_UNDEFINED(current_i) || i in this) {
477 var current_j = this[j];
478 if (!IS_UNDEFINED(current_j) || j in this) {
479 this[i] = current_j;
480 this[j] = current_i;
481 } else {
482 this[j] = current_i;
483 delete this[i];
484 }
485 } else {
486 var current_j = this[j];
487 if (!IS_UNDEFINED(current_j) || j in this) {
488 this[i] = current_j;
489 delete this[j];
490 }
491 }
492 }
493 return this;
494}
495
496
497function ArrayShift() {
Leon Clarkee46be812010-01-19 14:06:41 +0000498 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000499
500 if (len === 0) {
501 this.length = 0;
502 return;
503 }
504
505 var first = this[0];
506
507 if (IS_ARRAY(this))
508 SmartMove(this, 0, 1, len, 0);
509 else
510 SimpleMove(this, 0, 1, len, 0);
511
512 this.length = len - 1;
513
514 return first;
515}
516
517
518function ArrayUnshift(arg1) { // length == 1
Leon Clarkee46be812010-01-19 14:06:41 +0000519 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000520 var num_arguments = %_ArgumentsLength();
521
522 if (IS_ARRAY(this))
523 SmartMove(this, 0, 0, len, num_arguments);
524 else
525 SimpleMove(this, 0, 0, len, num_arguments);
526
527 for (var i = 0; i < num_arguments; i++) {
528 this[i] = %_Arguments(i);
529 }
530
531 this.length = len + num_arguments;
532
533 return len + num_arguments;
534}
535
536
537function ArraySlice(start, end) {
Leon Clarkee46be812010-01-19 14:06:41 +0000538 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000539 var start_i = TO_INTEGER(start);
540 var end_i = len;
541
542 if (end !== void 0) end_i = TO_INTEGER(end);
543
544 if (start_i < 0) {
545 start_i += len;
546 if (start_i < 0) start_i = 0;
547 } else {
548 if (start_i > len) start_i = len;
549 }
550
551 if (end_i < 0) {
552 end_i += len;
553 if (end_i < 0) end_i = 0;
554 } else {
555 if (end_i > len) end_i = len;
556 }
557
558 var result = [];
559
560 if (end_i < start_i) return result;
561
562 if (IS_ARRAY(this)) {
563 SmartSlice(this, start_i, end_i - start_i, len, result);
564 } else {
565 SimpleSlice(this, start_i, end_i - start_i, len, result);
566 }
567
568 result.length = end_i - start_i;
569
570 return result;
571}
572
573
574function ArraySplice(start, delete_count) {
575 var num_arguments = %_ArgumentsLength();
576
Leon Clarkee46be812010-01-19 14:06:41 +0000577 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000578 var start_i = TO_INTEGER(start);
579
580 if (start_i < 0) {
581 start_i += len;
582 if (start_i < 0) start_i = 0;
583 } else {
584 if (start_i > len) start_i = len;
585 }
586
Andrei Popescu402d9372010-02-26 13:31:12 +0000587 // SpiderMonkey, TraceMonkey and JSC treat the case where no delete count is
Steve Blocka7e24c12009-10-30 11:49:00 +0000588 // given differently from when an undefined delete count is given.
589 // This does not follow ECMA-262, but we do the same for
590 // compatibility.
591 var del_count = 0;
592 if (num_arguments > 1) {
593 del_count = TO_INTEGER(delete_count);
594 if (del_count < 0) del_count = 0;
595 if (del_count > len - start_i) del_count = len - start_i;
596 } else {
597 del_count = len - start_i;
598 }
599
600 var deleted_elements = [];
601 deleted_elements.length = del_count;
602
603 // Number of elements to add.
604 var num_additional_args = 0;
605 if (num_arguments > 2) {
606 num_additional_args = num_arguments - 2;
607 }
608
609 var use_simple_splice = true;
610
611 if (IS_ARRAY(this) && num_additional_args !== del_count) {
612 // If we are only deleting/moving a few things near the end of the
613 // array then the simple version is going to be faster, because it
614 // doesn't touch most of the array.
615 var estimated_non_hole_elements = %EstimateNumberOfElements(this);
616 if (len > 20 && (estimated_non_hole_elements >> 2) < (len - start_i)) {
617 use_simple_splice = false;
618 }
619 }
620
621 if (use_simple_splice) {
622 SimpleSlice(this, start_i, del_count, len, deleted_elements);
623 SimpleMove(this, start_i, del_count, len, num_additional_args);
624 } else {
625 SmartSlice(this, start_i, del_count, len, deleted_elements);
626 SmartMove(this, start_i, del_count, len, num_additional_args);
627 }
628
629 // Insert the arguments into the resulting array in
630 // place of the deleted elements.
631 var i = start_i;
632 var arguments_index = 2;
633 var arguments_length = %_ArgumentsLength();
634 while (arguments_index < arguments_length) {
635 this[i++] = %_Arguments(arguments_index++);
636 }
637 this.length = len - del_count + num_additional_args;
638
639 // Return the deleted elements.
640 return deleted_elements;
641}
642
643
644function ArraySort(comparefn) {
645 // In-place QuickSort algorithm.
646 // For short (length <= 22) arrays, insertion sort is used for efficiency.
647
Steve Block6ded16b2010-05-10 14:33:55 +0100648 if (!IS_FUNCTION(comparefn)) {
649 comparefn = function (x, y) {
650 if (x === y) return 0;
651 if (%_IsSmi(x) && %_IsSmi(y)) {
652 return %SmiLexicographicCompare(x, y);
653 }
654 x = ToString(x);
655 y = ToString(y);
656 if (x == y) return 0;
657 else return x < y ? -1 : 1;
658 };
659 }
660 var global_receiver = %GetGlobalReceiver();
Steve Blocka7e24c12009-10-30 11:49:00 +0000661
662 function InsertionSort(a, from, to) {
663 for (var i = from + 1; i < to; i++) {
664 var element = a[i];
Steve Block6ded16b2010-05-10 14:33:55 +0100665 for (var j = i - 1; j >= from; j--) {
666 var tmp = a[j];
667 var order = %_CallFunction(global_receiver, tmp, element, comparefn);
668 if (order > 0) {
669 a[j + 1] = tmp;
670 } else {
Steve Blocka7e24c12009-10-30 11:49:00 +0000671 break;
672 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000673 }
Steve Block6ded16b2010-05-10 14:33:55 +0100674 a[j + 1] = element;
Steve Blocka7e24c12009-10-30 11:49:00 +0000675 }
676 }
677
678 function QuickSort(a, from, to) {
679 // Insertion sort is faster for short arrays.
Ben Murdochb0fe1622011-05-05 13:52:32 +0100680 if (to - from <= 10) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000681 InsertionSort(a, from, to);
682 return;
683 }
Ben Murdochb0fe1622011-05-05 13:52:32 +0100684 // Find a pivot as the median of first, last and middle element.
685 var v0 = a[from];
686 var v1 = a[to - 1];
687 var middle_index = from + ((to - from) >> 1);
688 var v2 = a[middle_index];
689 var c01 = %_CallFunction(global_receiver, v0, v1, comparefn);
690 if (c01 > 0) {
691 // v1 < v0, so swap them.
692 var tmp = v0;
693 v0 = v1;
694 v1 = tmp;
695 } // v0 <= v1.
696 var c02 = %_CallFunction(global_receiver, v0, v2, comparefn);
697 if (c02 >= 0) {
698 // v2 <= v0 <= v1.
699 var tmp = v0;
700 v0 = v2;
701 v2 = v1;
702 v1 = tmp;
703 } else {
704 // v0 <= v1 && v0 < v2
705 var c12 = %_CallFunction(global_receiver, v1, v2, comparefn);
706 if (c12 > 0) {
707 // v0 <= v2 < v1
708 var tmp = v1;
709 v1 = v2;
710 v2 = tmp;
711 }
712 }
713 // v0 <= v1 <= v2
714 a[from] = v0;
715 a[to - 1] = v2;
716 var pivot = v1;
717 var low_end = from + 1; // Upper bound of elements lower than pivot.
718 var high_start = to - 1; // Lower bound of elements greater than pivot.
719 a[middle_index] = a[low_end];
720 a[low_end] = pivot;
721
Steve Blocka7e24c12009-10-30 11:49:00 +0000722 // From low_end to i are elements equal to pivot.
723 // From i to high_start are elements that haven't been compared yet.
Ben Murdochb0fe1622011-05-05 13:52:32 +0100724 partition: for (var i = low_end + 1; i < high_start; i++) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000725 var element = a[i];
Steve Block6ded16b2010-05-10 14:33:55 +0100726 var order = %_CallFunction(global_receiver, element, pivot, comparefn);
Steve Blocka7e24c12009-10-30 11:49:00 +0000727 if (order < 0) {
Steve Block6ded16b2010-05-10 14:33:55 +0100728 %_SwapElements(a, i, low_end);
Steve Blocka7e24c12009-10-30 11:49:00 +0000729 low_end++;
730 } else if (order > 0) {
Ben Murdochb0fe1622011-05-05 13:52:32 +0100731 do {
732 high_start--;
733 if (high_start == i) break partition;
734 var top_elem = a[high_start];
735 order = %_CallFunction(global_receiver, top_elem, pivot, comparefn);
736 } while (order > 0);
Steve Block6ded16b2010-05-10 14:33:55 +0100737 %_SwapElements(a, i, high_start);
Ben Murdochb0fe1622011-05-05 13:52:32 +0100738 if (order < 0) {
739 %_SwapElements(a, i, low_end);
740 low_end++;
741 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000742 }
743 }
744 QuickSort(a, from, low_end);
745 QuickSort(a, high_start, to);
746 }
747
Ben Murdochb0fe1622011-05-05 13:52:32 +0100748 // Copy elements in the range 0..length from obj's prototype chain
749 // to obj itself, if obj has holes. Return one more than the maximal index
Steve Blocka7e24c12009-10-30 11:49:00 +0000750 // of a prototype property.
751 function CopyFromPrototype(obj, length) {
752 var max = 0;
753 for (var proto = obj.__proto__; proto; proto = proto.__proto__) {
754 var indices = %GetArrayKeys(proto, length);
755 if (indices.length > 0) {
756 if (indices[0] == -1) {
757 // It's an interval.
758 var proto_length = indices[1];
759 for (var i = 0; i < proto_length; i++) {
760 if (!obj.hasOwnProperty(i) && proto.hasOwnProperty(i)) {
761 obj[i] = proto[i];
762 if (i >= max) { max = i + 1; }
763 }
764 }
765 } else {
766 for (var i = 0; i < indices.length; i++) {
767 var index = indices[i];
768 if (!IS_UNDEFINED(index) &&
769 !obj.hasOwnProperty(index) && proto.hasOwnProperty(index)) {
770 obj[index] = proto[index];
771 if (index >= max) { max = index + 1; }
772 }
773 }
774 }
775 }
776 }
777 return max;
778 }
779
780 // Set a value of "undefined" on all indices in the range from..to
781 // where a prototype of obj has an element. I.e., shadow all prototype
782 // elements in that range.
783 function ShadowPrototypeElements(obj, from, to) {
784 for (var proto = obj.__proto__; proto; proto = proto.__proto__) {
785 var indices = %GetArrayKeys(proto, to);
786 if (indices.length > 0) {
787 if (indices[0] == -1) {
788 // It's an interval.
789 var proto_length = indices[1];
790 for (var i = from; i < proto_length; i++) {
791 if (proto.hasOwnProperty(i)) {
792 obj[i] = void 0;
793 }
794 }
795 } else {
796 for (var i = 0; i < indices.length; i++) {
797 var index = indices[i];
798 if (!IS_UNDEFINED(index) && from <= index &&
799 proto.hasOwnProperty(index)) {
800 obj[index] = void 0;
801 }
802 }
803 }
804 }
805 }
806 }
807
808 function SafeRemoveArrayHoles(obj) {
809 // Copy defined elements from the end to fill in all holes and undefineds
810 // in the beginning of the array. Write undefineds and holes at the end
811 // after loop is finished.
812 var first_undefined = 0;
813 var last_defined = length - 1;
814 var num_holes = 0;
815 while (first_undefined < last_defined) {
816 // Find first undefined element.
817 while (first_undefined < last_defined &&
818 !IS_UNDEFINED(obj[first_undefined])) {
819 first_undefined++;
820 }
821 // Maintain the invariant num_holes = the number of holes in the original
822 // array with indices <= first_undefined or > last_defined.
823 if (!obj.hasOwnProperty(first_undefined)) {
824 num_holes++;
825 }
826
827 // Find last defined element.
828 while (first_undefined < last_defined &&
829 IS_UNDEFINED(obj[last_defined])) {
830 if (!obj.hasOwnProperty(last_defined)) {
831 num_holes++;
832 }
833 last_defined--;
834 }
835 if (first_undefined < last_defined) {
836 // Fill in hole or undefined.
837 obj[first_undefined] = obj[last_defined];
838 obj[last_defined] = void 0;
839 }
840 }
841 // If there were any undefineds in the entire array, first_undefined
842 // points to one past the last defined element. Make this true if
843 // there were no undefineds, as well, so that first_undefined == number
844 // of defined elements.
845 if (!IS_UNDEFINED(obj[first_undefined])) first_undefined++;
846 // Fill in the undefineds and the holes. There may be a hole where
847 // an undefined should be and vice versa.
848 var i;
849 for (i = first_undefined; i < length - num_holes; i++) {
850 obj[i] = void 0;
851 }
852 for (i = length - num_holes; i < length; i++) {
853 // For compatability with Webkit, do not expose elements in the prototype.
854 if (i in obj.__proto__) {
855 obj[i] = void 0;
856 } else {
857 delete obj[i];
858 }
859 }
860
861 // Return the number of defined elements.
862 return first_undefined;
863 }
864
Steve Block6ded16b2010-05-10 14:33:55 +0100865 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000866 if (length < 2) return this;
867
868 var is_array = IS_ARRAY(this);
869 var max_prototype_element;
870 if (!is_array) {
871 // For compatibility with JSC, we also sort elements inherited from
872 // the prototype chain on non-Array objects.
873 // We do this by copying them to this object and sorting only
874 // local elements. This is not very efficient, but sorting with
875 // inherited elements happens very, very rarely, if at all.
876 // The specification allows "implementation dependent" behavior
877 // if an element on the prototype chain has an element that
878 // might interact with sorting.
879 max_prototype_element = CopyFromPrototype(this, length);
880 }
881
882 var num_non_undefined = %RemoveArrayHoles(this, length);
883 if (num_non_undefined == -1) {
884 // There were indexed accessors in the array. Move array holes and
885 // undefineds to the end using a Javascript function that is safe
886 // in the presence of accessors.
887 num_non_undefined = SafeRemoveArrayHoles(this);
888 }
889
890 QuickSort(this, 0, num_non_undefined);
891
892 if (!is_array && (num_non_undefined + 1 < max_prototype_element)) {
893 // For compatibility with JSC, we shadow any elements in the prototype
894 // chain that has become exposed by sort moving a hole to its position.
895 ShadowPrototypeElements(this, num_non_undefined, max_prototype_element);
896 }
897
898 return this;
899}
900
901
902// The following functions cannot be made efficient on sparse arrays while
903// preserving the semantics, since the calls to the receiver function can add
904// or delete elements from the array.
905function ArrayFilter(f, receiver) {
906 if (!IS_FUNCTION(f)) {
907 throw MakeTypeError('called_non_callable', [ f ]);
908 }
909 // Pull out the length so that modifications to the length in the
910 // loop will not affect the looping.
911 var length = this.length;
912 var result = [];
913 var result_length = 0;
914 for (var i = 0; i < length; i++) {
915 var current = this[i];
916 if (!IS_UNDEFINED(current) || i in this) {
917 if (f.call(receiver, current, i, this)) result[result_length++] = current;
918 }
919 }
920 return result;
921}
922
923
924function ArrayForEach(f, receiver) {
925 if (!IS_FUNCTION(f)) {
926 throw MakeTypeError('called_non_callable', [ f ]);
927 }
928 // Pull out the length so that modifications to the length in the
929 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000930 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000931 for (var i = 0; i < length; i++) {
932 var current = this[i];
933 if (!IS_UNDEFINED(current) || i in this) {
934 f.call(receiver, current, i, this);
935 }
936 }
937}
938
939
940// Executes the function once for each element present in the
941// array until it finds one where callback returns true.
942function ArraySome(f, receiver) {
943 if (!IS_FUNCTION(f)) {
944 throw MakeTypeError('called_non_callable', [ f ]);
945 }
946 // Pull out the length so that modifications to the length in the
947 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000948 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000949 for (var i = 0; i < length; i++) {
950 var current = this[i];
951 if (!IS_UNDEFINED(current) || i in this) {
952 if (f.call(receiver, current, i, this)) return true;
953 }
954 }
955 return false;
956}
957
958
959function ArrayEvery(f, receiver) {
960 if (!IS_FUNCTION(f)) {
961 throw MakeTypeError('called_non_callable', [ f ]);
962 }
963 // Pull out the length so that modifications to the length in the
964 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000965 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000966 for (var i = 0; i < length; i++) {
967 var current = this[i];
968 if (!IS_UNDEFINED(current) || i in this) {
969 if (!f.call(receiver, current, i, this)) return false;
970 }
971 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000972 return true;
973}
974
Steve Blocka7e24c12009-10-30 11:49:00 +0000975function ArrayMap(f, receiver) {
976 if (!IS_FUNCTION(f)) {
977 throw MakeTypeError('called_non_callable', [ f ]);
978 }
979 // Pull out the length so that modifications to the length in the
980 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000981 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000982 var result = new $Array(length);
983 for (var i = 0; i < length; i++) {
984 var current = this[i];
985 if (!IS_UNDEFINED(current) || i in this) {
986 result[i] = f.call(receiver, current, i, this);
987 }
988 }
989 return result;
990}
991
992
993function ArrayIndexOf(element, index) {
Kristian Monsen80d68ea2010-09-08 11:05:35 +0100994 var length = TO_UINT32(this.length);
995 if (length == 0) return -1;
Steve Block8defd9f2010-07-08 12:39:36 +0100996 if (IS_UNDEFINED(index)) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000997 index = 0;
998 } else {
999 index = TO_INTEGER(index);
1000 // If index is negative, index from the end of the array.
1001 if (index < 0) index = length + index;
1002 // If index is still negative, search the entire array.
1003 if (index < 0) index = 0;
1004 }
Steve Block59151502010-09-22 15:07:15 +01001005 var min = index;
1006 var max = length;
1007 if (UseSparseVariant(this, length, true)) {
1008 var intervals = %GetArrayKeys(this, length);
1009 if (intervals.length == 2 && intervals[0] < 0) {
1010 // A single interval.
1011 var intervalMin = -(intervals[0] + 1);
1012 var intervalMax = intervalMin + intervals[1];
1013 min = MAX(min, intervalMin);
1014 max = intervalMax; // Capped by length already.
1015 // Fall through to loop below.
1016 } else {
1017 if (intervals.length == 0) return -1;
1018 // Get all the keys in sorted order.
1019 var sortedKeys = GetSortedArrayKeys(this, intervals);
1020 var n = sortedKeys.length;
1021 var i = 0;
1022 while (i < n && sortedKeys[i] < index) i++;
1023 while (i < n) {
1024 var key = sortedKeys[i];
1025 if (!IS_UNDEFINED(key) && this[key] === element) return key;
1026 i++;
1027 }
1028 return -1;
1029 }
1030 }
Kristian Monsen80d68ea2010-09-08 11:05:35 +01001031 // Lookup through the array.
Steve Block6ded16b2010-05-10 14:33:55 +01001032 if (!IS_UNDEFINED(element)) {
Steve Block59151502010-09-22 15:07:15 +01001033 for (var i = min; i < max; i++) {
Steve Block6ded16b2010-05-10 14:33:55 +01001034 if (this[i] === element) return i;
1035 }
1036 return -1;
1037 }
Steve Block59151502010-09-22 15:07:15 +01001038 // Lookup through the array.
1039 for (var i = min; i < max; i++) {
Steve Block6ded16b2010-05-10 14:33:55 +01001040 if (IS_UNDEFINED(this[i]) && i in this) {
1041 return i;
Steve Blocka7e24c12009-10-30 11:49:00 +00001042 }
1043 }
1044 return -1;
1045}
1046
1047
1048function ArrayLastIndexOf(element, index) {
Kristian Monsen80d68ea2010-09-08 11:05:35 +01001049 var length = TO_UINT32(this.length);
1050 if (length == 0) return -1;
Steve Block8defd9f2010-07-08 12:39:36 +01001051 if (%_ArgumentsLength() < 2) {
Steve Blocka7e24c12009-10-30 11:49:00 +00001052 index = length - 1;
1053 } else {
1054 index = TO_INTEGER(index);
1055 // If index is negative, index from end of the array.
Steve Block59151502010-09-22 15:07:15 +01001056 if (index < 0) index += length;
Steve Blocka7e24c12009-10-30 11:49:00 +00001057 // If index is still negative, do not search the array.
Steve Block59151502010-09-22 15:07:15 +01001058 if (index < 0) return -1;
Steve Blocka7e24c12009-10-30 11:49:00 +00001059 else if (index >= length) index = length - 1;
1060 }
Steve Block59151502010-09-22 15:07:15 +01001061 var min = 0;
1062 var max = index;
1063 if (UseSparseVariant(this, length, true)) {
1064 var intervals = %GetArrayKeys(this, index + 1);
1065 if (intervals.length == 2 && intervals[0] < 0) {
1066 // A single interval.
1067 var intervalMin = -(intervals[0] + 1);
1068 var intervalMax = intervalMin + intervals[1];
1069 min = MAX(min, intervalMin);
1070 max = intervalMax; // Capped by index already.
1071 // Fall through to loop below.
1072 } else {
1073 if (intervals.length == 0) return -1;
1074 // Get all the keys in sorted order.
1075 var sortedKeys = GetSortedArrayKeys(this, intervals);
1076 var i = sortedKeys.length - 1;
1077 while (i >= 0) {
1078 var key = sortedKeys[i];
1079 if (!IS_UNDEFINED(key) && this[key] === element) return key;
1080 i--;
1081 }
1082 return -1;
1083 }
1084 }
Steve Blocka7e24c12009-10-30 11:49:00 +00001085 // Lookup through the array.
Steve Block6ded16b2010-05-10 14:33:55 +01001086 if (!IS_UNDEFINED(element)) {
Steve Block59151502010-09-22 15:07:15 +01001087 for (var i = max; i >= min; i--) {
Steve Block6ded16b2010-05-10 14:33:55 +01001088 if (this[i] === element) return i;
1089 }
1090 return -1;
1091 }
Steve Block59151502010-09-22 15:07:15 +01001092 for (var i = max; i >= min; i--) {
Steve Block6ded16b2010-05-10 14:33:55 +01001093 if (IS_UNDEFINED(this[i]) && i in this) {
1094 return i;
Steve Blocka7e24c12009-10-30 11:49:00 +00001095 }
1096 }
1097 return -1;
1098}
1099
1100
1101function ArrayReduce(callback, current) {
1102 if (!IS_FUNCTION(callback)) {
1103 throw MakeTypeError('called_non_callable', [callback]);
1104 }
1105 // Pull out the length so that modifications to the length in the
1106 // loop will not affect the looping.
1107 var length = this.length;
1108 var i = 0;
1109
1110 find_initial: if (%_ArgumentsLength() < 2) {
1111 for (; i < length; i++) {
1112 current = this[i];
1113 if (!IS_UNDEFINED(current) || i in this) {
1114 i++;
1115 break find_initial;
1116 }
1117 }
1118 throw MakeTypeError('reduce_no_initial', []);
1119 }
1120
1121 for (; i < length; i++) {
1122 var element = this[i];
1123 if (!IS_UNDEFINED(element) || i in this) {
1124 current = callback.call(null, current, element, i, this);
1125 }
1126 }
1127 return current;
1128}
1129
1130function ArrayReduceRight(callback, current) {
1131 if (!IS_FUNCTION(callback)) {
1132 throw MakeTypeError('called_non_callable', [callback]);
1133 }
1134 var i = this.length - 1;
1135
1136 find_initial: if (%_ArgumentsLength() < 2) {
1137 for (; i >= 0; i--) {
1138 current = this[i];
1139 if (!IS_UNDEFINED(current) || i in this) {
1140 i--;
1141 break find_initial;
1142 }
1143 }
1144 throw MakeTypeError('reduce_no_initial', []);
1145 }
1146
1147 for (; i >= 0; i--) {
1148 var element = this[i];
1149 if (!IS_UNDEFINED(element) || i in this) {
1150 current = callback.call(null, current, element, i, this);
1151 }
1152 }
1153 return current;
1154}
1155
Steve Block3ce2e202009-11-05 08:53:23 +00001156// ES5, 15.4.3.2
1157function ArrayIsArray(obj) {
1158 return IS_ARRAY(obj);
1159}
Steve Blocka7e24c12009-10-30 11:49:00 +00001160
Steve Blocka7e24c12009-10-30 11:49:00 +00001161
1162// -------------------------------------------------------------------
1163function SetupArray() {
1164 // Setup non-enumerable constructor property on the Array.prototype
1165 // object.
1166 %SetProperty($Array.prototype, "constructor", $Array, DONT_ENUM);
1167
Steve Block3ce2e202009-11-05 08:53:23 +00001168 // Setup non-enumerable functions on the Array object.
1169 InstallFunctions($Array, DONT_ENUM, $Array(
1170 "isArray", ArrayIsArray
1171 ));
1172
Steve Block6ded16b2010-05-10 14:33:55 +01001173 var specialFunctions = %SpecialArrayFunctions({});
1174
1175 function getFunction(name, jsBuiltin, len) {
1176 var f = jsBuiltin;
1177 if (specialFunctions.hasOwnProperty(name)) {
1178 f = specialFunctions[name];
1179 }
1180 if (!IS_UNDEFINED(len)) {
1181 %FunctionSetLength(f, len);
1182 }
1183 return f;
1184 }
1185
Steve Blocka7e24c12009-10-30 11:49:00 +00001186 // Setup non-enumerable functions of the Array.prototype object and
1187 // set their names.
Steve Blocka7e24c12009-10-30 11:49:00 +00001188 // Manipulate the length of some of the functions to meet
1189 // expectations set by ECMA-262 or Mozilla.
Steve Block6ded16b2010-05-10 14:33:55 +01001190 InstallFunctionsOnHiddenPrototype($Array.prototype, DONT_ENUM, $Array(
1191 "toString", getFunction("toString", ArrayToString),
1192 "toLocaleString", getFunction("toLocaleString", ArrayToLocaleString),
1193 "join", getFunction("join", ArrayJoin),
1194 "pop", getFunction("pop", ArrayPop),
1195 "push", getFunction("push", ArrayPush, 1),
1196 "concat", getFunction("concat", ArrayConcat, 1),
1197 "reverse", getFunction("reverse", ArrayReverse),
1198 "shift", getFunction("shift", ArrayShift),
1199 "unshift", getFunction("unshift", ArrayUnshift, 1),
1200 "slice", getFunction("slice", ArraySlice, 2),
1201 "splice", getFunction("splice", ArraySplice, 2),
1202 "sort", getFunction("sort", ArraySort),
1203 "filter", getFunction("filter", ArrayFilter, 1),
1204 "forEach", getFunction("forEach", ArrayForEach, 1),
1205 "some", getFunction("some", ArraySome, 1),
1206 "every", getFunction("every", ArrayEvery, 1),
1207 "map", getFunction("map", ArrayMap, 1),
1208 "indexOf", getFunction("indexOf", ArrayIndexOf, 1),
1209 "lastIndexOf", getFunction("lastIndexOf", ArrayLastIndexOf, 1),
1210 "reduce", getFunction("reduce", ArrayReduce, 1),
1211 "reduceRight", getFunction("reduceRight", ArrayReduceRight, 1)
1212 ));
1213
1214 %FinishArrayPrototypeSetup($Array.prototype);
Steve Blocka7e24c12009-10-30 11:49:00 +00001215}
1216
1217
1218SetupArray();