• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2012 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// This file relies on the fact that the following declaration has been made
6// in runtime.js:
7// var $String = global.String;
8
9// -------------------------------------------------------------------
10
11function StringConstructor(x) {
12  var value = %_ArgumentsLength() == 0 ? '' : TO_STRING_INLINE(x);
13  if (%_IsConstructCall()) {
14    %_SetValueOf(this, value);
15  } else {
16    return value;
17  }
18}
19
20
21// ECMA-262 section 15.5.4.2
22function StringToString() {
23  if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) {
24    throw new $TypeError('String.prototype.toString is not generic');
25  }
26  return %_ValueOf(this);
27}
28
29
30// ECMA-262 section 15.5.4.3
31function StringValueOf() {
32  if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) {
33    throw new $TypeError('String.prototype.valueOf is not generic');
34  }
35  return %_ValueOf(this);
36}
37
38
39// ECMA-262, section 15.5.4.4
40function StringCharAt(pos) {
41  CHECK_OBJECT_COERCIBLE(this, "String.prototype.charAt");
42
43  var result = %_StringCharAt(this, pos);
44  if (%_IsSmi(result)) {
45    result = %_StringCharAt(TO_STRING_INLINE(this), TO_INTEGER(pos));
46  }
47  return result;
48}
49
50
51// ECMA-262 section 15.5.4.5
52function StringCharCodeAt(pos) {
53  CHECK_OBJECT_COERCIBLE(this, "String.prototype.charCodeAt");
54
55  var result = %_StringCharCodeAt(this, pos);
56  if (!%_IsSmi(result)) {
57    result = %_StringCharCodeAt(TO_STRING_INLINE(this), TO_INTEGER(pos));
58  }
59  return result;
60}
61
62
63// ECMA-262, section 15.5.4.6
64function StringConcat() {
65  CHECK_OBJECT_COERCIBLE(this, "String.prototype.concat");
66
67  var len = %_ArgumentsLength();
68  var this_as_string = TO_STRING_INLINE(this);
69  if (len === 1) {
70    return this_as_string + %_Arguments(0);
71  }
72  var parts = new InternalArray(len + 1);
73  parts[0] = this_as_string;
74  for (var i = 0; i < len; i++) {
75    var part = %_Arguments(i);
76    parts[i + 1] = TO_STRING_INLINE(part);
77  }
78  return %StringBuilderConcat(parts, len + 1, "");
79}
80
81// Match ES3 and Safari
82%FunctionSetLength(StringConcat, 1);
83
84
85// ECMA-262 section 15.5.4.7
86function StringIndexOfJS(pattern /* position */) {  // length == 1
87  CHECK_OBJECT_COERCIBLE(this, "String.prototype.indexOf");
88
89  var subject = TO_STRING_INLINE(this);
90  pattern = TO_STRING_INLINE(pattern);
91  var index = 0;
92  if (%_ArgumentsLength() > 1) {
93    index = %_Arguments(1);  // position
94    index = TO_INTEGER(index);
95    if (index < 0) index = 0;
96    if (index > subject.length) index = subject.length;
97  }
98  return %StringIndexOf(subject, pattern, index);
99}
100
101
102// ECMA-262 section 15.5.4.8
103function StringLastIndexOfJS(pat /* position */) {  // length == 1
104  CHECK_OBJECT_COERCIBLE(this, "String.prototype.lastIndexOf");
105
106  var sub = TO_STRING_INLINE(this);
107  var subLength = sub.length;
108  var pat = TO_STRING_INLINE(pat);
109  var patLength = pat.length;
110  var index = subLength - patLength;
111  if (%_ArgumentsLength() > 1) {
112    var position = ToNumber(%_Arguments(1));
113    if (!NUMBER_IS_NAN(position)) {
114      position = TO_INTEGER(position);
115      if (position < 0) {
116        position = 0;
117      }
118      if (position + patLength < subLength) {
119        index = position;
120      }
121    }
122  }
123  if (index < 0) {
124    return -1;
125  }
126  return %StringLastIndexOf(sub, pat, index);
127}
128
129
130// ECMA-262 section 15.5.4.9
131//
132// This function is implementation specific.  For now, we do not
133// do anything locale specific.
134function StringLocaleCompareJS(other) {
135  CHECK_OBJECT_COERCIBLE(this, "String.prototype.localeCompare");
136
137  return %StringLocaleCompare(TO_STRING_INLINE(this),
138                              TO_STRING_INLINE(other));
139}
140
141
142// ECMA-262 section 15.5.4.10
143function StringMatchJS(regexp) {
144  CHECK_OBJECT_COERCIBLE(this, "String.prototype.match");
145
146  var subject = TO_STRING_INLINE(this);
147  if (IS_REGEXP(regexp)) {
148    // Emulate RegExp.prototype.exec's side effect in step 5, even though
149    // value is discarded.
150    var lastIndex = regexp.lastIndex;
151    TO_INTEGER_FOR_SIDE_EFFECT(lastIndex);
152    if (!regexp.global) return RegExpExecNoTests(regexp, subject, 0);
153    // lastMatchInfo is defined in regexp.js.
154    var result = %StringMatch(subject, regexp, lastMatchInfo);
155    if (result !== null) lastMatchInfoOverride = null;
156    regexp.lastIndex = 0;
157    return result;
158  }
159  // Non-regexp argument.
160  regexp = new $RegExp(regexp);
161  return RegExpExecNoTests(regexp, subject, 0);
162}
163
164
165var NORMALIZATION_FORMS = ['NFC', 'NFD', 'NFKC', 'NFKD'];
166
167
168// ECMA-262 v6, section 21.1.3.12
169//
170// For now we do nothing, as proper normalization requires big tables.
171// If Intl is enabled, then i18n.js will override it and provide the the
172// proper functionality.
173function StringNormalizeJS(form) {
174  CHECK_OBJECT_COERCIBLE(this, "String.prototype.normalize");
175
176  var form = form ? TO_STRING_INLINE(form) : 'NFC';
177  var normalizationForm = NORMALIZATION_FORMS.indexOf(form);
178  if (normalizationForm === -1) {
179    throw new $RangeError('The normalization form should be one of '
180        + NORMALIZATION_FORMS.join(', ') + '.');
181  }
182
183  return %_ValueOf(this);
184}
185
186
187// This has the same size as the lastMatchInfo array, and can be used for
188// functions that expect that structure to be returned.  It is used when the
189// needle is a string rather than a regexp.  In this case we can't update
190// lastMatchArray without erroneously affecting the properties on the global
191// RegExp object.
192var reusableMatchInfo = [2, "", "", -1, -1];
193
194
195// ECMA-262, section 15.5.4.11
196function StringReplace(search, replace) {
197  CHECK_OBJECT_COERCIBLE(this, "String.prototype.replace");
198
199  var subject = TO_STRING_INLINE(this);
200
201  // Decision tree for dispatch
202  // .. regexp search
203  // .... string replace
204  // ...... non-global search
205  // ........ empty string replace
206  // ........ non-empty string replace (with $-expansion)
207  // ...... global search
208  // ........ no need to circumvent last match info override
209  // ........ need to circument last match info override
210  // .... function replace
211  // ...... global search
212  // ...... non-global search
213  // .. string search
214  // .... special case that replaces with one single character
215  // ...... function replace
216  // ...... string replace (with $-expansion)
217
218  if (IS_REGEXP(search)) {
219    // Emulate RegExp.prototype.exec's side effect in step 5, even if
220    // value is discarded.
221    var lastIndex = search.lastIndex;
222    TO_INTEGER_FOR_SIDE_EFFECT(lastIndex);
223
224    if (!IS_SPEC_FUNCTION(replace)) {
225      replace = TO_STRING_INLINE(replace);
226
227      if (!search.global) {
228        // Non-global regexp search, string replace.
229        var match = DoRegExpExec(search, subject, 0);
230        if (match == null) {
231          search.lastIndex = 0
232          return subject;
233        }
234        if (replace.length == 0) {
235          return %_SubString(subject, 0, match[CAPTURE0]) +
236                 %_SubString(subject, match[CAPTURE1], subject.length)
237        }
238        return ExpandReplacement(replace, subject, lastMatchInfo,
239                                 %_SubString(subject, 0, match[CAPTURE0])) +
240               %_SubString(subject, match[CAPTURE1], subject.length);
241      }
242
243      // Global regexp search, string replace.
244      search.lastIndex = 0;
245      if (lastMatchInfoOverride == null) {
246        return %StringReplaceGlobalRegExpWithString(
247            subject, search, replace, lastMatchInfo);
248      } else {
249        // We use this hack to detect whether StringReplaceRegExpWithString
250        // found at least one hit. In that case we need to remove any
251        // override.
252        var saved_subject = lastMatchInfo[LAST_SUBJECT_INDEX];
253        lastMatchInfo[LAST_SUBJECT_INDEX] = 0;
254        var answer = %StringReplaceGlobalRegExpWithString(
255            subject, search, replace, lastMatchInfo);
256        if (%_IsSmi(lastMatchInfo[LAST_SUBJECT_INDEX])) {
257          lastMatchInfo[LAST_SUBJECT_INDEX] = saved_subject;
258        } else {
259          lastMatchInfoOverride = null;
260        }
261        return answer;
262      }
263    }
264
265    if (search.global) {
266      // Global regexp search, function replace.
267      return StringReplaceGlobalRegExpWithFunction(subject, search, replace);
268    }
269    // Non-global regexp search, function replace.
270    return StringReplaceNonGlobalRegExpWithFunction(subject, search, replace);
271  }
272
273  search = TO_STRING_INLINE(search);
274
275  if (search.length == 1 &&
276      subject.length > 0xFF &&
277      IS_STRING(replace) &&
278      %StringIndexOf(replace, '$', 0) < 0) {
279    // Searching by traversing a cons string tree and replace with cons of
280    // slices works only when the replaced string is a single character, being
281    // replaced by a simple string and only pays off for long strings.
282    return %StringReplaceOneCharWithString(subject, search, replace);
283  }
284  var start = %StringIndexOf(subject, search, 0);
285  if (start < 0) return subject;
286  var end = start + search.length;
287
288  var result = %_SubString(subject, 0, start);
289
290  // Compute the string to replace with.
291  if (IS_SPEC_FUNCTION(replace)) {
292    var receiver = %GetDefaultReceiver(replace);
293    result += %_CallFunction(receiver, search, start, subject, replace);
294  } else {
295    reusableMatchInfo[CAPTURE0] = start;
296    reusableMatchInfo[CAPTURE1] = end;
297    result = ExpandReplacement(TO_STRING_INLINE(replace),
298                               subject,
299                               reusableMatchInfo,
300                               result);
301  }
302
303  return result + %_SubString(subject, end, subject.length);
304}
305
306
307// Expand the $-expressions in the string and return a new string with
308// the result.
309function ExpandReplacement(string, subject, matchInfo, result) {
310  var length = string.length;
311  var next = %StringIndexOf(string, '$', 0);
312  if (next < 0) {
313    if (length > 0) result += string;
314    return result;
315  }
316
317  if (next > 0) result += %_SubString(string, 0, next);
318
319  while (true) {
320    var expansion = '$';
321    var position = next + 1;
322    if (position < length) {
323      var peek = %_StringCharCodeAt(string, position);
324      if (peek == 36) {         // $$
325        ++position;
326        result += '$';
327      } else if (peek == 38) {  // $& - match
328        ++position;
329        result +=
330          %_SubString(subject, matchInfo[CAPTURE0], matchInfo[CAPTURE1]);
331      } else if (peek == 96) {  // $` - prefix
332        ++position;
333        result += %_SubString(subject, 0, matchInfo[CAPTURE0]);
334      } else if (peek == 39) {  // $' - suffix
335        ++position;
336        result += %_SubString(subject, matchInfo[CAPTURE1], subject.length);
337      } else if (peek >= 48 && peek <= 57) {
338        // Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99
339        var scaled_index = (peek - 48) << 1;
340        var advance = 1;
341        var number_of_captures = NUMBER_OF_CAPTURES(matchInfo);
342        if (position + 1 < string.length) {
343          var next = %_StringCharCodeAt(string, position + 1);
344          if (next >= 48 && next <= 57) {
345            var new_scaled_index = scaled_index * 10 + ((next - 48) << 1);
346            if (new_scaled_index < number_of_captures) {
347              scaled_index = new_scaled_index;
348              advance = 2;
349            }
350          }
351        }
352        if (scaled_index != 0 && scaled_index < number_of_captures) {
353          var start = matchInfo[CAPTURE(scaled_index)];
354          if (start >= 0) {
355            result +=
356              %_SubString(subject, start, matchInfo[CAPTURE(scaled_index + 1)]);
357          }
358          position += advance;
359        } else {
360          result += '$';
361        }
362      } else {
363        result += '$';
364      }
365    } else {
366      result += '$';
367    }
368
369    // Go the the next $ in the string.
370    next = %StringIndexOf(string, '$', position);
371
372    // Return if there are no more $ characters in the string. If we
373    // haven't reached the end, we need to append the suffix.
374    if (next < 0) {
375      if (position < length) {
376        result += %_SubString(string, position, length);
377      }
378      return result;
379    }
380
381    // Append substring between the previous and the next $ character.
382    if (next > position) {
383      result += %_SubString(string, position, next);
384    }
385  }
386  return result;
387}
388
389
390// Compute the string of a given regular expression capture.
391function CaptureString(string, lastCaptureInfo, index) {
392  // Scale the index.
393  var scaled = index << 1;
394  // Compute start and end.
395  var start = lastCaptureInfo[CAPTURE(scaled)];
396  // If start isn't valid, return undefined.
397  if (start < 0) return;
398  var end = lastCaptureInfo[CAPTURE(scaled + 1)];
399  return %_SubString(string, start, end);
400}
401
402
403// TODO(lrn): This array will survive indefinitely if replace is never
404// called again. However, it will be empty, since the contents are cleared
405// in the finally block.
406var reusableReplaceArray = new InternalArray(16);
407
408// Helper function for replacing regular expressions with the result of a
409// function application in String.prototype.replace.
410function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) {
411  var resultArray = reusableReplaceArray;
412  if (resultArray) {
413    reusableReplaceArray = null;
414  } else {
415    // Inside a nested replace (replace called from the replacement function
416    // of another replace) or we have failed to set the reusable array
417    // back due to an exception in a replacement function. Create a new
418    // array to use in the future, or until the original is written back.
419    resultArray = new InternalArray(16);
420  }
421  var res = %RegExpExecMultiple(regexp,
422                                subject,
423                                lastMatchInfo,
424                                resultArray);
425  regexp.lastIndex = 0;
426  if (IS_NULL(res)) {
427    // No matches at all.
428    reusableReplaceArray = resultArray;
429    return subject;
430  }
431  var len = res.length;
432  if (NUMBER_OF_CAPTURES(lastMatchInfo) == 2) {
433    // If the number of captures is two then there are no explicit captures in
434    // the regexp, just the implicit capture that captures the whole match.  In
435    // this case we can simplify quite a bit and end up with something faster.
436    // The builder will consist of some integers that indicate slices of the
437    // input string and some replacements that were returned from the replace
438    // function.
439    var match_start = 0;
440    var override = new InternalPackedArray(null, 0, subject);
441    var receiver = %GetDefaultReceiver(replace);
442    for (var i = 0; i < len; i++) {
443      var elem = res[i];
444      if (%_IsSmi(elem)) {
445        // Integers represent slices of the original string.  Use these to
446        // get the offsets we need for the override array (so things like
447        // RegExp.leftContext work during the callback function.
448        if (elem > 0) {
449          match_start = (elem >> 11) + (elem & 0x7ff);
450        } else {
451          match_start = res[++i] - elem;
452        }
453      } else {
454        override[0] = elem;
455        override[1] = match_start;
456        lastMatchInfoOverride = override;
457        var func_result =
458            %_CallFunction(receiver, elem, match_start, subject, replace);
459        // Overwrite the i'th element in the results with the string we got
460        // back from the callback function.
461        res[i] = TO_STRING_INLINE(func_result);
462        match_start += elem.length;
463      }
464    }
465  } else {
466    var receiver = %GetDefaultReceiver(replace);
467    for (var i = 0; i < len; i++) {
468      var elem = res[i];
469      if (!%_IsSmi(elem)) {
470        // elem must be an Array.
471        // Use the apply argument as backing for global RegExp properties.
472        lastMatchInfoOverride = elem;
473        var func_result = %Apply(replace, receiver, elem, 0, elem.length);
474        // Overwrite the i'th element in the results with the string we got
475        // back from the callback function.
476        res[i] = TO_STRING_INLINE(func_result);
477      }
478    }
479  }
480  var result = %StringBuilderConcat(res, res.length, subject);
481  resultArray.length = 0;
482  reusableReplaceArray = resultArray;
483  return result;
484}
485
486
487function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) {
488  var matchInfo = DoRegExpExec(regexp, subject, 0);
489  if (IS_NULL(matchInfo)) {
490    regexp.lastIndex = 0;
491    return subject;
492  }
493  var index = matchInfo[CAPTURE0];
494  var result = %_SubString(subject, 0, index);
495  var endOfMatch = matchInfo[CAPTURE1];
496  // Compute the parameter list consisting of the match, captures, index,
497  // and subject for the replace function invocation.
498  // The number of captures plus one for the match.
499  var m = NUMBER_OF_CAPTURES(matchInfo) >> 1;
500  var replacement;
501  var receiver = %GetDefaultReceiver(replace);
502  if (m == 1) {
503    // No captures, only the match, which is always valid.
504    var s = %_SubString(subject, index, endOfMatch);
505    // Don't call directly to avoid exposing the built-in global object.
506    replacement = %_CallFunction(receiver, s, index, subject, replace);
507  } else {
508    var parameters = new InternalArray(m + 2);
509    for (var j = 0; j < m; j++) {
510      parameters[j] = CaptureString(subject, matchInfo, j);
511    }
512    parameters[j] = index;
513    parameters[j + 1] = subject;
514
515    replacement = %Apply(replace, receiver, parameters, 0, j + 2);
516  }
517
518  result += replacement;  // The add method converts to string if necessary.
519  // Can't use matchInfo any more from here, since the function could
520  // overwrite it.
521  return result + %_SubString(subject, endOfMatch, subject.length);
522}
523
524
525// ECMA-262 section 15.5.4.12
526function StringSearch(re) {
527  CHECK_OBJECT_COERCIBLE(this, "String.prototype.search");
528
529  var regexp;
530  if (IS_STRING(re)) {
531    regexp = %_GetFromCache(STRING_TO_REGEXP_CACHE_ID, re);
532  } else if (IS_REGEXP(re)) {
533    regexp = re;
534  } else {
535    regexp = new $RegExp(re);
536  }
537  var match = DoRegExpExec(regexp, TO_STRING_INLINE(this), 0);
538  if (match) {
539    return match[CAPTURE0];
540  }
541  return -1;
542}
543
544
545// ECMA-262 section 15.5.4.13
546function StringSlice(start, end) {
547  CHECK_OBJECT_COERCIBLE(this, "String.prototype.slice");
548
549  var s = TO_STRING_INLINE(this);
550  var s_len = s.length;
551  var start_i = TO_INTEGER(start);
552  var end_i = s_len;
553  if (!IS_UNDEFINED(end)) {
554    end_i = TO_INTEGER(end);
555  }
556
557  if (start_i < 0) {
558    start_i += s_len;
559    if (start_i < 0) {
560      start_i = 0;
561    }
562  } else {
563    if (start_i > s_len) {
564      return '';
565    }
566  }
567
568  if (end_i < 0) {
569    end_i += s_len;
570    if (end_i < 0) {
571      return '';
572    }
573  } else {
574    if (end_i > s_len) {
575      end_i = s_len;
576    }
577  }
578
579  if (end_i <= start_i) {
580    return '';
581  }
582
583  return %_SubString(s, start_i, end_i);
584}
585
586
587// ECMA-262 section 15.5.4.14
588function StringSplitJS(separator, limit) {
589  CHECK_OBJECT_COERCIBLE(this, "String.prototype.split");
590
591  var subject = TO_STRING_INLINE(this);
592  limit = (IS_UNDEFINED(limit)) ? 0xffffffff : TO_UINT32(limit);
593
594  var length = subject.length;
595  if (!IS_REGEXP(separator)) {
596    var separator_string = TO_STRING_INLINE(separator);
597
598    if (limit === 0) return [];
599
600    // ECMA-262 says that if separator is undefined, the result should
601    // be an array of size 1 containing the entire string.
602    if (IS_UNDEFINED(separator)) return [subject];
603
604    var separator_length = separator_string.length;
605
606    // If the separator string is empty then return the elements in the subject.
607    if (separator_length === 0) return %StringToArray(subject, limit);
608
609    var result = %StringSplit(subject, separator_string, limit);
610
611    return result;
612  }
613
614  if (limit === 0) return [];
615
616  // Separator is a regular expression.
617  return StringSplitOnRegExp(subject, separator, limit, length);
618}
619
620
621function StringSplitOnRegExp(subject, separator, limit, length) {
622  if (length === 0) {
623    if (DoRegExpExec(separator, subject, 0, 0) != null) {
624      return [];
625    }
626    return [subject];
627  }
628
629  var currentIndex = 0;
630  var startIndex = 0;
631  var startMatch = 0;
632  var result = new InternalArray();
633
634  outer_loop:
635  while (true) {
636
637    if (startIndex === length) {
638      result[result.length] = %_SubString(subject, currentIndex, length);
639      break;
640    }
641
642    var matchInfo = DoRegExpExec(separator, subject, startIndex);
643    if (matchInfo == null || length === (startMatch = matchInfo[CAPTURE0])) {
644      result[result.length] = %_SubString(subject, currentIndex, length);
645      break;
646    }
647    var endIndex = matchInfo[CAPTURE1];
648
649    // We ignore a zero-length match at the currentIndex.
650    if (startIndex === endIndex && endIndex === currentIndex) {
651      startIndex++;
652      continue;
653    }
654
655    result[result.length] = %_SubString(subject, currentIndex, startMatch);
656
657    if (result.length === limit) break;
658
659    var matchinfo_len = NUMBER_OF_CAPTURES(matchInfo) + REGEXP_FIRST_CAPTURE;
660    for (var i = REGEXP_FIRST_CAPTURE + 2; i < matchinfo_len; ) {
661      var start = matchInfo[i++];
662      var end = matchInfo[i++];
663      if (end != -1) {
664        result[result.length] = %_SubString(subject, start, end);
665      } else {
666        result[result.length] = UNDEFINED;
667      }
668      if (result.length === limit) break outer_loop;
669    }
670
671    startIndex = currentIndex = endIndex;
672  }
673  var array_result = [];
674  %MoveArrayContents(result, array_result);
675  return array_result;
676}
677
678
679// ECMA-262 section 15.5.4.15
680function StringSubstring(start, end) {
681  CHECK_OBJECT_COERCIBLE(this, "String.prototype.subString");
682
683  var s = TO_STRING_INLINE(this);
684  var s_len = s.length;
685
686  var start_i = TO_INTEGER(start);
687  if (start_i < 0) {
688    start_i = 0;
689  } else if (start_i > s_len) {
690    start_i = s_len;
691  }
692
693  var end_i = s_len;
694  if (!IS_UNDEFINED(end)) {
695    end_i = TO_INTEGER(end);
696    if (end_i > s_len) {
697      end_i = s_len;
698    } else {
699      if (end_i < 0) end_i = 0;
700      if (start_i > end_i) {
701        var tmp = end_i;
702        end_i = start_i;
703        start_i = tmp;
704      }
705    }
706  }
707
708  return %_SubString(s, start_i, end_i);
709}
710
711
712// This is not a part of ECMA-262.
713function StringSubstr(start, n) {
714  CHECK_OBJECT_COERCIBLE(this, "String.prototype.substr");
715
716  var s = TO_STRING_INLINE(this);
717  var len;
718
719  // Correct n: If not given, set to string length; if explicitly
720  // set to undefined, zero, or negative, returns empty string.
721  if (IS_UNDEFINED(n)) {
722    len = s.length;
723  } else {
724    len = TO_INTEGER(n);
725    if (len <= 0) return '';
726  }
727
728  // Correct start: If not given (or undefined), set to zero; otherwise
729  // convert to integer and handle negative case.
730  if (IS_UNDEFINED(start)) {
731    start = 0;
732  } else {
733    start = TO_INTEGER(start);
734    // If positive, and greater than or equal to the string length,
735    // return empty string.
736    if (start >= s.length) return '';
737    // If negative and absolute value is larger than the string length,
738    // use zero.
739    if (start < 0) {
740      start += s.length;
741      if (start < 0) start = 0;
742    }
743  }
744
745  var end = start + len;
746  if (end > s.length) end = s.length;
747
748  return %_SubString(s, start, end);
749}
750
751
752// ECMA-262, 15.5.4.16
753function StringToLowerCaseJS() {
754  CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLowerCase");
755
756  return %StringToLowerCase(TO_STRING_INLINE(this));
757}
758
759
760// ECMA-262, 15.5.4.17
761function StringToLocaleLowerCase() {
762  CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleLowerCase");
763
764  return %StringToLowerCase(TO_STRING_INLINE(this));
765}
766
767
768// ECMA-262, 15.5.4.18
769function StringToUpperCaseJS() {
770  CHECK_OBJECT_COERCIBLE(this, "String.prototype.toUpperCase");
771
772  return %StringToUpperCase(TO_STRING_INLINE(this));
773}
774
775
776// ECMA-262, 15.5.4.19
777function StringToLocaleUpperCase() {
778  CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleUpperCase");
779
780  return %StringToUpperCase(TO_STRING_INLINE(this));
781}
782
783// ES5, 15.5.4.20
784function StringTrimJS() {
785  CHECK_OBJECT_COERCIBLE(this, "String.prototype.trim");
786
787  return %StringTrim(TO_STRING_INLINE(this), true, true);
788}
789
790function StringTrimLeft() {
791  CHECK_OBJECT_COERCIBLE(this, "String.prototype.trimLeft");
792
793  return %StringTrim(TO_STRING_INLINE(this), true, false);
794}
795
796function StringTrimRight() {
797  CHECK_OBJECT_COERCIBLE(this, "String.prototype.trimRight");
798
799  return %StringTrim(TO_STRING_INLINE(this), false, true);
800}
801
802
803// ECMA-262, section 15.5.3.2
804function StringFromCharCode(code) {
805  var n = %_ArgumentsLength();
806  if (n == 1) {
807    if (!%_IsSmi(code)) code = ToNumber(code);
808    return %_StringCharFromCode(code & 0xffff);
809  }
810
811  var one_byte = %NewString(n, NEW_ONE_BYTE_STRING);
812  var i;
813  for (i = 0; i < n; i++) {
814    var code = %_Arguments(i);
815    if (!%_IsSmi(code)) code = ToNumber(code) & 0xffff;
816    if (code < 0) code = code & 0xffff;
817    if (code > 0xff) break;
818    %_OneByteSeqStringSetChar(one_byte, i, code);
819  }
820  if (i == n) return one_byte;
821  one_byte = %TruncateString(one_byte, i);
822
823  var two_byte = %NewString(n - i, NEW_TWO_BYTE_STRING);
824  for (var j = 0; i < n; i++, j++) {
825    var code = %_Arguments(i);
826    if (!%_IsSmi(code)) code = ToNumber(code) & 0xffff;
827    %_TwoByteSeqStringSetChar(two_byte, j, code);
828  }
829  return one_byte + two_byte;
830}
831
832
833// Helper function for very basic XSS protection.
834function HtmlEscape(str) {
835  return TO_STRING_INLINE(str).replace(/</g, "&lt;")
836                              .replace(/>/g, "&gt;")
837                              .replace(/"/g, "&quot;")
838                              .replace(/'/g, "&#039;");
839}
840
841
842// Compatibility support for KJS.
843// Tested by mozilla/js/tests/js1_5/Regress/regress-276103.js.
844function StringLink(s) {
845  return "<a href=\"" + HtmlEscape(s) + "\">" + this + "</a>";
846}
847
848
849function StringAnchor(name) {
850  return "<a name=\"" + HtmlEscape(name) + "\">" + this + "</a>";
851}
852
853
854function StringFontcolor(color) {
855  return "<font color=\"" + HtmlEscape(color) + "\">" + this + "</font>";
856}
857
858
859function StringFontsize(size) {
860  return "<font size=\"" + HtmlEscape(size) + "\">" + this + "</font>";
861}
862
863
864function StringBig() {
865  return "<big>" + this + "</big>";
866}
867
868
869function StringBlink() {
870  return "<blink>" + this + "</blink>";
871}
872
873
874function StringBold() {
875  return "<b>" + this + "</b>";
876}
877
878
879function StringFixed() {
880  return "<tt>" + this + "</tt>";
881}
882
883
884function StringItalics() {
885  return "<i>" + this + "</i>";
886}
887
888
889function StringSmall() {
890  return "<small>" + this + "</small>";
891}
892
893
894function StringStrike() {
895  return "<strike>" + this + "</strike>";
896}
897
898
899function StringSub() {
900  return "<sub>" + this + "</sub>";
901}
902
903
904function StringSup() {
905  return "<sup>" + this + "</sup>";
906}
907
908// -------------------------------------------------------------------
909
910function SetUpString() {
911  %CheckIsBootstrapping();
912
913  // Set the String function and constructor.
914  %SetCode($String, StringConstructor);
915  %FunctionSetPrototype($String, new $String());
916
917  // Set up the constructor property on the String prototype object.
918  %SetProperty($String.prototype, "constructor", $String, DONT_ENUM);
919
920  // Set up the non-enumerable functions on the String object.
921  InstallFunctions($String, DONT_ENUM, $Array(
922    "fromCharCode", StringFromCharCode
923  ));
924
925  // Set up the non-enumerable functions on the String prototype object.
926  InstallFunctions($String.prototype, DONT_ENUM, $Array(
927    "valueOf", StringValueOf,
928    "toString", StringToString,
929    "charAt", StringCharAt,
930    "charCodeAt", StringCharCodeAt,
931    "concat", StringConcat,
932    "indexOf", StringIndexOfJS,
933    "lastIndexOf", StringLastIndexOfJS,
934    "localeCompare", StringLocaleCompareJS,
935    "match", StringMatchJS,
936    "normalize", StringNormalizeJS,
937    "replace", StringReplace,
938    "search", StringSearch,
939    "slice", StringSlice,
940    "split", StringSplitJS,
941    "substring", StringSubstring,
942    "substr", StringSubstr,
943    "toLowerCase", StringToLowerCaseJS,
944    "toLocaleLowerCase", StringToLocaleLowerCase,
945    "toUpperCase", StringToUpperCaseJS,
946    "toLocaleUpperCase", StringToLocaleUpperCase,
947    "trim", StringTrimJS,
948    "trimLeft", StringTrimLeft,
949    "trimRight", StringTrimRight,
950    "link", StringLink,
951    "anchor", StringAnchor,
952    "fontcolor", StringFontcolor,
953    "fontsize", StringFontsize,
954    "big", StringBig,
955    "blink", StringBlink,
956    "bold", StringBold,
957    "fixed", StringFixed,
958    "italics", StringItalics,
959    "small", StringSmall,
960    "strike", StringStrike,
961    "sub", StringSub,
962    "sup", StringSup
963  ));
964}
965
966SetUpString();
967