blob: 51f4b094d376638c33ed7274ebe1ed8da6d67ed7 [file] [log] [blame]
kasperl@chromium.org7be3c992009-03-12 07:19:55 +00001// Copyright 2006-2009 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// Expect $Object = global.Object;
29// Expect $Array = global.Array;
30
31const $RegExp = global.RegExp;
32
33// A recursive descent parser for Patterns according to the grammar of
34// ECMA-262 15.10.1, with deviations noted below.
35function DoConstructRegExp(object, pattern, flags, isConstructorCall) {
36 // RegExp : Called as constructor; see ECMA-262, section 15.10.4.
37 if (IS_REGEXP(pattern)) {
38 if (!IS_UNDEFINED(flags)) {
39 throw MakeTypeError('regexp_flags', []);
40 }
41 flags = (pattern.global ? 'g' : '')
42 + (pattern.ignoreCase ? 'i' : '')
43 + (pattern.multiline ? 'm' : '');
44 pattern = pattern.source;
45 }
46
47 pattern = IS_UNDEFINED(pattern) ? '' : ToString(pattern);
48 flags = IS_UNDEFINED(flags) ? '' : ToString(flags);
49
50 var global = false;
51 var ignoreCase = false;
52 var multiline = false;
53
54 for (var i = 0; i < flags.length; i++) {
kasperl@chromium.org7be3c992009-03-12 07:19:55 +000055 var c = StringCharAt.call(flags, i);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000056 switch (c) {
57 case 'g':
iposva@chromium.org245aa852009-02-10 00:49:54 +000058 // Allow duplicate flags to be consistent with JSC and others.
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000059 global = true;
60 break;
61 case 'i':
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000062 ignoreCase = true;
63 break;
64 case 'm':
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000065 multiline = true;
66 break;
67 default:
68 // Ignore flags that have no meaning to be consistent with
iposva@chromium.org245aa852009-02-10 00:49:54 +000069 // JSC.
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000070 break;
71 }
72 }
73
lrn@chromium.org25156de2010-04-06 13:10:27 +000074 if (!isConstructorCall) {
fschneider@chromium.org086aac62010-03-17 13:18:24 +000075 regExpCache.type = 'none';
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000076 }
lrn@chromium.org25156de2010-04-06 13:10:27 +000077 %RegExpInitializeObject(object, pattern, global, ignoreCase, multiline);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000078
79 // Call internal function to compile the pattern.
80 %RegExpCompile(object, pattern, flags);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +000081}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000082
83
84function RegExpConstructor(pattern, flags) {
kasperl@chromium.org2abc4502009-07-02 07:00:29 +000085 if (%_IsConstructCall()) {
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000086 DoConstructRegExp(this, pattern, flags, true);
87 } else {
88 // RegExp : Called as function; see ECMA-262, section 15.10.3.1.
89 if (IS_REGEXP(pattern) && IS_UNDEFINED(flags)) {
90 return pattern;
91 }
92 return new $RegExp(pattern, flags);
93 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +000094}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +000095
96
97// Deprecated RegExp.prototype.compile method. We behave like the constructor
98// were called again. In SpiderMonkey, this method returns the regexp object.
kasperl@chromium.org7be3c992009-03-12 07:19:55 +000099// In JSC, it returns undefined. For compatibility with JSC, we match their
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000100// behavior.
101function CompileRegExp(pattern, flags) {
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000102 // Both JSC and SpiderMonkey treat a missing pattern argument as the
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000103 // empty subject string, and an actual undefined value passed as the
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000104 // pattern as the string 'undefined'. Note that JSC is inconsistent
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000105 // here, treating undefined values differently in
106 // RegExp.prototype.compile and in the constructor, where they are
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000107 // the empty string. For compatibility with JSC, we match their
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000108 // behavior.
109 if (IS_UNDEFINED(pattern) && %_ArgumentsLength() != 0) {
110 DoConstructRegExp(this, 'undefined', flags, false);
111 } else {
112 DoConstructRegExp(this, pattern, flags, false);
113 }
114}
115
116
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000117function DoRegExpExec(regexp, string, index) {
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000118 var result = %_RegExpExec(regexp, string, index, lastMatchInfo);
119 if (result !== null) lastMatchInfoOverride = null;
120 return result;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000121}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000122
123
fschneider@chromium.org086aac62010-03-17 13:18:24 +0000124function RegExpCache() {
125 this.type = 'none';
126 this.regExp = 0;
127 this.subject = 0;
128 this.replaceString = 0;
fschneider@chromium.org086aac62010-03-17 13:18:24 +0000129 this.answer = 0;
ager@chromium.org357bf652010-04-12 11:30:10 +0000130 // answerSaved marks whether the contents of answer is valid for a cache
131 // hit in RegExpExec, StringMatch and StringSplit.
132 this.answerSaved = false;
vegorov@chromium.org42841962010-10-18 11:18:59 +0000133 this.splitLimit = 0; // Used only when type is "split".
fschneider@chromium.org086aac62010-03-17 13:18:24 +0000134}
135
136
137var regExpCache = new RegExpCache();
vegorov@chromium.orgf8372902010-03-15 10:26:20 +0000138
139
whesse@chromium.orgb6e43bb2010-04-14 09:36:28 +0000140function BuildResultFromMatchInfo(lastMatchInfo, s) {
141 var numResults = NUMBER_OF_CAPTURES(lastMatchInfo) >> 1;
142 var result = %_RegExpConstructResult(numResults, lastMatchInfo[CAPTURE0], s);
143 if (numResults === 1) {
144 var matchStart = lastMatchInfo[CAPTURE(0)];
145 var matchEnd = lastMatchInfo[CAPTURE(1)];
146 result[0] = SubString(s, matchStart, matchEnd);
147 } else {
148 for (var i = 0; i < numResults; i++) {
149 var matchStart = lastMatchInfo[CAPTURE(i << 1)];
150 var matchEnd = lastMatchInfo[CAPTURE((i << 1) + 1)];
151 if (matchStart != -1 && matchEnd != -1) {
152 result[i] = SubString(s, matchStart, matchEnd);
153 } else {
154 // Make sure the element is present. Avoid reading the undefined
155 // property from the global object since this may change.
156 result[i] = void 0;
157 }
158 }
159 }
160 return result;
161}
162
163
164function RegExpExecNoTests(regexp, string, start) {
165 // Must be called with RegExp, string and positive integer as arguments.
166 var matchInfo = DoRegExpExec(regexp, string, start);
167 var result = null;
168 if (matchInfo !== null) {
169 result = BuildResultFromMatchInfo(matchInfo, string);
170 }
171 return result;
172}
173
174
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000175function RegExpExec(string) {
fschneider@chromium.org086aac62010-03-17 13:18:24 +0000176 if (!IS_REGEXP(this)) {
177 throw MakeTypeError('incompatible_method_receiver',
178 ['RegExp.prototype.exec', this]);
179 }
180
181 var cache = regExpCache;
ager@chromium.org357bf652010-04-12 11:30:10 +0000182 var saveAnswer = false;
fschneider@chromium.org086aac62010-03-17 13:18:24 +0000183
vegorov@chromium.org42841962010-10-18 11:18:59 +0000184 var lastIndex = this.lastIndex;
185
186 // Since cache.subject is always a string, a matching input can not
187 // cause visible side-effects when converted to a string, so we can omit
188 // the conversion required by the specification.
189 // Likewise, the regexp.lastIndex and regexp.global properties are value
190 // properties that are not configurable, so reading them can also not cause
191 // any side effects (converting lastIndex to a number can, though).
fschneider@chromium.org086aac62010-03-17 13:18:24 +0000192 if (%_ObjectEquals(cache.type, 'exec') &&
vegorov@chromium.org42841962010-10-18 11:18:59 +0000193 %_ObjectEquals(0, lastIndex) &&
lrn@chromium.orgc4e51ac2010-08-09 09:47:21 +0000194 %_IsRegExpEquivalent(cache.regExp, this) &&
fschneider@chromium.org086aac62010-03-17 13:18:24 +0000195 %_ObjectEquals(cache.subject, string)) {
ager@chromium.org357bf652010-04-12 11:30:10 +0000196 if (cache.answerSaved) {
vegorov@chromium.org42841962010-10-18 11:18:59 +0000197 // The regexp.lastIndex value must be 0 for non-global RegExps, and for
198 // global RegExps we only cache negative results, which gives a lastIndex
199 // of zero as well.
200 this.lastIndex = 0;
erik.corry@gmail.com145eff52010-08-23 11:36:18 +0000201 return %_RegExpCloneResult(cache.answer);
vegorov@chromium.orgf8372902010-03-15 10:26:20 +0000202 } else {
ager@chromium.org357bf652010-04-12 11:30:10 +0000203 saveAnswer = true;
vegorov@chromium.orgf8372902010-03-15 10:26:20 +0000204 }
205 }
206
vegorov@chromium.org42841962010-10-18 11:18:59 +0000207 if (%_ArgumentsLength() === 0) {
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000208 var regExpInput = LAST_INPUT(lastMatchInfo);
ager@chromium.orga74f0da2008-12-03 16:05:52 +0000209 if (IS_UNDEFINED(regExpInput)) {
210 throw MakeError('no_input_to_regexp', [this]);
211 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000212 string = regExpInput;
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000213 }
ager@chromium.orgce5e87b2010-03-10 10:24:18 +0000214 var s;
215 if (IS_STRING(string)) {
216 s = string;
217 } else {
218 s = ToString(string);
219 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000220 var global = this.global;
vegorov@chromium.orgf8372902010-03-15 10:26:20 +0000221
vegorov@chromium.org42841962010-10-18 11:18:59 +0000222 // Conversion is required by the ES5 specification (RegExp.prototype.exec
223 // algorithm, step 5) even if the value is discarded for non-global RegExps.
224 var i = TO_INTEGER(lastIndex);
225 if (global) {
226 if (i < 0 || i > s.length) {
227 this.lastIndex = 0;
228 return null;
229 }
230 } else {
231 i = 0;
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000232 }
233
christian.plesner.hansen@gmail.com37abdec2009-01-06 14:43:28 +0000234 %_Log('regexp', 'regexp-exec,%0r,%1S,%2i', [this, s, lastIndex]);
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000235 // matchIndices is either null or the lastMatchInfo array.
fschneider@chromium.org0c20e672010-01-14 15:28:53 +0000236 var matchIndices = %_RegExpExec(this, s, i, lastMatchInfo);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000237
vegorov@chromium.org42841962010-10-18 11:18:59 +0000238 if (matchIndices === null) {
239 if (global) {
240 // Cache negative result only if initial lastIndex was zero.
fschneider@chromium.org40b9da32010-06-28 11:29:21 +0000241 this.lastIndex = 0;
vegorov@chromium.org42841962010-10-18 11:18:59 +0000242 if (lastIndex !== 0) return matchIndices;
fschneider@chromium.org40b9da32010-06-28 11:29:21 +0000243 }
fschneider@chromium.org086aac62010-03-17 13:18:24 +0000244 cache.regExp = this;
vegorov@chromium.org42841962010-10-18 11:18:59 +0000245 cache.subject = s; // Always a string.
246 cache.answer = null;
ager@chromium.org357bf652010-04-12 11:30:10 +0000247 cache.answerSaved = true; // Safe since no cloning is needed.
fschneider@chromium.org086aac62010-03-17 13:18:24 +0000248 cache.type = 'exec';
vegorov@chromium.orgf8372902010-03-15 10:26:20 +0000249 return matchIndices; // No match.
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000250 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000251
252 // Successful match.
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000253 lastMatchInfoOverride = null;
whesse@chromium.orgb6e43bb2010-04-14 09:36:28 +0000254 var result = BuildResultFromMatchInfo(matchIndices, s);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000255
vegorov@chromium.org42841962010-10-18 11:18:59 +0000256 if (global) {
257 // Don't cache positive results for global regexps.
vegorov@chromium.orgf8372902010-03-15 10:26:20 +0000258 this.lastIndex = lastMatchInfo[CAPTURE1];
vegorov@chromium.orgf8372902010-03-15 10:26:20 +0000259 } else {
fschneider@chromium.org086aac62010-03-17 13:18:24 +0000260 cache.regExp = this;
261 cache.subject = s;
erik.corry@gmail.com145eff52010-08-23 11:36:18 +0000262 if (saveAnswer) cache.answer = %_RegExpCloneResult(result);
ager@chromium.org357bf652010-04-12 11:30:10 +0000263 cache.answerSaved = saveAnswer;
fschneider@chromium.org086aac62010-03-17 13:18:24 +0000264 cache.type = 'exec';
vegorov@chromium.orgf8372902010-03-15 10:26:20 +0000265 }
ager@chromium.org357bf652010-04-12 11:30:10 +0000266 return result;
267
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000268}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000269
270
lrn@chromium.org1af7e1b2010-06-07 11:12:01 +0000271// One-element cache for the simplified test regexp.
272var regexp_key;
273var regexp_val;
274
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000275// Section 15.10.6.3 doesn't actually make sense, but the intention seems to be
ager@chromium.orgbb29dc92009-03-24 13:25:23 +0000276// that test is defined in terms of String.prototype.exec. However, it probably
277// means the original value of String.prototype.exec, which is what everybody
278// else implements.
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000279function RegExpTest(string) {
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000280 if (!IS_REGEXP(this)) {
ager@chromium.orgce5e87b2010-03-10 10:24:18 +0000281 throw MakeTypeError('incompatible_method_receiver',
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000282 ['RegExp.prototype.test', this]);
283 }
284 if (%_ArgumentsLength() == 0) {
285 var regExpInput = LAST_INPUT(lastMatchInfo);
286 if (IS_UNDEFINED(regExpInput)) {
287 throw MakeError('no_input_to_regexp', [this]);
288 }
289 string = regExpInput;
290 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000291
292 var lastIndex = this.lastIndex;
293
294 var cache = regExpCache;
295 if (%_ObjectEquals(cache.type, 'test') &&
296 %_IsRegExpEquivalent(cache.regExp, this) &&
297 %_ObjectEquals(cache.subject, string) &&
298 %_ObjectEquals(0, lastIndex)) {
299 // The regexp.lastIndex value must be 0 for non-global RegExps, and for
300 // global RegExps we only cache negative results, which gives a resulting
301 // lastIndex of zero as well.
302 if (global) this.lastIndex = 0;
303 return cache.answer;
304 }
305
fschneider@chromium.org086aac62010-03-17 13:18:24 +0000306 var s;
307 if (IS_STRING(string)) {
308 s = string;
309 } else {
310 s = ToString(string);
311 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000312 var length = s.length;
fschneider@chromium.org086aac62010-03-17 13:18:24 +0000313
vegorov@chromium.org42841962010-10-18 11:18:59 +0000314 // Conversion is required by the ES5 specification (RegExp.prototype.exec
315 // algorithm, step 5) even if the value is discarded for non-global RegExps.
316 var i = TO_INTEGER(lastIndex);
317 if (global) {
318 if (i < 0 || i > length) {
319 this.lastIndex = 0;
320 return false;
321 }
322 } else {
323 i = 0;
fschneider@chromium.org086aac62010-03-17 13:18:24 +0000324 }
325
vegorov@chromium.org42841962010-10-18 11:18:59 +0000326 var global = this.global;
327
lrn@chromium.org32d961d2010-06-30 09:09:34 +0000328 // Remove irrelevant preceeding '.*' in a test regexp. The expression
329 // checks whether this.source starts with '.*' and that the third
lrn@chromium.org1af7e1b2010-06-07 11:12:01 +0000330 // char is not a '?'
vegorov@chromium.org42841962010-10-18 11:18:59 +0000331 if (%_StringCharCodeAt(this.source, 0) == 46 && // '.'
332 %_StringCharCodeAt(this.source, 1) == 42 && // '*'
333 %_StringCharCodeAt(this.source, 2) != 63) { // '?'
lrn@chromium.org1af7e1b2010-06-07 11:12:01 +0000334 if (!%_ObjectEquals(regexp_key, this)) {
lrn@chromium.org32d961d2010-06-30 09:09:34 +0000335 regexp_key = this;
lrn@chromium.org1af7e1b2010-06-07 11:12:01 +0000336 regexp_val = new $RegExp(this.source.substring(2, this.source.length),
337 (this.global ? 'g' : '')
338 + (this.ignoreCase ? 'i' : '')
339 + (this.multiline ? 'm' : ''));
340 }
341 if (!regexp_val.test(s)) return false;
342 }
lrn@chromium.org32d961d2010-06-30 09:09:34 +0000343
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000344 %_Log('regexp', 'regexp-exec,%0r,%1S,%2i', [this, s, lastIndex]);
345 // matchIndices is either null or the lastMatchInfo array.
fschneider@chromium.org0c20e672010-01-14 15:28:53 +0000346 var matchIndices = %_RegExpExec(this, s, i, lastMatchInfo);
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000347
vegorov@chromium.org42841962010-10-18 11:18:59 +0000348 var result = (matchIndices !== null);
349 if (result) {
350 lastMatchInfoOverride = null;
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000351 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000352 if (global) {
353 if (result) {
354 this.lastIndex = lastMatchInfo[CAPTURE1];
355 return true;
356 } else {
357 this.lastIndex = 0;
358 if (lastIndex !== 0) return false;
359 }
360 }
361 cache.type = 'test';
362 cache.regExp = this;
363 cache.subject = s;
364 cache.answer = result;
365 return result;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000366}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000367
368
369function RegExpToString() {
370 // If this.source is an empty string, output /(?:)/.
371 // http://bugzilla.mozilla.org/show_bug.cgi?id=225550
372 // ecma_2/RegExp/properties-001.js.
373 var src = this.source ? this.source : '(?:)';
374 var result = '/' + src + '/';
vegorov@chromium.org42841962010-10-18 11:18:59 +0000375 if (this.global) result += 'g';
376 if (this.ignoreCase) result += 'i';
377 if (this.multiline) result += 'm';
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000378 return result;
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000379}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000380
381
382// Getters for the static properties lastMatch, lastParen, leftContext, and
383// rightContext of the RegExp constructor. The properties are computed based
384// on the captures array of the last successful match and the subject string
385// of the last successful match.
386function RegExpGetLastMatch() {
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000387 if (lastMatchInfoOverride !== null) {
388 return lastMatchInfoOverride[0];
389 }
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000390 var regExpSubject = LAST_SUBJECT(lastMatchInfo);
391 return SubString(regExpSubject,
392 lastMatchInfo[CAPTURE0],
393 lastMatchInfo[CAPTURE1]);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000394}
395
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000396
397function RegExpGetLastParen() {
lrn@chromium.org25156de2010-04-06 13:10:27 +0000398 if (lastMatchInfoOverride) {
399 var override = lastMatchInfoOverride;
400 if (override.length <= 3) return '';
401 return override[override.length - 3];
402 }
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000403 var length = NUMBER_OF_CAPTURES(lastMatchInfo);
404 if (length <= 2) return ''; // There were no captures.
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000405 // We match the SpiderMonkey behavior: return the substring defined by the
406 // last pair (after the first pair) of elements of the capture array even if
407 // it is empty.
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000408 var regExpSubject = LAST_SUBJECT(lastMatchInfo);
409 var start = lastMatchInfo[CAPTURE(length - 2)];
410 var end = lastMatchInfo[CAPTURE(length - 1)];
411 if (start != -1 && end != -1) {
412 return SubString(regExpSubject, start, end);
413 }
414 return "";
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000415}
416
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000417
418function RegExpGetLeftContext() {
lrn@chromium.org25156de2010-04-06 13:10:27 +0000419 var start_index;
420 var subject;
421 if (!lastMatchInfoOverride) {
422 start_index = lastMatchInfo[CAPTURE0];
423 subject = LAST_SUBJECT(lastMatchInfo);
424 } else {
425 var override = lastMatchInfoOverride;
426 start_index = override[override.length - 2];
427 subject = override[override.length - 1];
428 }
429 return SubString(subject, 0, start_index);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000430}
431
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000432
433function RegExpGetRightContext() {
lrn@chromium.org25156de2010-04-06 13:10:27 +0000434 var start_index;
435 var subject;
436 if (!lastMatchInfoOverride) {
437 start_index = lastMatchInfo[CAPTURE1];
438 subject = LAST_SUBJECT(lastMatchInfo);
439 } else {
440 var override = lastMatchInfoOverride;
441 subject = override[override.length - 1];
442 start_index = override[override.length - 2] + subject.length;
443 }
444 return SubString(subject, start_index, subject.length);
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000445}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000446
447
448// The properties $1..$9 are the first nine capturing substrings of the last
449// successful match, or ''. The function RegExpMakeCaptureGetter will be
ager@chromium.orgbb29dc92009-03-24 13:25:23 +0000450// called with indices from 1 to 9.
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000451function RegExpMakeCaptureGetter(n) {
452 return function() {
lrn@chromium.org25156de2010-04-06 13:10:27 +0000453 if (lastMatchInfoOverride) {
454 if (n < lastMatchInfoOverride.length - 2) return lastMatchInfoOverride[n];
455 return '';
456 }
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000457 var index = n * 2;
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000458 if (index >= NUMBER_OF_CAPTURES(lastMatchInfo)) return '';
459 var matchStart = lastMatchInfo[CAPTURE(index)];
460 var matchEnd = lastMatchInfo[CAPTURE(index + 1)];
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000461 if (matchStart == -1 || matchEnd == -1) return '';
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000462 return SubString(LAST_SUBJECT(lastMatchInfo), matchStart, matchEnd);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000463 };
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000464}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000465
466
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000467// Property of the builtins object for recording the result of the last
468// regexp match. The property lastMatchInfo includes the matchIndices
469// array of the last successful regexp match (an array of start/end index
470// pairs for the match and all the captured substrings), the invariant is
471// that there are at least two capture indeces. The array also contains
472// the subject string for the last successful match.
473var lastMatchInfo = [
474 2, // REGEXP_NUMBER_OF_CAPTURES
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000475 "", // Last subject.
476 void 0, // Last input - settable with RegExpSetInput.
ager@chromium.orgbb29dc92009-03-24 13:25:23 +0000477 0, // REGEXP_FIRST_CAPTURE + 0
478 0, // REGEXP_FIRST_CAPTURE + 1
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000479];
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000480
lrn@chromium.org25156de2010-04-06 13:10:27 +0000481// Override last match info with an array of actual substrings.
482// Used internally by replace regexp with function.
483// The array has the format of an "apply" argument for a replacement
484// function.
485var lastMatchInfoOverride = null;
486
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000487// -------------------------------------------------------------------
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000488
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000489function SetupRegExp() {
490 %FunctionSetInstanceClassName($RegExp, 'RegExp');
491 %FunctionSetPrototype($RegExp, new $Object());
492 %SetProperty($RegExp.prototype, 'constructor', $RegExp, DONT_ENUM);
493 %SetCode($RegExp, RegExpConstructor);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000494
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000495 InstallFunctions($RegExp.prototype, DONT_ENUM, $Array(
496 "exec", RegExpExec,
497 "test", RegExpTest,
498 "toString", RegExpToString,
499 "compile", CompileRegExp
500 ));
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000501
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000502 // The length of compile is 1 in SpiderMonkey.
503 %FunctionSetLength($RegExp.prototype.compile, 1);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000504
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000505 // The properties input, $input, and $_ are aliases for each other. When this
lrn@chromium.org25156de2010-04-06 13:10:27 +0000506 // value is set the value it is set to is coerced to a string.
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000507 // Getter and setter for the input.
ager@chromium.orga74f0da2008-12-03 16:05:52 +0000508 function RegExpGetInput() {
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000509 var regExpInput = LAST_INPUT(lastMatchInfo);
ager@chromium.orga74f0da2008-12-03 16:05:52 +0000510 return IS_UNDEFINED(regExpInput) ? "" : regExpInput;
511 }
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000512 function RegExpSetInput(string) {
fschneider@chromium.org086aac62010-03-17 13:18:24 +0000513 regExpCache.type = 'none';
ager@chromium.orgbb29dc92009-03-24 13:25:23 +0000514 LAST_INPUT(lastMatchInfo) = ToString(string);
kasperl@chromium.org7be3c992009-03-12 07:19:55 +0000515 };
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000516
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000517 %DefineAccessor($RegExp, 'input', GETTER, RegExpGetInput, DONT_DELETE);
518 %DefineAccessor($RegExp, 'input', SETTER, RegExpSetInput, DONT_DELETE);
519 %DefineAccessor($RegExp, '$_', GETTER, RegExpGetInput, DONT_ENUM | DONT_DELETE);
520 %DefineAccessor($RegExp, '$_', SETTER, RegExpSetInput, DONT_ENUM | DONT_DELETE);
521 %DefineAccessor($RegExp, '$input', GETTER, RegExpGetInput, DONT_ENUM | DONT_DELETE);
522 %DefineAccessor($RegExp, '$input', SETTER, RegExpSetInput, DONT_ENUM | DONT_DELETE);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000523
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000524 // The properties multiline and $* are aliases for each other. When this
525 // value is set in SpiderMonkey, the value it is set to is coerced to a
526 // boolean. We mimic that behavior with a slight difference: in SpiderMonkey
527 // the value of the expression 'RegExp.multiline = null' (for instance) is the
528 // boolean false (ie, the value after coercion), while in V8 it is the value
529 // null (ie, the value before coercion).
530
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000531 // Getter and setter for multiline.
532 var multiline = false;
533 function RegExpGetMultiline() { return multiline; };
534 function RegExpSetMultiline(flag) { multiline = flag ? true : false; };
535
536 %DefineAccessor($RegExp, 'multiline', GETTER, RegExpGetMultiline, DONT_DELETE);
537 %DefineAccessor($RegExp, 'multiline', SETTER, RegExpSetMultiline, DONT_DELETE);
538 %DefineAccessor($RegExp, '$*', GETTER, RegExpGetMultiline, DONT_ENUM | DONT_DELETE);
539 %DefineAccessor($RegExp, '$*', SETTER, RegExpSetMultiline, DONT_ENUM | DONT_DELETE);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000540
541
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000542 function NoOpSetter(ignored) {}
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000543
544
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000545 // Static properties set by a successful match.
546 %DefineAccessor($RegExp, 'lastMatch', GETTER, RegExpGetLastMatch, DONT_DELETE);
547 %DefineAccessor($RegExp, 'lastMatch', SETTER, NoOpSetter, DONT_DELETE);
548 %DefineAccessor($RegExp, '$&', GETTER, RegExpGetLastMatch, DONT_ENUM | DONT_DELETE);
549 %DefineAccessor($RegExp, '$&', SETTER, NoOpSetter, DONT_ENUM | DONT_DELETE);
550 %DefineAccessor($RegExp, 'lastParen', GETTER, RegExpGetLastParen, DONT_DELETE);
551 %DefineAccessor($RegExp, 'lastParen', SETTER, NoOpSetter, DONT_DELETE);
552 %DefineAccessor($RegExp, '$+', GETTER, RegExpGetLastParen, DONT_ENUM | DONT_DELETE);
553 %DefineAccessor($RegExp, '$+', SETTER, NoOpSetter, DONT_ENUM | DONT_DELETE);
554 %DefineAccessor($RegExp, 'leftContext', GETTER, RegExpGetLeftContext, DONT_DELETE);
555 %DefineAccessor($RegExp, 'leftContext', SETTER, NoOpSetter, DONT_DELETE);
556 %DefineAccessor($RegExp, '$`', GETTER, RegExpGetLeftContext, DONT_ENUM | DONT_DELETE);
557 %DefineAccessor($RegExp, '$`', SETTER, NoOpSetter, DONT_ENUM | DONT_DELETE);
558 %DefineAccessor($RegExp, 'rightContext', GETTER, RegExpGetRightContext, DONT_DELETE);
559 %DefineAccessor($RegExp, 'rightContext', SETTER, NoOpSetter, DONT_DELETE);
560 %DefineAccessor($RegExp, "$'", GETTER, RegExpGetRightContext, DONT_ENUM | DONT_DELETE);
561 %DefineAccessor($RegExp, "$'", SETTER, NoOpSetter, DONT_ENUM | DONT_DELETE);
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000562
christian.plesner.hansen43d26ec2008-07-03 15:10:15 +0000563 for (var i = 1; i < 10; ++i) {
564 %DefineAccessor($RegExp, '$' + i, GETTER, RegExpMakeCaptureGetter(i), DONT_DELETE);
565 %DefineAccessor($RegExp, '$' + i, SETTER, NoOpSetter, DONT_DELETE);
566 }
kasperl@chromium.org41044eb2008-10-06 08:24:46 +0000567}
568
569
570SetupRegExp();