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(function(global, utils) {
6
7%CheckIsBootstrapping();
8
9// -------------------------------------------------------------------
10// Imports
11
12var ArrayJoin;
13var GlobalRegExp = global.RegExp;
14var GlobalString = global.String;
15var MaxSimple;
16var MinSimple;
17var matchSymbol = utils.ImportNow("match_symbol");
18var replaceSymbol = utils.ImportNow("replace_symbol");
19var searchSymbol = utils.ImportNow("search_symbol");
20var splitSymbol = utils.ImportNow("split_symbol");
21
22utils.Import(function(from) {
23  ArrayJoin = from.ArrayJoin;
24  MaxSimple = from.MaxSimple;
25  MinSimple = from.MinSimple;
26});
27
28//-------------------------------------------------------------------
29
30// ECMA-262, section 15.5.4.6
31function StringConcat(other /* and more */) {  // length == 1
32  "use strict";
33  CHECK_OBJECT_COERCIBLE(this, "String.prototype.concat");
34  var s = TO_STRING(this);
35  var len = arguments.length;
36  for (var i = 0; i < len; ++i) {
37    s = s + TO_STRING(arguments[i]);
38  }
39  return s;
40}
41
42
43// ES6 21.1.3.11.
44function StringMatchJS(pattern) {
45  CHECK_OBJECT_COERCIBLE(this, "String.prototype.match");
46
47  if (!IS_NULL_OR_UNDEFINED(pattern)) {
48    var matcher = pattern[matchSymbol];
49    if (!IS_UNDEFINED(matcher)) {
50      return %_Call(matcher, pattern, this);
51    }
52  }
53
54  var subject = TO_STRING(this);
55
56  // Equivalent to RegExpCreate (ES#sec-regexpcreate)
57  var regexp = %RegExpCreate(pattern);
58  return regexp[matchSymbol](subject);
59}
60
61// ES#sec-getsubstitution
62// GetSubstitution(matched, str, position, captures, replacement)
63// Expand the $-expressions in the string and return a new string with
64// the result.
65function GetSubstitution(matched, string, position, captures, replacement) {
66  var matchLength = matched.length;
67  var stringLength = string.length;
68  var capturesLength = captures.length;
69  var tailPos = position + matchLength;
70  var result = "";
71  var pos, expansion, peek, next, scaledIndex, advance, newScaledIndex;
72
73  var next = %StringIndexOf(replacement, '$', 0);
74  if (next < 0) {
75    result += replacement;
76    return result;
77  }
78
79  if (next > 0) result += %_SubString(replacement, 0, next);
80
81  while (true) {
82    expansion = '$';
83    pos = next + 1;
84    if (pos < replacement.length) {
85      peek = %_StringCharCodeAt(replacement, pos);
86      if (peek == 36) {         // $$
87        ++pos;
88        result += '$';
89      } else if (peek == 38) {  // $& - match
90        ++pos;
91        result += matched;
92      } else if (peek == 96) {  // $` - prefix
93        ++pos;
94        result += %_SubString(string, 0, position);
95      } else if (peek == 39) {  // $' - suffix
96        ++pos;
97        result += %_SubString(string, tailPos, stringLength);
98      } else if (peek >= 48 && peek <= 57) {
99        // Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99
100        scaledIndex = (peek - 48);
101        advance = 1;
102        if (pos + 1 < replacement.length) {
103          next = %_StringCharCodeAt(replacement, pos + 1);
104          if (next >= 48 && next <= 57) {
105            newScaledIndex = scaledIndex * 10 + ((next - 48));
106            if (newScaledIndex < capturesLength) {
107              scaledIndex = newScaledIndex;
108              advance = 2;
109            }
110          }
111        }
112        if (scaledIndex != 0 && scaledIndex < capturesLength) {
113          var capture = captures.at(scaledIndex);
114          if (!IS_UNDEFINED(capture)) result += capture;
115          pos += advance;
116        } else {
117          result += '$';
118        }
119      } else {
120        result += '$';
121      }
122    } else {
123      result += '$';
124    }
125
126    // Go the the next $ in the replacement.
127    next = %StringIndexOf(replacement, '$', pos);
128
129    // Return if there are no more $ characters in the replacement. If we
130    // haven't reached the end, we need to append the suffix.
131    if (next < 0) {
132      if (pos < replacement.length) {
133        result += %_SubString(replacement, pos, replacement.length);
134      }
135      return result;
136    }
137
138    // Append substring between the previous and the next $ character.
139    if (next > pos) {
140      result += %_SubString(replacement, pos, next);
141    }
142  }
143  return result;
144}
145
146// ES6, section 21.1.3.14
147function StringReplace(search, replace) {
148  CHECK_OBJECT_COERCIBLE(this, "String.prototype.replace");
149
150  // Decision tree for dispatch
151  // .. regexp search (in src/js/regexp.js, RegExpReplace)
152  // .... string replace
153  // ...... non-global search
154  // ........ empty string replace
155  // ........ non-empty string replace (with $-expansion)
156  // ...... global search
157  // ........ no need to circumvent last match info override
158  // ........ need to circument last match info override
159  // .... function replace
160  // ...... global search
161  // ...... non-global search
162  // .. string search
163  // .... special case that replaces with one single character
164  // ...... function replace
165  // ...... string replace (with $-expansion)
166
167  if (!IS_NULL_OR_UNDEFINED(search)) {
168    var replacer = search[replaceSymbol];
169    if (!IS_UNDEFINED(replacer)) {
170      return %_Call(replacer, search, this, replace);
171    }
172  }
173
174  var subject = TO_STRING(this);
175
176  search = TO_STRING(search);
177
178  if (search.length == 1 &&
179      subject.length > 0xFF &&
180      IS_STRING(replace) &&
181      %StringIndexOf(replace, '$', 0) < 0) {
182    // Searching by traversing a cons string tree and replace with cons of
183    // slices works only when the replaced string is a single character, being
184    // replaced by a simple string and only pays off for long strings.
185    return %StringReplaceOneCharWithString(subject, search, replace);
186  }
187  var start = %StringIndexOf(subject, search, 0);
188  if (start < 0) return subject;
189  var end = start + search.length;
190
191  var result = %_SubString(subject, 0, start);
192
193  // Compute the string to replace with.
194  if (IS_CALLABLE(replace)) {
195    result += replace(search, start, subject);
196  } else {
197    // In this case, we don't have any capture groups and can get away with
198    // faking the captures object by simply setting its length to 1.
199    const captures = { length: 1 };
200    const matched = %_SubString(subject, start, end);
201    result += GetSubstitution(matched, subject, start, captures,
202                              TO_STRING(replace));
203  }
204
205  return result + %_SubString(subject, end, subject.length);
206}
207
208
209// ES6 21.1.3.15.
210function StringSearch(pattern) {
211  CHECK_OBJECT_COERCIBLE(this, "String.prototype.search");
212
213  if (!IS_NULL_OR_UNDEFINED(pattern)) {
214    var searcher = pattern[searchSymbol];
215    if (!IS_UNDEFINED(searcher)) {
216      return %_Call(searcher, pattern, this);
217    }
218  }
219
220  var subject = TO_STRING(this);
221
222  // Equivalent to RegExpCreate (ES#sec-regexpcreate)
223  var regexp = %RegExpCreate(pattern);
224  return %_Call(regexp[searchSymbol], regexp, subject);
225}
226
227
228// ECMA-262 section 15.5.4.13
229function StringSlice(start, end) {
230  CHECK_OBJECT_COERCIBLE(this, "String.prototype.slice");
231
232  var s = TO_STRING(this);
233  var s_len = s.length;
234  var start_i = TO_INTEGER(start);
235  var end_i = s_len;
236  if (!IS_UNDEFINED(end)) {
237    end_i = TO_INTEGER(end);
238  }
239
240  if (start_i < 0) {
241    start_i += s_len;
242    if (start_i < 0) {
243      start_i = 0;
244    }
245  } else {
246    if (start_i > s_len) {
247      return '';
248    }
249  }
250
251  if (end_i < 0) {
252    end_i += s_len;
253    if (end_i < 0) {
254      return '';
255    }
256  } else {
257    if (end_i > s_len) {
258      end_i = s_len;
259    }
260  }
261
262  if (end_i <= start_i) {
263    return '';
264  }
265
266  return %_SubString(s, start_i, end_i);
267}
268
269
270// ES6 21.1.3.17.
271function StringSplitJS(separator, limit) {
272  CHECK_OBJECT_COERCIBLE(this, "String.prototype.split");
273
274  if (!IS_NULL_OR_UNDEFINED(separator)) {
275    var splitter = separator[splitSymbol];
276    if (!IS_UNDEFINED(splitter)) {
277      return %_Call(splitter, separator, this, limit);
278    }
279  }
280
281  var subject = TO_STRING(this);
282  limit = (IS_UNDEFINED(limit)) ? kMaxUint32 : TO_UINT32(limit);
283
284  var length = subject.length;
285  var separator_string = TO_STRING(separator);
286
287  if (limit === 0) return [];
288
289  // ECMA-262 says that if separator is undefined, the result should
290  // be an array of size 1 containing the entire string.
291  if (IS_UNDEFINED(separator)) return [subject];
292
293  var separator_length = separator_string.length;
294
295  // If the separator string is empty then return the elements in the subject.
296  if (separator_length === 0) return %StringToArray(subject, limit);
297
298  return %StringSplit(subject, separator_string, limit);
299}
300
301
302// ECMA-262, 15.5.4.16
303function StringToLowerCaseJS() {
304  CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLowerCase");
305
306  return %StringToLowerCase(TO_STRING(this));
307}
308
309
310// ECMA-262, 15.5.4.17
311function StringToLocaleLowerCase() {
312  CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleLowerCase");
313
314  return %StringToLowerCase(TO_STRING(this));
315}
316
317
318// ECMA-262, 15.5.4.18
319function StringToUpperCaseJS() {
320  CHECK_OBJECT_COERCIBLE(this, "String.prototype.toUpperCase");
321
322  return %StringToUpperCase(TO_STRING(this));
323}
324
325
326// ECMA-262, 15.5.4.19
327function StringToLocaleUpperCase() {
328  CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleUpperCase");
329
330  return %StringToUpperCase(TO_STRING(this));
331}
332
333
334// ES6 draft, revision 26 (2014-07-18), section B.2.3.2.1
335function HtmlEscape(str) {
336  return %_Call(StringReplace, TO_STRING(str), /"/g, "&quot;");
337}
338
339
340// ES6 draft, revision 26 (2014-07-18), section B.2.3.2
341function StringAnchor(name) {
342  CHECK_OBJECT_COERCIBLE(this, "String.prototype.anchor");
343  return "<a name=\"" + HtmlEscape(name) + "\">" + TO_STRING(this) +
344         "</a>";
345}
346
347
348// ES6 draft, revision 26 (2014-07-18), section B.2.3.3
349function StringBig() {
350  CHECK_OBJECT_COERCIBLE(this, "String.prototype.big");
351  return "<big>" + TO_STRING(this) + "</big>";
352}
353
354
355// ES6 draft, revision 26 (2014-07-18), section B.2.3.4
356function StringBlink() {
357  CHECK_OBJECT_COERCIBLE(this, "String.prototype.blink");
358  return "<blink>" + TO_STRING(this) + "</blink>";
359}
360
361
362// ES6 draft, revision 26 (2014-07-18), section B.2.3.5
363function StringBold() {
364  CHECK_OBJECT_COERCIBLE(this, "String.prototype.bold");
365  return "<b>" + TO_STRING(this) + "</b>";
366}
367
368
369// ES6 draft, revision 26 (2014-07-18), section B.2.3.6
370function StringFixed() {
371  CHECK_OBJECT_COERCIBLE(this, "String.prototype.fixed");
372  return "<tt>" + TO_STRING(this) + "</tt>";
373}
374
375
376// ES6 draft, revision 26 (2014-07-18), section B.2.3.7
377function StringFontcolor(color) {
378  CHECK_OBJECT_COERCIBLE(this, "String.prototype.fontcolor");
379  return "<font color=\"" + HtmlEscape(color) + "\">" + TO_STRING(this) +
380         "</font>";
381}
382
383
384// ES6 draft, revision 26 (2014-07-18), section B.2.3.8
385function StringFontsize(size) {
386  CHECK_OBJECT_COERCIBLE(this, "String.prototype.fontsize");
387  return "<font size=\"" + HtmlEscape(size) + "\">" + TO_STRING(this) +
388         "</font>";
389}
390
391
392// ES6 draft, revision 26 (2014-07-18), section B.2.3.9
393function StringItalics() {
394  CHECK_OBJECT_COERCIBLE(this, "String.prototype.italics");
395  return "<i>" + TO_STRING(this) + "</i>";
396}
397
398
399// ES6 draft, revision 26 (2014-07-18), section B.2.3.10
400function StringLink(s) {
401  CHECK_OBJECT_COERCIBLE(this, "String.prototype.link");
402  return "<a href=\"" + HtmlEscape(s) + "\">" + TO_STRING(this) + "</a>";
403}
404
405
406// ES6 draft, revision 26 (2014-07-18), section B.2.3.11
407function StringSmall() {
408  CHECK_OBJECT_COERCIBLE(this, "String.prototype.small");
409  return "<small>" + TO_STRING(this) + "</small>";
410}
411
412
413// ES6 draft, revision 26 (2014-07-18), section B.2.3.12
414function StringStrike() {
415  CHECK_OBJECT_COERCIBLE(this, "String.prototype.strike");
416  return "<strike>" + TO_STRING(this) + "</strike>";
417}
418
419
420// ES6 draft, revision 26 (2014-07-18), section B.2.3.13
421function StringSub() {
422  CHECK_OBJECT_COERCIBLE(this, "String.prototype.sub");
423  return "<sub>" + TO_STRING(this) + "</sub>";
424}
425
426
427// ES6 draft, revision 26 (2014-07-18), section B.2.3.14
428function StringSup() {
429  CHECK_OBJECT_COERCIBLE(this, "String.prototype.sup");
430  return "<sup>" + TO_STRING(this) + "</sup>";
431}
432
433// ES6, section 21.1.3.13
434function StringRepeat(count) {
435  CHECK_OBJECT_COERCIBLE(this, "String.prototype.repeat");
436
437  var s = TO_STRING(this);
438  var n = TO_INTEGER(count);
439
440  if (n < 0 || n === INFINITY) throw %make_range_error(kInvalidCountValue);
441
442  // Early return to allow an arbitrarily-large repeat of the empty string.
443  if (s.length === 0) return "";
444
445  // The maximum string length is stored in a smi, so a longer repeat
446  // must result in a range error.
447  if (n > %_MaxSmi()) throw %make_range_error(kInvalidCountValue);
448
449  var r = "";
450  while (true) {
451    if (n & 1) r += s;
452    n >>= 1;
453    if (n === 0) return r;
454    s += s;
455  }
456}
457
458
459// ES6 Draft 05-22-2014, section 21.1.3.3
460function StringCodePointAt(pos) {
461  CHECK_OBJECT_COERCIBLE(this, "String.prototype.codePointAt");
462
463  var string = TO_STRING(this);
464  var size = string.length;
465  pos = TO_INTEGER(pos);
466  if (pos < 0 || pos >= size) {
467    return UNDEFINED;
468  }
469  var first = %_StringCharCodeAt(string, pos);
470  if (first < 0xD800 || first > 0xDBFF || pos + 1 == size) {
471    return first;
472  }
473  var second = %_StringCharCodeAt(string, pos + 1);
474  if (second < 0xDC00 || second > 0xDFFF) {
475    return first;
476  }
477  return (first - 0xD800) * 0x400 + second + 0x2400;
478}
479
480
481// -------------------------------------------------------------------
482// String methods related to templates
483
484// ES6 Draft 03-17-2015, section 21.1.2.4
485function StringRaw(callSite) {
486  "use strict";
487  var numberOfSubstitutions = arguments.length;
488  var cooked = TO_OBJECT(callSite);
489  var raw = TO_OBJECT(cooked.raw);
490  var literalSegments = TO_LENGTH(raw.length);
491  if (literalSegments <= 0) return "";
492
493  var result = TO_STRING(raw[0]);
494
495  for (var i = 1; i < literalSegments; ++i) {
496    if (i < numberOfSubstitutions) {
497      result += TO_STRING(arguments[i]);
498    }
499    result += TO_STRING(raw[i]);
500  }
501
502  return result;
503}
504
505// -------------------------------------------------------------------
506
507// Set up the non-enumerable functions on the String object.
508utils.InstallFunctions(GlobalString, DONT_ENUM, [
509  "raw", StringRaw
510]);
511
512// Set up the non-enumerable functions on the String prototype object.
513utils.InstallFunctions(GlobalString.prototype, DONT_ENUM, [
514  "codePointAt", StringCodePointAt,
515  "concat", StringConcat,
516  "match", StringMatchJS,
517  "repeat", StringRepeat,
518  "replace", StringReplace,
519  "search", StringSearch,
520  "slice", StringSlice,
521  "split", StringSplitJS,
522  "toLowerCase", StringToLowerCaseJS,
523  "toLocaleLowerCase", StringToLocaleLowerCase,
524  "toUpperCase", StringToUpperCaseJS,
525  "toLocaleUpperCase", StringToLocaleUpperCase,
526
527  "link", StringLink,
528  "anchor", StringAnchor,
529  "fontcolor", StringFontcolor,
530  "fontsize", StringFontsize,
531  "big", StringBig,
532  "blink", StringBlink,
533  "bold", StringBold,
534  "fixed", StringFixed,
535  "italics", StringItalics,
536  "small", StringSmall,
537  "strike", StringStrike,
538  "sub", StringSub,
539  "sup", StringSup
540]);
541
542// -------------------------------------------------------------------
543// Exports
544
545utils.Export(function(to) {
546  to.StringMatch = StringMatchJS;
547  to.StringReplace = StringReplace;
548  to.StringSlice = StringSlice;
549  to.StringSplit = StringSplitJS;
550});
551
552})
553