OLD | NEW |
| (Empty) |
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 | |
12 var ArrayIndexOf; | |
13 var ArrayJoin; | |
14 var GlobalRegExp = global.RegExp; | |
15 var GlobalString = global.String; | |
16 var InternalArray = utils.InternalArray; | |
17 var InternalPackedArray = utils.InternalPackedArray; | |
18 var RegExpExec; | |
19 var RegExpExecNoTests; | |
20 var RegExpLastMatchInfo; | |
21 | |
22 utils.Import(function(from) { | |
23 ArrayIndexOf = from.ArrayIndexOf; | |
24 ArrayJoin = from.ArrayJoin; | |
25 RegExpExec = from.RegExpExec; | |
26 RegExpExecNoTests = from.RegExpExecNoTests; | |
27 RegExpLastMatchInfo = from.RegExpLastMatchInfo; | |
28 }); | |
29 | |
30 //------------------------------------------------------------------- | |
31 | |
32 // ECMA-262 section 15.5.4.2 | |
33 function StringToString() { | |
34 if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) { | |
35 throw MakeTypeError(kNotGeneric, 'String.prototype.toString'); | |
36 } | |
37 return %_ValueOf(this); | |
38 } | |
39 | |
40 | |
41 // ECMA-262 section 15.5.4.3 | |
42 function StringValueOf() { | |
43 if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) { | |
44 throw MakeTypeError(kNotGeneric, 'String.prototype.valueOf'); | |
45 } | |
46 return %_ValueOf(this); | |
47 } | |
48 | |
49 | |
50 // ECMA-262, section 15.5.4.4 | |
51 function StringCharAtJS(pos) { | |
52 CHECK_OBJECT_COERCIBLE(this, "String.prototype.charAt"); | |
53 | |
54 var result = %_StringCharAt(this, pos); | |
55 if (%_IsSmi(result)) { | |
56 result = %_StringCharAt(TO_STRING(this), TO_INTEGER(pos)); | |
57 } | |
58 return result; | |
59 } | |
60 | |
61 | |
62 // ECMA-262 section 15.5.4.5 | |
63 function StringCharCodeAtJS(pos) { | |
64 CHECK_OBJECT_COERCIBLE(this, "String.prototype.charCodeAt"); | |
65 | |
66 var result = %_StringCharCodeAt(this, pos); | |
67 if (!%_IsSmi(result)) { | |
68 result = %_StringCharCodeAt(TO_STRING(this), TO_INTEGER(pos)); | |
69 } | |
70 return result; | |
71 } | |
72 | |
73 | |
74 // ECMA-262, section 15.5.4.6 | |
75 function StringConcat(other /* and more */) { // length == 1 | |
76 CHECK_OBJECT_COERCIBLE(this, "String.prototype.concat"); | |
77 var len = %_ArgumentsLength(); | |
78 var this_as_string = TO_STRING(this); | |
79 if (len === 1) { | |
80 return this_as_string + TO_STRING(other); | |
81 } | |
82 var parts = new InternalArray(len + 1); | |
83 parts[0] = this_as_string; | |
84 for (var i = 0; i < len; i++) { | |
85 var part = %_Arguments(i); | |
86 parts[i + 1] = TO_STRING(part); | |
87 } | |
88 return %StringBuilderConcat(parts, len + 1, ""); | |
89 } | |
90 | |
91 | |
92 // ECMA-262 section 15.5.4.7 | |
93 function StringIndexOfJS(pattern /* position */) { // length == 1 | |
94 CHECK_OBJECT_COERCIBLE(this, "String.prototype.indexOf"); | |
95 | |
96 var subject = TO_STRING(this); | |
97 pattern = TO_STRING(pattern); | |
98 var index = 0; | |
99 if (%_ArgumentsLength() > 1) { | |
100 index = %_Arguments(1); // position | |
101 index = TO_INTEGER(index); | |
102 if (index < 0) index = 0; | |
103 if (index > subject.length) index = subject.length; | |
104 } | |
105 return %StringIndexOf(subject, pattern, index); | |
106 } | |
107 | |
108 | |
109 // ECMA-262 section 15.5.4.8 | |
110 function StringLastIndexOfJS(pat /* position */) { // length == 1 | |
111 CHECK_OBJECT_COERCIBLE(this, "String.prototype.lastIndexOf"); | |
112 | |
113 var sub = TO_STRING(this); | |
114 var subLength = sub.length; | |
115 var pat = TO_STRING(pat); | |
116 var patLength = pat.length; | |
117 var index = subLength - patLength; | |
118 if (%_ArgumentsLength() > 1) { | |
119 var position = TO_NUMBER(%_Arguments(1)); | |
120 if (!NUMBER_IS_NAN(position)) { | |
121 position = TO_INTEGER(position); | |
122 if (position < 0) { | |
123 position = 0; | |
124 } | |
125 if (position + patLength < subLength) { | |
126 index = position; | |
127 } | |
128 } | |
129 } | |
130 if (index < 0) { | |
131 return -1; | |
132 } | |
133 return %StringLastIndexOf(sub, pat, index); | |
134 } | |
135 | |
136 | |
137 // ECMA-262 section 15.5.4.9 | |
138 // | |
139 // This function is implementation specific. For now, we do not | |
140 // do anything locale specific. | |
141 function StringLocaleCompareJS(other) { | |
142 CHECK_OBJECT_COERCIBLE(this, "String.prototype.localeCompare"); | |
143 | |
144 return %StringLocaleCompare(TO_STRING(this), TO_STRING(other)); | |
145 } | |
146 | |
147 | |
148 // ECMA-262 section 15.5.4.10 | |
149 function StringMatchJS(regexp) { | |
150 CHECK_OBJECT_COERCIBLE(this, "String.prototype.match"); | |
151 | |
152 var subject = TO_STRING(this); | |
153 if (IS_REGEXP(regexp)) { | |
154 // Emulate RegExp.prototype.exec's side effect in step 5, even though | |
155 // value is discarded. | |
156 var lastIndex = TO_INTEGER(regexp.lastIndex); | |
157 if (!regexp.global) return RegExpExecNoTests(regexp, subject, 0); | |
158 var result = %StringMatch(subject, regexp, RegExpLastMatchInfo); | |
159 if (result !== null) $regexpLastMatchInfoOverride = null; | |
160 regexp.lastIndex = 0; | |
161 return result; | |
162 } | |
163 // Non-regexp argument. | |
164 regexp = new GlobalRegExp(regexp); | |
165 return RegExpExecNoTests(regexp, subject, 0); | |
166 } | |
167 | |
168 | |
169 // ECMA-262 v6, section 21.1.3.12 | |
170 // | |
171 // For now we do nothing, as proper normalization requires big tables. | |
172 // If Intl is enabled, then i18n.js will override it and provide the the | |
173 // proper functionality. | |
174 function StringNormalizeJS() { | |
175 CHECK_OBJECT_COERCIBLE(this, "String.prototype.normalize"); | |
176 var s = TO_STRING(this); | |
177 | |
178 var formArg = %_Arguments(0); | |
179 var form = IS_UNDEFINED(formArg) ? 'NFC' : TO_STRING(formArg); | |
180 | |
181 var NORMALIZATION_FORMS = ['NFC', 'NFD', 'NFKC', 'NFKD']; | |
182 var normalizationForm = | |
183 %_CallFunction(NORMALIZATION_FORMS, form, ArrayIndexOf); | |
184 if (normalizationForm === -1) { | |
185 throw MakeRangeError(kNormalizationForm, | |
186 %_CallFunction(NORMALIZATION_FORMS, ', ', ArrayJoin)); | |
187 } | |
188 | |
189 return s; | |
190 } | |
191 | |
192 | |
193 // This has the same size as the RegExpLastMatchInfo array, and can be used | |
194 // for functions that expect that structure to be returned. It is used when | |
195 // the needle is a string rather than a regexp. In this case we can't update | |
196 // lastMatchArray without erroneously affecting the properties on the global | |
197 // RegExp object. | |
198 var reusableMatchInfo = [2, "", "", -1, -1]; | |
199 | |
200 | |
201 // ECMA-262, section 15.5.4.11 | |
202 function StringReplace(search, replace) { | |
203 CHECK_OBJECT_COERCIBLE(this, "String.prototype.replace"); | |
204 | |
205 var subject = TO_STRING(this); | |
206 | |
207 // Decision tree for dispatch | |
208 // .. regexp search | |
209 // .... string replace | |
210 // ...... non-global search | |
211 // ........ empty string replace | |
212 // ........ non-empty string replace (with $-expansion) | |
213 // ...... global search | |
214 // ........ no need to circumvent last match info override | |
215 // ........ need to circument last match info override | |
216 // .... function replace | |
217 // ...... global search | |
218 // ...... non-global search | |
219 // .. string search | |
220 // .... special case that replaces with one single character | |
221 // ...... function replace | |
222 // ...... string replace (with $-expansion) | |
223 | |
224 if (IS_REGEXP(search)) { | |
225 // Emulate RegExp.prototype.exec's side effect in step 5, even if | |
226 // value is discarded. | |
227 var lastIndex = TO_INTEGER(search.lastIndex); | |
228 | |
229 if (!IS_CALLABLE(replace)) { | |
230 replace = TO_STRING(replace); | |
231 | |
232 if (!search.global) { | |
233 // Non-global regexp search, string replace. | |
234 var match = RegExpExec(search, subject, 0); | |
235 if (match == null) { | |
236 search.lastIndex = 0 | |
237 return subject; | |
238 } | |
239 if (replace.length == 0) { | |
240 return %_SubString(subject, 0, match[CAPTURE0]) + | |
241 %_SubString(subject, match[CAPTURE1], subject.length) | |
242 } | |
243 return ExpandReplacement(replace, subject, RegExpLastMatchInfo, | |
244 %_SubString(subject, 0, match[CAPTURE0])) + | |
245 %_SubString(subject, match[CAPTURE1], subject.length); | |
246 } | |
247 | |
248 // Global regexp search, string replace. | |
249 search.lastIndex = 0; | |
250 if ($regexpLastMatchInfoOverride == null) { | |
251 return %StringReplaceGlobalRegExpWithString( | |
252 subject, search, replace, RegExpLastMatchInfo); | |
253 } else { | |
254 // We use this hack to detect whether StringReplaceRegExpWithString | |
255 // found at least one hit. In that case we need to remove any | |
256 // override. | |
257 var saved_subject = RegExpLastMatchInfo[LAST_SUBJECT_INDEX]; | |
258 RegExpLastMatchInfo[LAST_SUBJECT_INDEX] = 0; | |
259 var answer = %StringReplaceGlobalRegExpWithString( | |
260 subject, search, replace, RegExpLastMatchInfo); | |
261 if (%_IsSmi(RegExpLastMatchInfo[LAST_SUBJECT_INDEX])) { | |
262 RegExpLastMatchInfo[LAST_SUBJECT_INDEX] = saved_subject; | |
263 } else { | |
264 $regexpLastMatchInfoOverride = null; | |
265 } | |
266 return answer; | |
267 } | |
268 } | |
269 | |
270 if (search.global) { | |
271 // Global regexp search, function replace. | |
272 return StringReplaceGlobalRegExpWithFunction(subject, search, replace); | |
273 } | |
274 // Non-global regexp search, function replace. | |
275 return StringReplaceNonGlobalRegExpWithFunction(subject, search, replace); | |
276 } | |
277 | |
278 search = TO_STRING(search); | |
279 | |
280 if (search.length == 1 && | |
281 subject.length > 0xFF && | |
282 IS_STRING(replace) && | |
283 %StringIndexOf(replace, '$', 0) < 0) { | |
284 // Searching by traversing a cons string tree and replace with cons of | |
285 // slices works only when the replaced string is a single character, being | |
286 // replaced by a simple string and only pays off for long strings. | |
287 return %StringReplaceOneCharWithString(subject, search, replace); | |
288 } | |
289 var start = %StringIndexOf(subject, search, 0); | |
290 if (start < 0) return subject; | |
291 var end = start + search.length; | |
292 | |
293 var result = %_SubString(subject, 0, start); | |
294 | |
295 // Compute the string to replace with. | |
296 if (IS_CALLABLE(replace)) { | |
297 result += replace(search, start, subject); | |
298 } else { | |
299 reusableMatchInfo[CAPTURE0] = start; | |
300 reusableMatchInfo[CAPTURE1] = end; | |
301 result = ExpandReplacement(TO_STRING(replace), | |
302 subject, | |
303 reusableMatchInfo, | |
304 result); | |
305 } | |
306 | |
307 return result + %_SubString(subject, end, subject.length); | |
308 } | |
309 | |
310 | |
311 // Expand the $-expressions in the string and return a new string with | |
312 // the result. | |
313 function ExpandReplacement(string, subject, matchInfo, result) { | |
314 var length = string.length; | |
315 var next = %StringIndexOf(string, '$', 0); | |
316 if (next < 0) { | |
317 if (length > 0) result += string; | |
318 return result; | |
319 } | |
320 | |
321 if (next > 0) result += %_SubString(string, 0, next); | |
322 | |
323 while (true) { | |
324 var expansion = '$'; | |
325 var position = next + 1; | |
326 if (position < length) { | |
327 var peek = %_StringCharCodeAt(string, position); | |
328 if (peek == 36) { // $$ | |
329 ++position; | |
330 result += '$'; | |
331 } else if (peek == 38) { // $& - match | |
332 ++position; | |
333 result += | |
334 %_SubString(subject, matchInfo[CAPTURE0], matchInfo[CAPTURE1]); | |
335 } else if (peek == 96) { // $` - prefix | |
336 ++position; | |
337 result += %_SubString(subject, 0, matchInfo[CAPTURE0]); | |
338 } else if (peek == 39) { // $' - suffix | |
339 ++position; | |
340 result += %_SubString(subject, matchInfo[CAPTURE1], subject.length); | |
341 } else if (peek >= 48 && peek <= 57) { | |
342 // Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99 | |
343 var scaled_index = (peek - 48) << 1; | |
344 var advance = 1; | |
345 var number_of_captures = NUMBER_OF_CAPTURES(matchInfo); | |
346 if (position + 1 < string.length) { | |
347 var next = %_StringCharCodeAt(string, position + 1); | |
348 if (next >= 48 && next <= 57) { | |
349 var new_scaled_index = scaled_index * 10 + ((next - 48) << 1); | |
350 if (new_scaled_index < number_of_captures) { | |
351 scaled_index = new_scaled_index; | |
352 advance = 2; | |
353 } | |
354 } | |
355 } | |
356 if (scaled_index != 0 && scaled_index < number_of_captures) { | |
357 var start = matchInfo[CAPTURE(scaled_index)]; | |
358 if (start >= 0) { | |
359 result += | |
360 %_SubString(subject, start, matchInfo[CAPTURE(scaled_index + 1)]); | |
361 } | |
362 position += advance; | |
363 } else { | |
364 result += '$'; | |
365 } | |
366 } else { | |
367 result += '$'; | |
368 } | |
369 } else { | |
370 result += '$'; | |
371 } | |
372 | |
373 // Go the the next $ in the string. | |
374 next = %StringIndexOf(string, '$', position); | |
375 | |
376 // Return if there are no more $ characters in the string. If we | |
377 // haven't reached the end, we need to append the suffix. | |
378 if (next < 0) { | |
379 if (position < length) { | |
380 result += %_SubString(string, position, length); | |
381 } | |
382 return result; | |
383 } | |
384 | |
385 // Append substring between the previous and the next $ character. | |
386 if (next > position) { | |
387 result += %_SubString(string, position, next); | |
388 } | |
389 } | |
390 return result; | |
391 } | |
392 | |
393 | |
394 // Compute the string of a given regular expression capture. | |
395 function CaptureString(string, lastCaptureInfo, index) { | |
396 // Scale the index. | |
397 var scaled = index << 1; | |
398 // Compute start and end. | |
399 var start = lastCaptureInfo[CAPTURE(scaled)]; | |
400 // If start isn't valid, return undefined. | |
401 if (start < 0) return; | |
402 var end = lastCaptureInfo[CAPTURE(scaled + 1)]; | |
403 return %_SubString(string, start, end); | |
404 } | |
405 | |
406 | |
407 // TODO(lrn): This array will survive indefinitely if replace is never | |
408 // called again. However, it will be empty, since the contents are cleared | |
409 // in the finally block. | |
410 var reusableReplaceArray = new InternalArray(4); | |
411 | |
412 // Helper function for replacing regular expressions with the result of a | |
413 // function application in String.prototype.replace. | |
414 function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) { | |
415 var resultArray = reusableReplaceArray; | |
416 if (resultArray) { | |
417 reusableReplaceArray = null; | |
418 } else { | |
419 // Inside a nested replace (replace called from the replacement function | |
420 // of another replace) or we have failed to set the reusable array | |
421 // back due to an exception in a replacement function. Create a new | |
422 // array to use in the future, or until the original is written back. | |
423 resultArray = new InternalArray(16); | |
424 } | |
425 var res = %RegExpExecMultiple(regexp, | |
426 subject, | |
427 RegExpLastMatchInfo, | |
428 resultArray); | |
429 regexp.lastIndex = 0; | |
430 if (IS_NULL(res)) { | |
431 // No matches at all. | |
432 reusableReplaceArray = resultArray; | |
433 return subject; | |
434 } | |
435 var len = res.length; | |
436 if (NUMBER_OF_CAPTURES(RegExpLastMatchInfo) == 2) { | |
437 // If the number of captures is two then there are no explicit captures in | |
438 // the regexp, just the implicit capture that captures the whole match. In | |
439 // this case we can simplify quite a bit and end up with something faster. | |
440 // The builder will consist of some integers that indicate slices of the | |
441 // input string and some replacements that were returned from the replace | |
442 // function. | |
443 var match_start = 0; | |
444 var override = new InternalPackedArray(null, 0, subject); | |
445 for (var i = 0; i < len; i++) { | |
446 var elem = res[i]; | |
447 if (%_IsSmi(elem)) { | |
448 // Integers represent slices of the original string. Use these to | |
449 // get the offsets we need for the override array (so things like | |
450 // RegExp.leftContext work during the callback function. | |
451 if (elem > 0) { | |
452 match_start = (elem >> 11) + (elem & 0x7ff); | |
453 } else { | |
454 match_start = res[++i] - elem; | |
455 } | |
456 } else { | |
457 override[0] = elem; | |
458 override[1] = match_start; | |
459 $regexpLastMatchInfoOverride = override; | |
460 var func_result = replace(elem, match_start, subject); | |
461 // Overwrite the i'th element in the results with the string we got | |
462 // back from the callback function. | |
463 res[i] = TO_STRING(func_result); | |
464 match_start += elem.length; | |
465 } | |
466 } | |
467 } else { | |
468 for (var i = 0; i < len; i++) { | |
469 var elem = res[i]; | |
470 if (!%_IsSmi(elem)) { | |
471 // elem must be an Array. | |
472 // Use the apply argument as backing for global RegExp properties. | |
473 $regexpLastMatchInfoOverride = elem; | |
474 var func_result = %Apply(replace, UNDEFINED, elem, 0, elem.length); | |
475 // Overwrite the i'th element in the results with the string we got | |
476 // back from the callback function. | |
477 res[i] = TO_STRING(func_result); | |
478 } | |
479 } | |
480 } | |
481 var result = %StringBuilderConcat(res, res.length, subject); | |
482 resultArray.length = 0; | |
483 reusableReplaceArray = resultArray; | |
484 return result; | |
485 } | |
486 | |
487 | |
488 function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) { | |
489 var matchInfo = RegExpExec(regexp, subject, 0); | |
490 if (IS_NULL(matchInfo)) { | |
491 regexp.lastIndex = 0; | |
492 return subject; | |
493 } | |
494 var index = matchInfo[CAPTURE0]; | |
495 var result = %_SubString(subject, 0, index); | |
496 var endOfMatch = matchInfo[CAPTURE1]; | |
497 // Compute the parameter list consisting of the match, captures, index, | |
498 // and subject for the replace function invocation. | |
499 // The number of captures plus one for the match. | |
500 var m = NUMBER_OF_CAPTURES(matchInfo) >> 1; | |
501 var replacement; | |
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 = replace(s, index, subject); | |
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, UNDEFINED, 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 | |
526 function StringSearch(re) { | |
527 CHECK_OBJECT_COERCIBLE(this, "String.prototype.search"); | |
528 | |
529 var regexp; | |
530 if (IS_REGEXP(re)) { | |
531 regexp = re; | |
532 } else { | |
533 regexp = new GlobalRegExp(re); | |
534 } | |
535 var match = RegExpExec(regexp, TO_STRING(this), 0); | |
536 if (match) { | |
537 return match[CAPTURE0]; | |
538 } | |
539 return -1; | |
540 } | |
541 | |
542 | |
543 // ECMA-262 section 15.5.4.13 | |
544 function StringSlice(start, end) { | |
545 CHECK_OBJECT_COERCIBLE(this, "String.prototype.slice"); | |
546 | |
547 var s = TO_STRING(this); | |
548 var s_len = s.length; | |
549 var start_i = TO_INTEGER(start); | |
550 var end_i = s_len; | |
551 if (!IS_UNDEFINED(end)) { | |
552 end_i = TO_INTEGER(end); | |
553 } | |
554 | |
555 if (start_i < 0) { | |
556 start_i += s_len; | |
557 if (start_i < 0) { | |
558 start_i = 0; | |
559 } | |
560 } else { | |
561 if (start_i > s_len) { | |
562 return ''; | |
563 } | |
564 } | |
565 | |
566 if (end_i < 0) { | |
567 end_i += s_len; | |
568 if (end_i < 0) { | |
569 return ''; | |
570 } | |
571 } else { | |
572 if (end_i > s_len) { | |
573 end_i = s_len; | |
574 } | |
575 } | |
576 | |
577 if (end_i <= start_i) { | |
578 return ''; | |
579 } | |
580 | |
581 return %_SubString(s, start_i, end_i); | |
582 } | |
583 | |
584 | |
585 // ECMA-262 section 15.5.4.14 | |
586 function StringSplitJS(separator, limit) { | |
587 CHECK_OBJECT_COERCIBLE(this, "String.prototype.split"); | |
588 | |
589 var subject = TO_STRING(this); | |
590 limit = (IS_UNDEFINED(limit)) ? 0xffffffff : TO_UINT32(limit); | |
591 | |
592 var length = subject.length; | |
593 if (!IS_REGEXP(separator)) { | |
594 var separator_string = TO_STRING(separator); | |
595 | |
596 if (limit === 0) return []; | |
597 | |
598 // ECMA-262 says that if separator is undefined, the result should | |
599 // be an array of size 1 containing the entire string. | |
600 if (IS_UNDEFINED(separator)) return [subject]; | |
601 | |
602 var separator_length = separator_string.length; | |
603 | |
604 // If the separator string is empty then return the elements in the subject. | |
605 if (separator_length === 0) return %StringToArray(subject, limit); | |
606 | |
607 var result = %StringSplit(subject, separator_string, limit); | |
608 | |
609 return result; | |
610 } | |
611 | |
612 if (limit === 0) return []; | |
613 | |
614 // Separator is a regular expression. | |
615 return StringSplitOnRegExp(subject, separator, limit, length); | |
616 } | |
617 | |
618 | |
619 function StringSplitOnRegExp(subject, separator, limit, length) { | |
620 if (length === 0) { | |
621 if (RegExpExec(separator, subject, 0, 0) != null) { | |
622 return []; | |
623 } | |
624 return [subject]; | |
625 } | |
626 | |
627 var currentIndex = 0; | |
628 var startIndex = 0; | |
629 var startMatch = 0; | |
630 var result = new InternalArray(); | |
631 | |
632 outer_loop: | |
633 while (true) { | |
634 | |
635 if (startIndex === length) { | |
636 result[result.length] = %_SubString(subject, currentIndex, length); | |
637 break; | |
638 } | |
639 | |
640 var matchInfo = RegExpExec(separator, subject, startIndex); | |
641 if (matchInfo == null || length === (startMatch = matchInfo[CAPTURE0])) { | |
642 result[result.length] = %_SubString(subject, currentIndex, length); | |
643 break; | |
644 } | |
645 var endIndex = matchInfo[CAPTURE1]; | |
646 | |
647 // We ignore a zero-length match at the currentIndex. | |
648 if (startIndex === endIndex && endIndex === currentIndex) { | |
649 startIndex++; | |
650 continue; | |
651 } | |
652 | |
653 result[result.length] = %_SubString(subject, currentIndex, startMatch); | |
654 | |
655 if (result.length === limit) break; | |
656 | |
657 var matchinfo_len = NUMBER_OF_CAPTURES(matchInfo) + REGEXP_FIRST_CAPTURE; | |
658 for (var i = REGEXP_FIRST_CAPTURE + 2; i < matchinfo_len; ) { | |
659 var start = matchInfo[i++]; | |
660 var end = matchInfo[i++]; | |
661 if (end != -1) { | |
662 result[result.length] = %_SubString(subject, start, end); | |
663 } else { | |
664 result[result.length] = UNDEFINED; | |
665 } | |
666 if (result.length === limit) break outer_loop; | |
667 } | |
668 | |
669 startIndex = currentIndex = endIndex; | |
670 } | |
671 var array_result = []; | |
672 %MoveArrayContents(result, array_result); | |
673 return array_result; | |
674 } | |
675 | |
676 | |
677 // ECMA-262 section 15.5.4.15 | |
678 function StringSubstring(start, end) { | |
679 CHECK_OBJECT_COERCIBLE(this, "String.prototype.subString"); | |
680 | |
681 var s = TO_STRING(this); | |
682 var s_len = s.length; | |
683 | |
684 var start_i = TO_INTEGER(start); | |
685 if (start_i < 0) { | |
686 start_i = 0; | |
687 } else if (start_i > s_len) { | |
688 start_i = s_len; | |
689 } | |
690 | |
691 var end_i = s_len; | |
692 if (!IS_UNDEFINED(end)) { | |
693 end_i = TO_INTEGER(end); | |
694 if (end_i > s_len) { | |
695 end_i = s_len; | |
696 } else { | |
697 if (end_i < 0) end_i = 0; | |
698 if (start_i > end_i) { | |
699 var tmp = end_i; | |
700 end_i = start_i; | |
701 start_i = tmp; | |
702 } | |
703 } | |
704 } | |
705 | |
706 return %_SubString(s, start_i, end_i); | |
707 } | |
708 | |
709 | |
710 // ES6 draft, revision 26 (2014-07-18), section B.2.3.1 | |
711 function StringSubstr(start, n) { | |
712 CHECK_OBJECT_COERCIBLE(this, "String.prototype.substr"); | |
713 | |
714 var s = TO_STRING(this); | |
715 var len; | |
716 | |
717 // Correct n: If not given, set to string length; if explicitly | |
718 // set to undefined, zero, or negative, returns empty string. | |
719 if (IS_UNDEFINED(n)) { | |
720 len = s.length; | |
721 } else { | |
722 len = TO_INTEGER(n); | |
723 if (len <= 0) return ''; | |
724 } | |
725 | |
726 // Correct start: If not given (or undefined), set to zero; otherwise | |
727 // convert to integer and handle negative case. | |
728 if (IS_UNDEFINED(start)) { | |
729 start = 0; | |
730 } else { | |
731 start = TO_INTEGER(start); | |
732 // If positive, and greater than or equal to the string length, | |
733 // return empty string. | |
734 if (start >= s.length) return ''; | |
735 // If negative and absolute value is larger than the string length, | |
736 // use zero. | |
737 if (start < 0) { | |
738 start += s.length; | |
739 if (start < 0) start = 0; | |
740 } | |
741 } | |
742 | |
743 var end = start + len; | |
744 if (end > s.length) end = s.length; | |
745 | |
746 return %_SubString(s, start, end); | |
747 } | |
748 | |
749 | |
750 // ECMA-262, 15.5.4.16 | |
751 function StringToLowerCaseJS() { | |
752 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLowerCase"); | |
753 | |
754 return %StringToLowerCase(TO_STRING(this)); | |
755 } | |
756 | |
757 | |
758 // ECMA-262, 15.5.4.17 | |
759 function StringToLocaleLowerCase() { | |
760 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleLowerCase"); | |
761 | |
762 return %StringToLowerCase(TO_STRING(this)); | |
763 } | |
764 | |
765 | |
766 // ECMA-262, 15.5.4.18 | |
767 function StringToUpperCaseJS() { | |
768 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toUpperCase"); | |
769 | |
770 return %StringToUpperCase(TO_STRING(this)); | |
771 } | |
772 | |
773 | |
774 // ECMA-262, 15.5.4.19 | |
775 function StringToLocaleUpperCase() { | |
776 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleUpperCase"); | |
777 | |
778 return %StringToUpperCase(TO_STRING(this)); | |
779 } | |
780 | |
781 // ES5, 15.5.4.20 | |
782 function StringTrimJS() { | |
783 CHECK_OBJECT_COERCIBLE(this, "String.prototype.trim"); | |
784 | |
785 return %StringTrim(TO_STRING(this), true, true); | |
786 } | |
787 | |
788 function StringTrimLeft() { | |
789 CHECK_OBJECT_COERCIBLE(this, "String.prototype.trimLeft"); | |
790 | |
791 return %StringTrim(TO_STRING(this), true, false); | |
792 } | |
793 | |
794 function StringTrimRight() { | |
795 CHECK_OBJECT_COERCIBLE(this, "String.prototype.trimRight"); | |
796 | |
797 return %StringTrim(TO_STRING(this), false, true); | |
798 } | |
799 | |
800 | |
801 // ECMA-262, section 15.5.3.2 | |
802 function StringFromCharCode(code) { | |
803 var n = %_ArgumentsLength(); | |
804 if (n == 1) { | |
805 if (!%_IsSmi(code)) code = TO_NUMBER(code); | |
806 return %_StringCharFromCode(code & 0xffff); | |
807 } | |
808 | |
809 var one_byte = %NewString(n, NEW_ONE_BYTE_STRING); | |
810 var i; | |
811 for (i = 0; i < n; i++) { | |
812 var code = %_Arguments(i); | |
813 if (!%_IsSmi(code)) code = TO_NUMBER(code) & 0xffff; | |
814 if (code < 0) code = code & 0xffff; | |
815 if (code > 0xff) break; | |
816 %_OneByteSeqStringSetChar(i, code, one_byte); | |
817 } | |
818 if (i == n) return one_byte; | |
819 one_byte = %TruncateString(one_byte, i); | |
820 | |
821 var two_byte = %NewString(n - i, NEW_TWO_BYTE_STRING); | |
822 for (var j = 0; i < n; i++, j++) { | |
823 var code = %_Arguments(i); | |
824 if (!%_IsSmi(code)) code = TO_NUMBER(code) & 0xffff; | |
825 %_TwoByteSeqStringSetChar(j, code, two_byte); | |
826 } | |
827 return one_byte + two_byte; | |
828 } | |
829 | |
830 | |
831 // ES6 draft, revision 26 (2014-07-18), section B.2.3.2.1 | |
832 function HtmlEscape(str) { | |
833 return %_CallFunction(TO_STRING(str), /"/g, """, StringReplace); | |
834 } | |
835 | |
836 | |
837 // ES6 draft, revision 26 (2014-07-18), section B.2.3.2 | |
838 function StringAnchor(name) { | |
839 CHECK_OBJECT_COERCIBLE(this, "String.prototype.anchor"); | |
840 return "<a name=\"" + HtmlEscape(name) + "\">" + TO_STRING(this) + | |
841 "</a>"; | |
842 } | |
843 | |
844 | |
845 // ES6 draft, revision 26 (2014-07-18), section B.2.3.3 | |
846 function StringBig() { | |
847 CHECK_OBJECT_COERCIBLE(this, "String.prototype.big"); | |
848 return "<big>" + TO_STRING(this) + "</big>"; | |
849 } | |
850 | |
851 | |
852 // ES6 draft, revision 26 (2014-07-18), section B.2.3.4 | |
853 function StringBlink() { | |
854 CHECK_OBJECT_COERCIBLE(this, "String.prototype.blink"); | |
855 return "<blink>" + TO_STRING(this) + "</blink>"; | |
856 } | |
857 | |
858 | |
859 // ES6 draft, revision 26 (2014-07-18), section B.2.3.5 | |
860 function StringBold() { | |
861 CHECK_OBJECT_COERCIBLE(this, "String.prototype.bold"); | |
862 return "<b>" + TO_STRING(this) + "</b>"; | |
863 } | |
864 | |
865 | |
866 // ES6 draft, revision 26 (2014-07-18), section B.2.3.6 | |
867 function StringFixed() { | |
868 CHECK_OBJECT_COERCIBLE(this, "String.prototype.fixed"); | |
869 return "<tt>" + TO_STRING(this) + "</tt>"; | |
870 } | |
871 | |
872 | |
873 // ES6 draft, revision 26 (2014-07-18), section B.2.3.7 | |
874 function StringFontcolor(color) { | |
875 CHECK_OBJECT_COERCIBLE(this, "String.prototype.fontcolor"); | |
876 return "<font color=\"" + HtmlEscape(color) + "\">" + TO_STRING(this) + | |
877 "</font>"; | |
878 } | |
879 | |
880 | |
881 // ES6 draft, revision 26 (2014-07-18), section B.2.3.8 | |
882 function StringFontsize(size) { | |
883 CHECK_OBJECT_COERCIBLE(this, "String.prototype.fontsize"); | |
884 return "<font size=\"" + HtmlEscape(size) + "\">" + TO_STRING(this) + | |
885 "</font>"; | |
886 } | |
887 | |
888 | |
889 // ES6 draft, revision 26 (2014-07-18), section B.2.3.9 | |
890 function StringItalics() { | |
891 CHECK_OBJECT_COERCIBLE(this, "String.prototype.italics"); | |
892 return "<i>" + TO_STRING(this) + "</i>"; | |
893 } | |
894 | |
895 | |
896 // ES6 draft, revision 26 (2014-07-18), section B.2.3.10 | |
897 function StringLink(s) { | |
898 CHECK_OBJECT_COERCIBLE(this, "String.prototype.link"); | |
899 return "<a href=\"" + HtmlEscape(s) + "\">" + TO_STRING(this) + "</a>"; | |
900 } | |
901 | |
902 | |
903 // ES6 draft, revision 26 (2014-07-18), section B.2.3.11 | |
904 function StringSmall() { | |
905 CHECK_OBJECT_COERCIBLE(this, "String.prototype.small"); | |
906 return "<small>" + TO_STRING(this) + "</small>"; | |
907 } | |
908 | |
909 | |
910 // ES6 draft, revision 26 (2014-07-18), section B.2.3.12 | |
911 function StringStrike() { | |
912 CHECK_OBJECT_COERCIBLE(this, "String.prototype.strike"); | |
913 return "<strike>" + TO_STRING(this) + "</strike>"; | |
914 } | |
915 | |
916 | |
917 // ES6 draft, revision 26 (2014-07-18), section B.2.3.13 | |
918 function StringSub() { | |
919 CHECK_OBJECT_COERCIBLE(this, "String.prototype.sub"); | |
920 return "<sub>" + TO_STRING(this) + "</sub>"; | |
921 } | |
922 | |
923 | |
924 // ES6 draft, revision 26 (2014-07-18), section B.2.3.14 | |
925 function StringSup() { | |
926 CHECK_OBJECT_COERCIBLE(this, "String.prototype.sup"); | |
927 return "<sup>" + TO_STRING(this) + "</sup>"; | |
928 } | |
929 | |
930 // ES6 draft 01-20-14, section 21.1.3.13 | |
931 function StringRepeat(count) { | |
932 CHECK_OBJECT_COERCIBLE(this, "String.prototype.repeat"); | |
933 | |
934 var s = TO_STRING(this); | |
935 var n = TO_INTEGER(count); | |
936 // The maximum string length is stored in a smi, so a longer repeat | |
937 // must result in a range error. | |
938 if (n < 0 || n > %_MaxSmi()) throw MakeRangeError(kInvalidCountValue); | |
939 | |
940 var r = ""; | |
941 while (true) { | |
942 if (n & 1) r += s; | |
943 n >>= 1; | |
944 if (n === 0) return r; | |
945 s += s; | |
946 } | |
947 } | |
948 | |
949 | |
950 // ES6 draft 04-05-14, section 21.1.3.18 | |
951 function StringStartsWith(searchString /* position */) { // length == 1 | |
952 CHECK_OBJECT_COERCIBLE(this, "String.prototype.startsWith"); | |
953 | |
954 var s = TO_STRING(this); | |
955 | |
956 if (IS_REGEXP(searchString)) { | |
957 throw MakeTypeError(kFirstArgumentNotRegExp, "String.prototype.startsWith"); | |
958 } | |
959 | |
960 var ss = TO_STRING(searchString); | |
961 var pos = 0; | |
962 if (%_ArgumentsLength() > 1) { | |
963 var arg = %_Arguments(1); // position | |
964 if (!IS_UNDEFINED(arg)) { | |
965 pos = TO_INTEGER(arg); | |
966 } | |
967 } | |
968 | |
969 var s_len = s.length; | |
970 if (pos < 0) pos = 0; | |
971 if (pos > s_len) pos = s_len; | |
972 var ss_len = ss.length; | |
973 | |
974 if (ss_len + pos > s_len) { | |
975 return false; | |
976 } | |
977 | |
978 for (var i = 0; i < ss_len; i++) { | |
979 if (%_StringCharCodeAt(s, pos + i) !== %_StringCharCodeAt(ss, i)) { | |
980 return false; | |
981 } | |
982 } | |
983 | |
984 return true; | |
985 } | |
986 | |
987 | |
988 // ES6 draft 04-05-14, section 21.1.3.7 | |
989 function StringEndsWith(searchString /* position */) { // length == 1 | |
990 CHECK_OBJECT_COERCIBLE(this, "String.prototype.endsWith"); | |
991 | |
992 var s = TO_STRING(this); | |
993 | |
994 if (IS_REGEXP(searchString)) { | |
995 throw MakeTypeError(kFirstArgumentNotRegExp, "String.prototype.endsWith"); | |
996 } | |
997 | |
998 var ss = TO_STRING(searchString); | |
999 var s_len = s.length; | |
1000 var pos = s_len; | |
1001 if (%_ArgumentsLength() > 1) { | |
1002 var arg = %_Arguments(1); // position | |
1003 if (!IS_UNDEFINED(arg)) { | |
1004 pos = TO_INTEGER(arg); | |
1005 } | |
1006 } | |
1007 | |
1008 if (pos < 0) pos = 0; | |
1009 if (pos > s_len) pos = s_len; | |
1010 var ss_len = ss.length; | |
1011 pos = pos - ss_len; | |
1012 | |
1013 if (pos < 0) { | |
1014 return false; | |
1015 } | |
1016 | |
1017 for (var i = 0; i < ss_len; i++) { | |
1018 if (%_StringCharCodeAt(s, pos + i) !== %_StringCharCodeAt(ss, i)) { | |
1019 return false; | |
1020 } | |
1021 } | |
1022 | |
1023 return true; | |
1024 } | |
1025 | |
1026 | |
1027 // ES6 draft 04-05-14, section 21.1.3.6 | |
1028 function StringIncludes(searchString /* position */) { // length == 1 | |
1029 CHECK_OBJECT_COERCIBLE(this, "String.prototype.includes"); | |
1030 | |
1031 var string = TO_STRING(this); | |
1032 | |
1033 if (IS_REGEXP(searchString)) { | |
1034 throw MakeTypeError(kFirstArgumentNotRegExp, "String.prototype.includes"); | |
1035 } | |
1036 | |
1037 searchString = TO_STRING(searchString); | |
1038 var pos = 0; | |
1039 if (%_ArgumentsLength() > 1) { | |
1040 pos = %_Arguments(1); // position | |
1041 pos = TO_INTEGER(pos); | |
1042 } | |
1043 | |
1044 var stringLength = string.length; | |
1045 if (pos < 0) pos = 0; | |
1046 if (pos > stringLength) pos = stringLength; | |
1047 var searchStringLength = searchString.length; | |
1048 | |
1049 if (searchStringLength + pos > stringLength) { | |
1050 return false; | |
1051 } | |
1052 | |
1053 return %StringIndexOf(string, searchString, pos) !== -1; | |
1054 } | |
1055 | |
1056 | |
1057 // ES6 Draft 05-22-2014, section 21.1.3.3 | |
1058 function StringCodePointAt(pos) { | |
1059 CHECK_OBJECT_COERCIBLE(this, "String.prototype.codePointAt"); | |
1060 | |
1061 var string = TO_STRING(this); | |
1062 var size = string.length; | |
1063 pos = TO_INTEGER(pos); | |
1064 if (pos < 0 || pos >= size) { | |
1065 return UNDEFINED; | |
1066 } | |
1067 var first = %_StringCharCodeAt(string, pos); | |
1068 if (first < 0xD800 || first > 0xDBFF || pos + 1 == size) { | |
1069 return first; | |
1070 } | |
1071 var second = %_StringCharCodeAt(string, pos + 1); | |
1072 if (second < 0xDC00 || second > 0xDFFF) { | |
1073 return first; | |
1074 } | |
1075 return (first - 0xD800) * 0x400 + second + 0x2400; | |
1076 } | |
1077 | |
1078 | |
1079 // ES6 Draft 05-22-2014, section 21.1.2.2 | |
1080 function StringFromCodePoint(_) { // length = 1 | |
1081 var code; | |
1082 var length = %_ArgumentsLength(); | |
1083 var index; | |
1084 var result = ""; | |
1085 for (index = 0; index < length; index++) { | |
1086 code = %_Arguments(index); | |
1087 if (!%_IsSmi(code)) { | |
1088 code = TO_NUMBER(code); | |
1089 } | |
1090 if (code < 0 || code > 0x10FFFF || code !== TO_INTEGER(code)) { | |
1091 throw MakeRangeError(kInvalidCodePoint, code); | |
1092 } | |
1093 if (code <= 0xFFFF) { | |
1094 result += %_StringCharFromCode(code); | |
1095 } else { | |
1096 code -= 0x10000; | |
1097 result += %_StringCharFromCode((code >>> 10) & 0x3FF | 0xD800); | |
1098 result += %_StringCharFromCode(code & 0x3FF | 0xDC00); | |
1099 } | |
1100 } | |
1101 return result; | |
1102 } | |
1103 | |
1104 | |
1105 // ------------------------------------------------------------------- | |
1106 // String methods related to templates | |
1107 | |
1108 // ES6 Draft 03-17-2015, section 21.1.2.4 | |
1109 function StringRaw(callSite) { | |
1110 // TODO(caitp): Use rest parameters when implemented | |
1111 var numberOfSubstitutions = %_ArgumentsLength(); | |
1112 var cooked = TO_OBJECT(callSite); | |
1113 var raw = TO_OBJECT(cooked.raw); | |
1114 var literalSegments = TO_LENGTH(raw.length); | |
1115 if (literalSegments <= 0) return ""; | |
1116 | |
1117 var result = TO_STRING(raw[0]); | |
1118 | |
1119 for (var i = 1; i < literalSegments; ++i) { | |
1120 if (i < numberOfSubstitutions) { | |
1121 result += TO_STRING(%_Arguments(i)); | |
1122 } | |
1123 result += TO_STRING(raw[i]); | |
1124 } | |
1125 | |
1126 return result; | |
1127 } | |
1128 | |
1129 // ------------------------------------------------------------------- | |
1130 | |
1131 // Set the String function and constructor. | |
1132 %FunctionSetPrototype(GlobalString, new GlobalString()); | |
1133 | |
1134 // Set up the constructor property on the String prototype object. | |
1135 %AddNamedProperty( | |
1136 GlobalString.prototype, "constructor", GlobalString, DONT_ENUM); | |
1137 | |
1138 // Set up the non-enumerable functions on the String object. | |
1139 utils.InstallFunctions(GlobalString, DONT_ENUM, [ | |
1140 "fromCharCode", StringFromCharCode, | |
1141 "fromCodePoint", StringFromCodePoint, | |
1142 "raw", StringRaw | |
1143 ]); | |
1144 | |
1145 // Set up the non-enumerable functions on the String prototype object. | |
1146 utils.InstallFunctions(GlobalString.prototype, DONT_ENUM, [ | |
1147 "valueOf", StringValueOf, | |
1148 "toString", StringToString, | |
1149 "charAt", StringCharAtJS, | |
1150 "charCodeAt", StringCharCodeAtJS, | |
1151 "codePointAt", StringCodePointAt, | |
1152 "concat", StringConcat, | |
1153 "endsWith", StringEndsWith, | |
1154 "includes", StringIncludes, | |
1155 "indexOf", StringIndexOfJS, | |
1156 "lastIndexOf", StringLastIndexOfJS, | |
1157 "localeCompare", StringLocaleCompareJS, | |
1158 "match", StringMatchJS, | |
1159 "normalize", StringNormalizeJS, | |
1160 "repeat", StringRepeat, | |
1161 "replace", StringReplace, | |
1162 "search", StringSearch, | |
1163 "slice", StringSlice, | |
1164 "split", StringSplitJS, | |
1165 "substring", StringSubstring, | |
1166 "substr", StringSubstr, | |
1167 "startsWith", StringStartsWith, | |
1168 "toLowerCase", StringToLowerCaseJS, | |
1169 "toLocaleLowerCase", StringToLocaleLowerCase, | |
1170 "toUpperCase", StringToUpperCaseJS, | |
1171 "toLocaleUpperCase", StringToLocaleUpperCase, | |
1172 "trim", StringTrimJS, | |
1173 "trimLeft", StringTrimLeft, | |
1174 "trimRight", StringTrimRight, | |
1175 | |
1176 "link", StringLink, | |
1177 "anchor", StringAnchor, | |
1178 "fontcolor", StringFontcolor, | |
1179 "fontsize", StringFontsize, | |
1180 "big", StringBig, | |
1181 "blink", StringBlink, | |
1182 "bold", StringBold, | |
1183 "fixed", StringFixed, | |
1184 "italics", StringItalics, | |
1185 "small", StringSmall, | |
1186 "strike", StringStrike, | |
1187 "sub", StringSub, | |
1188 "sup", StringSup | |
1189 ]); | |
1190 | |
1191 // ------------------------------------------------------------------- | |
1192 // Exports | |
1193 | |
1194 utils.Export(function(to) { | |
1195 to.StringCharAt = StringCharAtJS; | |
1196 to.StringIndexOf = StringIndexOfJS; | |
1197 to.StringLastIndexOf = StringLastIndexOfJS; | |
1198 to.StringMatch = StringMatchJS; | |
1199 to.StringReplace = StringReplace; | |
1200 to.StringSlice = StringSlice; | |
1201 to.StringSplit = StringSplitJS; | |
1202 to.StringSubstr = StringSubstr; | |
1203 to.StringSubstring = StringSubstring; | |
1204 }); | |
1205 | |
1206 }) | |
OLD | NEW |