blob: 5ecf5e303be0dd05aaf2558ab61653c883d4486e [file] [log] [blame]
Steve Blocka7e24c12009-10-30 11:49:00 +00001// Copyright 2006-2008 the V8 project authors. All rights reserved.
2// 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 }
Leon Clarkee46be812010-01-19 14:06:41 +0000151 return %StringBuilderConcat(elements, elements_length, '');
Steve Blocka7e24c12009-10-30 11:49:00 +0000152 } finally {
153 // Make sure to pop the visited array no matter what happens.
154 if (is_array) visited_arrays.pop();
155 }
156}
157
158
159function ConvertToString(e) {
160 if (e == null) return '';
161 else return ToString(e);
162}
163
164
165function ConvertToLocaleString(e) {
Leon Clarkee46be812010-01-19 14:06:41 +0000166 if (e == null) {
167 return '';
168 } else {
Steve Blocka7e24c12009-10-30 11:49:00 +0000169 // e_obj's toLocaleString might be overwritten, check if it is a function.
170 // Call ToString if toLocaleString is not a function.
171 // See issue 877615.
172 var e_obj = ToObject(e);
173 if (IS_FUNCTION(e_obj.toLocaleString))
Steve Blockd0582a62009-12-15 09:54:21 +0000174 return ToString(e_obj.toLocaleString());
Steve Blocka7e24c12009-10-30 11:49:00 +0000175 else
176 return ToString(e);
177 }
178}
179
180
181// This function implements the optimized splice implementation that can use
182// special array operations to handle sparse arrays in a sensible fashion.
183function SmartSlice(array, start_i, del_count, len, deleted_elements) {
184 // Move deleted elements to a new array (the return value from splice).
185 // Intervals array can contain keys and intervals. See comment in Concat.
186 var intervals = %GetArrayKeys(array, start_i + del_count);
187 var length = intervals.length;
188 for (var k = 0; k < length; k++) {
189 var key = intervals[k];
190 if (key < 0) {
191 var j = -1 - key;
192 var interval_limit = j + intervals[++k];
193 if (j < start_i) {
194 j = start_i;
195 }
196 for (; j < interval_limit; j++) {
197 // ECMA-262 15.4.4.12 line 10. The spec could also be
198 // interpreted such that %HasLocalProperty would be the
199 // appropriate test. We follow KJS in consulting the
200 // prototype.
201 var current = array[j];
202 if (!IS_UNDEFINED(current) || j in array) {
203 deleted_elements[j - start_i] = current;
204 }
205 }
206 } else {
207 if (!IS_UNDEFINED(key)) {
208 if (key >= start_i) {
209 // ECMA-262 15.4.4.12 line 10. The spec could also be
210 // interpreted such that %HasLocalProperty would be the
211 // appropriate test. We follow KJS in consulting the
212 // prototype.
213 var current = array[key];
214 if (!IS_UNDEFINED(current) || key in array) {
215 deleted_elements[key - start_i] = current;
216 }
217 }
218 }
219 }
220 }
221}
222
223
224// This function implements the optimized splice implementation that can use
225// special array operations to handle sparse arrays in a sensible fashion.
226function SmartMove(array, start_i, del_count, len, num_additional_args) {
227 // Move data to new array.
228 var new_array = new $Array(len - del_count + num_additional_args);
229 var intervals = %GetArrayKeys(array, len);
230 var length = intervals.length;
231 for (var k = 0; k < length; k++) {
232 var key = intervals[k];
233 if (key < 0) {
234 var j = -1 - key;
235 var interval_limit = j + intervals[++k];
236 while (j < start_i && j < interval_limit) {
237 // The spec could also be interpreted such that
238 // %HasLocalProperty would be the appropriate test. We follow
239 // KJS in consulting the prototype.
240 var current = array[j];
241 if (!IS_UNDEFINED(current) || j in array) {
242 new_array[j] = current;
243 }
244 j++;
245 }
246 j = start_i + del_count;
247 while (j < interval_limit) {
248 // ECMA-262 15.4.4.12 lines 24 and 41. The spec could also be
249 // interpreted such that %HasLocalProperty would be the
250 // appropriate test. We follow KJS in consulting the
251 // prototype.
252 var current = array[j];
253 if (!IS_UNDEFINED(current) || j in array) {
254 new_array[j - del_count + num_additional_args] = current;
255 }
256 j++;
257 }
258 } else {
259 if (!IS_UNDEFINED(key)) {
260 if (key < start_i) {
261 // The spec could also be interpreted such that
262 // %HasLocalProperty would be the appropriate test. We follow
263 // KJS in consulting the prototype.
264 var current = array[key];
265 if (!IS_UNDEFINED(current) || key in array) {
266 new_array[key] = current;
267 }
268 } else if (key >= start_i + del_count) {
269 // ECMA-262 15.4.4.12 lines 24 and 41. The spec could also
270 // be interpreted such that %HasLocalProperty would be the
271 // appropriate test. We follow KJS in consulting the
272 // prototype.
273 var current = array[key];
274 if (!IS_UNDEFINED(current) || key in array) {
275 new_array[key - del_count + num_additional_args] = current;
276 }
277 }
278 }
279 }
280 }
281 // Move contents of new_array into this array
282 %MoveArrayContents(new_array, array);
283}
284
285
286// This is part of the old simple-minded splice. We are using it either
287// because the receiver is not an array (so we have no choice) or because we
288// know we are not deleting or moving a lot of elements.
289function SimpleSlice(array, start_i, del_count, len, deleted_elements) {
290 for (var i = 0; i < del_count; i++) {
291 var index = start_i + i;
292 // The spec could also be interpreted such that %HasLocalProperty
293 // would be the appropriate test. We follow KJS in consulting the
294 // prototype.
295 var current = array[index];
296 if (!IS_UNDEFINED(current) || index in array)
297 deleted_elements[i] = current;
298 }
299}
300
301
302function SimpleMove(array, start_i, del_count, len, num_additional_args) {
303 if (num_additional_args !== del_count) {
304 // Move the existing elements after the elements to be deleted
305 // to the right position in the resulting array.
306 if (num_additional_args > del_count) {
307 for (var i = len - del_count; i > start_i; i--) {
308 var from_index = i + del_count - 1;
309 var to_index = i + num_additional_args - 1;
310 // The spec could also be interpreted such that
311 // %HasLocalProperty would be the appropriate test. We follow
312 // KJS in consulting the prototype.
313 var current = array[from_index];
314 if (!IS_UNDEFINED(current) || from_index in array) {
315 array[to_index] = current;
316 } else {
317 delete array[to_index];
318 }
319 }
320 } else {
321 for (var i = start_i; i < len - del_count; i++) {
322 var from_index = i + del_count;
323 var to_index = i + num_additional_args;
324 // The spec could also be interpreted such that
325 // %HasLocalProperty would be the appropriate test. We follow
326 // KJS in consulting the prototype.
327 var current = array[from_index];
328 if (!IS_UNDEFINED(current) || from_index in array) {
329 array[to_index] = current;
330 } else {
331 delete array[to_index];
332 }
333 }
334 for (var i = len; i > len - del_count + num_additional_args; i--) {
335 delete array[i - 1];
336 }
337 }
338 }
339}
340
341
342// -------------------------------------------------------------------
343
344
345function ArrayToString() {
346 if (!IS_ARRAY(this)) {
347 throw new $TypeError('Array.prototype.toString is not generic');
348 }
349 return Join(this, this.length, ',', ConvertToString);
350}
351
352
353function ArrayToLocaleString() {
354 if (!IS_ARRAY(this)) {
355 throw new $TypeError('Array.prototype.toString is not generic');
356 }
357 return Join(this, this.length, ',', ConvertToLocaleString);
358}
359
360
361function ArrayJoin(separator) {
Leon Clarkee46be812010-01-19 14:06:41 +0000362 if (IS_UNDEFINED(separator)) {
363 separator = ',';
364 } else if (!IS_STRING(separator)) {
365 separator = ToString(separator);
366 }
Shimeng (Simon) Wang8a31eba2010-12-06 19:01:33 -0800367
368 var result = %_FastAsciiArrayJoin(this, separator);
369 if (typeof result != "undefined") return result;
370
Leon Clarkee46be812010-01-19 14:06:41 +0000371 var length = TO_UINT32(this.length);
372 return Join(this, length, separator, ConvertToString);
Steve Blocka7e24c12009-10-30 11:49:00 +0000373}
374
375
376// Removes the last element from the array and returns it. See
377// ECMA-262, section 15.4.4.6.
378function ArrayPop() {
Leon Clarkee46be812010-01-19 14:06:41 +0000379 var n = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000380 if (n == 0) {
381 this.length = n;
382 return;
383 }
384 n--;
385 var value = this[n];
386 this.length = n;
387 delete this[n];
388 return value;
389}
390
391
392// Appends the arguments to the end of the array and returns the new
393// length of the array. See ECMA-262, section 15.4.4.7.
394function ArrayPush() {
Leon Clarkee46be812010-01-19 14:06:41 +0000395 var n = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000396 var m = %_ArgumentsLength();
397 for (var i = 0; i < m; i++) {
398 this[i+n] = %_Arguments(i);
399 }
400 this.length = n + m;
401 return this.length;
402}
403
404
405function ArrayConcat(arg1) { // length == 1
406 // TODO: can we just use arguments?
407 var arg_count = %_ArgumentsLength();
408 var arrays = new $Array(1 + arg_count);
409 arrays[0] = this;
410 for (var i = 0; i < arg_count; i++) {
411 arrays[i + 1] = %_Arguments(i);
412 }
413
414 return %ArrayConcat(arrays);
415}
416
417
418// For implementing reverse() on large, sparse arrays.
419function SparseReverse(array, len) {
420 var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
421 var high_counter = keys.length - 1;
422 var low_counter = 0;
423 while (low_counter <= high_counter) {
424 var i = keys[low_counter];
425 var j = keys[high_counter];
426
427 var j_complement = len - j - 1;
428 var low, high;
429
430 if (j_complement <= i) {
431 high = j;
432 while (keys[--high_counter] == j);
433 low = j_complement;
434 }
435 if (j_complement >= i) {
436 low = i;
437 while (keys[++low_counter] == i);
438 high = len - i - 1;
439 }
440
441 var current_i = array[low];
442 if (!IS_UNDEFINED(current_i) || low in array) {
443 var current_j = array[high];
444 if (!IS_UNDEFINED(current_j) || high in array) {
445 array[low] = current_j;
446 array[high] = current_i;
447 } else {
448 array[high] = current_i;
449 delete array[low];
450 }
451 } else {
452 var current_j = array[high];
453 if (!IS_UNDEFINED(current_j) || high in array) {
454 array[low] = current_j;
455 delete array[high];
456 }
457 }
458 }
459}
460
461
462function ArrayReverse() {
Leon Clarkee46be812010-01-19 14:06:41 +0000463 var j = TO_UINT32(this.length) - 1;
Steve Blocka7e24c12009-10-30 11:49:00 +0000464
465 if (UseSparseVariant(this, j, IS_ARRAY(this))) {
466 SparseReverse(this, j+1);
467 return this;
468 }
469
470 for (var i = 0; i < j; i++, j--) {
471 var current_i = this[i];
472 if (!IS_UNDEFINED(current_i) || i in this) {
473 var current_j = this[j];
474 if (!IS_UNDEFINED(current_j) || j in this) {
475 this[i] = current_j;
476 this[j] = current_i;
477 } else {
478 this[j] = current_i;
479 delete this[i];
480 }
481 } else {
482 var current_j = this[j];
483 if (!IS_UNDEFINED(current_j) || j in this) {
484 this[i] = current_j;
485 delete this[j];
486 }
487 }
488 }
489 return this;
490}
491
492
493function ArrayShift() {
Leon Clarkee46be812010-01-19 14:06:41 +0000494 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000495
496 if (len === 0) {
497 this.length = 0;
498 return;
499 }
500
501 var first = this[0];
502
503 if (IS_ARRAY(this))
504 SmartMove(this, 0, 1, len, 0);
505 else
506 SimpleMove(this, 0, 1, len, 0);
507
508 this.length = len - 1;
509
510 return first;
511}
512
513
514function ArrayUnshift(arg1) { // length == 1
Leon Clarkee46be812010-01-19 14:06:41 +0000515 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000516 var num_arguments = %_ArgumentsLength();
517
518 if (IS_ARRAY(this))
519 SmartMove(this, 0, 0, len, num_arguments);
520 else
521 SimpleMove(this, 0, 0, len, num_arguments);
522
523 for (var i = 0; i < num_arguments; i++) {
524 this[i] = %_Arguments(i);
525 }
526
527 this.length = len + num_arguments;
528
529 return len + num_arguments;
530}
531
532
533function ArraySlice(start, end) {
Leon Clarkee46be812010-01-19 14:06:41 +0000534 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000535 var start_i = TO_INTEGER(start);
536 var end_i = len;
537
538 if (end !== void 0) end_i = TO_INTEGER(end);
539
540 if (start_i < 0) {
541 start_i += len;
542 if (start_i < 0) start_i = 0;
543 } else {
544 if (start_i > len) start_i = len;
545 }
546
547 if (end_i < 0) {
548 end_i += len;
549 if (end_i < 0) end_i = 0;
550 } else {
551 if (end_i > len) end_i = len;
552 }
553
554 var result = [];
555
556 if (end_i < start_i) return result;
557
558 if (IS_ARRAY(this)) {
559 SmartSlice(this, start_i, end_i - start_i, len, result);
560 } else {
561 SimpleSlice(this, start_i, end_i - start_i, len, result);
562 }
563
564 result.length = end_i - start_i;
565
566 return result;
567}
568
569
570function ArraySplice(start, delete_count) {
571 var num_arguments = %_ArgumentsLength();
572
Leon Clarkee46be812010-01-19 14:06:41 +0000573 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000574 var start_i = TO_INTEGER(start);
575
576 if (start_i < 0) {
577 start_i += len;
578 if (start_i < 0) start_i = 0;
579 } else {
580 if (start_i > len) start_i = len;
581 }
582
Andrei Popescu402d9372010-02-26 13:31:12 +0000583 // SpiderMonkey, TraceMonkey and JSC treat the case where no delete count is
Steve Blocka7e24c12009-10-30 11:49:00 +0000584 // given differently from when an undefined delete count is given.
585 // This does not follow ECMA-262, but we do the same for
586 // compatibility.
587 var del_count = 0;
588 if (num_arguments > 1) {
589 del_count = TO_INTEGER(delete_count);
590 if (del_count < 0) del_count = 0;
591 if (del_count > len - start_i) del_count = len - start_i;
592 } else {
593 del_count = len - start_i;
594 }
595
596 var deleted_elements = [];
597 deleted_elements.length = del_count;
598
599 // Number of elements to add.
600 var num_additional_args = 0;
601 if (num_arguments > 2) {
602 num_additional_args = num_arguments - 2;
603 }
604
605 var use_simple_splice = true;
606
607 if (IS_ARRAY(this) && num_additional_args !== del_count) {
608 // If we are only deleting/moving a few things near the end of the
609 // array then the simple version is going to be faster, because it
610 // doesn't touch most of the array.
611 var estimated_non_hole_elements = %EstimateNumberOfElements(this);
612 if (len > 20 && (estimated_non_hole_elements >> 2) < (len - start_i)) {
613 use_simple_splice = false;
614 }
615 }
616
617 if (use_simple_splice) {
618 SimpleSlice(this, start_i, del_count, len, deleted_elements);
619 SimpleMove(this, start_i, del_count, len, num_additional_args);
620 } else {
621 SmartSlice(this, start_i, del_count, len, deleted_elements);
622 SmartMove(this, start_i, del_count, len, num_additional_args);
623 }
624
625 // Insert the arguments into the resulting array in
626 // place of the deleted elements.
627 var i = start_i;
628 var arguments_index = 2;
629 var arguments_length = %_ArgumentsLength();
630 while (arguments_index < arguments_length) {
631 this[i++] = %_Arguments(arguments_index++);
632 }
633 this.length = len - del_count + num_additional_args;
634
635 // Return the deleted elements.
636 return deleted_elements;
637}
638
639
640function ArraySort(comparefn) {
641 // In-place QuickSort algorithm.
642 // For short (length <= 22) arrays, insertion sort is used for efficiency.
643
Steve Block6ded16b2010-05-10 14:33:55 +0100644 if (!IS_FUNCTION(comparefn)) {
645 comparefn = function (x, y) {
646 if (x === y) return 0;
647 if (%_IsSmi(x) && %_IsSmi(y)) {
648 return %SmiLexicographicCompare(x, y);
649 }
650 x = ToString(x);
651 y = ToString(y);
652 if (x == y) return 0;
653 else return x < y ? -1 : 1;
654 };
655 }
656 var global_receiver = %GetGlobalReceiver();
Steve Blocka7e24c12009-10-30 11:49:00 +0000657
658 function InsertionSort(a, from, to) {
659 for (var i = from + 1; i < to; i++) {
660 var element = a[i];
Steve Block6ded16b2010-05-10 14:33:55 +0100661 for (var j = i - 1; j >= from; j--) {
662 var tmp = a[j];
663 var order = %_CallFunction(global_receiver, tmp, element, comparefn);
664 if (order > 0) {
665 a[j + 1] = tmp;
666 } else {
Steve Blocka7e24c12009-10-30 11:49:00 +0000667 break;
668 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000669 }
Steve Block6ded16b2010-05-10 14:33:55 +0100670 a[j + 1] = element;
Steve Blocka7e24c12009-10-30 11:49:00 +0000671 }
672 }
673
674 function QuickSort(a, from, to) {
675 // Insertion sort is faster for short arrays.
676 if (to - from <= 22) {
677 InsertionSort(a, from, to);
678 return;
679 }
680 var pivot_index = $floor($random() * (to - from)) + from;
681 var pivot = a[pivot_index];
Steve Blocka7e24c12009-10-30 11:49:00 +0000682 // Issue 95: Keep the pivot element out of the comparisons to avoid
683 // infinite recursion if comparefn(pivot, pivot) != 0.
Steve Block6ded16b2010-05-10 14:33:55 +0100684 %_SwapElements(a, from, pivot_index);
Steve Blocka7e24c12009-10-30 11:49:00 +0000685 var low_end = from; // Upper bound of the elements lower than pivot.
686 var high_start = to; // Lower bound of the elements greater than pivot.
687 // From low_end to i are elements equal to pivot.
688 // From i to high_start are elements that haven't been compared yet.
689 for (var i = from + 1; i < high_start; ) {
690 var element = a[i];
Steve Block6ded16b2010-05-10 14:33:55 +0100691 var order = %_CallFunction(global_receiver, element, pivot, comparefn);
Steve Blocka7e24c12009-10-30 11:49:00 +0000692 if (order < 0) {
Steve Block6ded16b2010-05-10 14:33:55 +0100693 %_SwapElements(a, i, low_end);
Steve Blocka7e24c12009-10-30 11:49:00 +0000694 i++;
695 low_end++;
696 } else if (order > 0) {
697 high_start--;
Steve Block6ded16b2010-05-10 14:33:55 +0100698 %_SwapElements(a, i, high_start);
Steve Blocka7e24c12009-10-30 11:49:00 +0000699 } else { // order == 0
700 i++;
701 }
702 }
703 QuickSort(a, from, low_end);
704 QuickSort(a, high_start, to);
705 }
706
Steve Blocka7e24c12009-10-30 11:49:00 +0000707 // Copies elements in the range 0..length from obj's prototype chain
708 // to obj itself, if obj has holes. Returns one more than the maximal index
709 // of a prototype property.
710 function CopyFromPrototype(obj, length) {
711 var max = 0;
712 for (var proto = obj.__proto__; proto; proto = proto.__proto__) {
713 var indices = %GetArrayKeys(proto, length);
714 if (indices.length > 0) {
715 if (indices[0] == -1) {
716 // It's an interval.
717 var proto_length = indices[1];
718 for (var i = 0; i < proto_length; i++) {
719 if (!obj.hasOwnProperty(i) && proto.hasOwnProperty(i)) {
720 obj[i] = proto[i];
721 if (i >= max) { max = i + 1; }
722 }
723 }
724 } else {
725 for (var i = 0; i < indices.length; i++) {
726 var index = indices[i];
727 if (!IS_UNDEFINED(index) &&
728 !obj.hasOwnProperty(index) && proto.hasOwnProperty(index)) {
729 obj[index] = proto[index];
730 if (index >= max) { max = index + 1; }
731 }
732 }
733 }
734 }
735 }
736 return max;
737 }
738
739 // Set a value of "undefined" on all indices in the range from..to
740 // where a prototype of obj has an element. I.e., shadow all prototype
741 // elements in that range.
742 function ShadowPrototypeElements(obj, from, to) {
743 for (var proto = obj.__proto__; proto; proto = proto.__proto__) {
744 var indices = %GetArrayKeys(proto, to);
745 if (indices.length > 0) {
746 if (indices[0] == -1) {
747 // It's an interval.
748 var proto_length = indices[1];
749 for (var i = from; i < proto_length; i++) {
750 if (proto.hasOwnProperty(i)) {
751 obj[i] = void 0;
752 }
753 }
754 } else {
755 for (var i = 0; i < indices.length; i++) {
756 var index = indices[i];
757 if (!IS_UNDEFINED(index) && from <= index &&
758 proto.hasOwnProperty(index)) {
759 obj[index] = void 0;
760 }
761 }
762 }
763 }
764 }
765 }
766
767 function SafeRemoveArrayHoles(obj) {
768 // Copy defined elements from the end to fill in all holes and undefineds
769 // in the beginning of the array. Write undefineds and holes at the end
770 // after loop is finished.
771 var first_undefined = 0;
772 var last_defined = length - 1;
773 var num_holes = 0;
774 while (first_undefined < last_defined) {
775 // Find first undefined element.
776 while (first_undefined < last_defined &&
777 !IS_UNDEFINED(obj[first_undefined])) {
778 first_undefined++;
779 }
780 // Maintain the invariant num_holes = the number of holes in the original
781 // array with indices <= first_undefined or > last_defined.
782 if (!obj.hasOwnProperty(first_undefined)) {
783 num_holes++;
784 }
785
786 // Find last defined element.
787 while (first_undefined < last_defined &&
788 IS_UNDEFINED(obj[last_defined])) {
789 if (!obj.hasOwnProperty(last_defined)) {
790 num_holes++;
791 }
792 last_defined--;
793 }
794 if (first_undefined < last_defined) {
795 // Fill in hole or undefined.
796 obj[first_undefined] = obj[last_defined];
797 obj[last_defined] = void 0;
798 }
799 }
800 // If there were any undefineds in the entire array, first_undefined
801 // points to one past the last defined element. Make this true if
802 // there were no undefineds, as well, so that first_undefined == number
803 // of defined elements.
804 if (!IS_UNDEFINED(obj[first_undefined])) first_undefined++;
805 // Fill in the undefineds and the holes. There may be a hole where
806 // an undefined should be and vice versa.
807 var i;
808 for (i = first_undefined; i < length - num_holes; i++) {
809 obj[i] = void 0;
810 }
811 for (i = length - num_holes; i < length; i++) {
812 // For compatability with Webkit, do not expose elements in the prototype.
813 if (i in obj.__proto__) {
814 obj[i] = void 0;
815 } else {
816 delete obj[i];
817 }
818 }
819
820 // Return the number of defined elements.
821 return first_undefined;
822 }
823
Steve Block6ded16b2010-05-10 14:33:55 +0100824 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000825 if (length < 2) return this;
826
827 var is_array = IS_ARRAY(this);
828 var max_prototype_element;
829 if (!is_array) {
830 // For compatibility with JSC, we also sort elements inherited from
831 // the prototype chain on non-Array objects.
832 // We do this by copying them to this object and sorting only
833 // local elements. This is not very efficient, but sorting with
834 // inherited elements happens very, very rarely, if at all.
835 // The specification allows "implementation dependent" behavior
836 // if an element on the prototype chain has an element that
837 // might interact with sorting.
838 max_prototype_element = CopyFromPrototype(this, length);
839 }
840
841 var num_non_undefined = %RemoveArrayHoles(this, length);
842 if (num_non_undefined == -1) {
843 // There were indexed accessors in the array. Move array holes and
844 // undefineds to the end using a Javascript function that is safe
845 // in the presence of accessors.
846 num_non_undefined = SafeRemoveArrayHoles(this);
847 }
848
849 QuickSort(this, 0, num_non_undefined);
850
851 if (!is_array && (num_non_undefined + 1 < max_prototype_element)) {
852 // For compatibility with JSC, we shadow any elements in the prototype
853 // chain that has become exposed by sort moving a hole to its position.
854 ShadowPrototypeElements(this, num_non_undefined, max_prototype_element);
855 }
856
857 return this;
858}
859
860
861// The following functions cannot be made efficient on sparse arrays while
862// preserving the semantics, since the calls to the receiver function can add
863// or delete elements from the array.
864function ArrayFilter(f, receiver) {
865 if (!IS_FUNCTION(f)) {
866 throw MakeTypeError('called_non_callable', [ f ]);
867 }
868 // Pull out the length so that modifications to the length in the
869 // loop will not affect the looping.
870 var length = this.length;
871 var result = [];
872 var result_length = 0;
873 for (var i = 0; i < length; i++) {
874 var current = this[i];
875 if (!IS_UNDEFINED(current) || i in this) {
876 if (f.call(receiver, current, i, this)) result[result_length++] = current;
877 }
878 }
879 return result;
880}
881
882
883function ArrayForEach(f, receiver) {
884 if (!IS_FUNCTION(f)) {
885 throw MakeTypeError('called_non_callable', [ f ]);
886 }
887 // Pull out the length so that modifications to the length in the
888 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000889 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000890 for (var i = 0; i < length; i++) {
891 var current = this[i];
892 if (!IS_UNDEFINED(current) || i in this) {
893 f.call(receiver, current, i, this);
894 }
895 }
896}
897
898
899// Executes the function once for each element present in the
900// array until it finds one where callback returns true.
901function ArraySome(f, receiver) {
902 if (!IS_FUNCTION(f)) {
903 throw MakeTypeError('called_non_callable', [ f ]);
904 }
905 // Pull out the length so that modifications to the length in the
906 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000907 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000908 for (var i = 0; i < length; i++) {
909 var current = this[i];
910 if (!IS_UNDEFINED(current) || i in this) {
911 if (f.call(receiver, current, i, this)) return true;
912 }
913 }
914 return false;
915}
916
917
918function ArrayEvery(f, receiver) {
919 if (!IS_FUNCTION(f)) {
920 throw MakeTypeError('called_non_callable', [ f ]);
921 }
922 // Pull out the length so that modifications to the length in the
923 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000924 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000925 for (var i = 0; i < length; i++) {
926 var current = this[i];
927 if (!IS_UNDEFINED(current) || i in this) {
928 if (!f.call(receiver, current, i, this)) return false;
929 }
930 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000931 return true;
932}
933
Steve Blocka7e24c12009-10-30 11:49:00 +0000934function ArrayMap(f, receiver) {
935 if (!IS_FUNCTION(f)) {
936 throw MakeTypeError('called_non_callable', [ f ]);
937 }
938 // Pull out the length so that modifications to the length in the
939 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000940 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000941 var result = new $Array(length);
942 for (var i = 0; i < length; i++) {
943 var current = this[i];
944 if (!IS_UNDEFINED(current) || i in this) {
945 result[i] = f.call(receiver, current, i, this);
946 }
947 }
948 return result;
949}
950
951
952function ArrayIndexOf(element, index) {
Kristian Monsen80d68ea2010-09-08 11:05:35 +0100953 var length = TO_UINT32(this.length);
954 if (length == 0) return -1;
Steve Block8defd9f2010-07-08 12:39:36 +0100955 if (IS_UNDEFINED(index)) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000956 index = 0;
957 } else {
958 index = TO_INTEGER(index);
959 // If index is negative, index from the end of the array.
960 if (index < 0) index = length + index;
961 // If index is still negative, search the entire array.
962 if (index < 0) index = 0;
963 }
Steve Block59151502010-09-22 15:07:15 +0100964 var min = index;
965 var max = length;
966 if (UseSparseVariant(this, length, true)) {
967 var intervals = %GetArrayKeys(this, length);
968 if (intervals.length == 2 && intervals[0] < 0) {
969 // A single interval.
970 var intervalMin = -(intervals[0] + 1);
971 var intervalMax = intervalMin + intervals[1];
972 min = MAX(min, intervalMin);
973 max = intervalMax; // Capped by length already.
974 // Fall through to loop below.
975 } else {
976 if (intervals.length == 0) return -1;
977 // Get all the keys in sorted order.
978 var sortedKeys = GetSortedArrayKeys(this, intervals);
979 var n = sortedKeys.length;
980 var i = 0;
981 while (i < n && sortedKeys[i] < index) i++;
982 while (i < n) {
983 var key = sortedKeys[i];
984 if (!IS_UNDEFINED(key) && this[key] === element) return key;
985 i++;
986 }
987 return -1;
988 }
989 }
Kristian Monsen80d68ea2010-09-08 11:05:35 +0100990 // Lookup through the array.
Steve Block6ded16b2010-05-10 14:33:55 +0100991 if (!IS_UNDEFINED(element)) {
Steve Block59151502010-09-22 15:07:15 +0100992 for (var i = min; i < max; i++) {
Steve Block6ded16b2010-05-10 14:33:55 +0100993 if (this[i] === element) return i;
994 }
995 return -1;
996 }
Steve Block59151502010-09-22 15:07:15 +0100997 // Lookup through the array.
998 for (var i = min; i < max; i++) {
Steve Block6ded16b2010-05-10 14:33:55 +0100999 if (IS_UNDEFINED(this[i]) && i in this) {
1000 return i;
Steve Blocka7e24c12009-10-30 11:49:00 +00001001 }
1002 }
1003 return -1;
1004}
1005
1006
1007function ArrayLastIndexOf(element, index) {
Kristian Monsen80d68ea2010-09-08 11:05:35 +01001008 var length = TO_UINT32(this.length);
1009 if (length == 0) return -1;
Steve Block8defd9f2010-07-08 12:39:36 +01001010 if (%_ArgumentsLength() < 2) {
Steve Blocka7e24c12009-10-30 11:49:00 +00001011 index = length - 1;
1012 } else {
1013 index = TO_INTEGER(index);
1014 // If index is negative, index from end of the array.
Steve Block59151502010-09-22 15:07:15 +01001015 if (index < 0) index += length;
Steve Blocka7e24c12009-10-30 11:49:00 +00001016 // If index is still negative, do not search the array.
Steve Block59151502010-09-22 15:07:15 +01001017 if (index < 0) return -1;
Steve Blocka7e24c12009-10-30 11:49:00 +00001018 else if (index >= length) index = length - 1;
1019 }
Steve Block59151502010-09-22 15:07:15 +01001020 var min = 0;
1021 var max = index;
1022 if (UseSparseVariant(this, length, true)) {
1023 var intervals = %GetArrayKeys(this, index + 1);
1024 if (intervals.length == 2 && intervals[0] < 0) {
1025 // A single interval.
1026 var intervalMin = -(intervals[0] + 1);
1027 var intervalMax = intervalMin + intervals[1];
1028 min = MAX(min, intervalMin);
1029 max = intervalMax; // Capped by index already.
1030 // Fall through to loop below.
1031 } else {
1032 if (intervals.length == 0) return -1;
1033 // Get all the keys in sorted order.
1034 var sortedKeys = GetSortedArrayKeys(this, intervals);
1035 var i = sortedKeys.length - 1;
1036 while (i >= 0) {
1037 var key = sortedKeys[i];
1038 if (!IS_UNDEFINED(key) && this[key] === element) return key;
1039 i--;
1040 }
1041 return -1;
1042 }
1043 }
Steve Blocka7e24c12009-10-30 11:49:00 +00001044 // Lookup through the array.
Steve Block6ded16b2010-05-10 14:33:55 +01001045 if (!IS_UNDEFINED(element)) {
Steve Block59151502010-09-22 15:07:15 +01001046 for (var i = max; i >= min; i--) {
Steve Block6ded16b2010-05-10 14:33:55 +01001047 if (this[i] === element) return i;
1048 }
1049 return -1;
1050 }
Steve Block59151502010-09-22 15:07:15 +01001051 for (var i = max; i >= min; 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 ArrayReduce(callback, current) {
1061 if (!IS_FUNCTION(callback)) {
1062 throw MakeTypeError('called_non_callable', [callback]);
1063 }
1064 // Pull out the length so that modifications to the length in the
1065 // loop will not affect the looping.
1066 var length = this.length;
1067 var i = 0;
1068
1069 find_initial: if (%_ArgumentsLength() < 2) {
1070 for (; i < length; i++) {
1071 current = this[i];
1072 if (!IS_UNDEFINED(current) || i in this) {
1073 i++;
1074 break find_initial;
1075 }
1076 }
1077 throw MakeTypeError('reduce_no_initial', []);
1078 }
1079
1080 for (; i < length; i++) {
1081 var element = this[i];
1082 if (!IS_UNDEFINED(element) || i in this) {
1083 current = callback.call(null, current, element, i, this);
1084 }
1085 }
1086 return current;
1087}
1088
1089function ArrayReduceRight(callback, current) {
1090 if (!IS_FUNCTION(callback)) {
1091 throw MakeTypeError('called_non_callable', [callback]);
1092 }
1093 var i = this.length - 1;
1094
1095 find_initial: if (%_ArgumentsLength() < 2) {
1096 for (; i >= 0; i--) {
1097 current = this[i];
1098 if (!IS_UNDEFINED(current) || i in this) {
1099 i--;
1100 break find_initial;
1101 }
1102 }
1103 throw MakeTypeError('reduce_no_initial', []);
1104 }
1105
1106 for (; i >= 0; i--) {
1107 var element = this[i];
1108 if (!IS_UNDEFINED(element) || i in this) {
1109 current = callback.call(null, current, element, i, this);
1110 }
1111 }
1112 return current;
1113}
1114
Steve Block3ce2e202009-11-05 08:53:23 +00001115// ES5, 15.4.3.2
1116function ArrayIsArray(obj) {
1117 return IS_ARRAY(obj);
1118}
Steve Blocka7e24c12009-10-30 11:49:00 +00001119
Steve Blocka7e24c12009-10-30 11:49:00 +00001120
1121// -------------------------------------------------------------------
1122function SetupArray() {
1123 // Setup non-enumerable constructor property on the Array.prototype
1124 // object.
1125 %SetProperty($Array.prototype, "constructor", $Array, DONT_ENUM);
1126
Steve Block3ce2e202009-11-05 08:53:23 +00001127 // Setup non-enumerable functions on the Array object.
1128 InstallFunctions($Array, DONT_ENUM, $Array(
1129 "isArray", ArrayIsArray
1130 ));
1131
Steve Block6ded16b2010-05-10 14:33:55 +01001132 var specialFunctions = %SpecialArrayFunctions({});
1133
1134 function getFunction(name, jsBuiltin, len) {
1135 var f = jsBuiltin;
1136 if (specialFunctions.hasOwnProperty(name)) {
1137 f = specialFunctions[name];
1138 }
1139 if (!IS_UNDEFINED(len)) {
1140 %FunctionSetLength(f, len);
1141 }
1142 return f;
1143 }
1144
Steve Blocka7e24c12009-10-30 11:49:00 +00001145 // Setup non-enumerable functions of the Array.prototype object and
1146 // set their names.
Steve Blocka7e24c12009-10-30 11:49:00 +00001147 // Manipulate the length of some of the functions to meet
1148 // expectations set by ECMA-262 or Mozilla.
Steve Block6ded16b2010-05-10 14:33:55 +01001149 InstallFunctionsOnHiddenPrototype($Array.prototype, DONT_ENUM, $Array(
1150 "toString", getFunction("toString", ArrayToString),
1151 "toLocaleString", getFunction("toLocaleString", ArrayToLocaleString),
1152 "join", getFunction("join", ArrayJoin),
1153 "pop", getFunction("pop", ArrayPop),
1154 "push", getFunction("push", ArrayPush, 1),
1155 "concat", getFunction("concat", ArrayConcat, 1),
1156 "reverse", getFunction("reverse", ArrayReverse),
1157 "shift", getFunction("shift", ArrayShift),
1158 "unshift", getFunction("unshift", ArrayUnshift, 1),
1159 "slice", getFunction("slice", ArraySlice, 2),
1160 "splice", getFunction("splice", ArraySplice, 2),
1161 "sort", getFunction("sort", ArraySort),
1162 "filter", getFunction("filter", ArrayFilter, 1),
1163 "forEach", getFunction("forEach", ArrayForEach, 1),
1164 "some", getFunction("some", ArraySome, 1),
1165 "every", getFunction("every", ArrayEvery, 1),
1166 "map", getFunction("map", ArrayMap, 1),
1167 "indexOf", getFunction("indexOf", ArrayIndexOf, 1),
1168 "lastIndexOf", getFunction("lastIndexOf", ArrayLastIndexOf, 1),
1169 "reduce", getFunction("reduce", ArrayReduce, 1),
1170 "reduceRight", getFunction("reduceRight", ArrayReduceRight, 1)
1171 ));
1172
1173 %FinishArrayPrototypeSetup($Array.prototype);
Steve Blocka7e24c12009-10-30 11:49:00 +00001174}
1175
1176
1177SetupArray();