The data contained in this repository can be downloaded to your computer using one of several clients.
Please see the documentation of your version control software client for more information.

Please select the desired protocol below to get the URL.

This URL has Read-Only access.

Statistics
| Branch: | Revision:

main_repo / deps / v8 / src / string.js @ f230a1cf

History | View | Annotate | Download (30.7 KB)

1
// Copyright 2012 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 declaration has been made
29
// in runtime.js:
30
// var $String = global.String;
31

    
32
// -------------------------------------------------------------------
33

    
34
function StringConstructor(x) {
35
  var value = %_ArgumentsLength() == 0 ? '' : TO_STRING_INLINE(x);
36
  if (%_IsConstructCall()) {
37
    %_SetValueOf(this, value);
38
  } else {
39
    return value;
40
  }
41
}
42

    
43

    
44
// ECMA-262 section 15.5.4.2
45
function StringToString() {
46
  if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) {
47
    throw new $TypeError('String.prototype.toString is not generic');
48
  }
49
  return %_ValueOf(this);
50
}
51

    
52

    
53
// ECMA-262 section 15.5.4.3
54
function StringValueOf() {
55
  if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) {
56
    throw new $TypeError('String.prototype.valueOf is not generic');
57
  }
58
  return %_ValueOf(this);
59
}
60

    
61

    
62
// ECMA-262, section 15.5.4.4
63
function StringCharAt(pos) {
64
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
65
    throw MakeTypeError("called_on_null_or_undefined",
66
                        ["String.prototype.charAt"]);
67
  }
68
  var result = %_StringCharAt(this, pos);
69
  if (%_IsSmi(result)) {
70
    result = %_StringCharAt(TO_STRING_INLINE(this), TO_INTEGER(pos));
71
  }
72
  return result;
73
}
74

    
75

    
76
// ECMA-262 section 15.5.4.5
77
function StringCharCodeAt(pos) {
78
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
79
    throw MakeTypeError("called_on_null_or_undefined",
80
                        ["String.prototype.charCodeAt"]);
81
  }
82
  var result = %_StringCharCodeAt(this, pos);
83
  if (!%_IsSmi(result)) {
84
    result = %_StringCharCodeAt(TO_STRING_INLINE(this), TO_INTEGER(pos));
85
  }
86
  return result;
87
}
88

    
89

    
90
// ECMA-262, section 15.5.4.6
91
function StringConcat() {
92
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
93
    throw MakeTypeError("called_on_null_or_undefined",
94
                        ["String.prototype.concat"]);
95
  }
96
  var len = %_ArgumentsLength();
97
  var this_as_string = TO_STRING_INLINE(this);
98
  if (len === 1) {
99
    return this_as_string + %_Arguments(0);
100
  }
101
  var parts = new InternalArray(len + 1);
102
  parts[0] = this_as_string;
103
  for (var i = 0; i < len; i++) {
104
    var part = %_Arguments(i);
105
    parts[i + 1] = TO_STRING_INLINE(part);
106
  }
107
  return %StringBuilderConcat(parts, len + 1, "");
108
}
109

    
110
// Match ES3 and Safari
111
%FunctionSetLength(StringConcat, 1);
112

    
113

    
114
// ECMA-262 section 15.5.4.7
115
function StringIndexOf(pattern /* position */) {  // length == 1
116
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
117
    throw MakeTypeError("called_on_null_or_undefined",
118
                        ["String.prototype.indexOf"]);
119
  }
120
  var subject = TO_STRING_INLINE(this);
121
  pattern = TO_STRING_INLINE(pattern);
122
  var index = 0;
123
  if (%_ArgumentsLength() > 1) {
124
    index = %_Arguments(1);  // position
125
    index = TO_INTEGER(index);
126
    if (index < 0) index = 0;
127
    if (index > subject.length) index = subject.length;
128
  }
129
  return %StringIndexOf(subject, pattern, index);
130
}
131

    
132

    
133
// ECMA-262 section 15.5.4.8
134
function StringLastIndexOf(pat /* position */) {  // length == 1
135
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
136
    throw MakeTypeError("called_on_null_or_undefined",
137
                        ["String.prototype.lastIndexOf"]);
138
  }
139
  var sub = TO_STRING_INLINE(this);
140
  var subLength = sub.length;
141
  var pat = TO_STRING_INLINE(pat);
142
  var patLength = pat.length;
143
  var index = subLength - patLength;
144
  if (%_ArgumentsLength() > 1) {
145
    var position = ToNumber(%_Arguments(1));
146
    if (!NUMBER_IS_NAN(position)) {
147
      position = TO_INTEGER(position);
148
      if (position < 0) {
149
        position = 0;
150
      }
151
      if (position + patLength < subLength) {
152
        index = position;
153
      }
154
    }
155
  }
156
  if (index < 0) {
157
    return -1;
158
  }
159
  return %StringLastIndexOf(sub, pat, index);
160
}
161

    
162

    
163
// ECMA-262 section 15.5.4.9
164
//
165
// This function is implementation specific.  For now, we do not
166
// do anything locale specific.
167
function StringLocaleCompare(other) {
168
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
169
    throw MakeTypeError("called_on_null_or_undefined",
170
                        ["String.prototype.localeCompare"]);
171
  }
172
  return %StringLocaleCompare(TO_STRING_INLINE(this),
173
                              TO_STRING_INLINE(other));
174
}
175

    
176

    
177
// ECMA-262 section 15.5.4.10
178
function StringMatch(regexp) {
179
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
180
    throw MakeTypeError("called_on_null_or_undefined",
181
                        ["String.prototype.match"]);
182
  }
183
  var subject = TO_STRING_INLINE(this);
184
  if (IS_REGEXP(regexp)) {
185
    // Emulate RegExp.prototype.exec's side effect in step 5, even though
186
    // value is discarded.
187
    var lastIndex = regexp.lastIndex;
188
    TO_INTEGER_FOR_SIDE_EFFECT(lastIndex);
189
    if (!regexp.global) return RegExpExecNoTests(regexp, subject, 0);
190
    %_Log('regexp', 'regexp-match,%0S,%1r', [subject, regexp]);
191
    // lastMatchInfo is defined in regexp.js.
192
    var result = %StringMatch(subject, regexp, lastMatchInfo);
193
    if (result !== null) lastMatchInfoOverride = null;
194
    regexp.lastIndex = 0;
195
    return result;
196
  }
197
  // Non-regexp argument.
198
  regexp = new $RegExp(regexp);
199
  return RegExpExecNoTests(regexp, subject, 0);
200
}
201

    
202

    
203
// This has the same size as the lastMatchInfo array, and can be used for
204
// functions that expect that structure to be returned.  It is used when the
205
// needle is a string rather than a regexp.  In this case we can't update
206
// lastMatchArray without erroneously affecting the properties on the global
207
// RegExp object.
208
var reusableMatchInfo = [2, "", "", -1, -1];
209

    
210

    
211
// ECMA-262, section 15.5.4.11
212
function StringReplace(search, replace) {
213
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
214
    throw MakeTypeError("called_on_null_or_undefined",
215
                        ["String.prototype.replace"]);
216
  }
217
  var subject = TO_STRING_INLINE(this);
218

    
219
  // Decision tree for dispatch
220
  // .. regexp search
221
  // .... string replace
222
  // ...... non-global search
223
  // ........ empty string replace
224
  // ........ non-empty string replace (with $-expansion)
225
  // ...... global search
226
  // ........ no need to circumvent last match info override
227
  // ........ need to circument last match info override
228
  // .... function replace
229
  // ...... global search
230
  // ...... non-global search
231
  // .. string search
232
  // .... special case that replaces with one single character
233
  // ...... function replace
234
  // ...... string replace (with $-expansion)
235

    
236
  if (IS_REGEXP(search)) {
237
    // Emulate RegExp.prototype.exec's side effect in step 5, even if
238
    // value is discarded.
239
    var lastIndex = search.lastIndex;
240
    TO_INTEGER_FOR_SIDE_EFFECT(lastIndex);
241
    %_Log('regexp', 'regexp-replace,%0r,%1S', [search, subject]);
242

    
243
    if (!IS_SPEC_FUNCTION(replace)) {
244
      replace = TO_STRING_INLINE(replace);
245

    
246
      if (!search.global) {
247
        // Non-global regexp search, string replace.
248
        var match = DoRegExpExec(search, subject, 0);
249
        if (match == null) {
250
          search.lastIndex = 0
251
          return subject;
252
        }
253
        if (replace.length == 0) {
254
          return %_SubString(subject, 0, match[CAPTURE0]) +
255
                 %_SubString(subject, match[CAPTURE1], subject.length)
256
        }
257
        return ExpandReplacement(replace, subject, lastMatchInfo,
258
                                 %_SubString(subject, 0, match[CAPTURE0])) +
259
               %_SubString(subject, match[CAPTURE1], subject.length);
260
      }
261

    
262
      // Global regexp search, string replace.
263
      search.lastIndex = 0;
264
      if (lastMatchInfoOverride == null) {
265
        return %StringReplaceGlobalRegExpWithString(
266
            subject, search, replace, lastMatchInfo);
267
      } else {
268
        // We use this hack to detect whether StringReplaceRegExpWithString
269
        // found at least one hit. In that case we need to remove any
270
        // override.
271
        var saved_subject = lastMatchInfo[LAST_SUBJECT_INDEX];
272
        lastMatchInfo[LAST_SUBJECT_INDEX] = 0;
273
        var answer = %StringReplaceGlobalRegExpWithString(
274
            subject, search, replace, lastMatchInfo);
275
        if (%_IsSmi(lastMatchInfo[LAST_SUBJECT_INDEX])) {
276
          lastMatchInfo[LAST_SUBJECT_INDEX] = saved_subject;
277
        } else {
278
          lastMatchInfoOverride = null;
279
        }
280
        return answer;
281
      }
282
    }
283

    
284
    if (search.global) {
285
      // Global regexp search, function replace.
286
      return StringReplaceGlobalRegExpWithFunction(subject, search, replace);
287
    }
288
    // Non-global regexp search, function replace.
289
    return StringReplaceNonGlobalRegExpWithFunction(subject, search, replace);
290
  }
291

    
292
  search = TO_STRING_INLINE(search);
293

    
294
  if (search.length == 1 &&
295
      subject.length > 0xFF &&
296
      IS_STRING(replace) &&
297
      %StringIndexOf(replace, '$', 0) < 0) {
298
    // Searching by traversing a cons string tree and replace with cons of
299
    // slices works only when the replaced string is a single character, being
300
    // replaced by a simple string and only pays off for long strings.
301
    return %StringReplaceOneCharWithString(subject, search, replace);
302
  }
303
  var start = %StringIndexOf(subject, search, 0);
304
  if (start < 0) return subject;
305
  var end = start + search.length;
306

    
307
  var result = %_SubString(subject, 0, start);
308

    
309
  // Compute the string to replace with.
310
  if (IS_SPEC_FUNCTION(replace)) {
311
    var receiver = %GetDefaultReceiver(replace);
312
    result += %_CallFunction(receiver, search, start, subject, replace);
313
  } else {
314
    reusableMatchInfo[CAPTURE0] = start;
315
    reusableMatchInfo[CAPTURE1] = end;
316
    result = ExpandReplacement(TO_STRING_INLINE(replace),
317
                               subject,
318
                               reusableMatchInfo,
319
                               result);
320
  }
321

    
322
  return result + %_SubString(subject, end, subject.length);
323
}
324

    
325

    
326
// Expand the $-expressions in the string and return a new string with
327
// the result.
328
function ExpandReplacement(string, subject, matchInfo, result) {
329
  var length = string.length;
330
  var next = %StringIndexOf(string, '$', 0);
331
  if (next < 0) {
332
    if (length > 0) result += string;
333
    return result;
334
  }
335

    
336
  if (next > 0) result += %_SubString(string, 0, next);
337

    
338
  while (true) {
339
    var expansion = '$';
340
    var position = next + 1;
341
    if (position < length) {
342
      var peek = %_StringCharCodeAt(string, position);
343
      if (peek == 36) {         // $$
344
        ++position;
345
        result += '$';
346
      } else if (peek == 38) {  // $& - match
347
        ++position;
348
        result +=
349
          %_SubString(subject, matchInfo[CAPTURE0], matchInfo[CAPTURE1]);
350
      } else if (peek == 96) {  // $` - prefix
351
        ++position;
352
        result += %_SubString(subject, 0, matchInfo[CAPTURE0]);
353
      } else if (peek == 39) {  // $' - suffix
354
        ++position;
355
        result += %_SubString(subject, matchInfo[CAPTURE1], subject.length);
356
      } else if (peek >= 48 && peek <= 57) {
357
        // Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99
358
        var scaled_index = (peek - 48) << 1;
359
        var advance = 1;
360
        var number_of_captures = NUMBER_OF_CAPTURES(matchInfo);
361
        if (position + 1 < string.length) {
362
          var next = %_StringCharCodeAt(string, position + 1);
363
          if (next >= 48 && next <= 57) {
364
            var new_scaled_index = scaled_index * 10 + ((next - 48) << 1);
365
            if (new_scaled_index < number_of_captures) {
366
              scaled_index = new_scaled_index;
367
              advance = 2;
368
            }
369
          }
370
        }
371
        if (scaled_index != 0 && scaled_index < number_of_captures) {
372
          var start = matchInfo[CAPTURE(scaled_index)];
373
          if (start >= 0) {
374
            result +=
375
              %_SubString(subject, start, matchInfo[CAPTURE(scaled_index + 1)]);
376
          }
377
          position += advance;
378
        } else {
379
          result += '$';
380
        }
381
      } else {
382
        result += '$';
383
      }
384
    } else {
385
      result += '$';
386
    }
387

    
388
    // Go the the next $ in the string.
389
    next = %StringIndexOf(string, '$', position);
390

    
391
    // Return if there are no more $ characters in the string. If we
392
    // haven't reached the end, we need to append the suffix.
393
    if (next < 0) {
394
      if (position < length) {
395
        result += %_SubString(string, position, length);
396
      }
397
      return result;
398
    }
399

    
400
    // Append substring between the previous and the next $ character.
401
    if (next > position) {
402
      result += %_SubString(string, position, next);
403
    }
404
  }
405
  return result;
406
}
407

    
408

    
409
// Compute the string of a given regular expression capture.
410
function CaptureString(string, lastCaptureInfo, index) {
411
  // Scale the index.
412
  var scaled = index << 1;
413
  // Compute start and end.
414
  var start = lastCaptureInfo[CAPTURE(scaled)];
415
  // If start isn't valid, return undefined.
416
  if (start < 0) return;
417
  var end = lastCaptureInfo[CAPTURE(scaled + 1)];
418
  return %_SubString(string, start, end);
419
}
420

    
421

    
422
// TODO(lrn): This array will survive indefinitely if replace is never
423
// called again. However, it will be empty, since the contents are cleared
424
// in the finally block.
425
var reusableReplaceArray = new InternalArray(16);
426

    
427
// Helper function for replacing regular expressions with the result of a
428
// function application in String.prototype.replace.
429
function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) {
430
  var resultArray = reusableReplaceArray;
431
  if (resultArray) {
432
    reusableReplaceArray = null;
433
  } else {
434
    // Inside a nested replace (replace called from the replacement function
435
    // of another replace) or we have failed to set the reusable array
436
    // back due to an exception in a replacement function. Create a new
437
    // array to use in the future, or until the original is written back.
438
    resultArray = new InternalArray(16);
439
  }
440
  var res = %RegExpExecMultiple(regexp,
441
                                subject,
442
                                lastMatchInfo,
443
                                resultArray);
444
  regexp.lastIndex = 0;
445
  if (IS_NULL(res)) {
446
    // No matches at all.
447
    reusableReplaceArray = resultArray;
448
    return subject;
449
  }
450
  var len = res.length;
451
  if (NUMBER_OF_CAPTURES(lastMatchInfo) == 2) {
452
    // If the number of captures is two then there are no explicit captures in
453
    // the regexp, just the implicit capture that captures the whole match.  In
454
    // this case we can simplify quite a bit and end up with something faster.
455
    // The builder will consist of some integers that indicate slices of the
456
    // input string and some replacements that were returned from the replace
457
    // function.
458
    var match_start = 0;
459
    var override = new InternalPackedArray(null, 0, subject);
460
    var receiver = %GetDefaultReceiver(replace);
461
    for (var i = 0; i < len; i++) {
462
      var elem = res[i];
463
      if (%_IsSmi(elem)) {
464
        // Integers represent slices of the original string.  Use these to
465
        // get the offsets we need for the override array (so things like
466
        // RegExp.leftContext work during the callback function.
467
        if (elem > 0) {
468
          match_start = (elem >> 11) + (elem & 0x7ff);
469
        } else {
470
          match_start = res[++i] - elem;
471
        }
472
      } else {
473
        override[0] = elem;
474
        override[1] = match_start;
475
        lastMatchInfoOverride = override;
476
        var func_result =
477
            %_CallFunction(receiver, elem, match_start, subject, replace);
478
        // Overwrite the i'th element in the results with the string we got
479
        // back from the callback function.
480
        res[i] = TO_STRING_INLINE(func_result);
481
        match_start += elem.length;
482
      }
483
    }
484
  } else {
485
    var receiver = %GetDefaultReceiver(replace);
486
    for (var i = 0; i < len; i++) {
487
      var elem = res[i];
488
      if (!%_IsSmi(elem)) {
489
        // elem must be an Array.
490
        // Use the apply argument as backing for global RegExp properties.
491
        lastMatchInfoOverride = elem;
492
        var func_result = %Apply(replace, receiver, elem, 0, elem.length);
493
        // Overwrite the i'th element in the results with the string we got
494
        // back from the callback function.
495
        res[i] = TO_STRING_INLINE(func_result);
496
      }
497
    }
498
  }
499
  var result = %StringBuilderConcat(res, res.length, subject);
500
  resultArray.length = 0;
501
  reusableReplaceArray = resultArray;
502
  return result;
503
}
504

    
505

    
506
function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) {
507
  var matchInfo = DoRegExpExec(regexp, subject, 0);
508
  if (IS_NULL(matchInfo)) {
509
    regexp.lastIndex = 0;
510
    return subject;
511
  }
512
  var index = matchInfo[CAPTURE0];
513
  var result = %_SubString(subject, 0, index);
514
  var endOfMatch = matchInfo[CAPTURE1];
515
  // Compute the parameter list consisting of the match, captures, index,
516
  // and subject for the replace function invocation.
517
  // The number of captures plus one for the match.
518
  var m = NUMBER_OF_CAPTURES(matchInfo) >> 1;
519
  var replacement;
520
  var receiver = %GetDefaultReceiver(replace);
521
  if (m == 1) {
522
    // No captures, only the match, which is always valid.
523
    var s = %_SubString(subject, index, endOfMatch);
524
    // Don't call directly to avoid exposing the built-in global object.
525
    replacement = %_CallFunction(receiver, s, index, subject, replace);
526
  } else {
527
    var parameters = new InternalArray(m + 2);
528
    for (var j = 0; j < m; j++) {
529
      parameters[j] = CaptureString(subject, matchInfo, j);
530
    }
531
    parameters[j] = index;
532
    parameters[j + 1] = subject;
533

    
534
    replacement = %Apply(replace, receiver, parameters, 0, j + 2);
535
  }
536

    
537
  result += replacement;  // The add method converts to string if necessary.
538
  // Can't use matchInfo any more from here, since the function could
539
  // overwrite it.
540
  return result + %_SubString(subject, endOfMatch, subject.length);
541
}
542

    
543

    
544
// ECMA-262 section 15.5.4.12
545
function StringSearch(re) {
546
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
547
    throw MakeTypeError("called_on_null_or_undefined",
548
                        ["String.prototype.search"]);
549
  }
550
  var regexp;
551
  if (IS_STRING(re)) {
552
    regexp = %_GetFromCache(STRING_TO_REGEXP_CACHE_ID, re);
553
  } else if (IS_REGEXP(re)) {
554
    regexp = re;
555
  } else {
556
    regexp = new $RegExp(re);
557
  }
558
  var match = DoRegExpExec(regexp, TO_STRING_INLINE(this), 0);
559
  if (match) {
560
    return match[CAPTURE0];
561
  }
562
  return -1;
563
}
564

    
565

    
566
// ECMA-262 section 15.5.4.13
567
function StringSlice(start, end) {
568
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
569
    throw MakeTypeError("called_on_null_or_undefined",
570
                        ["String.prototype.slice"]);
571
  }
572
  var s = TO_STRING_INLINE(this);
573
  var s_len = s.length;
574
  var start_i = TO_INTEGER(start);
575
  var end_i = s_len;
576
  if (!IS_UNDEFINED(end)) {
577
    end_i = TO_INTEGER(end);
578
  }
579

    
580
  if (start_i < 0) {
581
    start_i += s_len;
582
    if (start_i < 0) {
583
      start_i = 0;
584
    }
585
  } else {
586
    if (start_i > s_len) {
587
      return '';
588
    }
589
  }
590

    
591
  if (end_i < 0) {
592
    end_i += s_len;
593
    if (end_i < 0) {
594
      return '';
595
    }
596
  } else {
597
    if (end_i > s_len) {
598
      end_i = s_len;
599
    }
600
  }
601

    
602
  if (end_i <= start_i) {
603
    return '';
604
  }
605

    
606
  return %_SubString(s, start_i, end_i);
607
}
608

    
609

    
610
// ECMA-262 section 15.5.4.14
611
function StringSplit(separator, limit) {
612
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
613
    throw MakeTypeError("called_on_null_or_undefined",
614
                        ["String.prototype.split"]);
615
  }
616
  var subject = TO_STRING_INLINE(this);
617
  limit = (IS_UNDEFINED(limit)) ? 0xffffffff : TO_UINT32(limit);
618

    
619
  // ECMA-262 says that if separator is undefined, the result should
620
  // be an array of size 1 containing the entire string.
621
  if (IS_UNDEFINED(separator)) {
622
    return [subject];
623
  }
624

    
625
  var length = subject.length;
626
  if (!IS_REGEXP(separator)) {
627
    separator = TO_STRING_INLINE(separator);
628

    
629
    if (limit === 0) return [];
630

    
631
    var separator_length = separator.length;
632

    
633
    // If the separator string is empty then return the elements in the subject.
634
    if (separator_length === 0) return %StringToArray(subject, limit);
635

    
636
    var result = %StringSplit(subject, separator, limit);
637

    
638
    return result;
639
  }
640

    
641
  if (limit === 0) return [];
642

    
643
  // Separator is a regular expression.
644
  return StringSplitOnRegExp(subject, separator, limit, length);
645
}
646

    
647

    
648
var ArrayPushBuiltin = $Array.prototype.push;
649

    
650
function StringSplitOnRegExp(subject, separator, limit, length) {
651
  %_Log('regexp', 'regexp-split,%0S,%1r', [subject, separator]);
652

    
653
  if (length === 0) {
654
    if (DoRegExpExec(separator, subject, 0, 0) != null) {
655
      return [];
656
    }
657
    return [subject];
658
  }
659

    
660
  var currentIndex = 0;
661
  var startIndex = 0;
662
  var startMatch = 0;
663
  var result = [];
664

    
665
  outer_loop:
666
  while (true) {
667

    
668
    if (startIndex === length) {
669
      %_CallFunction(result, %_SubString(subject, currentIndex, length),
670
                     ArrayPushBuiltin);
671
      break;
672
    }
673

    
674
    var matchInfo = DoRegExpExec(separator, subject, startIndex);
675
    if (matchInfo == null || length === (startMatch = matchInfo[CAPTURE0])) {
676
      %_CallFunction(result, %_SubString(subject, currentIndex, length),
677
                     ArrayPushBuiltin);
678
      break;
679
    }
680
    var endIndex = matchInfo[CAPTURE1];
681

    
682
    // We ignore a zero-length match at the currentIndex.
683
    if (startIndex === endIndex && endIndex === currentIndex) {
684
      startIndex++;
685
      continue;
686
    }
687

    
688
    %_CallFunction(result, %_SubString(subject, currentIndex, startMatch),
689
                   ArrayPushBuiltin);
690

    
691
    if (result.length === limit) break;
692

    
693
    var matchinfo_len = NUMBER_OF_CAPTURES(matchInfo) + REGEXP_FIRST_CAPTURE;
694
    for (var i = REGEXP_FIRST_CAPTURE + 2; i < matchinfo_len; ) {
695
      var start = matchInfo[i++];
696
      var end = matchInfo[i++];
697
      if (end != -1) {
698
        %_CallFunction(result, %_SubString(subject, start, end),
699
                       ArrayPushBuiltin);
700
      } else {
701
        %_CallFunction(result, UNDEFINED, ArrayPushBuiltin);
702
      }
703
      if (result.length === limit) break outer_loop;
704
    }
705

    
706
    startIndex = currentIndex = endIndex;
707
  }
708
  return result;
709
}
710

    
711

    
712
// ECMA-262 section 15.5.4.15
713
function StringSubstring(start, end) {
714
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
715
    throw MakeTypeError("called_on_null_or_undefined",
716
                        ["String.prototype.subString"]);
717
  }
718
  var s = TO_STRING_INLINE(this);
719
  var s_len = s.length;
720

    
721
  var start_i = TO_INTEGER(start);
722
  if (start_i < 0) {
723
    start_i = 0;
724
  } else if (start_i > s_len) {
725
    start_i = s_len;
726
  }
727

    
728
  var end_i = s_len;
729
  if (!IS_UNDEFINED(end)) {
730
    end_i = TO_INTEGER(end);
731
    if (end_i > s_len) {
732
      end_i = s_len;
733
    } else {
734
      if (end_i < 0) end_i = 0;
735
      if (start_i > end_i) {
736
        var tmp = end_i;
737
        end_i = start_i;
738
        start_i = tmp;
739
      }
740
    }
741
  }
742

    
743
  return %_SubString(s, start_i, end_i);
744
}
745

    
746

    
747
// This is not a part of ECMA-262.
748
function StringSubstr(start, n) {
749
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
750
    throw MakeTypeError("called_on_null_or_undefined",
751
                        ["String.prototype.substr"]);
752
  }
753
  var s = TO_STRING_INLINE(this);
754
  var len;
755

    
756
  // Correct n: If not given, set to string length; if explicitly
757
  // set to undefined, zero, or negative, returns empty string.
758
  if (IS_UNDEFINED(n)) {
759
    len = s.length;
760
  } else {
761
    len = TO_INTEGER(n);
762
    if (len <= 0) return '';
763
  }
764

    
765
  // Correct start: If not given (or undefined), set to zero; otherwise
766
  // convert to integer and handle negative case.
767
  if (IS_UNDEFINED(start)) {
768
    start = 0;
769
  } else {
770
    start = TO_INTEGER(start);
771
    // If positive, and greater than or equal to the string length,
772
    // return empty string.
773
    if (start >= s.length) return '';
774
    // If negative and absolute value is larger than the string length,
775
    // use zero.
776
    if (start < 0) {
777
      start += s.length;
778
      if (start < 0) start = 0;
779
    }
780
  }
781

    
782
  var end = start + len;
783
  if (end > s.length) end = s.length;
784

    
785
  return %_SubString(s, start, end);
786
}
787

    
788

    
789
// ECMA-262, 15.5.4.16
790
function StringToLowerCase() {
791
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
792
    throw MakeTypeError("called_on_null_or_undefined",
793
                        ["String.prototype.toLowerCase"]);
794
  }
795
  return %StringToLowerCase(TO_STRING_INLINE(this));
796
}
797

    
798

    
799
// ECMA-262, 15.5.4.17
800
function StringToLocaleLowerCase() {
801
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
802
    throw MakeTypeError("called_on_null_or_undefined",
803
                        ["String.prototype.toLocaleLowerCase"]);
804
  }
805
  return %StringToLowerCase(TO_STRING_INLINE(this));
806
}
807

    
808

    
809
// ECMA-262, 15.5.4.18
810
function StringToUpperCase() {
811
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
812
    throw MakeTypeError("called_on_null_or_undefined",
813
                        ["String.prototype.toUpperCase"]);
814
  }
815
  return %StringToUpperCase(TO_STRING_INLINE(this));
816
}
817

    
818

    
819
// ECMA-262, 15.5.4.19
820
function StringToLocaleUpperCase() {
821
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
822
    throw MakeTypeError("called_on_null_or_undefined",
823
                        ["String.prototype.toLocaleUpperCase"]);
824
  }
825
  return %StringToUpperCase(TO_STRING_INLINE(this));
826
}
827

    
828
// ES5, 15.5.4.20
829
function StringTrim() {
830
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
831
    throw MakeTypeError("called_on_null_or_undefined",
832
                        ["String.prototype.trim"]);
833
  }
834
  return %StringTrim(TO_STRING_INLINE(this), true, true);
835
}
836

    
837
function StringTrimLeft() {
838
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
839
    throw MakeTypeError("called_on_null_or_undefined",
840
                        ["String.prototype.trimLeft"]);
841
  }
842
  return %StringTrim(TO_STRING_INLINE(this), true, false);
843
}
844

    
845
function StringTrimRight() {
846
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
847
    throw MakeTypeError("called_on_null_or_undefined",
848
                        ["String.prototype.trimRight"]);
849
  }
850
  return %StringTrim(TO_STRING_INLINE(this), false, true);
851
}
852

    
853

    
854
// ECMA-262, section 15.5.3.2
855
function StringFromCharCode(code) {
856
  var n = %_ArgumentsLength();
857
  if (n == 1) {
858
    if (!%_IsSmi(code)) code = ToNumber(code);
859
    return %_StringCharFromCode(code & 0xffff);
860
  }
861

    
862
  var one_byte = %NewString(n, NEW_ONE_BYTE_STRING);
863
  var i;
864
  for (i = 0; i < n; i++) {
865
    var code = %_Arguments(i);
866
    if (!%_IsSmi(code)) code = ToNumber(code) & 0xffff;
867
    if (code < 0) code = code & 0xffff;
868
    if (code > 0xff) break;
869
    %_OneByteSeqStringSetChar(one_byte, i, code);
870
  }
871
  if (i == n) return one_byte;
872
  one_byte = %TruncateString(one_byte, i);
873

    
874
  var two_byte = %NewString(n - i, NEW_TWO_BYTE_STRING);
875
  for (var j = 0; i < n; i++, j++) {
876
    var code = %_Arguments(i);
877
    if (!%_IsSmi(code)) code = ToNumber(code) & 0xffff;
878
    %_TwoByteSeqStringSetChar(two_byte, j, code);
879
  }
880
  return one_byte + two_byte;
881
}
882

    
883

    
884
// Helper function for very basic XSS protection.
885
function HtmlEscape(str) {
886
  return TO_STRING_INLINE(str).replace(/</g, "&lt;")
887
                              .replace(/>/g, "&gt;")
888
                              .replace(/"/g, "&quot;")
889
                              .replace(/'/g, "&#039;");
890
}
891

    
892

    
893
// Compatibility support for KJS.
894
// Tested by mozilla/js/tests/js1_5/Regress/regress-276103.js.
895
function StringLink(s) {
896
  return "<a href=\"" + HtmlEscape(s) + "\">" + this + "</a>";
897
}
898

    
899

    
900
function StringAnchor(name) {
901
  return "<a name=\"" + HtmlEscape(name) + "\">" + this + "</a>";
902
}
903

    
904

    
905
function StringFontcolor(color) {
906
  return "<font color=\"" + HtmlEscape(color) + "\">" + this + "</font>";
907
}
908

    
909

    
910
function StringFontsize(size) {
911
  return "<font size=\"" + HtmlEscape(size) + "\">" + this + "</font>";
912
}
913

    
914

    
915
function StringBig() {
916
  return "<big>" + this + "</big>";
917
}
918

    
919

    
920
function StringBlink() {
921
  return "<blink>" + this + "</blink>";
922
}
923

    
924

    
925
function StringBold() {
926
  return "<b>" + this + "</b>";
927
}
928

    
929

    
930
function StringFixed() {
931
  return "<tt>" + this + "</tt>";
932
}
933

    
934

    
935
function StringItalics() {
936
  return "<i>" + this + "</i>";
937
}
938

    
939

    
940
function StringSmall() {
941
  return "<small>" + this + "</small>";
942
}
943

    
944

    
945
function StringStrike() {
946
  return "<strike>" + this + "</strike>";
947
}
948

    
949

    
950
function StringSub() {
951
  return "<sub>" + this + "</sub>";
952
}
953

    
954

    
955
function StringSup() {
956
  return "<sup>" + this + "</sup>";
957
}
958

    
959
// -------------------------------------------------------------------
960

    
961
function SetUpString() {
962
  %CheckIsBootstrapping();
963

    
964
  // Set the String function and constructor.
965
  %SetCode($String, StringConstructor);
966
  %FunctionSetPrototype($String, new $String());
967

    
968
  // Set up the constructor property on the String prototype object.
969
  %SetProperty($String.prototype, "constructor", $String, DONT_ENUM);
970

    
971
  // Set up the non-enumerable functions on the String object.
972
  InstallFunctions($String, DONT_ENUM, $Array(
973
    "fromCharCode", StringFromCharCode
974
  ));
975

    
976
  // Set up the non-enumerable functions on the String prototype object.
977
  InstallFunctions($String.prototype, DONT_ENUM, $Array(
978
    "valueOf", StringValueOf,
979
    "toString", StringToString,
980
    "charAt", StringCharAt,
981
    "charCodeAt", StringCharCodeAt,
982
    "concat", StringConcat,
983
    "indexOf", StringIndexOf,
984
    "lastIndexOf", StringLastIndexOf,
985
    "localeCompare", StringLocaleCompare,
986
    "match", StringMatch,
987
    "replace", StringReplace,
988
    "search", StringSearch,
989
    "slice", StringSlice,
990
    "split", StringSplit,
991
    "substring", StringSubstring,
992
    "substr", StringSubstr,
993
    "toLowerCase", StringToLowerCase,
994
    "toLocaleLowerCase", StringToLocaleLowerCase,
995
    "toUpperCase", StringToUpperCase,
996
    "toLocaleUpperCase", StringToLocaleUpperCase,
997
    "trim", StringTrim,
998
    "trimLeft", StringTrimLeft,
999
    "trimRight", StringTrimRight,
1000
    "link", StringLink,
1001
    "anchor", StringAnchor,
1002
    "fontcolor", StringFontcolor,
1003
    "fontsize", StringFontsize,
1004
    "big", StringBig,
1005
    "blink", StringBlink,
1006
    "bold", StringBold,
1007
    "fixed", StringFixed,
1008
    "italics", StringItalics,
1009
    "small", StringSmall,
1010
    "strike", StringStrike,
1011
    "sub", StringSub,
1012
    "sup", StringSup
1013
  ));
1014
}
1015

    
1016
SetUpString();