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