blob: 0d7a7cbc85e1f89a490d229fa759d047b3966fa6 [file] [log] [blame]
Ben Murdochb0fe1622011-05-05 13:52:32 +01001// Copyright 2010 the V8 project authors. All rights reserved.
Steve Blocka7e24c12009-10-30 11:49:00 +00002// Redistribution and use in source and binary forms, with or without
3// modification, are permitted provided that the following conditions are
4// met:
5//
6// * Redistributions of source code must retain the above copyright
7// notice, this list of conditions and the following disclaimer.
8// * Redistributions in binary form must reproduce the above
9// copyright notice, this list of conditions and the following
10// disclaimer in the documentation and/or other materials provided
11// with the distribution.
12// * Neither the name of Google Inc. nor the names of its
13// contributors may be used to endorse or promote products derived
14// from this software without specific prior written permission.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28// This file relies on the fact that the following declarations have been made
29// in runtime.js:
30// const $Array = global.Array;
31
32// -------------------------------------------------------------------
33
34// Global list of arrays visited during toString, toLocaleString and
35// join invocations.
36var visited_arrays = new $Array();
37
38
39// Gets a sorted array of array keys. Useful for operations on sparse
40// arrays. Dupes have not been removed.
41function GetSortedArrayKeys(array, intervals) {
42 var length = intervals.length;
43 var keys = [];
44 for (var k = 0; k < length; k++) {
45 var key = intervals[k];
46 if (key < 0) {
47 var j = -1 - key;
48 var limit = j + intervals[++k];
49 for (; j < limit; j++) {
50 var e = array[j];
51 if (!IS_UNDEFINED(e) || j in array) {
52 keys.push(j);
53 }
54 }
55 } else {
56 // The case where key is undefined also ends here.
57 if (!IS_UNDEFINED(key)) {
58 var e = array[key];
59 if (!IS_UNDEFINED(e) || key in array) {
60 keys.push(key);
61 }
62 }
63 }
64 }
65 keys.sort(function(a, b) { return a - b; });
66 return keys;
67}
68
69
70// Optimized for sparse arrays if separator is ''.
71function SparseJoin(array, len, convert) {
72 var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
Steve Blocka7e24c12009-10-30 11:49:00 +000073 var last_key = -1;
74 var keys_length = keys.length;
Leon Clarkee46be812010-01-19 14:06:41 +000075
76 var elements = new $Array(keys_length);
77 var elements_length = 0;
78
Steve Blocka7e24c12009-10-30 11:49:00 +000079 for (var i = 0; i < keys_length; i++) {
80 var key = keys[i];
81 if (key != last_key) {
82 var e = array[key];
Leon Clarkee46be812010-01-19 14:06:41 +000083 if (!IS_STRING(e)) e = convert(e);
84 elements[elements_length++] = e;
Steve Blocka7e24c12009-10-30 11:49:00 +000085 last_key = key;
86 }
87 }
Leon Clarkee46be812010-01-19 14:06:41 +000088 return %StringBuilderConcat(elements, elements_length, '');
Steve Blocka7e24c12009-10-30 11:49:00 +000089}
90
91
92function UseSparseVariant(object, length, is_array) {
93 return is_array &&
94 length > 1000 &&
95 (!%_IsSmi(length) ||
96 %EstimateNumberOfElements(object) < (length >> 2));
97}
98
99
100function Join(array, length, separator, convert) {
101 if (length == 0) return '';
102
103 var is_array = IS_ARRAY(array);
104
105 if (is_array) {
106 // If the array is cyclic, return the empty string for already
107 // visited arrays.
108 if (!%PushIfAbsent(visited_arrays, array)) return '';
109 }
110
111 // Attempt to convert the elements.
112 try {
Leon Clarkee46be812010-01-19 14:06:41 +0000113 if (UseSparseVariant(array, length, is_array) && (separator.length == 0)) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000114 return SparseJoin(array, length, convert);
115 }
116
117 // Fast case for one-element arrays.
118 if (length == 1) {
119 var e = array[0];
Ben Murdochb8e0da22011-05-16 14:20:40 +0100120 if (IS_STRING(e)) return e;
121 return convert(e);
Steve Blocka7e24c12009-10-30 11:49:00 +0000122 }
123
Leon Clarkee46be812010-01-19 14:06:41 +0000124 // Construct an array for the elements.
Ben Murdoch086aeea2011-05-13 15:57:08 +0100125 var elements = new $Array(length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000126
Steve Blockd0582a62009-12-15 09:54:21 +0000127 // We pull the empty separator check outside the loop for speed!
128 if (separator.length == 0) {
Ben Murdochb8e0da22011-05-16 14:20:40 +0100129 var elements_length = 0;
Steve Blockd0582a62009-12-15 09:54:21 +0000130 for (var i = 0; i < length; i++) {
131 var e = array[i];
Ben Murdoch086aeea2011-05-13 15:57:08 +0100132 if (!IS_UNDEFINED(e)) {
Leon Clarkee46be812010-01-19 14:06:41 +0000133 if (!IS_STRING(e)) e = convert(e);
134 elements[elements_length++] = e;
Steve Blockd0582a62009-12-15 09:54:21 +0000135 }
136 }
Ben Murdoch086aeea2011-05-13 15:57:08 +0100137 elements.length = elements_length;
138 var result = %_FastAsciiArrayJoin(elements, '');
139 if (!IS_UNDEFINED(result)) return result;
140 return %StringBuilderConcat(elements, elements_length, '');
141 }
Ben Murdochb8e0da22011-05-16 14:20:40 +0100142 // Non-empty separator case.
143 // If the first element is a number then use the heuristic that the
144 // remaining elements are also likely to be numbers.
145 if (!IS_NUMBER(array[0])) {
146 for (var i = 0; i < length; i++) {
147 var e = array[i];
Ben Murdoch086aeea2011-05-13 15:57:08 +0100148 if (!IS_STRING(e)) e = convert(e);
149 elements[i] = e;
Steve Blocka7e24c12009-10-30 11:49:00 +0000150 }
Ben Murdochb8e0da22011-05-16 14:20:40 +0100151 } else {
152 for (var i = 0; i < length; i++) {
153 var e = array[i];
154 if (IS_NUMBER(e)) elements[i] = %_NumberToString(e);
155 else {
156 if (!IS_STRING(e)) e = convert(e);
157 elements[i] = e;
158 }
159 }
160 }
Ben Murdoch086aeea2011-05-13 15:57:08 +0100161 var result = %_FastAsciiArrayJoin(elements, separator);
162 if (!IS_UNDEFINED(result)) return result;
163
164 var length2 = (length << 1) - 1;
165 var j = length2;
166 var i = length;
167 elements[--j] = elements[--i];
168 while (i > 0) {
169 elements[--j] = separator;
170 elements[--j] = elements[--i];
171 }
172 return %StringBuilderConcat(elements, length2, '');
Steve Blocka7e24c12009-10-30 11:49:00 +0000173 } finally {
174 // Make sure to pop the visited array no matter what happens.
175 if (is_array) visited_arrays.pop();
176 }
177}
178
179
Ben Murdochb0fe1622011-05-05 13:52:32 +0100180function ConvertToString(x) {
Ben Murdoch086aeea2011-05-13 15:57:08 +0100181 // Assumes x is a non-string.
Ben Murdochb0fe1622011-05-05 13:52:32 +0100182 if (IS_NUMBER(x)) return %_NumberToString(x);
183 if (IS_BOOLEAN(x)) return x ? 'true' : 'false';
184 return (IS_NULL_OR_UNDEFINED(x)) ? '' : %ToString(%DefaultString(x));
Steve Blocka7e24c12009-10-30 11:49:00 +0000185}
186
187
188function ConvertToLocaleString(e) {
Leon Clarkee46be812010-01-19 14:06:41 +0000189 if (e == null) {
190 return '';
191 } else {
Steve Blocka7e24c12009-10-30 11:49:00 +0000192 // e_obj's toLocaleString might be overwritten, check if it is a function.
193 // Call ToString if toLocaleString is not a function.
194 // See issue 877615.
195 var e_obj = ToObject(e);
196 if (IS_FUNCTION(e_obj.toLocaleString))
Steve Blockd0582a62009-12-15 09:54:21 +0000197 return ToString(e_obj.toLocaleString());
Steve Blocka7e24c12009-10-30 11:49:00 +0000198 else
199 return ToString(e);
200 }
201}
202
203
204// This function implements the optimized splice implementation that can use
205// special array operations to handle sparse arrays in a sensible fashion.
206function SmartSlice(array, start_i, del_count, len, deleted_elements) {
207 // Move deleted elements to a new array (the return value from splice).
208 // Intervals array can contain keys and intervals. See comment in Concat.
209 var intervals = %GetArrayKeys(array, start_i + del_count);
210 var length = intervals.length;
211 for (var k = 0; k < length; k++) {
212 var key = intervals[k];
213 if (key < 0) {
214 var j = -1 - key;
215 var interval_limit = j + intervals[++k];
216 if (j < start_i) {
217 j = start_i;
218 }
219 for (; j < interval_limit; j++) {
220 // ECMA-262 15.4.4.12 line 10. The spec could also be
221 // interpreted such that %HasLocalProperty would be the
222 // appropriate test. We follow KJS in consulting the
223 // prototype.
224 var current = array[j];
225 if (!IS_UNDEFINED(current) || j in array) {
226 deleted_elements[j - start_i] = current;
227 }
228 }
229 } else {
230 if (!IS_UNDEFINED(key)) {
231 if (key >= start_i) {
232 // ECMA-262 15.4.4.12 line 10. The spec could also be
233 // interpreted such that %HasLocalProperty would be the
234 // appropriate test. We follow KJS in consulting the
235 // prototype.
236 var current = array[key];
237 if (!IS_UNDEFINED(current) || key in array) {
238 deleted_elements[key - start_i] = current;
239 }
240 }
241 }
242 }
243 }
244}
245
246
247// This function implements the optimized splice implementation that can use
248// special array operations to handle sparse arrays in a sensible fashion.
249function SmartMove(array, start_i, del_count, len, num_additional_args) {
250 // Move data to new array.
251 var new_array = new $Array(len - del_count + num_additional_args);
252 var intervals = %GetArrayKeys(array, len);
253 var length = intervals.length;
254 for (var k = 0; k < length; k++) {
255 var key = intervals[k];
256 if (key < 0) {
257 var j = -1 - key;
258 var interval_limit = j + intervals[++k];
259 while (j < start_i && j < interval_limit) {
260 // The spec could also be interpreted such that
261 // %HasLocalProperty would be the appropriate test. We follow
262 // KJS in consulting the prototype.
263 var current = array[j];
264 if (!IS_UNDEFINED(current) || j in array) {
265 new_array[j] = current;
266 }
267 j++;
268 }
269 j = start_i + del_count;
270 while (j < interval_limit) {
271 // ECMA-262 15.4.4.12 lines 24 and 41. The spec could also be
272 // interpreted such that %HasLocalProperty would be the
273 // appropriate test. We follow KJS in consulting the
274 // prototype.
275 var current = array[j];
276 if (!IS_UNDEFINED(current) || j in array) {
277 new_array[j - del_count + num_additional_args] = current;
278 }
279 j++;
280 }
281 } else {
282 if (!IS_UNDEFINED(key)) {
283 if (key < start_i) {
284 // The spec could also be interpreted such that
285 // %HasLocalProperty would be the appropriate test. We follow
286 // KJS in consulting the prototype.
287 var current = array[key];
288 if (!IS_UNDEFINED(current) || key in array) {
289 new_array[key] = current;
290 }
291 } else if (key >= start_i + del_count) {
292 // ECMA-262 15.4.4.12 lines 24 and 41. The spec could also
293 // be interpreted such that %HasLocalProperty would be the
294 // appropriate test. We follow KJS in consulting the
295 // prototype.
296 var current = array[key];
297 if (!IS_UNDEFINED(current) || key in array) {
298 new_array[key - del_count + num_additional_args] = current;
299 }
300 }
301 }
302 }
303 }
304 // Move contents of new_array into this array
305 %MoveArrayContents(new_array, array);
306}
307
308
309// This is part of the old simple-minded splice. We are using it either
310// because the receiver is not an array (so we have no choice) or because we
311// know we are not deleting or moving a lot of elements.
312function SimpleSlice(array, start_i, del_count, len, deleted_elements) {
313 for (var i = 0; i < del_count; i++) {
314 var index = start_i + i;
315 // The spec could also be interpreted such that %HasLocalProperty
316 // would be the appropriate test. We follow KJS in consulting the
317 // prototype.
318 var current = array[index];
319 if (!IS_UNDEFINED(current) || index in array)
320 deleted_elements[i] = current;
321 }
322}
323
324
325function SimpleMove(array, start_i, del_count, len, num_additional_args) {
326 if (num_additional_args !== del_count) {
327 // Move the existing elements after the elements to be deleted
328 // to the right position in the resulting array.
329 if (num_additional_args > del_count) {
330 for (var i = len - del_count; i > start_i; i--) {
331 var from_index = i + del_count - 1;
332 var to_index = i + num_additional_args - 1;
333 // The spec could also be interpreted such that
334 // %HasLocalProperty would be the appropriate test. We follow
335 // KJS in consulting the prototype.
336 var current = array[from_index];
337 if (!IS_UNDEFINED(current) || from_index in array) {
338 array[to_index] = current;
339 } else {
340 delete array[to_index];
341 }
342 }
343 } else {
344 for (var i = start_i; i < len - del_count; i++) {
345 var from_index = i + del_count;
346 var to_index = i + num_additional_args;
347 // The spec could also be interpreted such that
348 // %HasLocalProperty would be the appropriate test. We follow
349 // KJS in consulting the prototype.
350 var current = array[from_index];
351 if (!IS_UNDEFINED(current) || from_index in array) {
352 array[to_index] = current;
353 } else {
354 delete array[to_index];
355 }
356 }
357 for (var i = len; i > len - del_count + num_additional_args; i--) {
358 delete array[i - 1];
359 }
360 }
361 }
362}
363
364
365// -------------------------------------------------------------------
366
367
368function ArrayToString() {
369 if (!IS_ARRAY(this)) {
370 throw new $TypeError('Array.prototype.toString is not generic');
371 }
372 return Join(this, this.length, ',', ConvertToString);
373}
374
375
376function ArrayToLocaleString() {
377 if (!IS_ARRAY(this)) {
378 throw new $TypeError('Array.prototype.toString is not generic');
379 }
380 return Join(this, this.length, ',', ConvertToLocaleString);
381}
382
383
384function ArrayJoin(separator) {
Leon Clarkee46be812010-01-19 14:06:41 +0000385 if (IS_UNDEFINED(separator)) {
386 separator = ',';
387 } else if (!IS_STRING(separator)) {
Ben Murdochb0fe1622011-05-05 13:52:32 +0100388 separator = NonStringToString(separator);
Leon Clarkee46be812010-01-19 14:06:41 +0000389 }
Shimeng (Simon) Wang8a31eba2010-12-06 19:01:33 -0800390
391 var result = %_FastAsciiArrayJoin(this, separator);
Ben Murdochb0fe1622011-05-05 13:52:32 +0100392 if (!IS_UNDEFINED(result)) return result;
Shimeng (Simon) Wang8a31eba2010-12-06 19:01:33 -0800393
Ben Murdochb0fe1622011-05-05 13:52:32 +0100394 return Join(this, TO_UINT32(this.length), separator, ConvertToString);
Steve Blocka7e24c12009-10-30 11:49:00 +0000395}
396
397
398// Removes the last element from the array and returns it. See
399// ECMA-262, section 15.4.4.6.
400function ArrayPop() {
Leon Clarkee46be812010-01-19 14:06:41 +0000401 var n = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000402 if (n == 0) {
403 this.length = n;
404 return;
405 }
406 n--;
407 var value = this[n];
408 this.length = n;
409 delete this[n];
410 return value;
411}
412
413
414// Appends the arguments to the end of the array and returns the new
415// length of the array. See ECMA-262, section 15.4.4.7.
416function ArrayPush() {
Leon Clarkee46be812010-01-19 14:06:41 +0000417 var n = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000418 var m = %_ArgumentsLength();
419 for (var i = 0; i < m; i++) {
420 this[i+n] = %_Arguments(i);
421 }
422 this.length = n + m;
423 return this.length;
424}
425
426
427function ArrayConcat(arg1) { // length == 1
428 // TODO: can we just use arguments?
429 var arg_count = %_ArgumentsLength();
430 var arrays = new $Array(1 + arg_count);
431 arrays[0] = this;
432 for (var i = 0; i < arg_count; i++) {
433 arrays[i + 1] = %_Arguments(i);
434 }
435
436 return %ArrayConcat(arrays);
437}
438
439
440// For implementing reverse() on large, sparse arrays.
441function SparseReverse(array, len) {
442 var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
443 var high_counter = keys.length - 1;
444 var low_counter = 0;
445 while (low_counter <= high_counter) {
446 var i = keys[low_counter];
447 var j = keys[high_counter];
448
449 var j_complement = len - j - 1;
450 var low, high;
451
452 if (j_complement <= i) {
453 high = j;
454 while (keys[--high_counter] == j);
455 low = j_complement;
456 }
457 if (j_complement >= i) {
458 low = i;
459 while (keys[++low_counter] == i);
460 high = len - i - 1;
461 }
462
463 var current_i = array[low];
464 if (!IS_UNDEFINED(current_i) || low in array) {
465 var current_j = array[high];
466 if (!IS_UNDEFINED(current_j) || high in array) {
467 array[low] = current_j;
468 array[high] = current_i;
469 } else {
470 array[high] = current_i;
471 delete array[low];
472 }
473 } else {
474 var current_j = array[high];
475 if (!IS_UNDEFINED(current_j) || high in array) {
476 array[low] = current_j;
477 delete array[high];
478 }
479 }
480 }
481}
482
483
484function ArrayReverse() {
Leon Clarkee46be812010-01-19 14:06:41 +0000485 var j = TO_UINT32(this.length) - 1;
Steve Blocka7e24c12009-10-30 11:49:00 +0000486
487 if (UseSparseVariant(this, j, IS_ARRAY(this))) {
488 SparseReverse(this, j+1);
489 return this;
490 }
491
492 for (var i = 0; i < j; i++, j--) {
493 var current_i = this[i];
494 if (!IS_UNDEFINED(current_i) || i in this) {
495 var current_j = this[j];
496 if (!IS_UNDEFINED(current_j) || j in this) {
497 this[i] = current_j;
498 this[j] = current_i;
499 } else {
500 this[j] = current_i;
501 delete this[i];
502 }
503 } else {
504 var current_j = this[j];
505 if (!IS_UNDEFINED(current_j) || j in this) {
506 this[i] = current_j;
507 delete this[j];
508 }
509 }
510 }
511 return this;
512}
513
514
515function ArrayShift() {
Leon Clarkee46be812010-01-19 14:06:41 +0000516 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000517
518 if (len === 0) {
519 this.length = 0;
520 return;
521 }
522
523 var first = this[0];
524
525 if (IS_ARRAY(this))
526 SmartMove(this, 0, 1, len, 0);
527 else
528 SimpleMove(this, 0, 1, len, 0);
529
530 this.length = len - 1;
531
532 return first;
533}
534
535
536function ArrayUnshift(arg1) { // length == 1
Leon Clarkee46be812010-01-19 14:06:41 +0000537 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000538 var num_arguments = %_ArgumentsLength();
539
540 if (IS_ARRAY(this))
541 SmartMove(this, 0, 0, len, num_arguments);
542 else
543 SimpleMove(this, 0, 0, len, num_arguments);
544
545 for (var i = 0; i < num_arguments; i++) {
546 this[i] = %_Arguments(i);
547 }
548
549 this.length = len + num_arguments;
550
551 return len + num_arguments;
552}
553
554
555function ArraySlice(start, end) {
Leon Clarkee46be812010-01-19 14:06:41 +0000556 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000557 var start_i = TO_INTEGER(start);
558 var end_i = len;
559
560 if (end !== void 0) end_i = TO_INTEGER(end);
561
562 if (start_i < 0) {
563 start_i += len;
564 if (start_i < 0) start_i = 0;
565 } else {
566 if (start_i > len) start_i = len;
567 }
568
569 if (end_i < 0) {
570 end_i += len;
571 if (end_i < 0) end_i = 0;
572 } else {
573 if (end_i > len) end_i = len;
574 }
575
576 var result = [];
577
578 if (end_i < start_i) return result;
579
580 if (IS_ARRAY(this)) {
581 SmartSlice(this, start_i, end_i - start_i, len, result);
582 } else {
583 SimpleSlice(this, start_i, end_i - start_i, len, result);
584 }
585
586 result.length = end_i - start_i;
587
588 return result;
589}
590
591
592function ArraySplice(start, delete_count) {
593 var num_arguments = %_ArgumentsLength();
594
Leon Clarkee46be812010-01-19 14:06:41 +0000595 var len = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000596 var start_i = TO_INTEGER(start);
597
598 if (start_i < 0) {
599 start_i += len;
600 if (start_i < 0) start_i = 0;
601 } else {
602 if (start_i > len) start_i = len;
603 }
604
Andrei Popescu402d9372010-02-26 13:31:12 +0000605 // SpiderMonkey, TraceMonkey and JSC treat the case where no delete count is
Steve Blocka7e24c12009-10-30 11:49:00 +0000606 // given differently from when an undefined delete count is given.
607 // This does not follow ECMA-262, but we do the same for
608 // compatibility.
609 var del_count = 0;
610 if (num_arguments > 1) {
611 del_count = TO_INTEGER(delete_count);
612 if (del_count < 0) del_count = 0;
613 if (del_count > len - start_i) del_count = len - start_i;
614 } else {
615 del_count = len - start_i;
616 }
617
618 var deleted_elements = [];
619 deleted_elements.length = del_count;
620
621 // Number of elements to add.
622 var num_additional_args = 0;
623 if (num_arguments > 2) {
624 num_additional_args = num_arguments - 2;
625 }
626
627 var use_simple_splice = true;
628
629 if (IS_ARRAY(this) && num_additional_args !== del_count) {
630 // If we are only deleting/moving a few things near the end of the
631 // array then the simple version is going to be faster, because it
632 // doesn't touch most of the array.
633 var estimated_non_hole_elements = %EstimateNumberOfElements(this);
634 if (len > 20 && (estimated_non_hole_elements >> 2) < (len - start_i)) {
635 use_simple_splice = false;
636 }
637 }
638
639 if (use_simple_splice) {
640 SimpleSlice(this, start_i, del_count, len, deleted_elements);
641 SimpleMove(this, start_i, del_count, len, num_additional_args);
642 } else {
643 SmartSlice(this, start_i, del_count, len, deleted_elements);
644 SmartMove(this, start_i, del_count, len, num_additional_args);
645 }
646
647 // Insert the arguments into the resulting array in
648 // place of the deleted elements.
649 var i = start_i;
650 var arguments_index = 2;
651 var arguments_length = %_ArgumentsLength();
652 while (arguments_index < arguments_length) {
653 this[i++] = %_Arguments(arguments_index++);
654 }
655 this.length = len - del_count + num_additional_args;
656
657 // Return the deleted elements.
658 return deleted_elements;
659}
660
661
662function ArraySort(comparefn) {
663 // In-place QuickSort algorithm.
664 // For short (length <= 22) arrays, insertion sort is used for efficiency.
665
Steve Block6ded16b2010-05-10 14:33:55 +0100666 if (!IS_FUNCTION(comparefn)) {
667 comparefn = function (x, y) {
668 if (x === y) return 0;
669 if (%_IsSmi(x) && %_IsSmi(y)) {
670 return %SmiLexicographicCompare(x, y);
671 }
672 x = ToString(x);
673 y = ToString(y);
674 if (x == y) return 0;
675 else return x < y ? -1 : 1;
676 };
677 }
678 var global_receiver = %GetGlobalReceiver();
Steve Blocka7e24c12009-10-30 11:49:00 +0000679
680 function InsertionSort(a, from, to) {
681 for (var i = from + 1; i < to; i++) {
682 var element = a[i];
Steve Block6ded16b2010-05-10 14:33:55 +0100683 for (var j = i - 1; j >= from; j--) {
684 var tmp = a[j];
685 var order = %_CallFunction(global_receiver, tmp, element, comparefn);
686 if (order > 0) {
687 a[j + 1] = tmp;
688 } else {
Steve Blocka7e24c12009-10-30 11:49:00 +0000689 break;
690 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000691 }
Steve Block6ded16b2010-05-10 14:33:55 +0100692 a[j + 1] = element;
Steve Blocka7e24c12009-10-30 11:49:00 +0000693 }
694 }
695
696 function QuickSort(a, from, to) {
697 // Insertion sort is faster for short arrays.
Ben Murdochb0fe1622011-05-05 13:52:32 +0100698 if (to - from <= 10) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000699 InsertionSort(a, from, to);
700 return;
701 }
Ben Murdochb0fe1622011-05-05 13:52:32 +0100702 // Find a pivot as the median of first, last and middle element.
703 var v0 = a[from];
704 var v1 = a[to - 1];
705 var middle_index = from + ((to - from) >> 1);
706 var v2 = a[middle_index];
707 var c01 = %_CallFunction(global_receiver, v0, v1, comparefn);
708 if (c01 > 0) {
709 // v1 < v0, so swap them.
710 var tmp = v0;
711 v0 = v1;
712 v1 = tmp;
713 } // v0 <= v1.
714 var c02 = %_CallFunction(global_receiver, v0, v2, comparefn);
715 if (c02 >= 0) {
716 // v2 <= v0 <= v1.
717 var tmp = v0;
718 v0 = v2;
719 v2 = v1;
720 v1 = tmp;
721 } else {
722 // v0 <= v1 && v0 < v2
723 var c12 = %_CallFunction(global_receiver, v1, v2, comparefn);
724 if (c12 > 0) {
725 // v0 <= v2 < v1
726 var tmp = v1;
727 v1 = v2;
728 v2 = tmp;
729 }
730 }
731 // v0 <= v1 <= v2
732 a[from] = v0;
733 a[to - 1] = v2;
734 var pivot = v1;
735 var low_end = from + 1; // Upper bound of elements lower than pivot.
736 var high_start = to - 1; // Lower bound of elements greater than pivot.
737 a[middle_index] = a[low_end];
738 a[low_end] = pivot;
739
Steve Blocka7e24c12009-10-30 11:49:00 +0000740 // From low_end to i are elements equal to pivot.
741 // From i to high_start are elements that haven't been compared yet.
Ben Murdochb0fe1622011-05-05 13:52:32 +0100742 partition: for (var i = low_end + 1; i < high_start; i++) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000743 var element = a[i];
Steve Block6ded16b2010-05-10 14:33:55 +0100744 var order = %_CallFunction(global_receiver, element, pivot, comparefn);
Steve Blocka7e24c12009-10-30 11:49:00 +0000745 if (order < 0) {
Steve Block6ded16b2010-05-10 14:33:55 +0100746 %_SwapElements(a, i, low_end);
Steve Blocka7e24c12009-10-30 11:49:00 +0000747 low_end++;
748 } else if (order > 0) {
Ben Murdochb0fe1622011-05-05 13:52:32 +0100749 do {
750 high_start--;
751 if (high_start == i) break partition;
752 var top_elem = a[high_start];
753 order = %_CallFunction(global_receiver, top_elem, pivot, comparefn);
754 } while (order > 0);
Steve Block6ded16b2010-05-10 14:33:55 +0100755 %_SwapElements(a, i, high_start);
Ben Murdochb0fe1622011-05-05 13:52:32 +0100756 if (order < 0) {
757 %_SwapElements(a, i, low_end);
758 low_end++;
759 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000760 }
761 }
762 QuickSort(a, from, low_end);
763 QuickSort(a, high_start, to);
764 }
765
Ben Murdochb0fe1622011-05-05 13:52:32 +0100766 // Copy elements in the range 0..length from obj's prototype chain
767 // to obj itself, if obj has holes. Return one more than the maximal index
Steve Blocka7e24c12009-10-30 11:49:00 +0000768 // of a prototype property.
769 function CopyFromPrototype(obj, length) {
770 var max = 0;
771 for (var proto = obj.__proto__; proto; proto = proto.__proto__) {
772 var indices = %GetArrayKeys(proto, length);
773 if (indices.length > 0) {
774 if (indices[0] == -1) {
775 // It's an interval.
776 var proto_length = indices[1];
777 for (var i = 0; i < proto_length; i++) {
778 if (!obj.hasOwnProperty(i) && proto.hasOwnProperty(i)) {
779 obj[i] = proto[i];
780 if (i >= max) { max = i + 1; }
781 }
782 }
783 } else {
784 for (var i = 0; i < indices.length; i++) {
785 var index = indices[i];
786 if (!IS_UNDEFINED(index) &&
787 !obj.hasOwnProperty(index) && proto.hasOwnProperty(index)) {
788 obj[index] = proto[index];
789 if (index >= max) { max = index + 1; }
790 }
791 }
792 }
793 }
794 }
795 return max;
796 }
797
798 // Set a value of "undefined" on all indices in the range from..to
799 // where a prototype of obj has an element. I.e., shadow all prototype
800 // elements in that range.
801 function ShadowPrototypeElements(obj, from, to) {
802 for (var proto = obj.__proto__; proto; proto = proto.__proto__) {
803 var indices = %GetArrayKeys(proto, to);
804 if (indices.length > 0) {
805 if (indices[0] == -1) {
806 // It's an interval.
807 var proto_length = indices[1];
808 for (var i = from; i < proto_length; i++) {
809 if (proto.hasOwnProperty(i)) {
810 obj[i] = void 0;
811 }
812 }
813 } else {
814 for (var i = 0; i < indices.length; i++) {
815 var index = indices[i];
816 if (!IS_UNDEFINED(index) && from <= index &&
817 proto.hasOwnProperty(index)) {
818 obj[index] = void 0;
819 }
820 }
821 }
822 }
823 }
824 }
825
826 function SafeRemoveArrayHoles(obj) {
827 // Copy defined elements from the end to fill in all holes and undefineds
828 // in the beginning of the array. Write undefineds and holes at the end
829 // after loop is finished.
830 var first_undefined = 0;
831 var last_defined = length - 1;
832 var num_holes = 0;
833 while (first_undefined < last_defined) {
834 // Find first undefined element.
835 while (first_undefined < last_defined &&
836 !IS_UNDEFINED(obj[first_undefined])) {
837 first_undefined++;
838 }
839 // Maintain the invariant num_holes = the number of holes in the original
840 // array with indices <= first_undefined or > last_defined.
841 if (!obj.hasOwnProperty(first_undefined)) {
842 num_holes++;
843 }
844
845 // Find last defined element.
846 while (first_undefined < last_defined &&
847 IS_UNDEFINED(obj[last_defined])) {
848 if (!obj.hasOwnProperty(last_defined)) {
849 num_holes++;
850 }
851 last_defined--;
852 }
853 if (first_undefined < last_defined) {
854 // Fill in hole or undefined.
855 obj[first_undefined] = obj[last_defined];
856 obj[last_defined] = void 0;
857 }
858 }
859 // If there were any undefineds in the entire array, first_undefined
860 // points to one past the last defined element. Make this true if
861 // there were no undefineds, as well, so that first_undefined == number
862 // of defined elements.
863 if (!IS_UNDEFINED(obj[first_undefined])) first_undefined++;
864 // Fill in the undefineds and the holes. There may be a hole where
865 // an undefined should be and vice versa.
866 var i;
867 for (i = first_undefined; i < length - num_holes; i++) {
868 obj[i] = void 0;
869 }
870 for (i = length - num_holes; i < length; i++) {
871 // For compatability with Webkit, do not expose elements in the prototype.
872 if (i in obj.__proto__) {
873 obj[i] = void 0;
874 } else {
875 delete obj[i];
876 }
877 }
878
879 // Return the number of defined elements.
880 return first_undefined;
881 }
882
Steve Block6ded16b2010-05-10 14:33:55 +0100883 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000884 if (length < 2) return this;
885
886 var is_array = IS_ARRAY(this);
887 var max_prototype_element;
888 if (!is_array) {
889 // For compatibility with JSC, we also sort elements inherited from
890 // the prototype chain on non-Array objects.
891 // We do this by copying them to this object and sorting only
892 // local elements. This is not very efficient, but sorting with
893 // inherited elements happens very, very rarely, if at all.
894 // The specification allows "implementation dependent" behavior
895 // if an element on the prototype chain has an element that
896 // might interact with sorting.
897 max_prototype_element = CopyFromPrototype(this, length);
898 }
899
900 var num_non_undefined = %RemoveArrayHoles(this, length);
901 if (num_non_undefined == -1) {
902 // There were indexed accessors in the array. Move array holes and
903 // undefineds to the end using a Javascript function that is safe
904 // in the presence of accessors.
905 num_non_undefined = SafeRemoveArrayHoles(this);
906 }
907
908 QuickSort(this, 0, num_non_undefined);
909
910 if (!is_array && (num_non_undefined + 1 < max_prototype_element)) {
911 // For compatibility with JSC, we shadow any elements in the prototype
912 // chain that has become exposed by sort moving a hole to its position.
913 ShadowPrototypeElements(this, num_non_undefined, max_prototype_element);
914 }
915
916 return this;
917}
918
919
920// The following functions cannot be made efficient on sparse arrays while
921// preserving the semantics, since the calls to the receiver function can add
922// or delete elements from the array.
923function ArrayFilter(f, receiver) {
924 if (!IS_FUNCTION(f)) {
925 throw MakeTypeError('called_non_callable', [ f ]);
926 }
927 // Pull out the length so that modifications to the length in the
928 // loop will not affect the looping.
929 var length = this.length;
930 var result = [];
931 var result_length = 0;
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)) result[result_length++] = current;
936 }
937 }
938 return result;
939}
940
941
942function ArrayForEach(f, receiver) {
943 if (!IS_FUNCTION(f)) {
944 throw MakeTypeError('called_non_callable', [ f ]);
945 }
946 // Pull out the length so that modifications to the length in the
947 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000948 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000949 for (var i = 0; i < length; i++) {
950 var current = this[i];
951 if (!IS_UNDEFINED(current) || i in this) {
952 f.call(receiver, current, i, this);
953 }
954 }
955}
956
957
958// Executes the function once for each element present in the
959// array until it finds one where callback returns true.
960function ArraySome(f, receiver) {
961 if (!IS_FUNCTION(f)) {
962 throw MakeTypeError('called_non_callable', [ f ]);
963 }
964 // Pull out the length so that modifications to the length in the
965 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000966 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000967 for (var i = 0; i < length; i++) {
968 var current = this[i];
969 if (!IS_UNDEFINED(current) || i in this) {
970 if (f.call(receiver, current, i, this)) return true;
971 }
972 }
973 return false;
974}
975
976
977function ArrayEvery(f, receiver) {
978 if (!IS_FUNCTION(f)) {
979 throw MakeTypeError('called_non_callable', [ f ]);
980 }
981 // Pull out the length so that modifications to the length in the
982 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000983 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +0000984 for (var i = 0; i < length; i++) {
985 var current = this[i];
986 if (!IS_UNDEFINED(current) || i in this) {
987 if (!f.call(receiver, current, i, this)) return false;
988 }
989 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000990 return true;
991}
992
Steve Blocka7e24c12009-10-30 11:49:00 +0000993function ArrayMap(f, receiver) {
994 if (!IS_FUNCTION(f)) {
995 throw MakeTypeError('called_non_callable', [ f ]);
996 }
997 // Pull out the length so that modifications to the length in the
998 // loop will not affect the looping.
Leon Clarkee46be812010-01-19 14:06:41 +0000999 var length = TO_UINT32(this.length);
Steve Blocka7e24c12009-10-30 11:49:00 +00001000 var result = new $Array(length);
1001 for (var i = 0; i < length; i++) {
1002 var current = this[i];
1003 if (!IS_UNDEFINED(current) || i in this) {
1004 result[i] = f.call(receiver, current, i, this);
1005 }
1006 }
1007 return result;
1008}
1009
1010
1011function ArrayIndexOf(element, index) {
Kristian Monsen80d68ea2010-09-08 11:05:35 +01001012 var length = TO_UINT32(this.length);
1013 if (length == 0) return -1;
Steve Block8defd9f2010-07-08 12:39:36 +01001014 if (IS_UNDEFINED(index)) {
Steve Blocka7e24c12009-10-30 11:49:00 +00001015 index = 0;
1016 } else {
1017 index = TO_INTEGER(index);
1018 // If index is negative, index from the end of the array.
1019 if (index < 0) index = length + index;
1020 // If index is still negative, search the entire array.
1021 if (index < 0) index = 0;
1022 }
Steve Block59151502010-09-22 15:07:15 +01001023 var min = index;
1024 var max = length;
1025 if (UseSparseVariant(this, length, true)) {
1026 var intervals = %GetArrayKeys(this, length);
1027 if (intervals.length == 2 && intervals[0] < 0) {
1028 // A single interval.
1029 var intervalMin = -(intervals[0] + 1);
1030 var intervalMax = intervalMin + intervals[1];
1031 min = MAX(min, intervalMin);
1032 max = intervalMax; // Capped by length already.
1033 // Fall through to loop below.
1034 } else {
1035 if (intervals.length == 0) return -1;
1036 // Get all the keys in sorted order.
1037 var sortedKeys = GetSortedArrayKeys(this, intervals);
1038 var n = sortedKeys.length;
1039 var i = 0;
1040 while (i < n && sortedKeys[i] < index) i++;
1041 while (i < n) {
1042 var key = sortedKeys[i];
1043 if (!IS_UNDEFINED(key) && this[key] === element) return key;
1044 i++;
1045 }
1046 return -1;
1047 }
1048 }
Kristian Monsen80d68ea2010-09-08 11:05:35 +01001049 // Lookup through the array.
Steve Block6ded16b2010-05-10 14:33:55 +01001050 if (!IS_UNDEFINED(element)) {
Steve Block59151502010-09-22 15:07:15 +01001051 for (var i = min; i < max; i++) {
Steve Block6ded16b2010-05-10 14:33:55 +01001052 if (this[i] === element) return i;
1053 }
1054 return -1;
1055 }
Steve Block59151502010-09-22 15:07:15 +01001056 // Lookup through the array.
1057 for (var i = min; i < max; i++) {
Steve Block6ded16b2010-05-10 14:33:55 +01001058 if (IS_UNDEFINED(this[i]) && i in this) {
1059 return i;
Steve Blocka7e24c12009-10-30 11:49:00 +00001060 }
1061 }
1062 return -1;
1063}
1064
1065
1066function ArrayLastIndexOf(element, index) {
Kristian Monsen80d68ea2010-09-08 11:05:35 +01001067 var length = TO_UINT32(this.length);
1068 if (length == 0) return -1;
Steve Block8defd9f2010-07-08 12:39:36 +01001069 if (%_ArgumentsLength() < 2) {
Steve Blocka7e24c12009-10-30 11:49:00 +00001070 index = length - 1;
1071 } else {
1072 index = TO_INTEGER(index);
1073 // If index is negative, index from end of the array.
Steve Block59151502010-09-22 15:07:15 +01001074 if (index < 0) index += length;
Steve Blocka7e24c12009-10-30 11:49:00 +00001075 // If index is still negative, do not search the array.
Steve Block59151502010-09-22 15:07:15 +01001076 if (index < 0) return -1;
Steve Blocka7e24c12009-10-30 11:49:00 +00001077 else if (index >= length) index = length - 1;
1078 }
Steve Block59151502010-09-22 15:07:15 +01001079 var min = 0;
1080 var max = index;
1081 if (UseSparseVariant(this, length, true)) {
1082 var intervals = %GetArrayKeys(this, index + 1);
1083 if (intervals.length == 2 && intervals[0] < 0) {
1084 // A single interval.
1085 var intervalMin = -(intervals[0] + 1);
1086 var intervalMax = intervalMin + intervals[1];
1087 min = MAX(min, intervalMin);
1088 max = intervalMax; // Capped by index already.
1089 // Fall through to loop below.
1090 } else {
1091 if (intervals.length == 0) return -1;
1092 // Get all the keys in sorted order.
1093 var sortedKeys = GetSortedArrayKeys(this, intervals);
1094 var i = sortedKeys.length - 1;
1095 while (i >= 0) {
1096 var key = sortedKeys[i];
1097 if (!IS_UNDEFINED(key) && this[key] === element) return key;
1098 i--;
1099 }
1100 return -1;
1101 }
1102 }
Steve Blocka7e24c12009-10-30 11:49:00 +00001103 // Lookup through the array.
Steve Block6ded16b2010-05-10 14:33:55 +01001104 if (!IS_UNDEFINED(element)) {
Steve Block59151502010-09-22 15:07:15 +01001105 for (var i = max; i >= min; i--) {
Steve Block6ded16b2010-05-10 14:33:55 +01001106 if (this[i] === element) return i;
1107 }
1108 return -1;
1109 }
Steve Block59151502010-09-22 15:07:15 +01001110 for (var i = max; i >= min; i--) {
Steve Block6ded16b2010-05-10 14:33:55 +01001111 if (IS_UNDEFINED(this[i]) && i in this) {
1112 return i;
Steve Blocka7e24c12009-10-30 11:49:00 +00001113 }
1114 }
1115 return -1;
1116}
1117
1118
1119function ArrayReduce(callback, current) {
1120 if (!IS_FUNCTION(callback)) {
1121 throw MakeTypeError('called_non_callable', [callback]);
1122 }
1123 // Pull out the length so that modifications to the length in the
1124 // loop will not affect the looping.
1125 var length = this.length;
1126 var i = 0;
1127
1128 find_initial: if (%_ArgumentsLength() < 2) {
1129 for (; i < length; i++) {
1130 current = this[i];
1131 if (!IS_UNDEFINED(current) || i in this) {
1132 i++;
1133 break find_initial;
1134 }
1135 }
1136 throw MakeTypeError('reduce_no_initial', []);
1137 }
1138
1139 for (; i < length; i++) {
1140 var element = this[i];
1141 if (!IS_UNDEFINED(element) || i in this) {
1142 current = callback.call(null, current, element, i, this);
1143 }
1144 }
1145 return current;
1146}
1147
1148function ArrayReduceRight(callback, current) {
1149 if (!IS_FUNCTION(callback)) {
1150 throw MakeTypeError('called_non_callable', [callback]);
1151 }
1152 var i = this.length - 1;
1153
1154 find_initial: if (%_ArgumentsLength() < 2) {
1155 for (; i >= 0; i--) {
1156 current = this[i];
1157 if (!IS_UNDEFINED(current) || i in this) {
1158 i--;
1159 break find_initial;
1160 }
1161 }
1162 throw MakeTypeError('reduce_no_initial', []);
1163 }
1164
1165 for (; i >= 0; i--) {
1166 var element = this[i];
1167 if (!IS_UNDEFINED(element) || i in this) {
1168 current = callback.call(null, current, element, i, this);
1169 }
1170 }
1171 return current;
1172}
1173
Steve Block3ce2e202009-11-05 08:53:23 +00001174// ES5, 15.4.3.2
1175function ArrayIsArray(obj) {
1176 return IS_ARRAY(obj);
1177}
Steve Blocka7e24c12009-10-30 11:49:00 +00001178
Steve Blocka7e24c12009-10-30 11:49:00 +00001179
1180// -------------------------------------------------------------------
1181function SetupArray() {
1182 // Setup non-enumerable constructor property on the Array.prototype
1183 // object.
1184 %SetProperty($Array.prototype, "constructor", $Array, DONT_ENUM);
1185
Steve Block3ce2e202009-11-05 08:53:23 +00001186 // Setup non-enumerable functions on the Array object.
1187 InstallFunctions($Array, DONT_ENUM, $Array(
1188 "isArray", ArrayIsArray
1189 ));
1190
Steve Block6ded16b2010-05-10 14:33:55 +01001191 var specialFunctions = %SpecialArrayFunctions({});
1192
1193 function getFunction(name, jsBuiltin, len) {
1194 var f = jsBuiltin;
1195 if (specialFunctions.hasOwnProperty(name)) {
1196 f = specialFunctions[name];
1197 }
1198 if (!IS_UNDEFINED(len)) {
1199 %FunctionSetLength(f, len);
1200 }
1201 return f;
1202 }
1203
Steve Blocka7e24c12009-10-30 11:49:00 +00001204 // Setup non-enumerable functions of the Array.prototype object and
1205 // set their names.
Steve Blocka7e24c12009-10-30 11:49:00 +00001206 // Manipulate the length of some of the functions to meet
1207 // expectations set by ECMA-262 or Mozilla.
Steve Block6ded16b2010-05-10 14:33:55 +01001208 InstallFunctionsOnHiddenPrototype($Array.prototype, DONT_ENUM, $Array(
1209 "toString", getFunction("toString", ArrayToString),
1210 "toLocaleString", getFunction("toLocaleString", ArrayToLocaleString),
1211 "join", getFunction("join", ArrayJoin),
1212 "pop", getFunction("pop", ArrayPop),
1213 "push", getFunction("push", ArrayPush, 1),
1214 "concat", getFunction("concat", ArrayConcat, 1),
1215 "reverse", getFunction("reverse", ArrayReverse),
1216 "shift", getFunction("shift", ArrayShift),
1217 "unshift", getFunction("unshift", ArrayUnshift, 1),
1218 "slice", getFunction("slice", ArraySlice, 2),
1219 "splice", getFunction("splice", ArraySplice, 2),
1220 "sort", getFunction("sort", ArraySort),
1221 "filter", getFunction("filter", ArrayFilter, 1),
1222 "forEach", getFunction("forEach", ArrayForEach, 1),
1223 "some", getFunction("some", ArraySome, 1),
1224 "every", getFunction("every", ArrayEvery, 1),
1225 "map", getFunction("map", ArrayMap, 1),
1226 "indexOf", getFunction("indexOf", ArrayIndexOf, 1),
1227 "lastIndexOf", getFunction("lastIndexOf", ArrayLastIndexOf, 1),
1228 "reduce", getFunction("reduce", ArrayReduce, 1),
1229 "reduceRight", getFunction("reduceRight", ArrayReduceRight, 1)
1230 ));
1231
1232 %FinishArrayPrototypeSetup($Array.prototype);
Steve Blocka7e24c12009-10-30 11:49:00 +00001233}
1234
1235
1236SetupArray();