blob: c7a838e9ffba2532bce7e9f9f85ccedfb36241ec [file] [log] [blame]
ager@chromium.org9258b6b2008-09-11 09:11:10 +00001// Copyright 2006-2008 the V8 project authors. All rights reserved.
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +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;
mads.s.ager@gmail.com9a4089a2008-09-01 08:55:01 +000032// const $NaN = 0/0;
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000033
34
35// Set the String function and constructor.
36%SetCode($String, function(x) {
37 var value = %_ArgumentsLength() == 0 ? '' : ToString(x);
mads.s.ager31e71382008-08-13 09:32:07 +000038 if (%IsConstructCall()) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000039 %_SetValueOf(this, value);
40 } else {
41 return value;
42 }
43});
44
45%FunctionSetPrototype($String, new $String());
46
kasperl@chromium.org41044eb2008-10-06 08:24:46 +000047// ECMA-262 section 15.5.4.2
48function StringToString() {
kasperl@chromium.org5a8ca6c2008-10-23 13:57:19 +000049 if (!IS_STRING(this) && !%HasStringClass(this))
kasperl@chromium.org41044eb2008-10-06 08:24:46 +000050 throw new $TypeError('String.prototype.toString is not generic');
51 return %_ValueOf(this);
52}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000053
kasperl@chromium.org41044eb2008-10-06 08:24:46 +000054
55// ECMA-262 section 15.5.4.3
56function StringValueOf() {
kasperl@chromium.org5a8ca6c2008-10-23 13:57:19 +000057 if (!IS_STRING(this) && !%HasStringClass(this))
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000058 throw new $TypeError('String.prototype.valueOf is not generic');
59 return %_ValueOf(this);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +000060}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000061
62
kasperl@chromium.org41044eb2008-10-06 08:24:46 +000063// ECMA-262, section 15.5.4.4
64function StringCharAt(pos) {
kasperl@chromium.org7be3c992009-03-12 07:19:55 +000065 var char_code = %_FastCharCodeAt(this, index);
ager@chromium.org381abbb2009-02-25 13:23:22 +000066 if (!%_IsSmi(char_code)) {
67 var subject = ToString(this);
68 var index = TO_INTEGER(pos);
69 if (index >= subject.length || index < 0) return "";
70 char_code = %StringCharCodeAt(subject, index);
71 }
72 return %CharFromCode(char_code);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +000073}
74
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000075
76// ECMA-262 section 15.5.4.5
kasperl@chromium.org41044eb2008-10-06 08:24:46 +000077function StringCharCodeAt(pos) {
kasper.lund7276f142008-07-30 08:49:36 +000078 var fast_answer = %_FastCharCodeAt(this, pos);
79 if (%_IsSmi(fast_answer)) {
80 return fast_answer;
81 }
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000082 var subject = ToString(this);
83 var index = TO_INTEGER(pos);
84 return %StringCharCodeAt(subject, index);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +000085}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000086
87
88// ECMA-262, section 15.5.4.6
kasperl@chromium.org41044eb2008-10-06 08:24:46 +000089function StringConcat() {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000090 var len = %_ArgumentsLength();
91 var parts = new $Array(len + 1);
92 parts[0] = ToString(this);
93 for (var i = 0; i < len; i++)
94 parts[i + 1] = ToString(%_Arguments(i));
95 return parts.join('');
kasperl@chromium.org41044eb2008-10-06 08:24:46 +000096}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000097
98// Match ES3 and Safari
kasperl@chromium.org41044eb2008-10-06 08:24:46 +000099%FunctionSetLength(StringConcat, 1);
100
101
102// ECMA-262 section 15.5.4.7
103function StringIndexOf(searchString /* position */) { // length == 1
104 var subject_str = ToString(this);
105 var pattern_str = ToString(searchString);
106 var subject_str_len = subject_str.length;
107 var pattern_str_len = pattern_str.length;
108 var index = 0;
109 if (%_ArgumentsLength() > 1) {
110 var arg1 = %_Arguments(1); // position
111 index = TO_INTEGER(arg1);
112 }
113 if (index < 0) index = 0;
114 if (index > subject_str_len) index = subject_str_len;
115 if (pattern_str_len + index > subject_str_len) return -1;
116 return %StringIndexOf(subject_str, pattern_str, index);
117}
118
119
120// ECMA-262 section 15.5.4.8
121function StringLastIndexOf(searchString /* position */) { // length == 1
122 var sub = ToString(this);
123 var pat = ToString(searchString);
124 var index = (%_ArgumentsLength() > 1)
125 ? ToNumber(%_Arguments(1) /* position */)
126 : $NaN;
127 var firstIndex;
128 if ($isNaN(index)) {
129 firstIndex = sub.length - pat.length;
130 } else {
131 firstIndex = TO_INTEGER(index);
132 if (firstIndex + pat.length > sub.length) {
133 firstIndex = sub.length - pat.length;
134 }
135 }
136 return %StringLastIndexOf(sub, pat, firstIndex);
137}
138
139
140// ECMA-262 section 15.5.4.9
141//
142// This function is implementation specific. For now, we do not
143// do anything locale specific.
144function StringLocaleCompare(other) {
145 if (%_ArgumentsLength() === 0) return 0;
146
147 var this_str = ToString(this);
148 var other_str = ToString(other);
149 return %StringLocaleCompare(this_str, other_str);
150}
151
152
153// ECMA-262 section 15.5.4.10
154function StringMatch(regexp) {
155 if (!IS_REGEXP(regexp)) regexp = new ORIGINAL_REGEXP(regexp);
156 var subject = ToString(this);
157
158 if (!regexp.global) return regexp.exec(subject);
christian.plesner.hansen@gmail.com37abdec2009-01-06 14:43:28 +0000159 %_Log('regexp', 'regexp-match,%0S,%1r', [subject, regexp]);
ager@chromium.org41826e72009-03-30 13:30:57 +0000160 // lastMatchInfo is defined in regexp-delay.js.
161 return %StringMatch(subject, regexp, lastMatchInfo);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000162}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000163
164
165// SubString is an internal function that returns the sub string of 'string'.
166// If resulting string is of length 1, we use the one character cache
167// otherwise we call the runtime system.
168function SubString(string, start, end) {
169 // Use the one character string cache.
ager@chromium.org381abbb2009-02-25 13:23:22 +0000170 if (start + 1 == end) {
171 var char_code = %_FastCharCodeAt(string, start);
172 if (!%_IsSmi(char_code)) {
173 char_code = %StringCharCodeAt(string, start);
174 }
175 return %CharFromCode(char_code);
176 }
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000177 return %StringSlice(string, start, end);
178}
179
180
181// ECMA-262, section 15.5.4.11
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000182function StringReplace(search, replace) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000183 var subject = ToString(this);
184
185 // Delegate to one of the regular expression variants if necessary.
186 if (IS_REGEXP(search)) {
christian.plesner.hansen@gmail.com37abdec2009-01-06 14:43:28 +0000187 %_Log('regexp', 'regexp-replace,%0r,%1S', [search, subject]);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000188 if (IS_FUNCTION(replace)) {
189 return StringReplaceRegExpWithFunction(subject, search, replace);
190 } else {
191 return StringReplaceRegExp(subject, search, replace);
192 }
193 }
194
195 // Convert the search argument to a string and search for it.
196 search = ToString(search);
197 var start = %StringIndexOf(subject, search, 0);
198 if (start < 0) return subject;
199 var end = start + search.length;
200
ager@chromium.org8bb60582008-12-11 12:02:20 +0000201 var builder = new ReplaceResultBuilder(subject);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000202 // prefix
ager@chromium.org8bb60582008-12-11 12:02:20 +0000203 builder.addSpecialSlice(0, start);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000204
205 // Compute the string to replace with.
206 if (IS_FUNCTION(replace)) {
207 builder.add(replace.call(null, search, start, subject));
208 } else {
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000209 reusableMatchInfo[CAPTURE0] = start;
210 reusableMatchInfo[CAPTURE1] = end;
211 ExpandReplacement(ToString(replace), subject, reusableMatchInfo, builder);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000212 }
213
214 // suffix
ager@chromium.org8bb60582008-12-11 12:02:20 +0000215 builder.addSpecialSlice(end, subject.length);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000216
217 return builder.generate();
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000218}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000219
220
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000221// This has the same size as the lastMatchInfo array, and can be used for
222// functions that expect that structure to be returned. It is used when the
223// needle is a string rather than a regexp. In this case we can't update
224// lastMatchArray without erroneously affecting the properties on the global
225// RegExp object.
ager@chromium.orgbb29dc92009-03-24 13:25:23 +0000226var reusableMatchInfo = [2, "", "", -1, -1];
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000227
228
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000229// Helper function for regular expressions in String.prototype.replace.
230function StringReplaceRegExp(subject, regexp, replace) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000231 replace = ToString(replace);
ager@chromium.orgbb29dc92009-03-24 13:25:23 +0000232 return %StringReplaceRegExpWithString(subject,
233 regexp,
234 replace,
235 lastMatchInfo);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000236};
237
238
239// Expand the $-expressions in the string and return a new string with
240// the result.
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000241function ExpandReplacement(string, subject, matchInfo, builder) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000242 var next = %StringIndexOf(string, '$', 0);
243 if (next < 0) {
244 builder.add(string);
245 return;
246 }
247
248 // Compute the number of captures; see ECMA-262, 15.5.4.11, p. 102.
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000249 var m = NUMBER_OF_CAPTURES(matchInfo) >> 1; // Includes the match.
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000250
251 if (next > 0) builder.add(SubString(string, 0, next));
252 var length = string.length;
253
254 while (true) {
255 var expansion = '$';
256 var position = next + 1;
257 if (position < length) {
ager@chromium.org381abbb2009-02-25 13:23:22 +0000258 var peek = %_FastCharCodeAt(string, position);
259 if (!%_IsSmi(peek)) {
260 peek = %StringCharCodeAt(string, position);
261 }
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000262 if (peek == 36) { // $$
263 ++position;
ager@chromium.org8bb60582008-12-11 12:02:20 +0000264 builder.add('$');
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000265 } else if (peek == 38) { // $& - match
266 ++position;
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000267 builder.addSpecialSlice(matchInfo[CAPTURE0],
268 matchInfo[CAPTURE1]);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000269 } else if (peek == 96) { // $` - prefix
270 ++position;
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000271 builder.addSpecialSlice(0, matchInfo[CAPTURE0]);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000272 } else if (peek == 39) { // $' - suffix
273 ++position;
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000274 builder.addSpecialSlice(matchInfo[CAPTURE1], subject.length);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000275 } else if (peek >= 48 && peek <= 57) { // $n, 0 <= n <= 9
276 ++position;
277 var n = peek - 48;
278 if (position < length) {
ager@chromium.org381abbb2009-02-25 13:23:22 +0000279 peek = %_FastCharCodeAt(string, position);
280 if (!%_IsSmi(peek)) {
281 peek = %StringCharCodeAt(string, position);
282 }
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000283 // $nn, 01 <= nn <= 99
284 if (n != 0 && peek == 48 || peek >= 49 && peek <= 57) {
285 var nn = n * 10 + (peek - 48);
286 if (nn < m) {
287 // If the two digit capture reference is within range of
288 // the captures, we use it instead of the single digit
289 // one. Otherwise, we fall back to using the single
290 // digit reference. This matches the behavior of
291 // SpiderMonkey.
292 ++position;
293 n = nn;
294 }
295 }
296 }
297 if (0 < n && n < m) {
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000298 addCaptureString(builder, matchInfo, n);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000299 } else {
300 // Because of the captures range check in the parsing of two
301 // digit capture references, we can only enter here when a
302 // single digit capture reference is outside the range of
303 // captures.
ager@chromium.org8bb60582008-12-11 12:02:20 +0000304 builder.add('$');
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000305 --position;
306 }
ager@chromium.org8c51fc92009-04-22 11:54:55 +0000307 } else {
308 builder.add('$');
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000309 }
ager@chromium.org8bb60582008-12-11 12:02:20 +0000310 } else {
311 builder.add('$');
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000312 }
313
ager@chromium.org8bb60582008-12-11 12:02:20 +0000314 // Go the the next $ in the string.
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000315 next = %StringIndexOf(string, '$', position);
316
317 // Return if there are no more $ characters in the string. If we
318 // haven't reached the end, we need to append the suffix.
319 if (next < 0) {
320 if (position < length) {
321 builder.add(SubString(string, position, length));
322 }
323 return;
324 }
325
326 // Append substring between the previous and the next $ character.
327 builder.add(SubString(string, position, next));
328 }
329};
330
331
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000332// Compute the string of a given regular expression capture.
333function CaptureString(string, lastCaptureInfo, index) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000334 // Scale the index.
335 var scaled = index << 1;
336 // Compute start and end.
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000337 var start = lastCaptureInfo[CAPTURE(scaled)];
338 var end = lastCaptureInfo[CAPTURE(scaled + 1)];
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000339 // If either start or end is missing return undefined.
340 if (start < 0 || end < 0) return;
341 return SubString(string, start, end);
342};
343
344
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000345// Add the string of a given regular expression capture to the
346// ReplaceResultBuilder
347function addCaptureString(builder, matchInfo, index) {
ager@chromium.org8bb60582008-12-11 12:02:20 +0000348 // Scale the index.
349 var scaled = index << 1;
350 // Compute start and end.
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000351 var start = matchInfo[CAPTURE(scaled)];
352 var end = matchInfo[CAPTURE(scaled + 1)];
ager@chromium.org8bb60582008-12-11 12:02:20 +0000353 // If either start or end is missing return.
ager@chromium.org381abbb2009-02-25 13:23:22 +0000354 if (start < 0 || end <= start) return;
ager@chromium.org8bb60582008-12-11 12:02:20 +0000355 builder.addSpecialSlice(start, end);
356};
357
358
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000359// Helper function for replacing regular expressions with the result of a
360// function application in String.prototype.replace. The function application
361// must be interleaved with the regexp matching (contrary to ECMA-262
362// 15.5.4.11) to mimic SpiderMonkey and KJS behavior when the function uses
363// the static properties of the RegExp constructor. Example:
364// 'abcd'.replace(/(.)/g, function() { return RegExp.$1; }
365// should be 'abcd' and not 'dddd' (or anything else).
366function StringReplaceRegExpWithFunction(subject, regexp, replace) {
367 var result = new ReplaceResultBuilder(subject);
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000368 var lastMatchInfo = DoRegExpExec(regexp, subject, 0);
369 if (IS_NULL(lastMatchInfo)) return subject;
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000370
371 // There's at least one match. If the regexp is global, we have to loop
372 // over all matches. The loop is not in C++ code here like the one in
373 // RegExp.prototype.exec, because of the interleaved function application.
374 // Unfortunately, that means this code is nearly duplicated, here and in
375 // jsregexp.cc.
376 if (regexp.global) {
377 var previous = 0;
378 do {
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000379 result.addSpecialSlice(previous, lastMatchInfo[CAPTURE0]);
380 var startOfMatch = lastMatchInfo[CAPTURE0];
381 previous = lastMatchInfo[CAPTURE1];
382 result.add(ApplyReplacementFunction(replace, lastMatchInfo, subject));
383 // Can't use lastMatchInfo any more from here, since the function could
384 // overwrite it.
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000385 // Continue with the next match.
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000386 // Increment previous if we matched an empty string, as per ECMA-262
387 // 15.5.4.10.
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000388 if (previous == startOfMatch) {
iposva@chromium.org245aa852009-02-10 00:49:54 +0000389 // Add the skipped character to the output, if any.
390 if (previous < subject.length) {
391 result.addSpecialSlice(previous, previous + 1);
392 }
393 previous++;
394 }
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000395
396 // Per ECMA-262 15.10.6.2, if the previous index is greater than the
397 // string length, there is no match
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000398 lastMatchInfo = (previous > subject.length)
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000399 ? null
400 : DoRegExpExec(regexp, subject, previous);
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000401 } while (!IS_NULL(lastMatchInfo));
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000402
403 // Tack on the final right substring after the last match, if necessary.
404 if (previous < subject.length) {
405 result.addSpecialSlice(previous, subject.length);
406 }
407 } else { // Not a global regexp, no need to loop.
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000408 result.addSpecialSlice(0, lastMatchInfo[CAPTURE0]);
409 var endOfMatch = lastMatchInfo[CAPTURE1];
410 result.add(ApplyReplacementFunction(replace, lastMatchInfo, subject));
411 // Can't use lastMatchInfo any more from here, since the function could
412 // overwrite it.
413 result.addSpecialSlice(endOfMatch, subject.length);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000414 }
415
416 return result.generate();
417}
418
419
420// Helper function to apply a string replacement function once.
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000421function ApplyReplacementFunction(replace, lastMatchInfo, subject) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000422 // Compute the parameter list consisting of the match, captures, index,
423 // and subject for the replace function invocation.
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000424 var index = lastMatchInfo[CAPTURE0];
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000425 // The number of captures plus one for the match.
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000426 var m = NUMBER_OF_CAPTURES(lastMatchInfo) >> 1;
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000427 if (m == 1) {
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000428 var s = CaptureString(subject, lastMatchInfo, 0);
ager@chromium.org381abbb2009-02-25 13:23:22 +0000429 // Don't call directly to avoid exposing the built-in global object.
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000430 return ToString(replace.call(null, s, index, subject));
431 }
432 var parameters = $Array(m + 2);
433 for (var j = 0; j < m; j++) {
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000434 parameters[j] = CaptureString(subject, lastMatchInfo, j);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000435 }
436 parameters[j] = index;
437 parameters[j + 1] = subject;
438 return ToString(replace.apply(null, parameters));
439}
440
441
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000442// ECMA-262 section 15.5.4.12
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000443function StringSearch(re) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000444 var regexp = new ORIGINAL_REGEXP(re);
445 var s = ToString(this);
446 var last_idx = regexp.lastIndex; // keep old lastIndex
447 regexp.lastIndex = 0; // ignore re.global property
448 var result = regexp.exec(s);
449 regexp.lastIndex = last_idx; // restore lastIndex
450 if (result == null)
451 return -1;
452 else
453 return result.index;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000454}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000455
456
457// ECMA-262 section 15.5.4.13
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000458function StringSlice(start, end) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000459 var s = ToString(this);
460 var s_len = s.length;
461 var start_i = TO_INTEGER(start);
462 var end_i = s_len;
463 if (end !== void 0)
464 end_i = TO_INTEGER(end);
465
466 if (start_i < 0) {
467 start_i += s_len;
468 if (start_i < 0)
469 start_i = 0;
470 } else {
471 if (start_i > s_len)
472 start_i = s_len;
473 }
474
475 if (end_i < 0) {
476 end_i += s_len;
477 if (end_i < 0)
478 end_i = 0;
479 } else {
480 if (end_i > s_len)
481 end_i = s_len;
482 }
483
484 var num_c = end_i - start_i;
485 if (num_c < 0)
486 num_c = 0;
487
488 return SubString(s, start_i, start_i + num_c);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000489}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000490
491
492// ECMA-262 section 15.5.4.14
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000493function StringSplit(separator, limit) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000494 var subject = ToString(this);
495 var result = [];
496 var lim = (limit === void 0) ? 0xffffffff : ToUint32(limit);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000497
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000498 if (lim === 0) return result;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000499
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000500 // ECMA-262 says that if separator is undefined, the result should
501 // be an array of size 1 containing the entire string. SpiderMonkey
502 // and KJS have this behaviour only when no separator is given. If
503 // undefined is explicitly given, they convert it to a string and
504 // use that. We do as SpiderMonkey and KJS.
505 if (%_ArgumentsLength() === 0) {
506 result[result.length] = subject;
507 return result;
508 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000509
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000510 var length = subject.length;
511 var currentIndex = 0;
512 var startIndex = 0;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000513
christian.plesner.hansen@gmail.com37abdec2009-01-06 14:43:28 +0000514 var sep;
515 if (IS_REGEXP(separator)) {
516 sep = separator;
517 %_Log('regexp', 'regexp-split,%0S,%1r', [subject, sep]);
518 } else {
519 sep = ToString(separator);
520 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000521
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000522 if (length === 0) {
523 if (splitMatch(sep, subject, 0, 0) != null) return result;
524 result[result.length] = subject;
525 return result;
526 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000527
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000528 while (true) {
529
530 if (startIndex === length) {
531 result[result.length] = subject.slice(currentIndex, length);
532 return result;
533 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000534
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000535 var lastMatchInfo = splitMatch(sep, subject, currentIndex, startIndex);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000536
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000537 if (IS_NULL(lastMatchInfo)) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000538 result[result.length] = subject.slice(currentIndex, length);
539 return result;
540 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000541
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000542 var endIndex = lastMatchInfo[CAPTURE1];
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000543
544 // We ignore a zero-length match at the currentIndex.
545 if (startIndex === endIndex && endIndex === currentIndex) {
546 startIndex++;
547 continue;
548 }
549
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000550 result[result.length] =
551 SubString(subject, currentIndex, lastMatchInfo[CAPTURE0]);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000552 if (result.length === lim) return result;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000553
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000554 for (var i = 2; i < NUMBER_OF_CAPTURES(lastMatchInfo); i += 2) {
555 var start = lastMatchInfo[CAPTURE(i)];
556 var end = lastMatchInfo[CAPTURE(i + 1)];
557 if (start != -1 && end != -1) {
558 result[result.length] = SubString(subject,
559 lastMatchInfo[CAPTURE(i)],
560 lastMatchInfo[CAPTURE(i + 1)]);
561 } else {
562 result[result.length] = void 0;
563 }
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000564 if (result.length === lim) return result;
565 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000566
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000567 startIndex = currentIndex = endIndex;
568 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000569}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000570
571
572// ECMA-262 section 15.5.4.14
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000573// Helper function used by split. This version returns the lastMatchInfo
574// instead of allocating a new array with basically the same information.
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000575function splitMatch(separator, subject, current_index, start_index) {
576 if (IS_REGEXP(separator)) {
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000577 var lastMatchInfo = DoRegExpExec(separator, subject, start_index);
578 if (lastMatchInfo == null) return null;
ager@chromium.orgddb913d2009-01-27 10:01:48 +0000579 // Section 15.5.4.14 paragraph two says that we do not allow zero length
580 // matches at the end of the string.
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000581 if (lastMatchInfo[CAPTURE0] === subject.length) return null;
582 return lastMatchInfo;
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000583 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000584
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000585 var separatorIndex = subject.indexOf(separator, start_index);
586 if (separatorIndex === -1) return null;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000587
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000588 reusableMatchInfo[CAPTURE0] = separatorIndex;
589 reusableMatchInfo[CAPTURE1] = separatorIndex + separator.length;
590 return reusableMatchInfo;
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000591};
592
593
594// ECMA-262 section 15.5.4.15
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000595function StringSubstring(start, end) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000596 var s = ToString(this);
597 var s_len = s.length;
598 var start_i = TO_INTEGER(start);
599 var end_i = s_len;
600 if (!IS_UNDEFINED(end))
601 end_i = TO_INTEGER(end);
602
603 if (start_i < 0) start_i = 0;
604 if (start_i > s_len) start_i = s_len;
605 if (end_i < 0) end_i = 0;
606 if (end_i > s_len) end_i = s_len;
607
608 if (start_i > end_i) {
609 var tmp = end_i;
610 end_i = start_i;
611 start_i = tmp;
612 }
613
614 return SubString(s, start_i, end_i);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000615}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000616
617
618// This is not a part of ECMA-262.
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000619function StringSubstr(start, n) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000620 var s = ToString(this);
621 var len;
622
623 // Correct n: If not given, set to string length; if explicitly
624 // set to undefined, zero, or negative, returns empty string.
625 if (n === void 0) {
626 len = s.length;
627 } else {
628 len = TO_INTEGER(n);
629 if (len <= 0) return '';
630 }
631
632 // Correct start: If not given (or undefined), set to zero; otherwise
633 // convert to integer and handle negative case.
634 if (start === void 0) {
635 start = 0;
636 } else {
637 start = TO_INTEGER(start);
638 // If positive, and greater than or equal to the string length,
639 // return empty string.
640 if (start >= s.length) return '';
641 // If negative and absolute value is larger than the string length,
642 // use zero.
643 if (start < 0) {
644 start += s.length;
645 if (start < 0) start = 0;
646 }
647 }
648
649 var end = start + len;
650 if (end > s.length) end = s.length;
651
652 return SubString(s, start, end);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000653}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000654
655
656// ECMA-262, 15.5.4.16
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000657function StringToLowerCase() {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000658 return %StringToLowerCase(ToString(this));
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000659}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000660
661
662// ECMA-262, 15.5.4.17
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000663function StringToLocaleLowerCase() {
664 return %StringToLowerCase(ToString(this));
665}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000666
667
668// ECMA-262, 15.5.4.18
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000669function StringToUpperCase() {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000670 return %StringToUpperCase(ToString(this));
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000671}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000672
673
674// ECMA-262, 15.5.4.19
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000675function StringToLocaleUpperCase() {
676 return %StringToUpperCase(ToString(this));
677}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000678
679
680// ECMA-262, section 15.5.3.2
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000681function StringFromCharCode(code) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000682 var n = %_ArgumentsLength();
683 if (n == 1) return %CharFromCode(ToNumber(code) & 0xffff)
684
685 // NOTE: This is not super-efficient, but it is necessary because we
686 // want to avoid converting to numbers from within the virtual
687 // machine. Maybe we can find another way of doing this?
688 var codes = new $Array(n);
689 for (var i = 0; i < n; i++) codes[i] = ToNumber(%_Arguments(i));
690 return %StringFromCharCodeArray(codes);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000691}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000692
693
694// Helper function for very basic XSS protection.
695function HtmlEscape(str) {
696 return ToString(str).replace(/</g, "&lt;")
697 .replace(/>/g, "&gt;")
698 .replace(/"/g, "&quot;")
699 .replace(/'/g, "&#039;");
700};
701
702
703// Compatibility support for KJS.
704// Tested by mozilla/js/tests/js1_5/Regress/regress-276103.js.
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000705function StringLink(s) {
706 return "<a href=\"" + HtmlEscape(s) + "\">" + this + "</a>";
707}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000708
709
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000710function StringAnchor(name) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000711 return "<a name=\"" + HtmlEscape(name) + "\">" + this + "</a>";
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000712}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000713
714
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000715function StringFontcolor(color) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000716 return "<font color=\"" + HtmlEscape(color) + "\">" + this + "</font>";
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000717}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000718
719
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000720function StringFontsize(size) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000721 return "<font size=\"" + HtmlEscape(size) + "\">" + this + "</font>";
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000722}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000723
724
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000725function StringBig() {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000726 return "<big>" + this + "</big>";
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000727}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000728
729
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000730function StringBlink() {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000731 return "<blink>" + this + "</blink>";
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000732}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000733
734
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000735function StringBold() {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000736 return "<b>" + this + "</b>";
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000737}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000738
739
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000740function StringFixed() {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000741 return "<tt>" + this + "</tt>";
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000742}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000743
744
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000745function StringItalics() {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000746 return "<i>" + this + "</i>";
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000747}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000748
749
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000750function StringSmall() {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000751 return "<small>" + this + "</small>";
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000752}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000753
754
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000755function StringStrike() {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000756 return "<strike>" + this + "</strike>";
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000757}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000758
759
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000760function StringSub() {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000761 return "<sub>" + this + "</sub>";
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000762}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000763
764
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000765function StringSup() {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000766 return "<sup>" + this + "</sup>";
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000767}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000768
769
770// StringBuilder support.
771
772function StringBuilder() {
773 this.elements = new $Array();
774}
775
776
777function ReplaceResultBuilder(str) {
778 this.elements = new $Array();
779 this.special_string = str;
780}
781
782
783ReplaceResultBuilder.prototype.add =
784StringBuilder.prototype.add = function(str) {
785 if (!IS_STRING(str)) str = ToString(str);
786 if (str.length > 0) {
787 var elements = this.elements;
788 elements[elements.length] = str;
789 }
790}
791
792
793ReplaceResultBuilder.prototype.addSpecialSlice = function(start, end) {
794 var len = end - start;
795 if (len == 0) return;
796 var elements = this.elements;
797 if (start >= 0 && len >= 0 && start < 0x80000 && len < 0x800) {
798 elements[elements.length] = (start << 11) + len;
799 } else {
800 elements[elements.length] = SubString(this.special_string, start, end);
801 }
802}
803
804
805StringBuilder.prototype.generate = function() {
806 return %StringBuilderConcat(this.elements, "");
807}
808
809
810ReplaceResultBuilder.prototype.generate = function() {
811 return %StringBuilderConcat(this.elements, this.special_string);
812}
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000813
814
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000815function StringToJSON(key) {
816 return CheckJSONPrimitive(this.valueOf());
817}
818
819
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000820// -------------------------------------------------------------------
821
822function SetupString() {
823 // Setup the constructor property on the String prototype object.
824 %SetProperty($String.prototype, "constructor", $String, DONT_ENUM);
825
826
827 // Setup the non-enumerable functions on the String object.
828 InstallFunctions($String, DONT_ENUM, $Array(
829 "fromCharCode", StringFromCharCode
830 ));
831
832
833 // Setup the non-enumerable functions on the String prototype object.
834 InstallFunctions($String.prototype, DONT_ENUM, $Array(
835 "valueOf", StringValueOf,
836 "toString", StringToString,
837 "charAt", StringCharAt,
838 "charCodeAt", StringCharCodeAt,
839 "concat", StringConcat,
840 "indexOf", StringIndexOf,
841 "lastIndexOf", StringLastIndexOf,
842 "localeCompare", StringLocaleCompare,
843 "match", StringMatch,
844 "replace", StringReplace,
845 "search", StringSearch,
846 "slice", StringSlice,
847 "split", StringSplit,
848 "substring", StringSubstring,
849 "substr", StringSubstr,
850 "toLowerCase", StringToLowerCase,
851 "toLocaleLowerCase", StringToLocaleLowerCase,
852 "toUpperCase", StringToUpperCase,
853 "toLocaleUpperCase", StringToLocaleUpperCase,
854 "link", StringLink,
855 "anchor", StringAnchor,
856 "fontcolor", StringFontcolor,
857 "fontsize", StringFontsize,
858 "big", StringBig,
859 "blink", StringBlink,
860 "bold", StringBold,
861 "fixed", StringFixed,
862 "italics", StringItalics,
863 "small", StringSmall,
864 "strike", StringStrike,
865 "sub", StringSub,
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000866 "sup", StringSup,
867 "toJSON", StringToJSON
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000868 ));
869}
870
871
872SetupString();