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