blob: 20d884ee6c9d60c85f2c4ad2a29bbe148995b0a5 [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));
73 var builder = new StringBuilder();
74 var last_key = -1;
75 var keys_length = keys.length;
76 for (var i = 0; i < keys_length; i++) {
77 var key = keys[i];
78 if (key != last_key) {
79 var e = array[key];
Steve Blockd0582a62009-12-15 09:54:21 +000080 if (typeof(e) !== 'string') e = convert(e);
81 builder.add(e);
Steve Blocka7e24c12009-10-30 11:49:00 +000082 last_key = key;
83 }
84 }
85 return builder.generate();
86}
87
88
89function UseSparseVariant(object, length, is_array) {
90 return is_array &&
91 length > 1000 &&
92 (!%_IsSmi(length) ||
93 %EstimateNumberOfElements(object) < (length >> 2));
94}
95
96
97function Join(array, length, separator, convert) {
98 if (length == 0) return '';
99
100 var is_array = IS_ARRAY(array);
101
102 if (is_array) {
103 // If the array is cyclic, return the empty string for already
104 // visited arrays.
105 if (!%PushIfAbsent(visited_arrays, array)) return '';
106 }
107
108 // Attempt to convert the elements.
109 try {
110 if (UseSparseVariant(array, length, is_array) && separator === '') {
111 return SparseJoin(array, length, convert);
112 }
113
114 // Fast case for one-element arrays.
115 if (length == 1) {
116 var e = array[0];
117 if (!IS_UNDEFINED(e) || (0 in array)) {
Steve Blockd0582a62009-12-15 09:54:21 +0000118 if (typeof(e) === 'string') return e;
Steve Blocka7e24c12009-10-30 11:49:00 +0000119 return convert(e);
120 }
121 }
122
123 var builder = new StringBuilder();
124
Steve Blockd0582a62009-12-15 09:54:21 +0000125 // We pull the empty separator check outside the loop for speed!
126 if (separator.length == 0) {
127 for (var i = 0; i < length; i++) {
128 var e = array[i];
129 if (!IS_UNDEFINED(e) || (i in array)) {
130 if (typeof(e) !== 'string') e = convert(e);
131 if (e.length > 0) {
132 var elements = builder.elements;
133 elements[elements.length] = e;
134 }
135 }
136 }
137 } else {
138 for (var i = 0; i < length; i++) {
139 var e = array[i];
140 if (i != 0) builder.add(separator);
141 if (!IS_UNDEFINED(e) || (i in array)) {
142 if (typeof(e) !== 'string') e = convert(e);
143 if (e.length > 0) {
144 var elements = builder.elements;
145 elements[elements.length] = e;
146 }
147 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000148 }
149 }
150 return builder.generate();
151 } finally {
152 // Make sure to pop the visited array no matter what happens.
153 if (is_array) visited_arrays.pop();
154 }
155}
156
157
158function ConvertToString(e) {
Steve Blockd0582a62009-12-15 09:54:21 +0000159 if (typeof(e) === 'string') return e;
Steve Blocka7e24c12009-10-30 11:49:00 +0000160 if (e == null) return '';
161 else return ToString(e);
162}
163
164
165function ConvertToLocaleString(e) {
Steve Blockd0582a62009-12-15 09:54:21 +0000166 if (typeof(e) === 'string') return e;
Steve Blocka7e24c12009-10-30 11:49:00 +0000167 if (e == null) return '';
168 else {
169 // 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) {
362 if (IS_UNDEFINED(separator)) separator = ',';
363 else separator = ToString(separator);
364 return Join(this, ToUint32(this.length), separator, ConvertToString);
365}
366
367
368// Removes the last element from the array and returns it. See
369// ECMA-262, section 15.4.4.6.
370function ArrayPop() {
371 var n = ToUint32(this.length);
372 if (n == 0) {
373 this.length = n;
374 return;
375 }
376 n--;
377 var value = this[n];
378 this.length = n;
379 delete this[n];
380 return value;
381}
382
383
384// Appends the arguments to the end of the array and returns the new
385// length of the array. See ECMA-262, section 15.4.4.7.
386function ArrayPush() {
387 var n = ToUint32(this.length);
388 var m = %_ArgumentsLength();
389 for (var i = 0; i < m; i++) {
390 this[i+n] = %_Arguments(i);
391 }
392 this.length = n + m;
393 return this.length;
394}
395
396
397function ArrayConcat(arg1) { // length == 1
398 // TODO: can we just use arguments?
399 var arg_count = %_ArgumentsLength();
400 var arrays = new $Array(1 + arg_count);
401 arrays[0] = this;
402 for (var i = 0; i < arg_count; i++) {
403 arrays[i + 1] = %_Arguments(i);
404 }
405
406 return %ArrayConcat(arrays);
407}
408
409
410// For implementing reverse() on large, sparse arrays.
411function SparseReverse(array, len) {
412 var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
413 var high_counter = keys.length - 1;
414 var low_counter = 0;
415 while (low_counter <= high_counter) {
416 var i = keys[low_counter];
417 var j = keys[high_counter];
418
419 var j_complement = len - j - 1;
420 var low, high;
421
422 if (j_complement <= i) {
423 high = j;
424 while (keys[--high_counter] == j);
425 low = j_complement;
426 }
427 if (j_complement >= i) {
428 low = i;
429 while (keys[++low_counter] == i);
430 high = len - i - 1;
431 }
432
433 var current_i = array[low];
434 if (!IS_UNDEFINED(current_i) || low in array) {
435 var current_j = array[high];
436 if (!IS_UNDEFINED(current_j) || high in array) {
437 array[low] = current_j;
438 array[high] = current_i;
439 } else {
440 array[high] = current_i;
441 delete array[low];
442 }
443 } else {
444 var current_j = array[high];
445 if (!IS_UNDEFINED(current_j) || high in array) {
446 array[low] = current_j;
447 delete array[high];
448 }
449 }
450 }
451}
452
453
454function ArrayReverse() {
455 var j = ToUint32(this.length) - 1;
456
457 if (UseSparseVariant(this, j, IS_ARRAY(this))) {
458 SparseReverse(this, j+1);
459 return this;
460 }
461
462 for (var i = 0; i < j; i++, j--) {
463 var current_i = this[i];
464 if (!IS_UNDEFINED(current_i) || i in this) {
465 var current_j = this[j];
466 if (!IS_UNDEFINED(current_j) || j in this) {
467 this[i] = current_j;
468 this[j] = current_i;
469 } else {
470 this[j] = current_i;
471 delete this[i];
472 }
473 } else {
474 var current_j = this[j];
475 if (!IS_UNDEFINED(current_j) || j in this) {
476 this[i] = current_j;
477 delete this[j];
478 }
479 }
480 }
481 return this;
482}
483
484
485function ArrayShift() {
486 var len = ToUint32(this.length);
487
488 if (len === 0) {
489 this.length = 0;
490 return;
491 }
492
493 var first = this[0];
494
495 if (IS_ARRAY(this))
496 SmartMove(this, 0, 1, len, 0);
497 else
498 SimpleMove(this, 0, 1, len, 0);
499
500 this.length = len - 1;
501
502 return first;
503}
504
505
506function ArrayUnshift(arg1) { // length == 1
507 var len = ToUint32(this.length);
508 var num_arguments = %_ArgumentsLength();
509
510 if (IS_ARRAY(this))
511 SmartMove(this, 0, 0, len, num_arguments);
512 else
513 SimpleMove(this, 0, 0, len, num_arguments);
514
515 for (var i = 0; i < num_arguments; i++) {
516 this[i] = %_Arguments(i);
517 }
518
519 this.length = len + num_arguments;
520
521 return len + num_arguments;
522}
523
524
525function ArraySlice(start, end) {
526 var len = ToUint32(this.length);
527 var start_i = TO_INTEGER(start);
528 var end_i = len;
529
530 if (end !== void 0) end_i = TO_INTEGER(end);
531
532 if (start_i < 0) {
533 start_i += len;
534 if (start_i < 0) start_i = 0;
535 } else {
536 if (start_i > len) start_i = len;
537 }
538
539 if (end_i < 0) {
540 end_i += len;
541 if (end_i < 0) end_i = 0;
542 } else {
543 if (end_i > len) end_i = len;
544 }
545
546 var result = [];
547
548 if (end_i < start_i) return result;
549
550 if (IS_ARRAY(this)) {
551 SmartSlice(this, start_i, end_i - start_i, len, result);
552 } else {
553 SimpleSlice(this, start_i, end_i - start_i, len, result);
554 }
555
556 result.length = end_i - start_i;
557
558 return result;
559}
560
561
562function ArraySplice(start, delete_count) {
563 var num_arguments = %_ArgumentsLength();
564
565 // SpiderMonkey and KJS return undefined in the case where no
566 // arguments are given instead of using the implicit undefined
567 // arguments. This does not follow ECMA-262, but we do the same for
568 // compatibility.
569 if (num_arguments == 0) return;
570
571 var len = ToUint32(this.length);
572 var start_i = TO_INTEGER(start);
573
574 if (start_i < 0) {
575 start_i += len;
576 if (start_i < 0) start_i = 0;
577 } else {
578 if (start_i > len) start_i = len;
579 }
580
581 // SpiderMonkey and KJS treat the case where no delete count is
582 // given differently from when an undefined delete count is given.
583 // This does not follow ECMA-262, but we do the same for
584 // compatibility.
585 var del_count = 0;
586 if (num_arguments > 1) {
587 del_count = TO_INTEGER(delete_count);
588 if (del_count < 0) del_count = 0;
589 if (del_count > len - start_i) del_count = len - start_i;
590 } else {
591 del_count = len - start_i;
592 }
593
594 var deleted_elements = [];
595 deleted_elements.length = del_count;
596
597 // Number of elements to add.
598 var num_additional_args = 0;
599 if (num_arguments > 2) {
600 num_additional_args = num_arguments - 2;
601 }
602
603 var use_simple_splice = true;
604
605 if (IS_ARRAY(this) && num_additional_args !== del_count) {
606 // If we are only deleting/moving a few things near the end of the
607 // array then the simple version is going to be faster, because it
608 // doesn't touch most of the array.
609 var estimated_non_hole_elements = %EstimateNumberOfElements(this);
610 if (len > 20 && (estimated_non_hole_elements >> 2) < (len - start_i)) {
611 use_simple_splice = false;
612 }
613 }
614
615 if (use_simple_splice) {
616 SimpleSlice(this, start_i, del_count, len, deleted_elements);
617 SimpleMove(this, start_i, del_count, len, num_additional_args);
618 } else {
619 SmartSlice(this, start_i, del_count, len, deleted_elements);
620 SmartMove(this, start_i, del_count, len, num_additional_args);
621 }
622
623 // Insert the arguments into the resulting array in
624 // place of the deleted elements.
625 var i = start_i;
626 var arguments_index = 2;
627 var arguments_length = %_ArgumentsLength();
628 while (arguments_index < arguments_length) {
629 this[i++] = %_Arguments(arguments_index++);
630 }
631 this.length = len - del_count + num_additional_args;
632
633 // Return the deleted elements.
634 return deleted_elements;
635}
636
637
638function ArraySort(comparefn) {
639 // In-place QuickSort algorithm.
640 // For short (length <= 22) arrays, insertion sort is used for efficiency.
641
642 var custom_compare = IS_FUNCTION(comparefn);
643
644 function Compare(x,y) {
645 // Assume the comparefn, if any, is a consistent comparison function.
646 // If it isn't, we are allowed arbitrary behavior by ECMA 15.4.4.11.
647 if (x === y) return 0;
648 if (custom_compare) {
649 // Don't call directly to avoid exposing the builtin's global object.
650 return comparefn.call(null, x, y);
651 }
652 if (%_IsSmi(x) && %_IsSmi(y)) {
653 return %SmiLexicographicCompare(x, y);
654 }
655 x = ToString(x);
656 y = ToString(y);
657 if (x == y) return 0;
658 else return x < y ? -1 : 1;
659 };
660
661 function InsertionSort(a, from, to) {
662 for (var i = from + 1; i < to; i++) {
663 var element = a[i];
664 // Pre-convert the element to a string for comparison if we know
665 // it will happen on each compare anyway.
666 var key =
667 (custom_compare || %_IsSmi(element)) ? element : ToString(element);
668 // place element in a[from..i[
669 // binary search
670 var min = from;
671 var max = i;
672 // The search interval is a[min..max[
673 while (min < max) {
674 var mid = min + ((max - min) >> 1);
675 var order = Compare(a[mid], key);
676 if (order == 0) {
677 min = max = mid;
678 break;
679 }
680 if (order < 0) {
681 min = mid + 1;
682 } else {
683 max = mid;
684 }
685 }
686 // place element at position min==max.
687 for (var j = i; j > min; j--) {
688 a[j] = a[j - 1];
689 }
690 a[min] = element;
691 }
692 }
693
694 function QuickSort(a, from, to) {
695 // Insertion sort is faster for short arrays.
696 if (to - from <= 22) {
697 InsertionSort(a, from, to);
698 return;
699 }
700 var pivot_index = $floor($random() * (to - from)) + from;
701 var pivot = a[pivot_index];
702 // Pre-convert the element to a string for comparison if we know
703 // it will happen on each compare anyway.
704 var pivot_key =
705 (custom_compare || %_IsSmi(pivot)) ? pivot : ToString(pivot);
706 // Issue 95: Keep the pivot element out of the comparisons to avoid
707 // infinite recursion if comparefn(pivot, pivot) != 0.
708 a[pivot_index] = a[from];
709 a[from] = pivot;
710 var low_end = from; // Upper bound of the elements lower than pivot.
711 var high_start = to; // Lower bound of the elements greater than pivot.
712 // From low_end to i are elements equal to pivot.
713 // From i to high_start are elements that haven't been compared yet.
714 for (var i = from + 1; i < high_start; ) {
715 var element = a[i];
716 var order = Compare(element, pivot_key);
717 if (order < 0) {
718 a[i] = a[low_end];
719 a[low_end] = element;
720 i++;
721 low_end++;
722 } else if (order > 0) {
723 high_start--;
724 a[i] = a[high_start];
725 a[high_start] = element;
726 } else { // order == 0
727 i++;
728 }
729 }
730 QuickSort(a, from, low_end);
731 QuickSort(a, high_start, to);
732 }
733
734 var length;
735
736 // Copies elements in the range 0..length from obj's prototype chain
737 // to obj itself, if obj has holes. Returns one more than the maximal index
738 // of a prototype property.
739 function CopyFromPrototype(obj, length) {
740 var max = 0;
741 for (var proto = obj.__proto__; proto; proto = proto.__proto__) {
742 var indices = %GetArrayKeys(proto, length);
743 if (indices.length > 0) {
744 if (indices[0] == -1) {
745 // It's an interval.
746 var proto_length = indices[1];
747 for (var i = 0; i < proto_length; i++) {
748 if (!obj.hasOwnProperty(i) && proto.hasOwnProperty(i)) {
749 obj[i] = proto[i];
750 if (i >= max) { max = i + 1; }
751 }
752 }
753 } else {
754 for (var i = 0; i < indices.length; i++) {
755 var index = indices[i];
756 if (!IS_UNDEFINED(index) &&
757 !obj.hasOwnProperty(index) && proto.hasOwnProperty(index)) {
758 obj[index] = proto[index];
759 if (index >= max) { max = index + 1; }
760 }
761 }
762 }
763 }
764 }
765 return max;
766 }
767
768 // Set a value of "undefined" on all indices in the range from..to
769 // where a prototype of obj has an element. I.e., shadow all prototype
770 // elements in that range.
771 function ShadowPrototypeElements(obj, from, to) {
772 for (var proto = obj.__proto__; proto; proto = proto.__proto__) {
773 var indices = %GetArrayKeys(proto, to);
774 if (indices.length > 0) {
775 if (indices[0] == -1) {
776 // It's an interval.
777 var proto_length = indices[1];
778 for (var i = from; i < proto_length; i++) {
779 if (proto.hasOwnProperty(i)) {
780 obj[i] = void 0;
781 }
782 }
783 } else {
784 for (var i = 0; i < indices.length; i++) {
785 var index = indices[i];
786 if (!IS_UNDEFINED(index) && from <= index &&
787 proto.hasOwnProperty(index)) {
788 obj[index] = void 0;
789 }
790 }
791 }
792 }
793 }
794 }
795
796 function SafeRemoveArrayHoles(obj) {
797 // Copy defined elements from the end to fill in all holes and undefineds
798 // in the beginning of the array. Write undefineds and holes at the end
799 // after loop is finished.
800 var first_undefined = 0;
801 var last_defined = length - 1;
802 var num_holes = 0;
803 while (first_undefined < last_defined) {
804 // Find first undefined element.
805 while (first_undefined < last_defined &&
806 !IS_UNDEFINED(obj[first_undefined])) {
807 first_undefined++;
808 }
809 // Maintain the invariant num_holes = the number of holes in the original
810 // array with indices <= first_undefined or > last_defined.
811 if (!obj.hasOwnProperty(first_undefined)) {
812 num_holes++;
813 }
814
815 // Find last defined element.
816 while (first_undefined < last_defined &&
817 IS_UNDEFINED(obj[last_defined])) {
818 if (!obj.hasOwnProperty(last_defined)) {
819 num_holes++;
820 }
821 last_defined--;
822 }
823 if (first_undefined < last_defined) {
824 // Fill in hole or undefined.
825 obj[first_undefined] = obj[last_defined];
826 obj[last_defined] = void 0;
827 }
828 }
829 // If there were any undefineds in the entire array, first_undefined
830 // points to one past the last defined element. Make this true if
831 // there were no undefineds, as well, so that first_undefined == number
832 // of defined elements.
833 if (!IS_UNDEFINED(obj[first_undefined])) first_undefined++;
834 // Fill in the undefineds and the holes. There may be a hole where
835 // an undefined should be and vice versa.
836 var i;
837 for (i = first_undefined; i < length - num_holes; i++) {
838 obj[i] = void 0;
839 }
840 for (i = length - num_holes; i < length; i++) {
841 // For compatability with Webkit, do not expose elements in the prototype.
842 if (i in obj.__proto__) {
843 obj[i] = void 0;
844 } else {
845 delete obj[i];
846 }
847 }
848
849 // Return the number of defined elements.
850 return first_undefined;
851 }
852
853 length = ToUint32(this.length);
854 if (length < 2) return this;
855
856 var is_array = IS_ARRAY(this);
857 var max_prototype_element;
858 if (!is_array) {
859 // For compatibility with JSC, we also sort elements inherited from
860 // the prototype chain on non-Array objects.
861 // We do this by copying them to this object and sorting only
862 // local elements. This is not very efficient, but sorting with
863 // inherited elements happens very, very rarely, if at all.
864 // The specification allows "implementation dependent" behavior
865 // if an element on the prototype chain has an element that
866 // might interact with sorting.
867 max_prototype_element = CopyFromPrototype(this, length);
868 }
869
870 var num_non_undefined = %RemoveArrayHoles(this, length);
871 if (num_non_undefined == -1) {
872 // There were indexed accessors in the array. Move array holes and
873 // undefineds to the end using a Javascript function that is safe
874 // in the presence of accessors.
875 num_non_undefined = SafeRemoveArrayHoles(this);
876 }
877
878 QuickSort(this, 0, num_non_undefined);
879
880 if (!is_array && (num_non_undefined + 1 < max_prototype_element)) {
881 // For compatibility with JSC, we shadow any elements in the prototype
882 // chain that has become exposed by sort moving a hole to its position.
883 ShadowPrototypeElements(this, num_non_undefined, max_prototype_element);
884 }
885
886 return this;
887}
888
889
890// The following functions cannot be made efficient on sparse arrays while
891// preserving the semantics, since the calls to the receiver function can add
892// or delete elements from the array.
893function ArrayFilter(f, receiver) {
894 if (!IS_FUNCTION(f)) {
895 throw MakeTypeError('called_non_callable', [ f ]);
896 }
897 // Pull out the length so that modifications to the length in the
898 // loop will not affect the looping.
899 var length = this.length;
900 var result = [];
901 var result_length = 0;
902 for (var i = 0; i < length; i++) {
903 var current = this[i];
904 if (!IS_UNDEFINED(current) || i in this) {
905 if (f.call(receiver, current, i, this)) result[result_length++] = current;
906 }
907 }
908 return result;
909}
910
911
912function ArrayForEach(f, receiver) {
913 if (!IS_FUNCTION(f)) {
914 throw MakeTypeError('called_non_callable', [ f ]);
915 }
916 // Pull out the length so that modifications to the length in the
917 // loop will not affect the looping.
918 var length = this.length;
919 for (var i = 0; i < length; i++) {
920 var current = this[i];
921 if (!IS_UNDEFINED(current) || i in this) {
922 f.call(receiver, current, i, this);
923 }
924 }
925}
926
927
928// Executes the function once for each element present in the
929// array until it finds one where callback returns true.
930function ArraySome(f, receiver) {
931 if (!IS_FUNCTION(f)) {
932 throw MakeTypeError('called_non_callable', [ f ]);
933 }
934 // Pull out the length so that modifications to the length in the
935 // loop will not affect the looping.
936 var length = this.length;
937 for (var i = 0; i < length; i++) {
938 var current = this[i];
939 if (!IS_UNDEFINED(current) || i in this) {
940 if (f.call(receiver, current, i, this)) return true;
941 }
942 }
943 return false;
944}
945
946
947function ArrayEvery(f, receiver) {
948 if (!IS_FUNCTION(f)) {
949 throw MakeTypeError('called_non_callable', [ f ]);
950 }
951 // Pull out the length so that modifications to the length in the
952 // loop will not affect the looping.
953 var length = this.length;
954 for (var i = 0; i < length; i++) {
955 var current = this[i];
956 if (!IS_UNDEFINED(current) || i in this) {
957 if (!f.call(receiver, current, i, this)) return false;
958 }
959 }
960
961 return true;
962}
963
964
965function ArrayMap(f, receiver) {
966 if (!IS_FUNCTION(f)) {
967 throw MakeTypeError('called_non_callable', [ f ]);
968 }
969 // Pull out the length so that modifications to the length in the
970 // loop will not affect the looping.
971 var length = this.length;
972 var result = new $Array(length);
973 for (var i = 0; i < length; i++) {
974 var current = this[i];
975 if (!IS_UNDEFINED(current) || i in this) {
976 result[i] = f.call(receiver, current, i, this);
977 }
978 }
979 return result;
980}
981
982
983function ArrayIndexOf(element, index) {
984 var length = this.length;
985 if (index == null) {
986 index = 0;
987 } else {
988 index = TO_INTEGER(index);
989 // If index is negative, index from the end of the array.
990 if (index < 0) index = length + index;
991 // If index is still negative, search the entire array.
992 if (index < 0) index = 0;
993 }
994 // Lookup through the array.
995 for (var i = index; i < length; i++) {
996 var current = this[i];
997 if (!IS_UNDEFINED(current) || i in this) {
998 if (current === element) return i;
999 }
1000 }
1001 return -1;
1002}
1003
1004
1005function ArrayLastIndexOf(element, index) {
1006 var length = this.length;
1007 if (index == null) {
1008 index = length - 1;
1009 } else {
1010 index = TO_INTEGER(index);
1011 // If index is negative, index from end of the array.
1012 if (index < 0) index = length + index;
1013 // If index is still negative, do not search the array.
1014 if (index < 0) index = -1;
1015 else if (index >= length) index = length - 1;
1016 }
1017 // Lookup through the array.
1018 for (var i = index; i >= 0; i--) {
1019 var current = this[i];
1020 if (!IS_UNDEFINED(current) || i in this) {
1021 if (current === element) return i;
1022 }
1023 }
1024 return -1;
1025}
1026
1027
1028function ArrayReduce(callback, current) {
1029 if (!IS_FUNCTION(callback)) {
1030 throw MakeTypeError('called_non_callable', [callback]);
1031 }
1032 // Pull out the length so that modifications to the length in the
1033 // loop will not affect the looping.
1034 var length = this.length;
1035 var i = 0;
1036
1037 find_initial: if (%_ArgumentsLength() < 2) {
1038 for (; i < length; i++) {
1039 current = this[i];
1040 if (!IS_UNDEFINED(current) || i in this) {
1041 i++;
1042 break find_initial;
1043 }
1044 }
1045 throw MakeTypeError('reduce_no_initial', []);
1046 }
1047
1048 for (; i < length; i++) {
1049 var element = this[i];
1050 if (!IS_UNDEFINED(element) || i in this) {
1051 current = callback.call(null, current, element, i, this);
1052 }
1053 }
1054 return current;
1055}
1056
1057function ArrayReduceRight(callback, current) {
1058 if (!IS_FUNCTION(callback)) {
1059 throw MakeTypeError('called_non_callable', [callback]);
1060 }
1061 var i = this.length - 1;
1062
1063 find_initial: if (%_ArgumentsLength() < 2) {
1064 for (; i >= 0; i--) {
1065 current = this[i];
1066 if (!IS_UNDEFINED(current) || i in this) {
1067 i--;
1068 break find_initial;
1069 }
1070 }
1071 throw MakeTypeError('reduce_no_initial', []);
1072 }
1073
1074 for (; i >= 0; i--) {
1075 var element = this[i];
1076 if (!IS_UNDEFINED(element) || i in this) {
1077 current = callback.call(null, current, element, i, this);
1078 }
1079 }
1080 return current;
1081}
1082
Steve Block3ce2e202009-11-05 08:53:23 +00001083// ES5, 15.4.3.2
1084function ArrayIsArray(obj) {
1085 return IS_ARRAY(obj);
1086}
Steve Blocka7e24c12009-10-30 11:49:00 +00001087
1088// -------------------------------------------------------------------
1089
1090
1091function UpdateFunctionLengths(lengths) {
1092 for (var key in lengths) {
1093 %FunctionSetLength(this[key], lengths[key]);
1094 }
1095}
1096
1097
1098// -------------------------------------------------------------------
1099function SetupArray() {
1100 // Setup non-enumerable constructor property on the Array.prototype
1101 // object.
1102 %SetProperty($Array.prototype, "constructor", $Array, DONT_ENUM);
1103
Steve Block3ce2e202009-11-05 08:53:23 +00001104 // Setup non-enumerable functions on the Array object.
1105 InstallFunctions($Array, DONT_ENUM, $Array(
1106 "isArray", ArrayIsArray
1107 ));
1108
Steve Blocka7e24c12009-10-30 11:49:00 +00001109 // Setup non-enumerable functions of the Array.prototype object and
1110 // set their names.
1111 InstallFunctionsOnHiddenPrototype($Array.prototype, DONT_ENUM, $Array(
1112 "toString", ArrayToString,
1113 "toLocaleString", ArrayToLocaleString,
1114 "join", ArrayJoin,
1115 "pop", ArrayPop,
1116 "push", ArrayPush,
1117 "concat", ArrayConcat,
1118 "reverse", ArrayReverse,
1119 "shift", ArrayShift,
1120 "unshift", ArrayUnshift,
1121 "slice", ArraySlice,
1122 "splice", ArraySplice,
1123 "sort", ArraySort,
1124 "filter", ArrayFilter,
1125 "forEach", ArrayForEach,
1126 "some", ArraySome,
1127 "every", ArrayEvery,
1128 "map", ArrayMap,
1129 "indexOf", ArrayIndexOf,
1130 "lastIndexOf", ArrayLastIndexOf,
1131 "reduce", ArrayReduce,
Steve Block3ce2e202009-11-05 08:53:23 +00001132 "reduceRight", ArrayReduceRight
1133 ));
1134
Steve Blocka7e24c12009-10-30 11:49:00 +00001135 // Manipulate the length of some of the functions to meet
1136 // expectations set by ECMA-262 or Mozilla.
1137 UpdateFunctionLengths({
1138 ArrayFilter: 1,
1139 ArrayForEach: 1,
1140 ArraySome: 1,
1141 ArrayEvery: 1,
1142 ArrayMap: 1,
1143 ArrayIndexOf: 1,
1144 ArrayLastIndexOf: 1,
1145 ArrayPush: 1,
1146 ArrayReduce: 1,
1147 ArrayReduceRight: 1
1148 });
1149}
1150
1151
1152SetupArray();