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