OLD | NEW |
1 // Copyright 2012 the V8 project authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 (function(global, utils) { | 5 (function(global, utils) { |
6 | 6 |
7 %CheckIsBootstrapping(); | 7 %CheckIsBootstrapping(); |
8 | 8 |
9 // ------------------------------------------------------------------- | 9 // ------------------------------------------------------------------- |
10 // Imports | 10 // Imports |
11 | 11 |
12 var ArrayJoin; | |
13 var GlobalRegExp = global.RegExp; | |
14 var GlobalString = global.String; | 12 var GlobalString = global.String; |
15 var MaxSimple; | |
16 var MinSimple; | |
17 var matchSymbol = utils.ImportNow("match_symbol"); | 13 var matchSymbol = utils.ImportNow("match_symbol"); |
18 var replaceSymbol = utils.ImportNow("replace_symbol"); | |
19 var searchSymbol = utils.ImportNow("search_symbol"); | 14 var searchSymbol = utils.ImportNow("search_symbol"); |
20 var splitSymbol = utils.ImportNow("split_symbol"); | |
21 | |
22 utils.Import(function(from) { | |
23 ArrayJoin = from.ArrayJoin; | |
24 MaxSimple = from.MaxSimple; | |
25 MinSimple = from.MinSimple; | |
26 }); | |
27 | 15 |
28 //------------------------------------------------------------------- | 16 //------------------------------------------------------------------- |
29 | 17 |
30 // ECMA-262, section 15.5.4.6 | 18 // ECMA-262, section 15.5.4.6 |
31 function StringConcat(other /* and more */) { // length == 1 | 19 function StringConcat(other /* and more */) { // length == 1 |
32 "use strict"; | 20 "use strict"; |
33 CHECK_OBJECT_COERCIBLE(this, "String.prototype.concat"); | 21 CHECK_OBJECT_COERCIBLE(this, "String.prototype.concat"); |
34 var s = TO_STRING(this); | 22 var s = TO_STRING(this); |
35 var len = arguments.length; | 23 var len = arguments.length; |
36 for (var i = 0; i < len; ++i) { | 24 for (var i = 0; i < len; ++i) { |
(...skipping 14 matching lines...) Expand all Loading... |
51 } | 39 } |
52 } | 40 } |
53 | 41 |
54 var subject = TO_STRING(this); | 42 var subject = TO_STRING(this); |
55 | 43 |
56 // Equivalent to RegExpCreate (ES#sec-regexpcreate) | 44 // Equivalent to RegExpCreate (ES#sec-regexpcreate) |
57 var regexp = %RegExpCreate(pattern); | 45 var regexp = %RegExpCreate(pattern); |
58 return regexp[matchSymbol](subject); | 46 return regexp[matchSymbol](subject); |
59 } | 47 } |
60 | 48 |
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. | |
65 function 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 | |
147 function 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. | 49 // ES6 21.1.3.15. |
210 function StringSearch(pattern) { | 50 function StringSearch(pattern) { |
211 CHECK_OBJECT_COERCIBLE(this, "String.prototype.search"); | 51 CHECK_OBJECT_COERCIBLE(this, "String.prototype.search"); |
212 | 52 |
213 if (!IS_NULL_OR_UNDEFINED(pattern)) { | 53 if (!IS_NULL_OR_UNDEFINED(pattern)) { |
214 var searcher = pattern[searchSymbol]; | 54 var searcher = pattern[searchSymbol]; |
215 if (!IS_UNDEFINED(searcher)) { | 55 if (!IS_UNDEFINED(searcher)) { |
216 return %_Call(searcher, pattern, this); | 56 return %_Call(searcher, pattern, this); |
217 } | 57 } |
218 } | 58 } |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
259 } | 99 } |
260 } | 100 } |
261 | 101 |
262 if (end_i <= start_i) { | 102 if (end_i <= start_i) { |
263 return ''; | 103 return ''; |
264 } | 104 } |
265 | 105 |
266 return %_SubString(s, start_i, end_i); | 106 return %_SubString(s, start_i, end_i); |
267 } | 107 } |
268 | 108 |
269 | |
270 // ES6 21.1.3.17. | |
271 function 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 | 109 // ECMA-262, 15.5.4.16 |
303 function StringToLowerCaseJS() { | 110 function StringToLowerCaseJS() { |
304 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLowerCase"); | 111 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLowerCase"); |
305 | 112 |
306 return %StringToLowerCase(TO_STRING(this)); | 113 return %StringToLowerCase(TO_STRING(this)); |
307 } | 114 } |
308 | 115 |
309 | 116 |
310 // ECMA-262, 15.5.4.17 | 117 // ECMA-262, 15.5.4.17 |
311 function StringToLocaleLowerCase() { | 118 function StringToLocaleLowerCase() { |
(...skipping 196 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
508 utils.InstallFunctions(GlobalString, DONT_ENUM, [ | 315 utils.InstallFunctions(GlobalString, DONT_ENUM, [ |
509 "raw", StringRaw | 316 "raw", StringRaw |
510 ]); | 317 ]); |
511 | 318 |
512 // Set up the non-enumerable functions on the String prototype object. | 319 // Set up the non-enumerable functions on the String prototype object. |
513 utils.InstallFunctions(GlobalString.prototype, DONT_ENUM, [ | 320 utils.InstallFunctions(GlobalString.prototype, DONT_ENUM, [ |
514 "codePointAt", StringCodePointAt, | 321 "codePointAt", StringCodePointAt, |
515 "concat", StringConcat, | 322 "concat", StringConcat, |
516 "match", StringMatchJS, | 323 "match", StringMatchJS, |
517 "repeat", StringRepeat, | 324 "repeat", StringRepeat, |
518 "replace", StringReplace, | |
519 "search", StringSearch, | 325 "search", StringSearch, |
520 "slice", StringSlice, | 326 "slice", StringSlice, |
521 "split", StringSplitJS, | |
522 "toLowerCase", StringToLowerCaseJS, | 327 "toLowerCase", StringToLowerCaseJS, |
523 "toLocaleLowerCase", StringToLocaleLowerCase, | 328 "toLocaleLowerCase", StringToLocaleLowerCase, |
524 "toUpperCase", StringToUpperCaseJS, | 329 "toUpperCase", StringToUpperCaseJS, |
525 "toLocaleUpperCase", StringToLocaleUpperCase, | 330 "toLocaleUpperCase", StringToLocaleUpperCase, |
526 | 331 |
527 "link", StringLink, | 332 "link", StringLink, |
528 "anchor", StringAnchor, | 333 "anchor", StringAnchor, |
529 "fontcolor", StringFontcolor, | 334 "fontcolor", StringFontcolor, |
530 "fontsize", StringFontsize, | 335 "fontsize", StringFontsize, |
531 "big", StringBig, | 336 "big", StringBig, |
532 "blink", StringBlink, | 337 "blink", StringBlink, |
533 "bold", StringBold, | 338 "bold", StringBold, |
534 "fixed", StringFixed, | 339 "fixed", StringFixed, |
535 "italics", StringItalics, | 340 "italics", StringItalics, |
536 "small", StringSmall, | 341 "small", StringSmall, |
537 "strike", StringStrike, | 342 "strike", StringStrike, |
538 "sub", StringSub, | 343 "sub", StringSub, |
539 "sup", StringSup | 344 "sup", StringSup |
540 ]); | 345 ]); |
541 | 346 |
542 // ------------------------------------------------------------------- | |
543 // Exports | |
544 | |
545 utils.Export(function(to) { | |
546 to.StringMatch = StringMatchJS; | |
547 to.StringReplace = StringReplace; | |
548 to.StringSlice = StringSlice; | |
549 to.StringSplit = StringSplitJS; | |
550 }); | |
551 | |
552 }) | 347 }) |
OLD | NEW |