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 @ 40c0f755

History | View | Annotate | Download (25 KB)

1
// Copyright 2006-2008 the V8 project authors. All rights reserved.
2
// Redistribution and use in source and binary forms, with or without
3
// modification, are permitted provided that the following conditions are
4
// met:
5
//
6
//     * Redistributions of source code must retain the above copyright
7
//       notice, this list of conditions and the following disclaimer.
8
//     * Redistributions in binary form must reproduce the above
9
//       copyright notice, this list of conditions and the following
10
//       disclaimer in the documentation and/or other materials provided
11
//       with the distribution.
12
//     * Neither the name of Google Inc. nor the names of its
13
//       contributors may be used to endorse or promote products derived
14
//       from this software without specific prior written permission.
15
//
16
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27

    
28

    
29
// This file relies on the fact that the following declaration has been made
30
// in runtime.js:
31
// const $String = global.String;
32
// const $NaN = 0/0;
33

    
34

    
35
// Set the String function and constructor.
36
%SetCode($String, function(x) {
37
  var value = %_ArgumentsLength() == 0 ? '' : ToString(x);
38
  if (%IsConstructCall()) {
39
    %_SetValueOf(this, value);
40
  } else {
41
    return value;
42
  }
43
});
44

    
45
%FunctionSetPrototype($String, new $String());
46

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

    
54

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

    
62

    
63
// ECMA-262, section 15.5.4.4
64
function StringCharAt(pos) {
65
  var char_code = %_FastCharCodeAt(this, index);
66
  if (!%_IsSmi(char_code)) {
67
    var subject = ToString(this);
68
    var index = TO_INTEGER(pos);
69
    if (index >= subject.length || index < 0) return "";
70
    char_code = %StringCharCodeAt(subject, index);
71
  }
72
  return %CharFromCode(char_code);
73
}
74

    
75

    
76
// ECMA-262 section 15.5.4.5
77
function StringCharCodeAt(pos) {
78
  var fast_answer = %_FastCharCodeAt(this, pos);
79
  if (%_IsSmi(fast_answer)) {
80
    return fast_answer;
81
  }
82
  var subject = ToString(this);
83
  var index = TO_INTEGER(pos);
84
  return %StringCharCodeAt(subject, index);
85
}
86

    
87

    
88
// ECMA-262, section 15.5.4.6
89
function StringConcat() {
90
  var len = %_ArgumentsLength();
91
  var parts = new $Array(len + 1);
92
  parts[0] = ToString(this);
93
  for (var i = 0; i < len; i++)
94
    parts[i + 1] = ToString(%_Arguments(i));
95
  return parts.join('');
96
}
97

    
98
// Match ES3 and Safari
99
%FunctionSetLength(StringConcat, 1);
100

    
101

    
102
// ECMA-262 section 15.5.4.7
103
function StringIndexOf(searchString /* position */) {  // length == 1
104
  var subject_str = ToString(this);
105
  var pattern_str = ToString(searchString);
106
  var subject_str_len = subject_str.length;
107
  var pattern_str_len = pattern_str.length;
108
  var index = 0;
109
  if (%_ArgumentsLength() > 1) {
110
    var arg1 = %_Arguments(1);  // position
111
    index = TO_INTEGER(arg1);
112
  }
113
  if (index < 0) index = 0;
114
  if (index > subject_str_len) index = subject_str_len;
115
  if (pattern_str_len + index > subject_str_len) return -1;
116
  return %StringIndexOf(subject_str, pattern_str, index);
117
}
118

    
119

    
120
// ECMA-262 section 15.5.4.8
121
function StringLastIndexOf(searchString /* position */) {  // length == 1
122
  var sub = ToString(this);
123
  var pat = ToString(searchString);
124
  var index = (%_ArgumentsLength() > 1)
125
      ? ToNumber(%_Arguments(1) /* position */)
126
      : $NaN;
127
  var firstIndex;
128
  if ($isNaN(index)) {
129
    firstIndex = sub.length - pat.length;
130
  } else {
131
    firstIndex = TO_INTEGER(index);
132
    if (firstIndex + pat.length > sub.length) {
133
      firstIndex = sub.length - pat.length;
134
    }
135
  }
136
  return %StringLastIndexOf(sub, pat, firstIndex);
137
}
138

    
139

    
140
// ECMA-262 section 15.5.4.9
141
//
142
// This function is implementation specific.  For now, we do not
143
// do anything locale specific.
144
function StringLocaleCompare(other) {
145
  if (%_ArgumentsLength() === 0) return 0;
146

    
147
  var this_str = ToString(this);
148
  var other_str = ToString(other);
149
  return %StringLocaleCompare(this_str, other_str);
150
}
151

    
152

    
153
// ECMA-262 section 15.5.4.10
154
function StringMatch(regexp) {
155
  if (!IS_REGEXP(regexp)) regexp = new ORIGINAL_REGEXP(regexp);
156
  var subject = ToString(this);
157

    
158
  if (!regexp.global) return regexp.exec(subject);
159
  %_Log('regexp', 'regexp-match,%0S,%1r', [subject, regexp]);
160
  // lastMatchInfo is defined in regexp-delay.js.
161
  return %StringMatch(subject, regexp, lastMatchInfo);
162
}
163

    
164

    
165
// SubString is an internal function that returns the sub string of 'string'.
166
// If resulting string is of length 1, we use the one character cache
167
// otherwise we call the runtime system.
168
function SubString(string, start, end) {
169
  // Use the one character string cache.
170
  if (start + 1 == end) {
171
    var char_code = %_FastCharCodeAt(string, start);
172
    if (!%_IsSmi(char_code)) {
173
      char_code = %StringCharCodeAt(string, start);
174
    }
175
    return %CharFromCode(char_code);
176
  }
177
  return %StringSlice(string, start, end);
178
}
179

    
180

    
181
// ECMA-262, section 15.5.4.11
182
function StringReplace(search, replace) {
183
  var subject = ToString(this);
184

    
185
  // Delegate to one of the regular expression variants if necessary.
186
  if (IS_REGEXP(search)) {
187
    %_Log('regexp', 'regexp-replace,%0r,%1S', [search, subject]);
188
    if (IS_FUNCTION(replace)) {
189
      return StringReplaceRegExpWithFunction(subject, search, replace);
190
    } else {
191
      return StringReplaceRegExp(subject, search, replace);
192
    }
193
  }
194

    
195
  // Convert the search argument to a string and search for it.
196
  search = ToString(search);
197
  var start = %StringIndexOf(subject, search, 0);
198
  if (start < 0) return subject;
199
  var end = start + search.length;
200

    
201
  var builder = new ReplaceResultBuilder(subject);
202
  // prefix
203
  builder.addSpecialSlice(0, start);
204

    
205
  // Compute the string to replace with.
206
  if (IS_FUNCTION(replace)) {
207
    builder.add(replace.call(null, search, start, subject));
208
  } else {
209
    reusableMatchInfo[CAPTURE0] = start;
210
    reusableMatchInfo[CAPTURE1] = end;
211
    ExpandReplacement(ToString(replace), subject, reusableMatchInfo, builder);
212
  }
213

    
214
  // suffix
215
  builder.addSpecialSlice(end, subject.length);
216

    
217
  return builder.generate();
218
}
219

    
220

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

    
228

    
229
// Helper function for regular expressions in String.prototype.replace.
230
function StringReplaceRegExp(subject, regexp, replace) {
231
  replace = ToString(replace);
232
  return %StringReplaceRegExpWithString(subject,
233
                                        regexp,
234
                                        replace,
235
                                        lastMatchInfo);
236
};
237

    
238

    
239
// Expand the $-expressions in the string and return a new string with
240
// the result.
241
function ExpandReplacement(string, subject, matchInfo, builder) {
242
  var next = %StringIndexOf(string, '$', 0);
243
  if (next < 0) {
244
    builder.add(string);
245
    return;
246
  }
247

    
248
  // Compute the number of captures; see ECMA-262, 15.5.4.11, p. 102.
249
  var m = NUMBER_OF_CAPTURES(matchInfo) >> 1;  // Includes the match.
250

    
251
  if (next > 0) builder.add(SubString(string, 0, next));
252
  var length = string.length;
253

    
254
  while (true) {
255
    var expansion = '$';
256
    var position = next + 1;
257
    if (position < length) {
258
      var peek = %_FastCharCodeAt(string, position);
259
      if (!%_IsSmi(peek)) {
260
        peek = %StringCharCodeAt(string, position);
261
      }
262
      if (peek == 36) {         // $$
263
        ++position;
264
        builder.add('$');
265
      } else if (peek == 38) {  // $& - match
266
        ++position;
267
        builder.addSpecialSlice(matchInfo[CAPTURE0],
268
                                matchInfo[CAPTURE1]);
269
      } else if (peek == 96) {  // $` - prefix
270
        ++position;
271
        builder.addSpecialSlice(0, matchInfo[CAPTURE0]);
272
      } else if (peek == 39) {  // $' - suffix
273
        ++position;
274
        builder.addSpecialSlice(matchInfo[CAPTURE1], subject.length);
275
      } else if (peek >= 48 && peek <= 57) {  // $n, 0 <= n <= 9
276
        ++position;
277
        var n = peek - 48;
278
        if (position < length) {
279
          peek = %_FastCharCodeAt(string, position);
280
          if (!%_IsSmi(peek)) {
281
            peek = %StringCharCodeAt(string, position);
282
          }
283
          // $nn, 01 <= nn <= 99
284
          if (n != 0 && peek == 48 || peek >= 49 && peek <= 57) {
285
            var nn = n * 10 + (peek - 48);
286
            if (nn < m) {
287
              // If the two digit capture reference is within range of
288
              // the captures, we use it instead of the single digit
289
              // one. Otherwise, we fall back to using the single
290
              // digit reference. This matches the behavior of
291
              // SpiderMonkey.
292
              ++position;
293
              n = nn;
294
            }
295
          }
296
        }
297
        if (0 < n && n < m) {
298
          addCaptureString(builder, matchInfo, n);
299
        } else {
300
          // Because of the captures range check in the parsing of two
301
          // digit capture references, we can only enter here when a
302
          // single digit capture reference is outside the range of
303
          // captures.
304
          builder.add('$');
305
          --position;
306
        }
307
      } else {
308
        builder.add('$');
309
      }
310
    } else {
311
      builder.add('$');
312
    }
313

    
314
    // Go the the next $ in the string.
315
    next = %StringIndexOf(string, '$', position);
316

    
317
    // Return if there are no more $ characters in the string. If we
318
    // haven't reached the end, we need to append the suffix.
319
    if (next < 0) {
320
      if (position < length) {
321
        builder.add(SubString(string, position, length));
322
      }
323
      return;
324
    }
325

    
326
    // Append substring between the previous and the next $ character.
327
    builder.add(SubString(string, position, next));
328
  }
329
};
330

    
331

    
332
// Compute the string of a given regular expression capture.
333
function CaptureString(string, lastCaptureInfo, index) {
334
  // Scale the index.
335
  var scaled = index << 1;
336
  // Compute start and end.
337
  var start = lastCaptureInfo[CAPTURE(scaled)];
338
  var end = lastCaptureInfo[CAPTURE(scaled + 1)];
339
  // If either start or end is missing return undefined.
340
  if (start < 0 || end < 0) return;
341
  return SubString(string, start, end);
342
};
343

    
344

    
345
// Add the string of a given regular expression capture to the
346
// ReplaceResultBuilder
347
function addCaptureString(builder, matchInfo, index) {
348
  // Scale the index.
349
  var scaled = index << 1;
350
  // Compute start and end.
351
  var start = matchInfo[CAPTURE(scaled)];
352
  var end = matchInfo[CAPTURE(scaled + 1)];
353
  // If either start or end is missing return.
354
  if (start < 0 || end <= start) return;
355
  builder.addSpecialSlice(start, end);
356
};
357

    
358

    
359
// Helper function for replacing regular expressions with the result of a
360
// function application in String.prototype.replace.  The function application
361
// must be interleaved with the regexp matching (contrary to ECMA-262
362
// 15.5.4.11) to mimic SpiderMonkey and KJS behavior when the function uses
363
// the static properties of the RegExp constructor.  Example:
364
//     'abcd'.replace(/(.)/g, function() { return RegExp.$1; }
365
// should be 'abcd' and not 'dddd' (or anything else).
366
function StringReplaceRegExpWithFunction(subject, regexp, replace) {
367
  var result = new ReplaceResultBuilder(subject);
368
  var lastMatchInfo = DoRegExpExec(regexp, subject, 0);
369
  if (IS_NULL(lastMatchInfo)) return subject;
370

    
371
  // There's at least one match.  If the regexp is global, we have to loop
372
  // over all matches.  The loop is not in C++ code here like the one in
373
  // RegExp.prototype.exec, because of the interleaved function application.
374
  // Unfortunately, that means this code is nearly duplicated, here and in
375
  // jsregexp.cc.
376
  if (regexp.global) {
377
    var previous = 0;
378
    do {
379
      result.addSpecialSlice(previous, lastMatchInfo[CAPTURE0]);
380
      var startOfMatch = lastMatchInfo[CAPTURE0];
381
      previous = lastMatchInfo[CAPTURE1];
382
      result.add(ApplyReplacementFunction(replace, lastMatchInfo, subject));
383
      // Can't use lastMatchInfo any more from here, since the function could
384
      // overwrite it.
385
      // Continue with the next match.
386
      // Increment previous if we matched an empty string, as per ECMA-262
387
      // 15.5.4.10.
388
      if (previous == startOfMatch) {
389
        // Add the skipped character to the output, if any.
390
        if (previous < subject.length) {
391
          result.addSpecialSlice(previous, previous + 1);
392
        }
393
        previous++;
394
      }
395

    
396
      // Per ECMA-262 15.10.6.2, if the previous index is greater than the
397
      // string length, there is no match
398
      lastMatchInfo = (previous > subject.length)
399
          ? null
400
          : DoRegExpExec(regexp, subject, previous);
401
    } while (!IS_NULL(lastMatchInfo));
402

    
403
    // Tack on the final right substring after the last match, if necessary.
404
    if (previous < subject.length) {
405
      result.addSpecialSlice(previous, subject.length);
406
    }
407
  } else { // Not a global regexp, no need to loop.
408
    result.addSpecialSlice(0, lastMatchInfo[CAPTURE0]);
409
    var endOfMatch = lastMatchInfo[CAPTURE1];
410
    result.add(ApplyReplacementFunction(replace, lastMatchInfo, subject));
411
    // Can't use lastMatchInfo any more from here, since the function could
412
    // overwrite it.
413
    result.addSpecialSlice(endOfMatch, subject.length);
414
  }
415

    
416
  return result.generate();
417
}
418

    
419

    
420
// Helper function to apply a string replacement function once.
421
function ApplyReplacementFunction(replace, lastMatchInfo, subject) {
422
  // Compute the parameter list consisting of the match, captures, index,
423
  // and subject for the replace function invocation.
424
  var index = lastMatchInfo[CAPTURE0];
425
  // The number of captures plus one for the match.
426
  var m = NUMBER_OF_CAPTURES(lastMatchInfo) >> 1;
427
  if (m == 1) {
428
    var s = CaptureString(subject, lastMatchInfo, 0);
429
    // Don't call directly to avoid exposing the built-in global object.
430
    return ToString(replace.call(null, s, index, subject));
431
  }
432
  var parameters = $Array(m + 2);
433
  for (var j = 0; j < m; j++) {
434
    parameters[j] = CaptureString(subject, lastMatchInfo, j);
435
  }
436
  parameters[j] = index;
437
  parameters[j + 1] = subject;
438
  return ToString(replace.apply(null, parameters));
439
}
440

    
441

    
442
// ECMA-262 section 15.5.4.12
443
function StringSearch(re) {
444
  var regexp = new ORIGINAL_REGEXP(re);
445
  var s = ToString(this);
446
  var last_idx = regexp.lastIndex; // keep old lastIndex
447
  regexp.lastIndex = 0;            // ignore re.global property
448
  var result = regexp.exec(s);
449
  regexp.lastIndex = last_idx;     // restore lastIndex
450
  if (result == null)
451
    return -1;
452
  else
453
    return result.index;
454
}
455

    
456

    
457
// ECMA-262 section 15.5.4.13
458
function StringSlice(start, end) {
459
  var s = ToString(this);
460
  var s_len = s.length;
461
  var start_i = TO_INTEGER(start);
462
  var end_i = s_len;
463
  if (end !== void 0)
464
    end_i = TO_INTEGER(end);
465

    
466
  if (start_i < 0) {
467
    start_i += s_len;
468
    if (start_i < 0)
469
      start_i = 0;
470
  } else {
471
    if (start_i > s_len)
472
      start_i = s_len;
473
  }
474

    
475
  if (end_i < 0) {
476
    end_i += s_len;
477
    if (end_i < 0)
478
      end_i = 0;
479
  } else {
480
    if (end_i > s_len)
481
      end_i = s_len;
482
  }
483

    
484
  var num_c = end_i - start_i;
485
  if (num_c < 0)
486
    num_c = 0;
487

    
488
  return SubString(s, start_i, start_i + num_c);
489
}
490

    
491

    
492
// ECMA-262 section 15.5.4.14
493
function StringSplit(separator, limit) {
494
  var subject = ToString(this);
495
  var result = [];
496
  var lim = (limit === void 0) ? 0xffffffff : ToUint32(limit);
497

    
498
  if (lim === 0) return result;
499

    
500
  // ECMA-262 says that if separator is undefined, the result should
501
  // be an array of size 1 containing the entire string.  SpiderMonkey
502
  // and KJS have this behaviour only when no separator is given.  If
503
  // undefined is explicitly given, they convert it to a string and
504
  // use that.  We do as SpiderMonkey and KJS.
505
  if (%_ArgumentsLength() === 0) {
506
    result[result.length] = subject;
507
    return result;
508
  }
509

    
510
  var length = subject.length;
511
  var currentIndex = 0;
512
  var startIndex = 0;
513

    
514
  var sep;
515
  if (IS_REGEXP(separator)) {
516
    sep = separator;
517
    %_Log('regexp', 'regexp-split,%0S,%1r', [subject, sep]);
518
  } else {
519
    sep = ToString(separator);
520
  }
521

    
522
  if (length === 0) {
523
    if (splitMatch(sep, subject, 0, 0) != null) return result;
524
    result[result.length] = subject;
525
    return result;
526
  }
527

    
528
  while (true) {
529

    
530
    if (startIndex === length) {
531
      result[result.length] = subject.slice(currentIndex, length);
532
      return result;
533
    }
534

    
535
    var lastMatchInfo = splitMatch(sep, subject, currentIndex, startIndex);
536

    
537
    if (IS_NULL(lastMatchInfo)) {
538
      result[result.length] = subject.slice(currentIndex, length);
539
      return result;
540
    }
541

    
542
    var endIndex = lastMatchInfo[CAPTURE1];
543

    
544
    // We ignore a zero-length match at the currentIndex.
545
    if (startIndex === endIndex && endIndex === currentIndex) {
546
      startIndex++;
547
      continue;
548
    }
549

    
550
    result[result.length] =
551
        SubString(subject, currentIndex, lastMatchInfo[CAPTURE0]);
552
    if (result.length === lim) return result;
553

    
554
    for (var i = 2; i < NUMBER_OF_CAPTURES(lastMatchInfo); i += 2) {
555
      var start = lastMatchInfo[CAPTURE(i)];
556
      var end = lastMatchInfo[CAPTURE(i + 1)];
557
      if (start != -1 && end != -1) {
558
        result[result.length] = SubString(subject,
559
                                          lastMatchInfo[CAPTURE(i)],
560
                                          lastMatchInfo[CAPTURE(i + 1)]);
561
      } else {
562
        result[result.length] = void 0;
563
      }
564
      if (result.length === lim) return result;
565
    }
566

    
567
    startIndex = currentIndex = endIndex;
568
  }
569
}
570

    
571

    
572
// ECMA-262 section 15.5.4.14
573
// Helper function used by split.  This version returns the lastMatchInfo
574
// instead of allocating a new array with basically the same information.
575
function splitMatch(separator, subject, current_index, start_index) {
576
  if (IS_REGEXP(separator)) {
577
    var lastMatchInfo = DoRegExpExec(separator, subject, start_index);
578
    if (lastMatchInfo == null) return null;
579
    // Section 15.5.4.14 paragraph two says that we do not allow zero length
580
    // matches at the end of the string.
581
    if (lastMatchInfo[CAPTURE0] === subject.length) return null;
582
    return lastMatchInfo;
583
  }
584

    
585
  var separatorIndex = subject.indexOf(separator, start_index);
586
  if (separatorIndex === -1) return null;
587

    
588
  reusableMatchInfo[CAPTURE0] = separatorIndex;
589
  reusableMatchInfo[CAPTURE1] = separatorIndex + separator.length;
590
  return reusableMatchInfo;
591
};
592

    
593

    
594
// ECMA-262 section 15.5.4.15
595
function StringSubstring(start, end) {
596
  var s = ToString(this);
597
  var s_len = s.length;
598
  var start_i = TO_INTEGER(start);
599
  var end_i = s_len;
600
  if (!IS_UNDEFINED(end))
601
    end_i = TO_INTEGER(end);
602

    
603
  if (start_i < 0) start_i = 0;
604
  if (start_i > s_len) start_i = s_len;
605
  if (end_i < 0) end_i = 0;
606
  if (end_i > s_len) end_i = s_len;
607

    
608
  if (start_i > end_i) {
609
    var tmp = end_i;
610
    end_i = start_i;
611
    start_i = tmp;
612
  }
613

    
614
  return SubString(s, start_i, end_i);
615
}
616

    
617

    
618
// This is not a part of ECMA-262.
619
function StringSubstr(start, n) {
620
  var s = ToString(this);
621
  var len;
622

    
623
  // Correct n: If not given, set to string length; if explicitly
624
  // set to undefined, zero, or negative, returns empty string.
625
  if (n === void 0) {
626
    len = s.length;
627
  } else {
628
    len = TO_INTEGER(n);
629
    if (len <= 0) return '';
630
  }
631

    
632
  // Correct start: If not given (or undefined), set to zero; otherwise
633
  // convert to integer and handle negative case.
634
  if (start === void 0) {
635
    start = 0;
636
  } else {
637
    start = TO_INTEGER(start);
638
    // If positive, and greater than or equal to the string length,
639
    // return empty string.
640
    if (start >= s.length) return '';
641
    // If negative and absolute value is larger than the string length,
642
    // use zero.
643
    if (start < 0) {
644
      start += s.length;
645
      if (start < 0) start = 0;
646
    }
647
  }
648

    
649
  var end = start + len;
650
  if (end > s.length) end = s.length;
651

    
652
  return SubString(s, start, end);
653
}
654

    
655

    
656
// ECMA-262, 15.5.4.16
657
function StringToLowerCase() {
658
  return %StringToLowerCase(ToString(this));
659
}
660

    
661

    
662
// ECMA-262, 15.5.4.17
663
function StringToLocaleLowerCase() {
664
  return %StringToLowerCase(ToString(this));
665
}
666

    
667

    
668
// ECMA-262, 15.5.4.18
669
function StringToUpperCase() {
670
  return %StringToUpperCase(ToString(this));
671
}
672

    
673

    
674
// ECMA-262, 15.5.4.19
675
function StringToLocaleUpperCase() {
676
  return %StringToUpperCase(ToString(this));
677
}
678

    
679

    
680
// ECMA-262, section 15.5.3.2
681
function StringFromCharCode(code) {
682
  var n = %_ArgumentsLength();
683
  if (n == 1) return %CharFromCode(ToNumber(code) & 0xffff)
684

    
685
  // NOTE: This is not super-efficient, but it is necessary because we
686
  // want to avoid converting to numbers from within the virtual
687
  // machine. Maybe we can find another way of doing this?
688
  var codes = new $Array(n);
689
  for (var i = 0; i < n; i++) codes[i] = ToNumber(%_Arguments(i));
690
  return %StringFromCharCodeArray(codes);
691
}
692

    
693

    
694
// Helper function for very basic XSS protection.
695
function HtmlEscape(str) {
696
  return ToString(str).replace(/</g, "&lt;")
697
                      .replace(/>/g, "&gt;")
698
                      .replace(/"/g, "&quot;")
699
                      .replace(/'/g, "&#039;");
700
};
701

    
702

    
703
// Compatibility support for KJS.
704
// Tested by mozilla/js/tests/js1_5/Regress/regress-276103.js.
705
function StringLink(s) {
706
  return "<a href=\"" + HtmlEscape(s) + "\">" + this + "</a>";
707
}
708

    
709

    
710
function StringAnchor(name) {
711
  return "<a name=\"" + HtmlEscape(name) + "\">" + this + "</a>";
712
}
713

    
714

    
715
function StringFontcolor(color) {
716
  return "<font color=\"" + HtmlEscape(color) + "\">" + this + "</font>";
717
}
718

    
719

    
720
function StringFontsize(size) {
721
  return "<font size=\"" + HtmlEscape(size) + "\">" + this + "</font>";
722
}
723

    
724

    
725
function StringBig() {
726
  return "<big>" + this + "</big>";
727
}
728

    
729

    
730
function StringBlink() {
731
  return "<blink>" + this + "</blink>";
732
}
733

    
734

    
735
function StringBold() {
736
  return "<b>" + this + "</b>";
737
}
738

    
739

    
740
function StringFixed() {
741
  return "<tt>" + this + "</tt>";
742
}
743

    
744

    
745
function StringItalics() {
746
  return "<i>" + this + "</i>";
747
}
748

    
749

    
750
function StringSmall() {
751
  return "<small>" + this + "</small>";
752
}
753

    
754

    
755
function StringStrike() {
756
  return "<strike>" + this + "</strike>";
757
}
758

    
759

    
760
function StringSub() {
761
  return "<sub>" + this + "</sub>";
762
}
763

    
764

    
765
function StringSup() {
766
  return "<sup>" + this + "</sup>";
767
}
768

    
769

    
770
// StringBuilder support.
771

    
772
function StringBuilder() {
773
  this.elements = new $Array();
774
}
775

    
776

    
777
function ReplaceResultBuilder(str) {
778
  this.elements = new $Array();
779
  this.special_string = str;
780
}
781

    
782

    
783
ReplaceResultBuilder.prototype.add =
784
StringBuilder.prototype.add = function(str) {
785
  if (!IS_STRING(str)) str = ToString(str);
786
  if (str.length > 0) {
787
    var elements = this.elements;
788
    elements[elements.length] = str;
789
  }
790
}
791

    
792

    
793
ReplaceResultBuilder.prototype.addSpecialSlice = function(start, end) {
794
  var len = end - start;
795
  if (len == 0) return;
796
  var elements = this.elements;
797
  if (start >= 0 && len >= 0 && start < 0x80000 && len < 0x800) {
798
    elements[elements.length] = (start << 11) + len;
799
  } else {
800
    elements[elements.length] = SubString(this.special_string, start, end);
801
  }
802
}
803

    
804

    
805
StringBuilder.prototype.generate = function() {
806
  return %StringBuilderConcat(this.elements, "");
807
}
808

    
809

    
810
ReplaceResultBuilder.prototype.generate = function() {
811
  return %StringBuilderConcat(this.elements, this.special_string);
812
}
813

    
814

    
815
// -------------------------------------------------------------------
816

    
817
function SetupString() {
818
  // Setup the constructor property on the String prototype object.
819
  %SetProperty($String.prototype, "constructor", $String, DONT_ENUM);
820

    
821

    
822
  // Setup the non-enumerable functions on the String object.
823
  InstallFunctions($String, DONT_ENUM, $Array(
824
    "fromCharCode", StringFromCharCode
825
  ));
826

    
827

    
828
  // Setup the non-enumerable functions on the String prototype object.
829
  InstallFunctions($String.prototype, DONT_ENUM, $Array(
830
    "valueOf", StringValueOf,
831
    "toString", StringToString,
832
    "charAt", StringCharAt,
833
    "charCodeAt", StringCharCodeAt,
834
    "concat", StringConcat,
835
    "indexOf", StringIndexOf,
836
    "lastIndexOf", StringLastIndexOf,
837
    "localeCompare", StringLocaleCompare,
838
    "match", StringMatch,
839
    "replace", StringReplace,
840
    "search", StringSearch,
841
    "slice", StringSlice,
842
    "split", StringSplit,
843
    "substring", StringSubstring,
844
    "substr", StringSubstr,
845
    "toLowerCase", StringToLowerCase,
846
    "toLocaleLowerCase", StringToLocaleLowerCase,
847
    "toUpperCase", StringToUpperCase,
848
    "toLocaleUpperCase", StringToLocaleUpperCase,
849
    "link", StringLink,
850
    "anchor", StringAnchor,
851
    "fontcolor", StringFontcolor,
852
    "fontsize", StringFontsize,
853
    "big", StringBig,
854
    "blink", StringBlink,
855
    "bold", StringBold,
856
    "fixed", StringFixed,
857
    "italics", StringItalics,
858
    "small", StringSmall,
859
    "strike", StringStrike,
860
    "sub", StringSub,
861
    "sup", StringSup
862
  ));
863
}
864

    
865

    
866
SetupString();