blob: ab0ab54f036da02a981eeae833558fbf6e16c604 [file] [log] [blame]
Steve Blockd0582a62009-12-15 09:54:21 +00001// Copyright 2006-2009 the V8 project authors. All rights reserved.
Steve Blocka7e24c12009-10-30 11:49:00 +00002// 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
29// This file relies on the fact that the following declaration has been made
30// in runtime.js:
31// const $String = global.String;
32// const $NaN = 0/0;
33
34
35// Set the String function and constructor.
36%SetCode($String, function(x) {
Andrei Popescu402d9372010-02-26 13:31:12 +000037 var value = %_ArgumentsLength() == 0 ? '' : TO_STRING_INLINE(x);
Steve Blocka7e24c12009-10-30 11:49:00 +000038 if (%_IsConstructCall()) {
39 %_SetValueOf(this, value);
40 } else {
41 return value;
42 }
43});
44
45%FunctionSetPrototype($String, new $String());
46
47// ECMA-262 section 15.5.4.2
48function StringToString() {
49 if (!IS_STRING(this) && !IS_STRING_WRAPPER(this))
50 throw new $TypeError('String.prototype.toString is not generic');
51 return %_ValueOf(this);
52}
53
54
55// ECMA-262 section 15.5.4.3
56function StringValueOf() {
57 if (!IS_STRING(this) && !IS_STRING_WRAPPER(this))
58 throw new $TypeError('String.prototype.valueOf is not generic');
59 return %_ValueOf(this);
60}
61
62
63// ECMA-262, section 15.5.4.4
64function StringCharAt(pos) {
Ben Murdoch7f4d5bd2010-06-15 11:15:29 +010065 var result = %_StringCharAt(this, pos);
66 if (%_IsSmi(result)) {
67 result = %_StringCharAt(TO_STRING_INLINE(this), TO_INTEGER(pos));
Steve Blocka7e24c12009-10-30 11:49:00 +000068 }
Ben Murdoch7f4d5bd2010-06-15 11:15:29 +010069 return result;
Steve Blocka7e24c12009-10-30 11:49:00 +000070}
71
72
73// ECMA-262 section 15.5.4.5
74function StringCharCodeAt(pos) {
Ben Murdoch7f4d5bd2010-06-15 11:15:29 +010075 var result = %_StringCharCodeAt(this, pos);
76 if (!%_IsSmi(result)) {
77 result = %_StringCharCodeAt(TO_STRING_INLINE(this), TO_INTEGER(pos));
Steve Blocka7e24c12009-10-30 11:49:00 +000078 }
Ben Murdoch7f4d5bd2010-06-15 11:15:29 +010079 return result;
Steve Blocka7e24c12009-10-30 11:49:00 +000080}
81
82
83// ECMA-262, section 15.5.4.6
84function StringConcat() {
Andrei Popescu402d9372010-02-26 13:31:12 +000085 var len = %_ArgumentsLength();
86 var this_as_string = TO_STRING_INLINE(this);
87 if (len === 1) {
88 return this_as_string + %_Arguments(0);
Leon Clarkee46be812010-01-19 14:06:41 +000089 }
Andrei Popescu402d9372010-02-26 13:31:12 +000090 var parts = new $Array(len + 1);
91 parts[0] = this_as_string;
92 for (var i = 0; i < len; i++) {
93 var part = %_Arguments(i);
94 parts[i + 1] = TO_STRING_INLINE(part);
95 }
96 return %StringBuilderConcat(parts, len + 1, "");
Steve Blocka7e24c12009-10-30 11:49:00 +000097}
98
99// Match ES3 and Safari
100%FunctionSetLength(StringConcat, 1);
101
102
103// ECMA-262 section 15.5.4.7
Ben Murdochb0fe1622011-05-05 13:52:32 +0100104function StringIndexOf(pattern /* position */) { // length == 1
105 var subject = TO_STRING_INLINE(this);
106 var pattern = TO_STRING_INLINE(pattern);
107 var subject_len = subject.length;
108 var pattern_len = pattern.length;
Steve Blocka7e24c12009-10-30 11:49:00 +0000109 var index = 0;
110 if (%_ArgumentsLength() > 1) {
111 var arg1 = %_Arguments(1); // position
112 index = TO_INTEGER(arg1);
113 }
114 if (index < 0) index = 0;
Ben Murdochb0fe1622011-05-05 13:52:32 +0100115 if (index > subject_len) index = subject_len;
116 if (pattern_len + index > subject_len) return -1;
117 return %StringIndexOf(subject, pattern, index);
Steve Blocka7e24c12009-10-30 11:49:00 +0000118}
119
120
121// ECMA-262 section 15.5.4.8
Ben Murdochb0fe1622011-05-05 13:52:32 +0100122function StringLastIndexOf(pat /* position */) { // length == 1
Andrei Popescu402d9372010-02-26 13:31:12 +0000123 var sub = TO_STRING_INLINE(this);
Steve Blocka7e24c12009-10-30 11:49:00 +0000124 var subLength = sub.length;
Ben Murdochb0fe1622011-05-05 13:52:32 +0100125 var pat = TO_STRING_INLINE(pat);
Steve Blocka7e24c12009-10-30 11:49:00 +0000126 var patLength = pat.length;
127 var index = subLength - patLength;
128 if (%_ArgumentsLength() > 1) {
129 var position = ToNumber(%_Arguments(1));
Steve Block9fac8402011-05-12 15:51:54 +0100130 if (!NUMBER_IS_NAN(position)) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000131 position = TO_INTEGER(position);
132 if (position < 0) {
133 position = 0;
134 }
135 if (position + patLength < subLength) {
136 index = position
137 }
138 }
139 }
140 if (index < 0) {
141 return -1;
142 }
143 return %StringLastIndexOf(sub, pat, index);
144}
145
146
147// ECMA-262 section 15.5.4.9
148//
149// This function is implementation specific. For now, we do not
150// do anything locale specific.
151function StringLocaleCompare(other) {
152 if (%_ArgumentsLength() === 0) return 0;
Ben Murdochb0fe1622011-05-05 13:52:32 +0100153 return %StringLocaleCompare(TO_STRING_INLINE(this),
154 TO_STRING_INLINE(other));
Steve Blocka7e24c12009-10-30 11:49:00 +0000155}
156
157
158// ECMA-262 section 15.5.4.10
159function StringMatch(regexp) {
Andrei Popescu402d9372010-02-26 13:31:12 +0000160 var subject = TO_STRING_INLINE(this);
Steve Block6ded16b2010-05-10 14:33:55 +0100161 if (IS_REGEXP(regexp)) {
Ben Murdochb0fe1622011-05-05 13:52:32 +0100162 if (!regexp.global) return RegExpExecNoTests(regexp, subject, 0);
Steve Block6ded16b2010-05-10 14:33:55 +0100163 %_Log('regexp', 'regexp-match,%0S,%1r', [subject, regexp]);
164 // lastMatchInfo is defined in regexp.js.
Teng-Hui Zhu3e5fa292010-11-09 16:16:48 -0800165 return %StringMatch(subject, regexp, lastMatchInfo);
Steve Block6ded16b2010-05-10 14:33:55 +0100166 }
167 // Non-regexp argument.
168 regexp = new $RegExp(regexp);
Steve Block6ded16b2010-05-10 14:33:55 +0100169 return RegExpExecNoTests(regexp, subject, 0);
Steve Blocka7e24c12009-10-30 11:49:00 +0000170}
171
172
173// SubString is an internal function that returns the sub string of 'string'.
174// If resulting string is of length 1, we use the one character cache
175// otherwise we call the runtime system.
176function SubString(string, start, end) {
177 // Use the one character string cache.
Ben Murdochb0fe1622011-05-05 13:52:32 +0100178 if (start + 1 == end) return %_StringCharAt(string, start);
Leon Clarkee46be812010-01-19 14:06:41 +0000179 return %_SubString(string, start, end);
Steve Blocka7e24c12009-10-30 11:49:00 +0000180}
181
182
183// This has the same size as the lastMatchInfo array, and can be used for
184// functions that expect that structure to be returned. It is used when the
185// needle is a string rather than a regexp. In this case we can't update
186// lastMatchArray without erroneously affecting the properties on the global
187// RegExp object.
188var reusableMatchInfo = [2, "", "", -1, -1];
189
190
191// ECMA-262, section 15.5.4.11
192function StringReplace(search, replace) {
Andrei Popescu402d9372010-02-26 13:31:12 +0000193 var subject = TO_STRING_INLINE(this);
Steve Blocka7e24c12009-10-30 11:49:00 +0000194
195 // Delegate to one of the regular expression variants if necessary.
196 if (IS_REGEXP(search)) {
197 %_Log('regexp', 'regexp-replace,%0r,%1S', [search, subject]);
198 if (IS_FUNCTION(replace)) {
Kristian Monsen25f61362010-05-21 11:50:48 +0100199 if (search.global) {
200 return StringReplaceGlobalRegExpWithFunction(subject, search, replace);
201 } else {
202 return StringReplaceNonGlobalRegExpWithFunction(subject,
203 search,
204 replace);
205 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000206 } else {
Ben Murdochb0fe1622011-05-05 13:52:32 +0100207 return %StringReplaceRegExpWithString(subject,
208 search,
209 TO_STRING_INLINE(replace),
210 lastMatchInfo);
Steve Blocka7e24c12009-10-30 11:49:00 +0000211 }
212 }
213
214 // Convert the search argument to a string and search for it.
Andrei Popescu402d9372010-02-26 13:31:12 +0000215 search = TO_STRING_INLINE(search);
Steve Blocka7e24c12009-10-30 11:49:00 +0000216 var start = %StringIndexOf(subject, search, 0);
217 if (start < 0) return subject;
218 var end = start + search.length;
219
220 var builder = new ReplaceResultBuilder(subject);
221 // prefix
222 builder.addSpecialSlice(0, start);
223
224 // Compute the string to replace with.
225 if (IS_FUNCTION(replace)) {
Ben Murdochb0fe1622011-05-05 13:52:32 +0100226 builder.add(%_CallFunction(%GetGlobalReceiver(),
227 search,
228 start,
229 subject,
230 replace));
Steve Blocka7e24c12009-10-30 11:49:00 +0000231 } else {
232 reusableMatchInfo[CAPTURE0] = start;
233 reusableMatchInfo[CAPTURE1] = end;
Andrei Popescu402d9372010-02-26 13:31:12 +0000234 replace = TO_STRING_INLINE(replace);
Leon Clarkee46be812010-01-19 14:06:41 +0000235 ExpandReplacement(replace, subject, reusableMatchInfo, builder);
Steve Blocka7e24c12009-10-30 11:49:00 +0000236 }
237
238 // suffix
239 builder.addSpecialSlice(end, subject.length);
240
241 return builder.generate();
242}
243
244
Steve Blocka7e24c12009-10-30 11:49:00 +0000245// Expand the $-expressions in the string and return a new string with
246// the result.
247function ExpandReplacement(string, subject, matchInfo, builder) {
Ben Murdochb0fe1622011-05-05 13:52:32 +0100248 var length = string.length;
249 var builder_elements = builder.elements;
Steve Blocka7e24c12009-10-30 11:49:00 +0000250 var next = %StringIndexOf(string, '$', 0);
251 if (next < 0) {
Ben Murdochb0fe1622011-05-05 13:52:32 +0100252 if (length > 0) builder_elements.push(string);
Steve Blocka7e24c12009-10-30 11:49:00 +0000253 return;
254 }
255
256 // Compute the number of captures; see ECMA-262, 15.5.4.11, p. 102.
257 var m = NUMBER_OF_CAPTURES(matchInfo) >> 1; // Includes the match.
258
Ben Murdochb0fe1622011-05-05 13:52:32 +0100259 if (next > 0) builder_elements.push(SubString(string, 0, next));
Steve Blocka7e24c12009-10-30 11:49:00 +0000260
261 while (true) {
262 var expansion = '$';
263 var position = next + 1;
264 if (position < length) {
Ben Murdoch7f4d5bd2010-06-15 11:15:29 +0100265 var peek = %_StringCharCodeAt(string, position);
Steve Blocka7e24c12009-10-30 11:49:00 +0000266 if (peek == 36) { // $$
267 ++position;
Ben Murdochb0fe1622011-05-05 13:52:32 +0100268 builder_elements.push('$');
Steve Blocka7e24c12009-10-30 11:49:00 +0000269 } else if (peek == 38) { // $& - match
270 ++position;
271 builder.addSpecialSlice(matchInfo[CAPTURE0],
272 matchInfo[CAPTURE1]);
273 } else if (peek == 96) { // $` - prefix
274 ++position;
275 builder.addSpecialSlice(0, matchInfo[CAPTURE0]);
276 } else if (peek == 39) { // $' - suffix
277 ++position;
278 builder.addSpecialSlice(matchInfo[CAPTURE1], subject.length);
279 } else if (peek >= 48 && peek <= 57) { // $n, 0 <= n <= 9
280 ++position;
281 var n = peek - 48;
282 if (position < length) {
Ben Murdoch7f4d5bd2010-06-15 11:15:29 +0100283 peek = %_StringCharCodeAt(string, position);
Steve Blocka7e24c12009-10-30 11:49:00 +0000284 // $nn, 01 <= nn <= 99
285 if (n != 0 && peek == 48 || peek >= 49 && peek <= 57) {
286 var nn = n * 10 + (peek - 48);
287 if (nn < m) {
288 // If the two digit capture reference is within range of
289 // the captures, we use it instead of the single digit
290 // one. Otherwise, we fall back to using the single
291 // digit reference. This matches the behavior of
292 // SpiderMonkey.
293 ++position;
294 n = nn;
295 }
296 }
297 }
298 if (0 < n && n < m) {
299 addCaptureString(builder, matchInfo, n);
300 } else {
301 // Because of the captures range check in the parsing of two
302 // digit capture references, we can only enter here when a
303 // single digit capture reference is outside the range of
304 // captures.
Ben Murdochb0fe1622011-05-05 13:52:32 +0100305 builder_elements.push('$');
Steve Blocka7e24c12009-10-30 11:49:00 +0000306 --position;
307 }
308 } else {
Ben Murdochb0fe1622011-05-05 13:52:32 +0100309 builder_elements.push('$');
Steve Blocka7e24c12009-10-30 11:49:00 +0000310 }
311 } else {
Ben Murdochb0fe1622011-05-05 13:52:32 +0100312 builder_elements.push('$');
Steve Blocka7e24c12009-10-30 11:49:00 +0000313 }
314
315 // Go the the next $ in the string.
316 next = %StringIndexOf(string, '$', position);
317
318 // Return if there are no more $ characters in the string. If we
319 // haven't reached the end, we need to append the suffix.
320 if (next < 0) {
321 if (position < length) {
Ben Murdochb0fe1622011-05-05 13:52:32 +0100322 builder_elements.push(SubString(string, position, length));
Steve Blocka7e24c12009-10-30 11:49:00 +0000323 }
324 return;
325 }
326
327 // Append substring between the previous and the next $ character.
Ben Murdochb0fe1622011-05-05 13:52:32 +0100328 if (next > position) {
329 builder_elements.push(SubString(string, position, next));
330 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000331 }
332};
333
334
335// Compute the string of a given regular expression capture.
336function CaptureString(string, lastCaptureInfo, index) {
337 // Scale the index.
338 var scaled = index << 1;
339 // Compute start and end.
340 var start = lastCaptureInfo[CAPTURE(scaled)];
Kristian Monsen25f61362010-05-21 11:50:48 +0100341 // If start isn't valid, return undefined.
342 if (start < 0) return;
Steve Blocka7e24c12009-10-30 11:49:00 +0000343 var end = lastCaptureInfo[CAPTURE(scaled + 1)];
Steve Blocka7e24c12009-10-30 11:49:00 +0000344 return SubString(string, start, end);
345};
346
347
348// Add the string of a given regular expression capture to the
349// ReplaceResultBuilder
350function addCaptureString(builder, matchInfo, index) {
351 // Scale the index.
352 var scaled = index << 1;
353 // Compute start and end.
354 var start = matchInfo[CAPTURE(scaled)];
Kristian Monsen25f61362010-05-21 11:50:48 +0100355 if (start < 0) return;
Steve Blocka7e24c12009-10-30 11:49:00 +0000356 var end = matchInfo[CAPTURE(scaled + 1)];
Steve Blocka7e24c12009-10-30 11:49:00 +0000357 builder.addSpecialSlice(start, end);
358};
359
Steve Block6ded16b2010-05-10 14:33:55 +0100360// TODO(lrn): This array will survive indefinitely if replace is never
361// called again. However, it will be empty, since the contents are cleared
362// in the finally block.
363var reusableReplaceArray = $Array(16);
Steve Blocka7e24c12009-10-30 11:49:00 +0000364
365// Helper function for replacing regular expressions with the result of a
Steve Block6ded16b2010-05-10 14:33:55 +0100366// function application in String.prototype.replace.
Kristian Monsen25f61362010-05-21 11:50:48 +0100367function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) {
368 var resultArray = reusableReplaceArray;
369 if (resultArray) {
370 reusableReplaceArray = null;
371 } else {
372 // Inside a nested replace (replace called from the replacement function
373 // of another replace) or we have failed to set the reusable array
374 // back due to an exception in a replacement function. Create a new
375 // array to use in the future, or until the original is written back.
376 resultArray = $Array(16);
Steve Blocka7e24c12009-10-30 11:49:00 +0000377 }
Kristian Monsen25f61362010-05-21 11:50:48 +0100378 var res = %RegExpExecMultiple(regexp,
379 subject,
380 lastMatchInfo,
381 resultArray);
382 regexp.lastIndex = 0;
383 if (IS_NULL(res)) {
384 // No matches at all.
385 reusableReplaceArray = resultArray;
386 return subject;
387 }
388 var len = res.length;
389 var i = 0;
390 if (NUMBER_OF_CAPTURES(lastMatchInfo) == 2) {
391 var match_start = 0;
392 var override = [null, 0, subject];
393 var receiver = %GetGlobalReceiver();
394 while (i < len) {
395 var elem = res[i];
396 if (%_IsSmi(elem)) {
397 if (elem > 0) {
398 match_start = (elem >> 11) + (elem & 0x7ff);
399 } else {
400 match_start = res[++i] - elem;
401 }
402 } else {
403 override[0] = elem;
404 override[1] = match_start;
405 lastMatchInfoOverride = override;
406 var func_result =
407 %_CallFunction(receiver, elem, match_start, subject, replace);
Ben Murdochb0fe1622011-05-05 13:52:32 +0100408 func_result = TO_STRING_INLINE(func_result);
Kristian Monsen25f61362010-05-21 11:50:48 +0100409 res[i] = func_result;
410 match_start += elem.length;
411 }
412 i++;
413 }
414 } else {
415 while (i < len) {
416 var elem = res[i];
417 if (!%_IsSmi(elem)) {
418 // elem must be an Array.
419 // Use the apply argument as backing for global RegExp properties.
420 lastMatchInfoOverride = elem;
421 var func_result = replace.apply(null, elem);
Ben Murdochb0fe1622011-05-05 13:52:32 +0100422 func_result = TO_STRING_INLINE(func_result);
Kristian Monsen25f61362010-05-21 11:50:48 +0100423 res[i] = func_result;
424 }
425 i++;
426 }
427 }
428 var resultBuilder = new ReplaceResultBuilder(subject, res);
429 var result = resultBuilder.generate();
430 resultArray.length = 0;
431 reusableReplaceArray = resultArray;
432 return result;
Steve Blocka7e24c12009-10-30 11:49:00 +0000433}
434
435
Kristian Monsen25f61362010-05-21 11:50:48 +0100436function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) {
437 var matchInfo = DoRegExpExec(regexp, subject, 0);
438 if (IS_NULL(matchInfo)) return subject;
439 var result = new ReplaceResultBuilder(subject);
440 var index = matchInfo[CAPTURE0];
441 result.addSpecialSlice(0, index);
442 var endOfMatch = matchInfo[CAPTURE1];
Steve Blocka7e24c12009-10-30 11:49:00 +0000443 // Compute the parameter list consisting of the match, captures, index,
444 // and subject for the replace function invocation.
Steve Blocka7e24c12009-10-30 11:49:00 +0000445 // The number of captures plus one for the match.
446 var m = NUMBER_OF_CAPTURES(matchInfo) >> 1;
Kristian Monsen25f61362010-05-21 11:50:48 +0100447 var replacement;
Steve Blocka7e24c12009-10-30 11:49:00 +0000448 if (m == 1) {
Kristian Monsen25f61362010-05-21 11:50:48 +0100449 // No captures, only the match, which is always valid.
450 var s = SubString(subject, index, endOfMatch);
Steve Blocka7e24c12009-10-30 11:49:00 +0000451 // Don't call directly to avoid exposing the built-in global object.
Kristian Monsen25f61362010-05-21 11:50:48 +0100452 replacement =
453 %_CallFunction(%GetGlobalReceiver(), s, index, subject, replace);
454 } else {
455 var parameters = $Array(m + 2);
456 for (var j = 0; j < m; j++) {
457 parameters[j] = CaptureString(subject, matchInfo, j);
458 }
459 parameters[j] = index;
460 parameters[j + 1] = subject;
461
462 replacement = replace.apply(null, parameters);
Steve Blocka7e24c12009-10-30 11:49:00 +0000463 }
Kristian Monsen25f61362010-05-21 11:50:48 +0100464
465 result.add(replacement); // The add method converts to string if necessary.
466 // Can't use matchInfo any more from here, since the function could
467 // overwrite it.
468 result.addSpecialSlice(endOfMatch, subject.length);
469 return result.generate();
Steve Blocka7e24c12009-10-30 11:49:00 +0000470}
471
Kristian Monsen25f61362010-05-21 11:50:48 +0100472
Steve Blocka7e24c12009-10-30 11:49:00 +0000473// ECMA-262 section 15.5.4.12
474function StringSearch(re) {
Steve Block6ded16b2010-05-10 14:33:55 +0100475 var regexp;
476 if (IS_STRING(re)) {
477 regexp = %_GetFromCache(STRING_TO_REGEXP_CACHE_ID, re);
478 } else if (IS_REGEXP(re)) {
479 regexp = re;
480 } else {
481 regexp = new $RegExp(re);
482 }
Ben Murdochb0fe1622011-05-05 13:52:32 +0100483 var match = DoRegExpExec(regexp, TO_STRING_INLINE(this), 0);
Steve Block6ded16b2010-05-10 14:33:55 +0100484 if (match) {
485 return match[CAPTURE0];
486 }
487 return -1;
Steve Blocka7e24c12009-10-30 11:49:00 +0000488}
489
490
491// ECMA-262 section 15.5.4.13
492function StringSlice(start, end) {
Andrei Popescu402d9372010-02-26 13:31:12 +0000493 var s = TO_STRING_INLINE(this);
Steve Blocka7e24c12009-10-30 11:49:00 +0000494 var s_len = s.length;
495 var start_i = TO_INTEGER(start);
496 var end_i = s_len;
497 if (end !== void 0)
498 end_i = TO_INTEGER(end);
499
500 if (start_i < 0) {
501 start_i += s_len;
502 if (start_i < 0)
503 start_i = 0;
504 } else {
505 if (start_i > s_len)
506 start_i = s_len;
507 }
508
509 if (end_i < 0) {
510 end_i += s_len;
511 if (end_i < 0)
512 end_i = 0;
513 } else {
514 if (end_i > s_len)
515 end_i = s_len;
516 }
517
518 var num_c = end_i - start_i;
519 if (num_c < 0)
520 num_c = 0;
521
522 return SubString(s, start_i, start_i + num_c);
523}
524
525
526// ECMA-262 section 15.5.4.14
527function StringSplit(separator, limit) {
Andrei Popescu402d9372010-02-26 13:31:12 +0000528 var subject = TO_STRING_INLINE(this);
Leon Clarkee46be812010-01-19 14:06:41 +0000529 limit = (IS_UNDEFINED(limit)) ? 0xffffffff : TO_UINT32(limit);
Steve Blocka7e24c12009-10-30 11:49:00 +0000530 if (limit === 0) return [];
531
532 // ECMA-262 says that if separator is undefined, the result should
533 // be an array of size 1 containing the entire string. SpiderMonkey
Steve Block6ded16b2010-05-10 14:33:55 +0100534 // and KJS have this behavior only when no separator is given. If
Steve Blocka7e24c12009-10-30 11:49:00 +0000535 // undefined is explicitly given, they convert it to a string and
536 // use that. We do as SpiderMonkey and KJS.
537 if (%_ArgumentsLength() === 0) {
538 return [subject];
539 }
540
541 var length = subject.length;
Andrei Popescu402d9372010-02-26 13:31:12 +0000542 if (!IS_REGEXP(separator)) {
543 separator = TO_STRING_INLINE(separator);
544 var separator_length = separator.length;
545
Steve Blocka7e24c12009-10-30 11:49:00 +0000546 // If the separator string is empty then return the elements in the subject.
Shimeng (Simon) Wang8a31eba2010-12-06 19:01:33 -0800547 if (separator_length === 0) return %StringToArray(subject, limit);
Andrei Popescu402d9372010-02-26 13:31:12 +0000548
Steve Block6ded16b2010-05-10 14:33:55 +0100549 var result = %StringSplit(subject, separator, limit);
Andrei Popescu402d9372010-02-26 13:31:12 +0000550
551 return result;
Steve Blocka7e24c12009-10-30 11:49:00 +0000552 }
553
Andrei Popescu402d9372010-02-26 13:31:12 +0000554 %_Log('regexp', 'regexp-split,%0S,%1r', [subject, separator]);
555
Steve Blocka7e24c12009-10-30 11:49:00 +0000556 if (length === 0) {
Teng-Hui Zhu3e5fa292010-11-09 16:16:48 -0800557 if (DoRegExpExec(separator, subject, 0, 0) != null) {
Steve Block6ded16b2010-05-10 14:33:55 +0100558 return [];
559 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000560 return [subject];
561 }
562
563 var currentIndex = 0;
564 var startIndex = 0;
Ben Murdochb0fe1622011-05-05 13:52:32 +0100565 var startMatch = 0;
Steve Blocka7e24c12009-10-30 11:49:00 +0000566 var result = [];
567
Steve Block6ded16b2010-05-10 14:33:55 +0100568 outer_loop:
Steve Blocka7e24c12009-10-30 11:49:00 +0000569 while (true) {
570
571 if (startIndex === length) {
Ben Murdochb0fe1622011-05-05 13:52:32 +0100572 result.push(SubString(subject, currentIndex, length));
Steve Block6ded16b2010-05-10 14:33:55 +0100573 break;
Steve Blocka7e24c12009-10-30 11:49:00 +0000574 }
575
Ben Murdochb0fe1622011-05-05 13:52:32 +0100576 var matchInfo = DoRegExpExec(separator, subject, startIndex);
577 if (matchInfo == null || length === (startMatch = matchInfo[CAPTURE0])) {
578 result.push(SubString(subject, currentIndex, length));
Steve Block6ded16b2010-05-10 14:33:55 +0100579 break;
Steve Blocka7e24c12009-10-30 11:49:00 +0000580 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000581 var endIndex = matchInfo[CAPTURE1];
582
583 // We ignore a zero-length match at the currentIndex.
584 if (startIndex === endIndex && endIndex === currentIndex) {
585 startIndex++;
586 continue;
587 }
588
Ben Murdochb0fe1622011-05-05 13:52:32 +0100589 if (currentIndex + 1 == startMatch) {
590 result.push(%_StringCharAt(subject, currentIndex));
591 } else {
592 result.push(%_SubString(subject, currentIndex, startMatch));
593 }
594
Steve Block6ded16b2010-05-10 14:33:55 +0100595 if (result.length === limit) break;
Steve Blocka7e24c12009-10-30 11:49:00 +0000596
Ben Murdochb0fe1622011-05-05 13:52:32 +0100597 var matchinfo_len = NUMBER_OF_CAPTURES(matchInfo) + REGEXP_FIRST_CAPTURE;
598 for (var i = REGEXP_FIRST_CAPTURE + 2; i < matchinfo_len; ) {
599 var start = matchInfo[i++];
600 var end = matchInfo[i++];
601 if (end != -1) {
602 if (start + 1 == end) {
603 result.push(%_StringCharAt(subject, start));
604 } else {
605 result.push(%_SubString(subject, start, end));
606 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000607 } else {
Ben Murdochb0fe1622011-05-05 13:52:32 +0100608 result.push(void 0);
Steve Blocka7e24c12009-10-30 11:49:00 +0000609 }
Steve Block6ded16b2010-05-10 14:33:55 +0100610 if (result.length === limit) break outer_loop;
Steve Blocka7e24c12009-10-30 11:49:00 +0000611 }
612
613 startIndex = currentIndex = endIndex;
614 }
Steve Block6ded16b2010-05-10 14:33:55 +0100615 return result;
Steve Blocka7e24c12009-10-30 11:49:00 +0000616}
617
618
Steve Blocka7e24c12009-10-30 11:49:00 +0000619// ECMA-262 section 15.5.4.15
620function StringSubstring(start, end) {
Andrei Popescu402d9372010-02-26 13:31:12 +0000621 var s = TO_STRING_INLINE(this);
Steve Blocka7e24c12009-10-30 11:49:00 +0000622 var s_len = s.length;
Leon Clarkee46be812010-01-19 14:06:41 +0000623
Steve Blocka7e24c12009-10-30 11:49:00 +0000624 var start_i = TO_INTEGER(start);
Leon Clarkee46be812010-01-19 14:06:41 +0000625 if (start_i < 0) {
626 start_i = 0;
627 } else if (start_i > s_len) {
628 start_i = s_len;
629 }
630
Steve Blocka7e24c12009-10-30 11:49:00 +0000631 var end_i = s_len;
Leon Clarkee46be812010-01-19 14:06:41 +0000632 if (!IS_UNDEFINED(end)) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000633 end_i = TO_INTEGER(end);
Leon Clarkee46be812010-01-19 14:06:41 +0000634 if (end_i > s_len) {
635 end_i = s_len;
636 } else {
637 if (end_i < 0) end_i = 0;
638 if (start_i > end_i) {
639 var tmp = end_i;
640 end_i = start_i;
641 start_i = tmp;
642 }
643 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000644 }
645
Ben Murdochb0fe1622011-05-05 13:52:32 +0100646 return (start_i + 1 == end_i
647 ? %_StringCharAt(s, start_i)
648 : %_SubString(s, start_i, end_i));
Steve Blocka7e24c12009-10-30 11:49:00 +0000649}
650
651
652// This is not a part of ECMA-262.
653function StringSubstr(start, n) {
Andrei Popescu402d9372010-02-26 13:31:12 +0000654 var s = TO_STRING_INLINE(this);
Steve Blocka7e24c12009-10-30 11:49:00 +0000655 var len;
656
657 // Correct n: If not given, set to string length; if explicitly
658 // set to undefined, zero, or negative, returns empty string.
659 if (n === void 0) {
660 len = s.length;
661 } else {
662 len = TO_INTEGER(n);
663 if (len <= 0) return '';
664 }
665
666 // Correct start: If not given (or undefined), set to zero; otherwise
667 // convert to integer and handle negative case.
668 if (start === void 0) {
669 start = 0;
670 } else {
671 start = TO_INTEGER(start);
672 // If positive, and greater than or equal to the string length,
673 // return empty string.
674 if (start >= s.length) return '';
675 // If negative and absolute value is larger than the string length,
676 // use zero.
677 if (start < 0) {
678 start += s.length;
679 if (start < 0) start = 0;
680 }
681 }
682
683 var end = start + len;
684 if (end > s.length) end = s.length;
685
Ben Murdochb0fe1622011-05-05 13:52:32 +0100686 return (start + 1 == end
687 ? %_StringCharAt(s, start)
688 : %_SubString(s, start, end));
Steve Blocka7e24c12009-10-30 11:49:00 +0000689}
690
691
692// ECMA-262, 15.5.4.16
693function StringToLowerCase() {
Andrei Popescu402d9372010-02-26 13:31:12 +0000694 return %StringToLowerCase(TO_STRING_INLINE(this));
Steve Blocka7e24c12009-10-30 11:49:00 +0000695}
696
697
698// ECMA-262, 15.5.4.17
699function StringToLocaleLowerCase() {
Andrei Popescu402d9372010-02-26 13:31:12 +0000700 return %StringToLowerCase(TO_STRING_INLINE(this));
Steve Blocka7e24c12009-10-30 11:49:00 +0000701}
702
703
704// ECMA-262, 15.5.4.18
705function StringToUpperCase() {
Andrei Popescu402d9372010-02-26 13:31:12 +0000706 return %StringToUpperCase(TO_STRING_INLINE(this));
Steve Blocka7e24c12009-10-30 11:49:00 +0000707}
708
709
710// ECMA-262, 15.5.4.19
711function StringToLocaleUpperCase() {
Andrei Popescu402d9372010-02-26 13:31:12 +0000712 return %StringToUpperCase(TO_STRING_INLINE(this));
Steve Blocka7e24c12009-10-30 11:49:00 +0000713}
714
Steve Block3ce2e202009-11-05 08:53:23 +0000715// ES5, 15.5.4.20
716function StringTrim() {
Andrei Popescu402d9372010-02-26 13:31:12 +0000717 return %StringTrim(TO_STRING_INLINE(this), true, true);
Steve Block3ce2e202009-11-05 08:53:23 +0000718}
719
720function StringTrimLeft() {
Andrei Popescu402d9372010-02-26 13:31:12 +0000721 return %StringTrim(TO_STRING_INLINE(this), true, false);
Steve Block3ce2e202009-11-05 08:53:23 +0000722}
723
724function StringTrimRight() {
Andrei Popescu402d9372010-02-26 13:31:12 +0000725 return %StringTrim(TO_STRING_INLINE(this), false, true);
Steve Block3ce2e202009-11-05 08:53:23 +0000726}
Steve Blocka7e24c12009-10-30 11:49:00 +0000727
Steve Block6ded16b2010-05-10 14:33:55 +0100728var static_charcode_array = new $Array(4);
729
Steve Blocka7e24c12009-10-30 11:49:00 +0000730// ECMA-262, section 15.5.3.2
731function StringFromCharCode(code) {
732 var n = %_ArgumentsLength();
Steve Block6ded16b2010-05-10 14:33:55 +0100733 if (n == 1) {
734 if (!%_IsSmi(code)) code = ToNumber(code);
Ben Murdoch7f4d5bd2010-06-15 11:15:29 +0100735 return %_StringCharFromCode(code & 0xffff);
Steve Block6ded16b2010-05-10 14:33:55 +0100736 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000737
738 // NOTE: This is not super-efficient, but it is necessary because we
739 // want to avoid converting to numbers from within the virtual
740 // machine. Maybe we can find another way of doing this?
Steve Block6ded16b2010-05-10 14:33:55 +0100741 var codes = static_charcode_array;
742 for (var i = 0; i < n; i++) {
743 var code = %_Arguments(i);
744 if (!%_IsSmi(code)) code = ToNumber(code);
745 codes[i] = code;
746 }
747 codes.length = n;
Steve Blocka7e24c12009-10-30 11:49:00 +0000748 return %StringFromCharCodeArray(codes);
749}
750
751
752// Helper function for very basic XSS protection.
753function HtmlEscape(str) {
Andrei Popescu402d9372010-02-26 13:31:12 +0000754 return TO_STRING_INLINE(str).replace(/</g, "&lt;")
755 .replace(/>/g, "&gt;")
756 .replace(/"/g, "&quot;")
757 .replace(/'/g, "&#039;");
Steve Blocka7e24c12009-10-30 11:49:00 +0000758};
759
760
761// Compatibility support for KJS.
762// Tested by mozilla/js/tests/js1_5/Regress/regress-276103.js.
763function StringLink(s) {
764 return "<a href=\"" + HtmlEscape(s) + "\">" + this + "</a>";
765}
766
767
768function StringAnchor(name) {
769 return "<a name=\"" + HtmlEscape(name) + "\">" + this + "</a>";
770}
771
772
773function StringFontcolor(color) {
774 return "<font color=\"" + HtmlEscape(color) + "\">" + this + "</font>";
775}
776
777
778function StringFontsize(size) {
779 return "<font size=\"" + HtmlEscape(size) + "\">" + this + "</font>";
780}
781
782
783function StringBig() {
784 return "<big>" + this + "</big>";
785}
786
787
788function StringBlink() {
789 return "<blink>" + this + "</blink>";
790}
791
792
793function StringBold() {
794 return "<b>" + this + "</b>";
795}
796
797
798function StringFixed() {
799 return "<tt>" + this + "</tt>";
800}
801
802
803function StringItalics() {
804 return "<i>" + this + "</i>";
805}
806
807
808function StringSmall() {
809 return "<small>" + this + "</small>";
810}
811
812
813function StringStrike() {
814 return "<strike>" + this + "</strike>";
815}
816
817
818function StringSub() {
819 return "<sub>" + this + "</sub>";
820}
821
822
823function StringSup() {
824 return "<sup>" + this + "</sup>";
825}
826
827
Leon Clarkee46be812010-01-19 14:06:41 +0000828// ReplaceResultBuilder support.
Steve Blocka7e24c12009-10-30 11:49:00 +0000829function ReplaceResultBuilder(str) {
Steve Block6ded16b2010-05-10 14:33:55 +0100830 if (%_ArgumentsLength() > 1) {
831 this.elements = %_Arguments(1);
832 } else {
833 this.elements = new $Array();
834 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000835 this.special_string = str;
836}
837
838
Leon Clarkee46be812010-01-19 14:06:41 +0000839ReplaceResultBuilder.prototype.add = function(str) {
Andrei Popescu402d9372010-02-26 13:31:12 +0000840 str = TO_STRING_INLINE(str);
Ben Murdochb0fe1622011-05-05 13:52:32 +0100841 if (str.length > 0) this.elements.push(str);
Steve Blocka7e24c12009-10-30 11:49:00 +0000842}
843
844
845ReplaceResultBuilder.prototype.addSpecialSlice = function(start, end) {
846 var len = end - start;
Steve Block6ded16b2010-05-10 14:33:55 +0100847 if (start < 0 || len <= 0) return;
Steve Blockd0582a62009-12-15 09:54:21 +0000848 if (start < 0x80000 && len < 0x800) {
Ben Murdochb0fe1622011-05-05 13:52:32 +0100849 this.elements.push((start << 11) | len);
Steve Blocka7e24c12009-10-30 11:49:00 +0000850 } else {
Steve Blockd0582a62009-12-15 09:54:21 +0000851 // 0 < len <= String::kMaxLength and Smi::kMaxValue >= String::kMaxLength,
852 // so -len is a smi.
Ben Murdochb0fe1622011-05-05 13:52:32 +0100853 var elements = this.elements;
854 elements.push(-len);
855 elements.push(start);
Steve Blocka7e24c12009-10-30 11:49:00 +0000856 }
857}
858
859
Steve Blocka7e24c12009-10-30 11:49:00 +0000860ReplaceResultBuilder.prototype.generate = function() {
Leon Clarkee46be812010-01-19 14:06:41 +0000861 var elements = this.elements;
862 return %StringBuilderConcat(elements, elements.length, this.special_string);
Steve Blocka7e24c12009-10-30 11:49:00 +0000863}
864
865
Steve Blocka7e24c12009-10-30 11:49:00 +0000866// -------------------------------------------------------------------
867
868function SetupString() {
869 // Setup the constructor property on the String prototype object.
870 %SetProperty($String.prototype, "constructor", $String, DONT_ENUM);
871
872
873 // Setup the non-enumerable functions on the String object.
874 InstallFunctions($String, DONT_ENUM, $Array(
875 "fromCharCode", StringFromCharCode
876 ));
877
878
879 // Setup the non-enumerable functions on the String prototype object.
880 InstallFunctionsOnHiddenPrototype($String.prototype, DONT_ENUM, $Array(
881 "valueOf", StringValueOf,
882 "toString", StringToString,
883 "charAt", StringCharAt,
884 "charCodeAt", StringCharCodeAt,
885 "concat", StringConcat,
886 "indexOf", StringIndexOf,
887 "lastIndexOf", StringLastIndexOf,
888 "localeCompare", StringLocaleCompare,
889 "match", StringMatch,
890 "replace", StringReplace,
891 "search", StringSearch,
892 "slice", StringSlice,
893 "split", StringSplit,
894 "substring", StringSubstring,
895 "substr", StringSubstr,
896 "toLowerCase", StringToLowerCase,
897 "toLocaleLowerCase", StringToLocaleLowerCase,
898 "toUpperCase", StringToUpperCase,
899 "toLocaleUpperCase", StringToLocaleUpperCase,
Steve Block3ce2e202009-11-05 08:53:23 +0000900 "trim", StringTrim,
901 "trimLeft", StringTrimLeft,
902 "trimRight", StringTrimRight,
Steve Blocka7e24c12009-10-30 11:49:00 +0000903 "link", StringLink,
904 "anchor", StringAnchor,
905 "fontcolor", StringFontcolor,
906 "fontsize", StringFontsize,
907 "big", StringBig,
908 "blink", StringBlink,
909 "bold", StringBold,
910 "fixed", StringFixed,
911 "italics", StringItalics,
912 "small", StringSmall,
913 "strike", StringStrike,
914 "sub", StringSub,
Ben Murdochb0fe1622011-05-05 13:52:32 +0100915 "sup", StringSup
Steve Blocka7e24c12009-10-30 11:49:00 +0000916 ));
917}
918
919
920SetupString();