blob: 3355c94540ff94030a85d49836eafd09bddc1e88 [file] [log] [blame]
ager@chromium.org9258b6b2008-09-11 09:11:10 +00001// Copyright 2006-2008 the V8 project authors. All rights reserved.
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +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
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000034// 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.
ager@chromium.org9258b6b2008-09-11 09:11:10 +0000104 if (!%PushIfAbsent(visited_arrays, array)) return '';
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000105 }
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
ager@chromium.org9258b6b2008-09-11 09:11:10 +0000113 // 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
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000121 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 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000135}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000136
137
138function ConvertToString(e) {
139 if (e == null) return '';
140 else return ToString(e);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000141}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000142
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 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000156}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000157
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 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000199}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000200
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];
ager@chromium.org9258b6b2008-09-11 09:11:10 +0000219 if (!IS_UNDEFINED(current) || j in array) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000220 new_array[j] = current;
ager@chromium.org9258b6b2008-09-11 09:11:10 +0000221 }
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000222 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];
ager@chromium.org9258b6b2008-09-11 09:11:10 +0000231 if (!IS_UNDEFINED(current) || j in array) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000232 new_array[j - del_count + num_additional_args] = current;
ager@chromium.org9258b6b2008-09-11 09:11:10 +0000233 }
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000234 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];
ager@chromium.org9258b6b2008-09-11 09:11:10 +0000243 if (!IS_UNDEFINED(current) || key in array) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000244 new_array[key] = current;
ager@chromium.org9258b6b2008-09-11 09:11:10 +0000245 }
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000246 } 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];
ager@chromium.org9258b6b2008-09-11 09:11:10 +0000252 if (!IS_UNDEFINED(current) || key in array) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000253 new_array[key - del_count + num_additional_args] = current;
ager@chromium.org9258b6b2008-09-11 09:11:10 +0000254 }
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000255 }
256 }
257 }
258 }
259 // Move contents of new_array into this array
260 %MoveArrayContents(new_array, array);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000261}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000262
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 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000277}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000278
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 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000317}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000318
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);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000328}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000329
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);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000336}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000337
338
339function ArrayJoin(separator) {
340 if (IS_UNDEFINED(separator)) separator = ',';
341 else separator = ToString(separator);
342 return Join(this, ToUint32(this.length), separator, ConvertToString);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000343}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000344
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;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000359}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000360
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;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000372}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000373
374
375function ArrayConcat(arg1) { // length == 1
kasperl@chromium.org9bbf9682008-10-30 11:53:07 +0000376 // 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);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000382 }
383
kasperl@chromium.org9bbf9682008-10-30 11:53:07 +0000384 return %ArrayConcat(arrays);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000385}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000386
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;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000460}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000461
462
463function ArrayShift() {
464 var len = ToUint32(this.length);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000465
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000466 if (len === 0) {
467 this.length = 0;
468 return;
469 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000470
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000471 var first = this[0];
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000472
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000473 if (IS_ARRAY(this))
474 SmartMove(this, 0, 1, len, 0);
475 else
476 SimpleMove(this, 0, 1, len, 0);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000477
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000478 this.length = len - 1;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000479
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000480 return first;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000481}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000482
483
484function ArrayUnshift(arg1) { // length == 1
485 var len = ToUint32(this.length);
486 var num_arguments = %_ArgumentsLength();
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000487
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000488 if (IS_ARRAY(this))
489 SmartMove(this, 0, 0, len, num_arguments);
490 else
491 SimpleMove(this, 0, 0, len, num_arguments);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000492
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000493 for (var i = 0; i < num_arguments; i++) {
494 this[i] = %_Arguments(i);
495 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000496
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000497 this.length = len + num_arguments;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000498
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000499 return len + num_arguments;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000500}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000501
502
503function ArraySlice(start, end) {
504 var len = ToUint32(this.length);
505 var start_i = TO_INTEGER(start);
506 var end_i = len;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000507
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000508 if (end !== void 0) end_i = TO_INTEGER(end);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000509
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000510 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 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000516
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000517 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 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000523
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000524 var result = [];
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000525
526 if (end_i < start_i) return result;
527
528 if (IS_ARRAY(this)) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000529 SmartSlice(this, start_i, end_i - start_i, len, result);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000530 } else {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000531 SimpleSlice(this, start_i, end_i - start_i, len, result);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000532 }
533
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000534 result.length = end_i - start_i;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000535
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000536 return result;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000537}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000538
539
540function ArraySplice(start, delete_count) {
541 var num_arguments = %_ArgumentsLength();
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000542
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000543 // 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;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000548
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000549 var len = ToUint32(this.length);
550 var start_i = TO_INTEGER(start);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000551
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000552 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 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000558
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000559 // 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 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000571
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000572 var deleted_elements = [];
573 deleted_elements.length = del_count;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000574
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000575 // Number of elements to add.
576 var num_additional_args = 0;
577 if (num_arguments > 2) {
578 num_additional_args = num_arguments - 2;
579 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000580
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000581 var use_simple_splice = true;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000582
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000583 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 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000592
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000593 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 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000600
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000601 // 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;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000610
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000611 // Return the deleted elements.
612 return deleted_elements;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000613}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000614
615
616function ArraySort(comparefn) {
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000617 // In-place QuickSort algorithm.
618 // For short (length <= 22) arrays, insertion sort is used for efficiency.
619
620 var custom_compare = IS_FUNCTION(comparefn);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000621
622 function Compare(x,y) {
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000623 if (custom_compare) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000624 return comparefn.call(null, x, y);
625 }
ager@chromium.org9258b6b2008-09-11 09:11:10 +0000626 if (%_IsSmi(x) && %_IsSmi(y)) {
627 return %SmiLexicographicCompare(x, y);
628 }
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000629 x = ToString(x);
630 y = ToString(y);
631 if (x == y) return 0;
632 else return x < y ? -1 : 1;
633 };
634
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000635 function InsertionSort(a, from, to) {
636 for (var i = from + 1; i < to; i++) {
637 var element = a[i];
638 // place element in a[from..i[
639 // binary search
640 var min = from;
641 var max = i;
642 // The search interval is a[min..max[
643 while (min < max) {
644 var mid = min + ((max - min) >> 1);
645 var order = Compare(a[mid], element);
646 if (order == 0) {
647 min = max = mid;
648 break;
649 }
650 if (order < 0) {
651 min = mid + 1;
652 } else {
653 max = mid;
654 }
655 }
656 // place element at position min==max.
657 for (var j = i; j > min; j--) {
658 a[j] = a[j - 1];
659 }
660 a[min] = element;
661 }
662 }
663
664 function QuickSort(a, from, to) {
665 // Insertion sort is faster for short arrays.
666 if (to - from <= 22) {
667 InsertionSort(a, from, to);
668 return;
669 }
670 var pivot_index = $floor($random() * (to - from)) + from;
671 var pivot = a[pivot_index];
672 // Issue 95: Keep the pivot element out of the comparisons to avoid
673 // infinite recursion if comparefn(pivot, pivot) != 0.
674 a[pivot_index] = a[to - 1];
675 a[to - 1] = pivot;
676 var low_end = from; // Upper bound of the elements lower than pivot.
677 var high_start = to - 1; // Lower bound of the elements greater than pivot.
678 for (var i = from; i < high_start; ) {
679 var element = a[i];
680 var order = Compare(element, pivot);
681 if (order < 0) {
682 a[i] = a[low_end];
683 a[low_end] = element;
684 low_end++;
685 i++;
686 } else if (order > 0) {
687 high_start--;
688 a[i] = a[high_start];
689 a[high_start] = element;
690 } else { // order == 0
691 i++;
692 }
693 }
694 // Restore the pivot element to its rightful place.
695 a[to - 1] = a[high_start];
696 a[high_start] = pivot;
697 high_start++;
698 QuickSort(a, from, low_end);
699 QuickSort(a, high_start, to);
700 }
701
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000702 var old_length = ToUint32(this.length);
703
704 %RemoveArrayHoles(this);
705
706 var length = ToUint32(this.length);
707
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000708 // Move undefined elements to the end of the array.
709 for (var i = 0; i < length; ) {
710 if (IS_UNDEFINED(this[i])) {
711 length--;
712 this[i] = this[length];
713 this[length] = void 0;
714 } else {
715 i++;
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000716 }
717 }
718
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000719 QuickSort(this, 0, length);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000720
721 // We only changed the length of the this object (in
722 // RemoveArrayHoles) if it was an array. We are not allowed to set
723 // the length of the this object if it is not an array because this
724 // might introduce a new length property.
725 if (IS_ARRAY(this)) {
726 this.length = old_length;
727 }
728
729 return this;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000730}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000731
732
733// The following functions cannot be made efficient on sparse arrays while
734// preserving the semantics, since the calls to the receiver function can add
735// or delete elements from the array.
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000736function ArrayFilter(f, receiver) {
737 if (!IS_FUNCTION(f)) {
738 throw MakeTypeError('called_non_callable', [ f ]);
739 }
740 // Pull out the length so that modifications to the length in the
741 // loop will not affect the looping.
742 var length = this.length;
743 var result = [];
744 for (var i = 0; i < length; i++) {
745 var current = this[i];
746 if (!IS_UNDEFINED(current) || i in this) {
747 if (f.call(receiver, current, i, this)) result.push(current);
748 }
749 }
750 return result;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000751}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000752
753
754function ArrayForEach(f, receiver) {
755 if (!IS_FUNCTION(f)) {
756 throw MakeTypeError('called_non_callable', [ f ]);
757 }
758 // Pull out the length so that modifications to the length in the
759 // loop will not affect the looping.
760 var length = this.length;
761 for (var i = 0; i < length; i++) {
762 var current = this[i];
763 if (!IS_UNDEFINED(current) || i in this) {
764 f.call(receiver, current, i, this);
765 }
766 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000767}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000768
769
770// Executes the function once for each element present in the
771// array until it finds one where callback returns true.
772function ArraySome(f, receiver) {
773 if (!IS_FUNCTION(f)) {
774 throw MakeTypeError('called_non_callable', [ f ]);
775 }
776 // Pull out the length so that modifications to the length in the
777 // loop will not affect the looping.
778 var length = this.length;
779 for (var i = 0; i < length; i++) {
780 var current = this[i];
781 if (!IS_UNDEFINED(current) || i in this) {
782 if (f.call(receiver, current, i, this)) return true;
783 }
784 }
785 return false;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000786}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000787
788
789function ArrayEvery(f, receiver) {
790 if (!IS_FUNCTION(f)) {
791 throw MakeTypeError('called_non_callable', [ f ]);
792 }
793 // Pull out the length so that modifications to the length in the
794 // loop will not affect the looping.
795 var length = this.length;
796 for (var i = 0; i < length; i++) {
797 var current = this[i];
798 if (!IS_UNDEFINED(current) || i in this) {
799 if (!f.call(receiver, current, i, this)) return false;
800 }
801 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000802
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000803 return true;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000804}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000805
806
807function ArrayMap(f, receiver) {
808 if (!IS_FUNCTION(f)) {
809 throw MakeTypeError('called_non_callable', [ f ]);
810 }
811 // Pull out the length so that modifications to the length in the
812 // loop will not affect the looping.
813 var length = this.length;
814 var result = new $Array(length);
815 for (var i = 0; i < length; i++) {
816 var current = this[i];
817 if (!IS_UNDEFINED(current) || i in this) {
818 result[i] = f.call(receiver, current, i, this);
819 }
820 }
821 return result;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000822}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000823
824
825function ArrayIndexOf(element, index) {
826 var length = this.length;
827 if (index == null) {
828 index = 0;
829 } else {
830 index = TO_INTEGER(index);
831 // If index is negative, index from the end of the array.
832 if (index < 0) index = length + index;
833 // If index is still negative, search the entire array.
834 if (index < 0) index = 0;
835 }
836 // Lookup through the array.
837 for (var i = index; i < length; i++) {
838 var current = this[i];
839 if (!IS_UNDEFINED(current) || i in this) {
840 if (current === element) return i;
841 }
842 }
843 return -1;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000844}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000845
846
847function ArrayLastIndexOf(element, index) {
848 var length = this.length;
849 if (index == null) {
850 index = length - 1;
851 } else {
852 index = TO_INTEGER(index);
853 // If index is negative, index from end of the array.
854 if (index < 0) index = length + index;
855 // If index is still negative, do not search the array.
856 if (index < 0) index = -1;
857 else if (index >= length) index = length - 1;
858 }
859 // Lookup through the array.
860 for (var i = index; i >= 0; i--) {
861 var current = this[i];
862 if (!IS_UNDEFINED(current) || i in this) {
863 if (current === element) return i;
864 }
865 }
866 return -1;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000867}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000868
869
870// -------------------------------------------------------------------
871
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000872
873function UpdateFunctionLengths(lengths) {
874 for (var key in lengths) {
875 %FunctionSetLength(this[key], lengths[key]);
876 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000877}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000878
879
880// -------------------------------------------------------------------
881
882function SetupArray() {
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000883 // Setup non-enumerable constructor property on the Array.prototype
884 // object.
885 %SetProperty($Array.prototype, "constructor", $Array, DONT_ENUM);
886
887 // Setup non-enumerable functions of the Array.prototype object and
888 // set their names.
889 InstallFunctions($Array.prototype, DONT_ENUM, $Array(
890 "toString", ArrayToString,
891 "toLocaleString", ArrayToLocaleString,
892 "join", ArrayJoin,
893 "pop", ArrayPop,
894 "push", ArrayPush,
895 "concat", ArrayConcat,
896 "reverse", ArrayReverse,
897 "shift", ArrayShift,
898 "unshift", ArrayUnshift,
899 "slice", ArraySlice,
900 "splice", ArraySplice,
901 "sort", ArraySort,
902 "filter", ArrayFilter,
903 "forEach", ArrayForEach,
904 "some", ArraySome,
905 "every", ArrayEvery,
906 "map", ArrayMap,
907 "indexOf", ArrayIndexOf,
908 "lastIndexOf", ArrayLastIndexOf
909 ));
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000910
911 // Manipulate the length of some of the functions to meet
912 // expectations set by ECMA-262 or Mozilla.
913 UpdateFunctionLengths({
914 ArrayFilter: 1,
915 ArrayForEach: 1,
916 ArraySome: 1,
917 ArrayEvery: 1,
918 ArrayMap: 1,
919 ArrayIndexOf: 1,
920 ArrayLastIndexOf: 1,
921 ArrayPush: 1
922 });
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000923}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000924
925
926SetupArray();