blob: f3c0697b994b36f50f4a6c966816d3359ef9375b [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 }
367 var length = TO_UINT32(this.length);
368 return Join(this, length, separator, ConvertToString);
Steve Blocka7e24c12009-10-30 11:49:00 +0000369}
370
371
372// Removes the last element from the array and returns it. See
373// ECMA-262, section 15.4.4.6.
374function ArrayPop() {
Leon Clarkee46be812010-01-19 14:06:41 +0000375 var n = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000376 if (n == 0) {
377 this.length = n;
378 return;
379 }
380 n--;
381 var value = this[n];
382 this.length = n;
383 delete this[n];
384 return value;
385}
386
387
388// Appends the arguments to the end of the array and returns the new
389// length of the array. See ECMA-262, section 15.4.4.7.
390function ArrayPush() {
Leon Clarkee46be812010-01-19 14:06:41 +0000391 var n = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000392 var m = %_ArgumentsLength();
393 for (var i = 0; i < m; i++) {
394 this[i+n] = %_Arguments(i);
395 }
396 this.length = n + m;
397 return this.length;
398}
399
400
401function ArrayConcat(arg1) { // length == 1
402 // TODO: can we just use arguments?
403 var arg_count = %_ArgumentsLength();
404 var arrays = new $Array(1 + arg_count);
405 arrays[0] = this;
406 for (var i = 0; i < arg_count; i++) {
407 arrays[i + 1] = %_Arguments(i);
408 }
409
410 return %ArrayConcat(arrays);
411}
412
413
414// For implementing reverse() on large, sparse arrays.
415function SparseReverse(array, len) {
416 var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
417 var high_counter = keys.length - 1;
418 var low_counter = 0;
419 while (low_counter <= high_counter) {
420 var i = keys[low_counter];
421 var j = keys[high_counter];
422
423 var j_complement = len - j - 1;
424 var low, high;
425
426 if (j_complement <= i) {
427 high = j;
428 while (keys[--high_counter] == j);
429 low = j_complement;
430 }
431 if (j_complement >= i) {
432 low = i;
433 while (keys[++low_counter] == i);
434 high = len - i - 1;
435 }
436
437 var current_i = array[low];
438 if (!IS_UNDEFINED(current_i) || low in array) {
439 var current_j = array[high];
440 if (!IS_UNDEFINED(current_j) || high in array) {
441 array[low] = current_j;
442 array[high] = current_i;
443 } else {
444 array[high] = current_i;
445 delete array[low];
446 }
447 } else {
448 var current_j = array[high];
449 if (!IS_UNDEFINED(current_j) || high in array) {
450 array[low] = current_j;
451 delete array[high];
452 }
453 }
454 }
455}
456
457
458function ArrayReverse() {
Leon Clarkee46be812010-01-19 14:06:41 +0000459 var j = TO_UINT32(this.length) - 1;
Steve Blocka7e24c12009-10-30 11:49:00 +0000460
461 if (UseSparseVariant(this, j, IS_ARRAY(this))) {
462 SparseReverse(this, j+1);
463 return this;
464 }
465
466 for (var i = 0; i < j; i++, j--) {
467 var current_i = this[i];
468 if (!IS_UNDEFINED(current_i) || i in this) {
469 var current_j = this[j];
470 if (!IS_UNDEFINED(current_j) || j in this) {
471 this[i] = current_j;
472 this[j] = current_i;
473 } else {
474 this[j] = current_i;
475 delete this[i];
476 }
477 } else {
478 var current_j = this[j];
479 if (!IS_UNDEFINED(current_j) || j in this) {
480 this[i] = current_j;
481 delete this[j];
482 }
483 }
484 }
485 return this;
486}
487
488
489function ArrayShift() {
Leon Clarkee46be812010-01-19 14:06:41 +0000490 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000491
492 if (len === 0) {
493 this.length = 0;
494 return;
495 }
496
497 var first = this[0];
498
499 if (IS_ARRAY(this))
500 SmartMove(this, 0, 1, len, 0);
501 else
502 SimpleMove(this, 0, 1, len, 0);
503
504 this.length = len - 1;
505
506 return first;
507}
508
509
510function ArrayUnshift(arg1) { // length == 1
Leon Clarkee46be812010-01-19 14:06:41 +0000511 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000512 var num_arguments = %_ArgumentsLength();
513
514 if (IS_ARRAY(this))
515 SmartMove(this, 0, 0, len, num_arguments);
516 else
517 SimpleMove(this, 0, 0, len, num_arguments);
518
519 for (var i = 0; i < num_arguments; i++) {
520 this[i] = %_Arguments(i);
521 }
522
523 this.length = len + num_arguments;
524
525 return len + num_arguments;
526}
527
528
529function ArraySlice(start, end) {
Leon Clarkee46be812010-01-19 14:06:41 +0000530 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000531 var start_i = TO_INTEGER(start);
532 var end_i = len;
533
534 if (end !== void 0) end_i = TO_INTEGER(end);
535
536 if (start_i < 0) {
537 start_i += len;
538 if (start_i < 0) start_i = 0;
539 } else {
540 if (start_i > len) start_i = len;
541 }
542
543 if (end_i < 0) {
544 end_i += len;
545 if (end_i < 0) end_i = 0;
546 } else {
547 if (end_i > len) end_i = len;
548 }
549
550 var result = [];
551
552 if (end_i < start_i) return result;
553
554 if (IS_ARRAY(this)) {
555 SmartSlice(this, start_i, end_i - start_i, len, result);
556 } else {
557 SimpleSlice(this, start_i, end_i - start_i, len, result);
558 }
559
560 result.length = end_i - start_i;
561
562 return result;
563}
564
565
566function ArraySplice(start, delete_count) {
567 var num_arguments = %_ArgumentsLength();
568
Andrei Popescu402d9372010-02-26 13:31:12 +0000569 // SpiderMonkey and JSC return undefined in the case where no
Steve Blocka7e24c12009-10-30 11:49:00 +0000570 // arguments are given instead of using the implicit undefined
571 // arguments. This does not follow ECMA-262, but we do the same for
572 // compatibility.
Andrei Popescu402d9372010-02-26 13:31:12 +0000573 // TraceMonkey follows ECMA-262 though.
Steve Blocka7e24c12009-10-30 11:49:00 +0000574 if (num_arguments == 0) return;
575
Leon Clarkee46be812010-01-19 14:06:41 +0000576 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000577 var start_i = TO_INTEGER(start);
578
579 if (start_i < 0) {
580 start_i += len;
581 if (start_i < 0) start_i = 0;
582 } else {
583 if (start_i > len) start_i = len;
584 }
585
Andrei Popescu402d9372010-02-26 13:31:12 +0000586 // SpiderMonkey, TraceMonkey and JSC treat the case where no delete count is
Steve Blocka7e24c12009-10-30 11:49:00 +0000587 // given differently from when an undefined delete count is given.
588 // This does not follow ECMA-262, but we do the same for
589 // compatibility.
590 var del_count = 0;
591 if (num_arguments > 1) {
592 del_count = TO_INTEGER(delete_count);
593 if (del_count < 0) del_count = 0;
594 if (del_count > len - start_i) del_count = len - start_i;
595 } else {
596 del_count = len - start_i;
597 }
598
599 var deleted_elements = [];
600 deleted_elements.length = del_count;
601
602 // Number of elements to add.
603 var num_additional_args = 0;
604 if (num_arguments > 2) {
605 num_additional_args = num_arguments - 2;
606 }
607
608 var use_simple_splice = true;
609
610 if (IS_ARRAY(this) && num_additional_args !== del_count) {
611 // If we are only deleting/moving a few things near the end of the
612 // array then the simple version is going to be faster, because it
613 // doesn't touch most of the array.
614 var estimated_non_hole_elements = %EstimateNumberOfElements(this);
615 if (len > 20 && (estimated_non_hole_elements >> 2) < (len - start_i)) {
616 use_simple_splice = false;
617 }
618 }
619
620 if (use_simple_splice) {
621 SimpleSlice(this, start_i, del_count, len, deleted_elements);
622 SimpleMove(this, start_i, del_count, len, num_additional_args);
623 } else {
624 SmartSlice(this, start_i, del_count, len, deleted_elements);
625 SmartMove(this, start_i, del_count, len, num_additional_args);
626 }
627
628 // Insert the arguments into the resulting array in
629 // place of the deleted elements.
630 var i = start_i;
631 var arguments_index = 2;
632 var arguments_length = %_ArgumentsLength();
633 while (arguments_index < arguments_length) {
634 this[i++] = %_Arguments(arguments_index++);
635 }
636 this.length = len - del_count + num_additional_args;
637
638 // Return the deleted elements.
639 return deleted_elements;
640}
641
642
643function ArraySort(comparefn) {
644 // In-place QuickSort algorithm.
645 // For short (length <= 22) arrays, insertion sort is used for efficiency.
646
Steve Block6ded16b2010-05-10 14:33:55 +0100647 if (!IS_FUNCTION(comparefn)) {
648 comparefn = function (x, y) {
649 if (x === y) return 0;
650 if (%_IsSmi(x) && %_IsSmi(y)) {
651 return %SmiLexicographicCompare(x, y);
652 }
653 x = ToString(x);
654 y = ToString(y);
655 if (x == y) return 0;
656 else return x < y ? -1 : 1;
657 };
658 }
659 var global_receiver = %GetGlobalReceiver();
Steve Blocka7e24c12009-10-30 11:49:00 +0000660
661 function InsertionSort(a, from, to) {
662 for (var i = from + 1; i < to; i++) {
663 var element = a[i];
Steve Block6ded16b2010-05-10 14:33:55 +0100664 for (var j = i - 1; j >= from; j--) {
665 var tmp = a[j];
666 var order = %_CallFunction(global_receiver, tmp, element, comparefn);
667 if (order > 0) {
668 a[j + 1] = tmp;
669 } else {
Steve Blocka7e24c12009-10-30 11:49:00 +0000670 break;
671 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000672 }
Steve Block6ded16b2010-05-10 14:33:55 +0100673 a[j + 1] = element;
Steve Blocka7e24c12009-10-30 11:49:00 +0000674 }
675 }
676
677 function QuickSort(a, from, to) {
678 // Insertion sort is faster for short arrays.
679 if (to - from <= 22) {
680 InsertionSort(a, from, to);
681 return;
682 }
683 var pivot_index = $floor($random() * (to - from)) + from;
684 var pivot = a[pivot_index];
Steve Blocka7e24c12009-10-30 11:49:00 +0000685 // Issue 95: Keep the pivot element out of the comparisons to avoid
686 // infinite recursion if comparefn(pivot, pivot) != 0.
Steve Block6ded16b2010-05-10 14:33:55 +0100687 %_SwapElements(a, from, pivot_index);
Steve Blocka7e24c12009-10-30 11:49:00 +0000688 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];
Steve Block6ded16b2010-05-10 14:33:55 +0100694 var order = %_CallFunction(global_receiver, element, pivot, comparefn);
Steve Blocka7e24c12009-10-30 11:49:00 +0000695 if (order < 0) {
Steve Block6ded16b2010-05-10 14:33:55 +0100696 %_SwapElements(a, i, low_end);
Steve Blocka7e24c12009-10-30 11:49:00 +0000697 i++;
698 low_end++;
699 } else if (order > 0) {
700 high_start--;
Steve Block6ded16b2010-05-10 14:33:55 +0100701 %_SwapElements(a, i, high_start);
Steve Blocka7e24c12009-10-30 11:49:00 +0000702 } else { // order == 0
703 i++;
704 }
705 }
706 QuickSort(a, from, low_end);
707 QuickSort(a, high_start, to);
708 }
709
Steve Blocka7e24c12009-10-30 11:49:00 +0000710 // Copies elements in the range 0..length from obj's prototype chain
711 // to obj itself, if obj has holes. Returns one more than the maximal index
712 // of a prototype property.
713 function CopyFromPrototype(obj, length) {
714 var max = 0;
715 for (var proto = obj.__proto__; proto; proto = proto.__proto__) {
716 var indices = %GetArrayKeys(proto, length);
717 if (indices.length > 0) {
718 if (indices[0] == -1) {
719 // It's an interval.
720 var proto_length = indices[1];
721 for (var i = 0; i < proto_length; i++) {
722 if (!obj.hasOwnProperty(i) && proto.hasOwnProperty(i)) {
723 obj[i] = proto[i];
724 if (i >= max) { max = i + 1; }
725 }
726 }
727 } else {
728 for (var i = 0; i < indices.length; i++) {
729 var index = indices[i];
730 if (!IS_UNDEFINED(index) &&
731 !obj.hasOwnProperty(index) && proto.hasOwnProperty(index)) {
732 obj[index] = proto[index];
733 if (index >= max) { max = index + 1; }
734 }
735 }
736 }
737 }
738 }
739 return max;
740 }
741
742 // Set a value of "undefined" on all indices in the range from..to
743 // where a prototype of obj has an element. I.e., shadow all prototype
744 // elements in that range.
745 function ShadowPrototypeElements(obj, from, to) {
746 for (var proto = obj.__proto__; proto; proto = proto.__proto__) {
747 var indices = %GetArrayKeys(proto, to);
748 if (indices.length > 0) {
749 if (indices[0] == -1) {
750 // It's an interval.
751 var proto_length = indices[1];
752 for (var i = from; i < proto_length; i++) {
753 if (proto.hasOwnProperty(i)) {
754 obj[i] = void 0;
755 }
756 }
757 } else {
758 for (var i = 0; i < indices.length; i++) {
759 var index = indices[i];
760 if (!IS_UNDEFINED(index) && from <= index &&
761 proto.hasOwnProperty(index)) {
762 obj[index] = void 0;
763 }
764 }
765 }
766 }
767 }
768 }
769
770 function SafeRemoveArrayHoles(obj) {
771 // Copy defined elements from the end to fill in all holes and undefineds
772 // in the beginning of the array. Write undefineds and holes at the end
773 // after loop is finished.
774 var first_undefined = 0;
775 var last_defined = length - 1;
776 var num_holes = 0;
777 while (first_undefined < last_defined) {
778 // Find first undefined element.
779 while (first_undefined < last_defined &&
780 !IS_UNDEFINED(obj[first_undefined])) {
781 first_undefined++;
782 }
783 // Maintain the invariant num_holes = the number of holes in the original
784 // array with indices <= first_undefined or > last_defined.
785 if (!obj.hasOwnProperty(first_undefined)) {
786 num_holes++;
787 }
788
789 // Find last defined element.
790 while (first_undefined < last_defined &&
791 IS_UNDEFINED(obj[last_defined])) {
792 if (!obj.hasOwnProperty(last_defined)) {
793 num_holes++;
794 }
795 last_defined--;
796 }
797 if (first_undefined < last_defined) {
798 // Fill in hole or undefined.
799 obj[first_undefined] = obj[last_defined];
800 obj[last_defined] = void 0;
801 }
802 }
803 // If there were any undefineds in the entire array, first_undefined
804 // points to one past the last defined element. Make this true if
805 // there were no undefineds, as well, so that first_undefined == number
806 // of defined elements.
807 if (!IS_UNDEFINED(obj[first_undefined])) first_undefined++;
808 // Fill in the undefineds and the holes. There may be a hole where
809 // an undefined should be and vice versa.
810 var i;
811 for (i = first_undefined; i < length - num_holes; i++) {
812 obj[i] = void 0;
813 }
814 for (i = length - num_holes; i < length; i++) {
815 // For compatability with Webkit, do not expose elements in the prototype.
816 if (i in obj.__proto__) {
817 obj[i] = void 0;
818 } else {
819 delete obj[i];
820 }
821 }
822
823 // Return the number of defined elements.
824 return first_undefined;
825 }
826
Steve Block6ded16b2010-05-10 14:33:55 +0100827 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000828 if (length < 2) return this;
829
830 var is_array = IS_ARRAY(this);
831 var max_prototype_element;
832 if (!is_array) {
833 // For compatibility with JSC, we also sort elements inherited from
834 // the prototype chain on non-Array objects.
835 // We do this by copying them to this object and sorting only
836 // local elements. This is not very efficient, but sorting with
837 // inherited elements happens very, very rarely, if at all.
838 // The specification allows "implementation dependent" behavior
839 // if an element on the prototype chain has an element that
840 // might interact with sorting.
841 max_prototype_element = CopyFromPrototype(this, length);
842 }
843
844 var num_non_undefined = %RemoveArrayHoles(this, length);
845 if (num_non_undefined == -1) {
846 // There were indexed accessors in the array. Move array holes and
847 // undefineds to the end using a Javascript function that is safe
848 // in the presence of accessors.
849 num_non_undefined = SafeRemoveArrayHoles(this);
850 }
851
852 QuickSort(this, 0, num_non_undefined);
853
854 if (!is_array && (num_non_undefined + 1 < max_prototype_element)) {
855 // For compatibility with JSC, we shadow any elements in the prototype
856 // chain that has become exposed by sort moving a hole to its position.
857 ShadowPrototypeElements(this, num_non_undefined, max_prototype_element);
858 }
859
860 return this;
861}
862
863
864// The following functions cannot be made efficient on sparse arrays while
865// preserving the semantics, since the calls to the receiver function can add
866// or delete elements from the array.
867function ArrayFilter(f, receiver) {
868 if (!IS_FUNCTION(f)) {
869 throw MakeTypeError('called_non_callable', [ f ]);
870 }
871 // Pull out the length so that modifications to the length in the
872 // loop will not affect the looping.
873 var length = this.length;
874 var result = [];
875 var result_length = 0;
876 for (var i = 0; i < length; i++) {
877 var current = this[i];
878 if (!IS_UNDEFINED(current) || i in this) {
879 if (f.call(receiver, current, i, this)) result[result_length++] = current;
880 }
881 }
882 return result;
883}
884
885
886function ArrayForEach(f, receiver) {
887 if (!IS_FUNCTION(f)) {
888 throw MakeTypeError('called_non_callable', [ f ]);
889 }
890 // Pull out the length so that modifications to the length in the
891 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000892 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000893 for (var i = 0; i < length; i++) {
894 var current = this[i];
895 if (!IS_UNDEFINED(current) || i in this) {
896 f.call(receiver, current, i, this);
897 }
898 }
899}
900
901
902// Executes the function once for each element present in the
903// array until it finds one where callback returns true.
904function ArraySome(f, receiver) {
905 if (!IS_FUNCTION(f)) {
906 throw MakeTypeError('called_non_callable', [ f ]);
907 }
908 // Pull out the length so that modifications to the length in the
909 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000910 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000911 for (var i = 0; i < length; i++) {
912 var current = this[i];
913 if (!IS_UNDEFINED(current) || i in this) {
914 if (f.call(receiver, current, i, this)) return true;
915 }
916 }
917 return false;
918}
919
920
921function ArrayEvery(f, receiver) {
922 if (!IS_FUNCTION(f)) {
923 throw MakeTypeError('called_non_callable', [ f ]);
924 }
925 // Pull out the length so that modifications to the length in the
926 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000927 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000928 for (var i = 0; i < length; i++) {
929 var current = this[i];
930 if (!IS_UNDEFINED(current) || i in this) {
931 if (!f.call(receiver, current, i, this)) return false;
932 }
933 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000934 return true;
935}
936
Steve Blocka7e24c12009-10-30 11:49:00 +0000937function ArrayMap(f, receiver) {
938 if (!IS_FUNCTION(f)) {
939 throw MakeTypeError('called_non_callable', [ f ]);
940 }
941 // Pull out the length so that modifications to the length in the
942 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000943 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000944 var result = new $Array(length);
945 for (var i = 0; i < length; i++) {
946 var current = this[i];
947 if (!IS_UNDEFINED(current) || i in this) {
948 result[i] = f.call(receiver, current, i, this);
949 }
950 }
951 return result;
952}
953
954
955function ArrayIndexOf(element, index) {
956 var length = this.length;
Steve Block8defd9f2010-07-08 12:39:36 +0100957 if (IS_UNDEFINED(index)) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000958 index = 0;
959 } else {
960 index = TO_INTEGER(index);
961 // If index is negative, index from the end of the array.
962 if (index < 0) index = length + index;
963 // If index is still negative, search the entire array.
964 if (index < 0) index = 0;
965 }
Steve Block6ded16b2010-05-10 14:33:55 +0100966 if (!IS_UNDEFINED(element)) {
967 for (var i = index; i < length; i++) {
968 if (this[i] === element) return i;
969 }
970 return -1;
971 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000972 // Lookup through the array.
973 for (var i = index; i < length; i++) {
Steve Block6ded16b2010-05-10 14:33:55 +0100974 if (IS_UNDEFINED(this[i]) && i in this) {
975 return i;
Steve Blocka7e24c12009-10-30 11:49:00 +0000976 }
977 }
978 return -1;
979}
980
981
982function ArrayLastIndexOf(element, index) {
983 var length = this.length;
Steve Block8defd9f2010-07-08 12:39:36 +0100984 if (%_ArgumentsLength() < 2) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000985 index = length - 1;
986 } else {
987 index = TO_INTEGER(index);
988 // If index is negative, index from end of the array.
989 if (index < 0) index = length + index;
990 // If index is still negative, do not search the array.
991 if (index < 0) index = -1;
992 else if (index >= length) index = length - 1;
993 }
994 // Lookup through the array.
Steve Block6ded16b2010-05-10 14:33:55 +0100995 if (!IS_UNDEFINED(element)) {
996 for (var i = index; i >= 0; i--) {
997 if (this[i] === element) return i;
998 }
999 return -1;
1000 }
Steve Blocka7e24c12009-10-30 11:49:00 +00001001 for (var i = index; i >= 0; i--) {
Steve Block6ded16b2010-05-10 14:33:55 +01001002 if (IS_UNDEFINED(this[i]) && i in this) {
1003 return i;
Steve Blocka7e24c12009-10-30 11:49:00 +00001004 }
1005 }
1006 return -1;
1007}
1008
1009
1010function ArrayReduce(callback, current) {
1011 if (!IS_FUNCTION(callback)) {
1012 throw MakeTypeError('called_non_callable', [callback]);
1013 }
1014 // Pull out the length so that modifications to the length in the
1015 // loop will not affect the looping.
1016 var length = this.length;
1017 var i = 0;
1018
1019 find_initial: if (%_ArgumentsLength() < 2) {
1020 for (; i < length; i++) {
1021 current = this[i];
1022 if (!IS_UNDEFINED(current) || i in this) {
1023 i++;
1024 break find_initial;
1025 }
1026 }
1027 throw MakeTypeError('reduce_no_initial', []);
1028 }
1029
1030 for (; i < length; i++) {
1031 var element = this[i];
1032 if (!IS_UNDEFINED(element) || i in this) {
1033 current = callback.call(null, current, element, i, this);
1034 }
1035 }
1036 return current;
1037}
1038
1039function ArrayReduceRight(callback, current) {
1040 if (!IS_FUNCTION(callback)) {
1041 throw MakeTypeError('called_non_callable', [callback]);
1042 }
1043 var i = this.length - 1;
1044
1045 find_initial: if (%_ArgumentsLength() < 2) {
1046 for (; i >= 0; i--) {
1047 current = this[i];
1048 if (!IS_UNDEFINED(current) || i in this) {
1049 i--;
1050 break find_initial;
1051 }
1052 }
1053 throw MakeTypeError('reduce_no_initial', []);
1054 }
1055
1056 for (; i >= 0; i--) {
1057 var element = this[i];
1058 if (!IS_UNDEFINED(element) || i in this) {
1059 current = callback.call(null, current, element, i, this);
1060 }
1061 }
1062 return current;
1063}
1064
Steve Block3ce2e202009-11-05 08:53:23 +00001065// ES5, 15.4.3.2
1066function ArrayIsArray(obj) {
1067 return IS_ARRAY(obj);
1068}
Steve Blocka7e24c12009-10-30 11:49:00 +00001069
Steve Blocka7e24c12009-10-30 11:49:00 +00001070
1071// -------------------------------------------------------------------
1072function SetupArray() {
1073 // Setup non-enumerable constructor property on the Array.prototype
1074 // object.
1075 %SetProperty($Array.prototype, "constructor", $Array, DONT_ENUM);
1076
Steve Block3ce2e202009-11-05 08:53:23 +00001077 // Setup non-enumerable functions on the Array object.
1078 InstallFunctions($Array, DONT_ENUM, $Array(
1079 "isArray", ArrayIsArray
1080 ));
1081
Steve Block6ded16b2010-05-10 14:33:55 +01001082 var specialFunctions = %SpecialArrayFunctions({});
1083
1084 function getFunction(name, jsBuiltin, len) {
1085 var f = jsBuiltin;
1086 if (specialFunctions.hasOwnProperty(name)) {
1087 f = specialFunctions[name];
1088 }
1089 if (!IS_UNDEFINED(len)) {
1090 %FunctionSetLength(f, len);
1091 }
1092 return f;
1093 }
1094
Steve Blocka7e24c12009-10-30 11:49:00 +00001095 // Setup non-enumerable functions of the Array.prototype object and
1096 // set their names.
Steve Blocka7e24c12009-10-30 11:49:00 +00001097 // Manipulate the length of some of the functions to meet
1098 // expectations set by ECMA-262 or Mozilla.
Steve Block6ded16b2010-05-10 14:33:55 +01001099 InstallFunctionsOnHiddenPrototype($Array.prototype, DONT_ENUM, $Array(
1100 "toString", getFunction("toString", ArrayToString),
1101 "toLocaleString", getFunction("toLocaleString", ArrayToLocaleString),
1102 "join", getFunction("join", ArrayJoin),
1103 "pop", getFunction("pop", ArrayPop),
1104 "push", getFunction("push", ArrayPush, 1),
1105 "concat", getFunction("concat", ArrayConcat, 1),
1106 "reverse", getFunction("reverse", ArrayReverse),
1107 "shift", getFunction("shift", ArrayShift),
1108 "unshift", getFunction("unshift", ArrayUnshift, 1),
1109 "slice", getFunction("slice", ArraySlice, 2),
1110 "splice", getFunction("splice", ArraySplice, 2),
1111 "sort", getFunction("sort", ArraySort),
1112 "filter", getFunction("filter", ArrayFilter, 1),
1113 "forEach", getFunction("forEach", ArrayForEach, 1),
1114 "some", getFunction("some", ArraySome, 1),
1115 "every", getFunction("every", ArrayEvery, 1),
1116 "map", getFunction("map", ArrayMap, 1),
1117 "indexOf", getFunction("indexOf", ArrayIndexOf, 1),
1118 "lastIndexOf", getFunction("lastIndexOf", ArrayLastIndexOf, 1),
1119 "reduce", getFunction("reduce", ArrayReduce, 1),
1120 "reduceRight", getFunction("reduceRight", ArrayReduceRight, 1)
1121 ));
1122
1123 %FinishArrayPrototypeSetup($Array.prototype);
Steve Blocka7e24c12009-10-30 11:49:00 +00001124}
1125
1126
1127SetupArray();