blob: e80d0190f49c9d2868783b6e8947e203e0922640 [file] [log] [blame]
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001// Copyright 2012 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5(function(global, utils) {
6
7%CheckIsBootstrapping();
8
9// -------------------------------------------------------------------
10// Imports
11
Ben Murdoch097c5b22016-05-18 11:27:45 +010012var ExpandReplacement;
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000013var GlobalObject = global.Object;
14var GlobalRegExp = global.RegExp;
15var GlobalRegExpPrototype;
16var InternalArray = utils.InternalArray;
17var InternalPackedArray = utils.InternalPackedArray;
18var MakeTypeError;
19var matchSymbol = utils.ImportNow("match_symbol");
Ben Murdoch097c5b22016-05-18 11:27:45 +010020var replaceSymbol = utils.ImportNow("replace_symbol");
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000021var searchSymbol = utils.ImportNow("search_symbol");
22var splitSymbol = utils.ImportNow("split_symbol");
23
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000024utils.Import(function(from) {
Ben Murdoch097c5b22016-05-18 11:27:45 +010025 ExpandReplacement = from.ExpandReplacement;
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000026 MakeTypeError = from.MakeTypeError;
27});
28
29// -------------------------------------------------------------------
30
31// Property of the builtins object for recording the result of the last
32// regexp match. The property RegExpLastMatchInfo includes the matchIndices
33// array of the last successful regexp match (an array of start/end index
34// pairs for the match and all the captured substrings), the invariant is
35// that there are at least two capture indeces. The array also contains
36// the subject string for the last successful match.
37var RegExpLastMatchInfo = new InternalPackedArray(
38 2, // REGEXP_NUMBER_OF_CAPTURES
39 "", // Last subject.
40 UNDEFINED, // Last input - settable with RegExpSetInput.
41 0, // REGEXP_FIRST_CAPTURE + 0
42 0 // REGEXP_FIRST_CAPTURE + 1
43);
44
45// -------------------------------------------------------------------
46
47function IsRegExp(o) {
48 if (!IS_RECEIVER(o)) return false;
49 var is_regexp = o[matchSymbol];
50 if (!IS_UNDEFINED(is_regexp)) return TO_BOOLEAN(is_regexp);
51 return IS_REGEXP(o);
52}
53
54
55// ES6 section 21.2.3.2.2
56function RegExpInitialize(object, pattern, flags) {
57 pattern = IS_UNDEFINED(pattern) ? '' : TO_STRING(pattern);
58 flags = IS_UNDEFINED(flags) ? '' : TO_STRING(flags);
59 %RegExpInitializeAndCompile(object, pattern, flags);
60 return object;
61}
62
63
64function PatternFlags(pattern) {
65 return (REGEXP_GLOBAL(pattern) ? 'g' : '') +
66 (REGEXP_IGNORE_CASE(pattern) ? 'i' : '') +
67 (REGEXP_MULTILINE(pattern) ? 'm' : '') +
68 (REGEXP_UNICODE(pattern) ? 'u' : '') +
69 (REGEXP_STICKY(pattern) ? 'y' : '');
70}
71
72
73function RegExpConstructor(pattern, flags) {
74 var newtarget = new.target;
75 var pattern_is_regexp = IsRegExp(pattern);
76
77 if (IS_UNDEFINED(newtarget)) {
78 newtarget = GlobalRegExp;
79
80 // ES6 section 21.2.3.1 step 3.b
81 if (pattern_is_regexp && IS_UNDEFINED(flags) &&
82 pattern.constructor === newtarget) {
83 return pattern;
84 }
85 }
86
87 if (IS_REGEXP(pattern)) {
88 if (IS_UNDEFINED(flags)) flags = PatternFlags(pattern);
89 pattern = REGEXP_SOURCE(pattern);
90
91 } else if (pattern_is_regexp) {
92 var input_pattern = pattern;
93 pattern = pattern.source;
94 if (IS_UNDEFINED(flags)) flags = input_pattern.flags;
95 }
96
97 var object = %NewObject(GlobalRegExp, newtarget);
98 return RegExpInitialize(object, pattern, flags);
99}
100
101
102function RegExpCompileJS(pattern, flags) {
103 if (!IS_REGEXP(this)) {
104 throw MakeTypeError(kIncompatibleMethodReceiver,
105 "RegExp.prototype.compile", this);
106 }
107
108 if (IS_REGEXP(pattern)) {
109 if (!IS_UNDEFINED(flags)) throw MakeTypeError(kRegExpFlags);
110
111 flags = PatternFlags(pattern);
112 pattern = REGEXP_SOURCE(pattern);
113 }
114
115 RegExpInitialize(this, pattern, flags);
116
117 // Return undefined for compatibility with JSC.
118 // See http://crbug.com/585775 for web compat details.
119}
120
121
122function DoRegExpExec(regexp, string, index) {
123 return %_RegExpExec(regexp, string, index, RegExpLastMatchInfo);
124}
125
126
127// This is kind of performance sensitive, so we want to avoid unnecessary
128// type checks on inputs. But we also don't want to inline it several times
129// manually, so we use a macro :-)
130macro RETURN_NEW_RESULT_FROM_MATCH_INFO(MATCHINFO, STRING)
131 var numResults = NUMBER_OF_CAPTURES(MATCHINFO) >> 1;
132 var start = MATCHINFO[CAPTURE0];
133 var end = MATCHINFO[CAPTURE1];
134 // Calculate the substring of the first match before creating the result array
135 // to avoid an unnecessary write barrier storing the first result.
136 var first = %_SubString(STRING, start, end);
137 var result = %_RegExpConstructResult(numResults, start, STRING);
138 result[0] = first;
139 if (numResults == 1) return result;
140 var j = REGEXP_FIRST_CAPTURE + 2;
141 for (var i = 1; i < numResults; i++) {
142 start = MATCHINFO[j++];
143 if (start != -1) {
144 end = MATCHINFO[j];
145 result[i] = %_SubString(STRING, start, end);
146 }
147 j++;
148 }
149 return result;
150endmacro
151
152
153function RegExpExecNoTests(regexp, string, start) {
154 // Must be called with RegExp, string and positive integer as arguments.
155 var matchInfo = %_RegExpExec(regexp, string, start, RegExpLastMatchInfo);
156 if (matchInfo !== null) {
157 // ES6 21.2.5.2.2 step 18.
158 if (REGEXP_STICKY(regexp)) regexp.lastIndex = matchInfo[CAPTURE1];
159 RETURN_NEW_RESULT_FROM_MATCH_INFO(matchInfo, string);
160 }
161 regexp.lastIndex = 0;
162 return null;
163}
164
165
166function RegExpExecJS(string) {
167 if (!IS_REGEXP(this)) {
168 throw MakeTypeError(kIncompatibleMethodReceiver,
169 'RegExp.prototype.exec', this);
170 }
171
172 string = TO_STRING(string);
173 var lastIndex = this.lastIndex;
174
175 // Conversion is required by the ES2015 specification (RegExpBuiltinExec
176 // algorithm, step 4) even if the value is discarded for non-global RegExps.
Ben Murdoch097c5b22016-05-18 11:27:45 +0100177 var i = TO_LENGTH(lastIndex);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000178
179 var updateLastIndex = REGEXP_GLOBAL(this) || REGEXP_STICKY(this);
180 if (updateLastIndex) {
181 if (i < 0 || i > string.length) {
182 this.lastIndex = 0;
183 return null;
184 }
185 } else {
186 i = 0;
187 }
188
189 // matchIndices is either null or the RegExpLastMatchInfo array.
190 var matchIndices = %_RegExpExec(this, string, i, RegExpLastMatchInfo);
191
192 if (IS_NULL(matchIndices)) {
193 this.lastIndex = 0;
194 return null;
195 }
196
197 // Successful match.
198 if (updateLastIndex) {
199 this.lastIndex = RegExpLastMatchInfo[CAPTURE1];
200 }
201 RETURN_NEW_RESULT_FROM_MATCH_INFO(matchIndices, string);
202}
203
204
205// One-element cache for the simplified test regexp.
206var regexp_key;
207var regexp_val;
208
209// Section 15.10.6.3 doesn't actually make sense, but the intention seems to be
210// that test is defined in terms of String.prototype.exec. However, it probably
211// means the original value of String.prototype.exec, which is what everybody
212// else implements.
213function RegExpTest(string) {
214 if (!IS_REGEXP(this)) {
215 throw MakeTypeError(kIncompatibleMethodReceiver,
216 'RegExp.prototype.test', this);
217 }
218 string = TO_STRING(string);
219
220 var lastIndex = this.lastIndex;
221
222 // Conversion is required by the ES2015 specification (RegExpBuiltinExec
223 // algorithm, step 4) even if the value is discarded for non-global RegExps.
Ben Murdoch097c5b22016-05-18 11:27:45 +0100224 var i = TO_LENGTH(lastIndex);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000225
226 if (REGEXP_GLOBAL(this) || REGEXP_STICKY(this)) {
227 if (i < 0 || i > string.length) {
228 this.lastIndex = 0;
229 return false;
230 }
231 // matchIndices is either null or the RegExpLastMatchInfo array.
232 var matchIndices = %_RegExpExec(this, string, i, RegExpLastMatchInfo);
233 if (IS_NULL(matchIndices)) {
234 this.lastIndex = 0;
235 return false;
236 }
237 this.lastIndex = RegExpLastMatchInfo[CAPTURE1];
238 return true;
239 } else {
240 // Non-global, non-sticky regexp.
241 // Remove irrelevant preceeding '.*' in a test regexp. The expression
242 // checks whether this.source starts with '.*' and that the third char is
243 // not a '?'. But see https://code.google.com/p/v8/issues/detail?id=3560
244 var regexp = this;
245 var source = REGEXP_SOURCE(regexp);
246 if (regexp.length >= 3 &&
247 %_StringCharCodeAt(regexp, 0) == 46 && // '.'
248 %_StringCharCodeAt(regexp, 1) == 42 && // '*'
249 %_StringCharCodeAt(regexp, 2) != 63) { // '?'
250 regexp = TrimRegExp(regexp);
251 }
252 // matchIndices is either null or the RegExpLastMatchInfo array.
253 var matchIndices = %_RegExpExec(regexp, string, 0, RegExpLastMatchInfo);
254 if (IS_NULL(matchIndices)) {
255 this.lastIndex = 0;
256 return false;
257 }
258 return true;
259 }
260}
261
262function TrimRegExp(regexp) {
Ben Murdoch097c5b22016-05-18 11:27:45 +0100263 if (regexp_key !== regexp) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000264 regexp_key = regexp;
265 regexp_val =
266 new GlobalRegExp(
267 %_SubString(REGEXP_SOURCE(regexp), 2, REGEXP_SOURCE(regexp).length),
268 (REGEXP_IGNORE_CASE(regexp) ? REGEXP_MULTILINE(regexp) ? "im" : "i"
269 : REGEXP_MULTILINE(regexp) ? "m" : ""));
270 }
271 return regexp_val;
272}
273
274
275function RegExpToString() {
276 if (!IS_REGEXP(this)) {
277 // RegExp.prototype.toString() returns '/(?:)/' as a compatibility fix;
278 // a UseCounter is incremented to track it.
279 // TODO(littledan): Remove this workaround or standardize it
280 if (this === GlobalRegExpPrototype) {
281 %IncrementUseCounter(kRegExpPrototypeToString);
282 return '/(?:)/';
283 }
Ben Murdoch097c5b22016-05-18 11:27:45 +0100284 if (!IS_RECEIVER(this)) {
285 throw MakeTypeError(
286 kIncompatibleMethodReceiver, 'RegExp.prototype.toString', this);
287 }
288 return '/' + TO_STRING(this.source) + '/' + TO_STRING(this.flags);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000289 }
290 var result = '/' + REGEXP_SOURCE(this) + '/';
291 if (REGEXP_GLOBAL(this)) result += 'g';
292 if (REGEXP_IGNORE_CASE(this)) result += 'i';
293 if (REGEXP_MULTILINE(this)) result += 'm';
294 if (REGEXP_UNICODE(this)) result += 'u';
295 if (REGEXP_STICKY(this)) result += 'y';
296 return result;
297}
298
299
Ben Murdoch097c5b22016-05-18 11:27:45 +0100300function AtSurrogatePair(subject, index) {
301 if (index + 1 >= subject.length) return false;
302 var first = %_StringCharCodeAt(subject, index);
303 if (first < 0xD800 || first > 0xDBFF) return false;
304 var second = %_StringCharCodeAt(subject, index + 1);
305 return second >= 0xDC00 || second <= 0xDFFF;
306}
307
308
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000309// ES6 21.2.5.11.
310function RegExpSplit(string, limit) {
311 // TODO(yangguo): allow non-regexp receivers.
312 if (!IS_REGEXP(this)) {
313 throw MakeTypeError(kIncompatibleMethodReceiver,
314 "RegExp.prototype.@@split", this);
315 }
316 var separator = this;
317 var subject = TO_STRING(string);
318
319 limit = (IS_UNDEFINED(limit)) ? kMaxUint32 : TO_UINT32(limit);
320 var length = subject.length;
321
322 if (limit === 0) return [];
323
324 if (length === 0) {
325 if (DoRegExpExec(separator, subject, 0, 0) !== null) return [];
326 return [subject];
327 }
328
329 var currentIndex = 0;
330 var startIndex = 0;
331 var startMatch = 0;
332 var result = new InternalArray();
333
334 outer_loop:
335 while (true) {
336 if (startIndex === length) {
337 result[result.length] = %_SubString(subject, currentIndex, length);
338 break;
339 }
340
341 var matchInfo = DoRegExpExec(separator, subject, startIndex);
342 if (matchInfo === null || length === (startMatch = matchInfo[CAPTURE0])) {
343 result[result.length] = %_SubString(subject, currentIndex, length);
344 break;
345 }
346 var endIndex = matchInfo[CAPTURE1];
347
348 // We ignore a zero-length match at the currentIndex.
349 if (startIndex === endIndex && endIndex === currentIndex) {
Ben Murdoch097c5b22016-05-18 11:27:45 +0100350 if (REGEXP_UNICODE(this) && AtSurrogatePair(subject, startIndex)) {
351 startIndex += 2;
352 } else {
353 startIndex++;
354 }
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000355 continue;
356 }
357
358 result[result.length] = %_SubString(subject, currentIndex, startMatch);
359
360 if (result.length === limit) break;
361
362 var matchinfo_len = NUMBER_OF_CAPTURES(matchInfo) + REGEXP_FIRST_CAPTURE;
363 for (var i = REGEXP_FIRST_CAPTURE + 2; i < matchinfo_len; ) {
364 var start = matchInfo[i++];
365 var end = matchInfo[i++];
366 if (end != -1) {
367 result[result.length] = %_SubString(subject, start, end);
368 } else {
369 result[result.length] = UNDEFINED;
370 }
371 if (result.length === limit) break outer_loop;
372 }
373
374 startIndex = currentIndex = endIndex;
375 }
376
377 var array_result = [];
378 %MoveArrayContents(result, array_result);
379 return array_result;
380}
381
382
383// ES6 21.2.5.6.
384function RegExpMatch(string) {
385 // TODO(yangguo): allow non-regexp receivers.
386 if (!IS_REGEXP(this)) {
387 throw MakeTypeError(kIncompatibleMethodReceiver,
388 "RegExp.prototype.@@match", this);
389 }
390 var subject = TO_STRING(string);
391
392 if (!REGEXP_GLOBAL(this)) return RegExpExecNoTests(this, subject, 0);
393 this.lastIndex = 0;
394 var result = %StringMatch(subject, this, RegExpLastMatchInfo);
395 return result;
396}
397
398
Ben Murdoch097c5b22016-05-18 11:27:45 +0100399// ES6 21.2.5.8.
400
401// TODO(lrn): This array will survive indefinitely if replace is never
402// called again. However, it will be empty, since the contents are cleared
403// in the finally block.
404var reusableReplaceArray = new InternalArray(4);
405
406// Helper function for replacing regular expressions with the result of a
407// function application in String.prototype.replace.
408function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) {
409 var resultArray = reusableReplaceArray;
410 if (resultArray) {
411 reusableReplaceArray = null;
412 } else {
413 // Inside a nested replace (replace called from the replacement function
414 // of another replace) or we have failed to set the reusable array
415 // back due to an exception in a replacement function. Create a new
416 // array to use in the future, or until the original is written back.
417 resultArray = new InternalArray(16);
418 }
419 var res = %RegExpExecMultiple(regexp,
420 subject,
421 RegExpLastMatchInfo,
422 resultArray);
423 regexp.lastIndex = 0;
424 if (IS_NULL(res)) {
425 // No matches at all.
426 reusableReplaceArray = resultArray;
427 return subject;
428 }
429 var len = res.length;
430 if (NUMBER_OF_CAPTURES(RegExpLastMatchInfo) == 2) {
431 // If the number of captures is two then there are no explicit captures in
432 // the regexp, just the implicit capture that captures the whole match. In
433 // this case we can simplify quite a bit and end up with something faster.
434 // The builder will consist of some integers that indicate slices of the
435 // input string and some replacements that were returned from the replace
436 // function.
437 var match_start = 0;
438 for (var i = 0; i < len; i++) {
439 var elem = res[i];
440 if (%_IsSmi(elem)) {
441 // Integers represent slices of the original string.
442 if (elem > 0) {
443 match_start = (elem >> 11) + (elem & 0x7ff);
444 } else {
445 match_start = res[++i] - elem;
446 }
447 } else {
448 var func_result = replace(elem, match_start, subject);
449 // Overwrite the i'th element in the results with the string we got
450 // back from the callback function.
451 res[i] = TO_STRING(func_result);
452 match_start += elem.length;
453 }
454 }
455 } else {
456 for (var i = 0; i < len; i++) {
457 var elem = res[i];
458 if (!%_IsSmi(elem)) {
459 // elem must be an Array.
460 // Use the apply argument as backing for global RegExp properties.
461 var func_result = %Apply(replace, UNDEFINED, elem, 0, elem.length);
462 // Overwrite the i'th element in the results with the string we got
463 // back from the callback function.
464 res[i] = TO_STRING(func_result);
465 }
466 }
467 }
468 var result = %StringBuilderConcat(res, len, subject);
469 resultArray.length = 0;
470 reusableReplaceArray = resultArray;
471 return result;
472}
473
474
475// Compute the string of a given regular expression capture.
476function CaptureString(string, lastCaptureInfo, index) {
477 // Scale the index.
478 var scaled = index << 1;
479 // Compute start and end.
480 var start = lastCaptureInfo[CAPTURE(scaled)];
481 // If start isn't valid, return undefined.
482 if (start < 0) return;
483 var end = lastCaptureInfo[CAPTURE(scaled + 1)];
484 return %_SubString(string, start, end);
485}
486
487
488function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) {
489 var matchInfo = DoRegExpExec(regexp, subject, 0);
490 if (IS_NULL(matchInfo)) {
491 regexp.lastIndex = 0;
492 return subject;
493 }
494 var index = matchInfo[CAPTURE0];
495 var result = %_SubString(subject, 0, index);
496 var endOfMatch = matchInfo[CAPTURE1];
497 // Compute the parameter list consisting of the match, captures, index,
498 // and subject for the replace function invocation.
499 // The number of captures plus one for the match.
500 var m = NUMBER_OF_CAPTURES(matchInfo) >> 1;
501 var replacement;
502 if (m == 1) {
503 // No captures, only the match, which is always valid.
504 var s = %_SubString(subject, index, endOfMatch);
505 // Don't call directly to avoid exposing the built-in global object.
506 replacement = replace(s, index, subject);
507 } else {
508 var parameters = new InternalArray(m + 2);
509 for (var j = 0; j < m; j++) {
510 parameters[j] = CaptureString(subject, matchInfo, j);
511 }
512 parameters[j] = index;
513 parameters[j + 1] = subject;
514
515 replacement = %Apply(replace, UNDEFINED, parameters, 0, j + 2);
516 }
517
518 result += replacement; // The add method converts to string if necessary.
519 // Can't use matchInfo any more from here, since the function could
520 // overwrite it.
521 return result + %_SubString(subject, endOfMatch, subject.length);
522}
523
524
525function RegExpReplace(string, replace) {
526 // TODO(littledan): allow non-regexp receivers.
527 if (!IS_REGEXP(this)) {
528 throw MakeTypeError(kIncompatibleMethodReceiver,
529 "RegExp.prototype.@@replace", this);
530 }
531 var subject = TO_STRING(string);
532 var search = this;
533
534 if (!IS_CALLABLE(replace)) {
535 replace = TO_STRING(replace);
536
537 if (!REGEXP_GLOBAL(search)) {
538 // Non-global regexp search, string replace.
539 var match = DoRegExpExec(search, subject, 0);
540 if (match == null) {
541 search.lastIndex = 0
542 return subject;
543 }
544 if (replace.length == 0) {
545 return %_SubString(subject, 0, match[CAPTURE0]) +
546 %_SubString(subject, match[CAPTURE1], subject.length)
547 }
548 return ExpandReplacement(replace, subject, RegExpLastMatchInfo,
549 %_SubString(subject, 0, match[CAPTURE0])) +
550 %_SubString(subject, match[CAPTURE1], subject.length);
551 }
552
553 // Global regexp search, string replace.
554 search.lastIndex = 0;
555 return %StringReplaceGlobalRegExpWithString(
556 subject, search, replace, RegExpLastMatchInfo);
557 }
558
559 if (REGEXP_GLOBAL(search)) {
560 // Global regexp search, function replace.
561 return StringReplaceGlobalRegExpWithFunction(subject, search, replace);
562 }
563 // Non-global regexp search, function replace.
564 return StringReplaceNonGlobalRegExpWithFunction(subject, search, replace);
565}
566
567
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000568// ES6 21.2.5.9.
569function RegExpSearch(string) {
570 // TODO(yangguo): allow non-regexp receivers.
571 if (!IS_REGEXP(this)) {
572 throw MakeTypeError(kIncompatibleMethodReceiver,
573 "RegExp.prototype.@@search", this);
574 }
575 var match = DoRegExpExec(this, TO_STRING(string), 0);
576 if (match) return match[CAPTURE0];
577 return -1;
578}
579
580
581// Getters for the static properties lastMatch, lastParen, leftContext, and
582// rightContext of the RegExp constructor. The properties are computed based
583// on the captures array of the last successful match and the subject string
584// of the last successful match.
585function RegExpGetLastMatch() {
586 var regExpSubject = LAST_SUBJECT(RegExpLastMatchInfo);
587 return %_SubString(regExpSubject,
588 RegExpLastMatchInfo[CAPTURE0],
589 RegExpLastMatchInfo[CAPTURE1]);
590}
591
592
593function RegExpGetLastParen() {
594 var length = NUMBER_OF_CAPTURES(RegExpLastMatchInfo);
595 if (length <= 2) return ''; // There were no captures.
596 // We match the SpiderMonkey behavior: return the substring defined by the
597 // last pair (after the first pair) of elements of the capture array even if
598 // it is empty.
599 var regExpSubject = LAST_SUBJECT(RegExpLastMatchInfo);
600 var start = RegExpLastMatchInfo[CAPTURE(length - 2)];
601 var end = RegExpLastMatchInfo[CAPTURE(length - 1)];
602 if (start != -1 && end != -1) {
603 return %_SubString(regExpSubject, start, end);
604 }
605 return "";
606}
607
608
609function RegExpGetLeftContext() {
610 var start_index;
611 var subject;
612 start_index = RegExpLastMatchInfo[CAPTURE0];
613 subject = LAST_SUBJECT(RegExpLastMatchInfo);
614 return %_SubString(subject, 0, start_index);
615}
616
617
618function RegExpGetRightContext() {
619 var start_index;
620 var subject;
621 start_index = RegExpLastMatchInfo[CAPTURE1];
622 subject = LAST_SUBJECT(RegExpLastMatchInfo);
623 return %_SubString(subject, start_index, subject.length);
624}
625
626
627// The properties $1..$9 are the first nine capturing substrings of the last
628// successful match, or ''. The function RegExpMakeCaptureGetter will be
629// called with indices from 1 to 9.
630function RegExpMakeCaptureGetter(n) {
631 return function foo() {
632 var index = n * 2;
633 if (index >= NUMBER_OF_CAPTURES(RegExpLastMatchInfo)) return '';
634 var matchStart = RegExpLastMatchInfo[CAPTURE(index)];
635 var matchEnd = RegExpLastMatchInfo[CAPTURE(index + 1)];
636 if (matchStart == -1 || matchEnd == -1) return '';
637 return %_SubString(LAST_SUBJECT(RegExpLastMatchInfo), matchStart, matchEnd);
638 };
639}
640
641
642// ES6 21.2.5.4.
643function RegExpGetGlobal() {
644 if (!IS_REGEXP(this)) {
645 // TODO(littledan): Remove this RegExp compat workaround
646 if (this === GlobalRegExpPrototype) {
647 return UNDEFINED;
648 }
649 throw MakeTypeError(kRegExpNonRegExp, "RegExp.prototype.global");
650 }
651 return !!REGEXP_GLOBAL(this);
652}
653%FunctionSetName(RegExpGetGlobal, "RegExp.prototype.global");
654%SetNativeFlag(RegExpGetGlobal);
655
656
657// ES6 21.2.5.5.
658function RegExpGetIgnoreCase() {
659 if (!IS_REGEXP(this)) {
660 // TODO(littledan): Remove this RegExp compat workaround
661 if (this === GlobalRegExpPrototype) {
662 return UNDEFINED;
663 }
664 throw MakeTypeError(kRegExpNonRegExp, "RegExp.prototype.ignoreCase");
665 }
666 return !!REGEXP_IGNORE_CASE(this);
667}
668%FunctionSetName(RegExpGetIgnoreCase, "RegExp.prototype.ignoreCase");
669%SetNativeFlag(RegExpGetIgnoreCase);
670
671
672// ES6 21.2.5.7.
673function RegExpGetMultiline() {
674 if (!IS_REGEXP(this)) {
675 // TODO(littledan): Remove this RegExp compat workaround
676 if (this === GlobalRegExpPrototype) {
677 return UNDEFINED;
678 }
679 throw MakeTypeError(kRegExpNonRegExp, "RegExp.prototype.multiline");
680 }
681 return !!REGEXP_MULTILINE(this);
682}
683%FunctionSetName(RegExpGetMultiline, "RegExp.prototype.multiline");
684%SetNativeFlag(RegExpGetMultiline);
685
686
687// ES6 21.2.5.10.
688function RegExpGetSource() {
689 if (!IS_REGEXP(this)) {
690 // TODO(littledan): Remove this RegExp compat workaround
691 if (this === GlobalRegExpPrototype) {
692 return UNDEFINED;
693 }
694 throw MakeTypeError(kRegExpNonRegExp, "RegExp.prototype.source");
695 }
696 return REGEXP_SOURCE(this);
697}
698%FunctionSetName(RegExpGetSource, "RegExp.prototype.source");
699%SetNativeFlag(RegExpGetSource);
700
701// -------------------------------------------------------------------
702
703%FunctionSetInstanceClassName(GlobalRegExp, 'RegExp');
704GlobalRegExpPrototype = new GlobalObject();
705%FunctionSetPrototype(GlobalRegExp, GlobalRegExpPrototype);
706%AddNamedProperty(
707 GlobalRegExp.prototype, 'constructor', GlobalRegExp, DONT_ENUM);
708%SetCode(GlobalRegExp, RegExpConstructor);
709
710utils.InstallFunctions(GlobalRegExp.prototype, DONT_ENUM, [
711 "exec", RegExpExecJS,
712 "test", RegExpTest,
713 "toString", RegExpToString,
714 "compile", RegExpCompileJS,
715 matchSymbol, RegExpMatch,
Ben Murdoch097c5b22016-05-18 11:27:45 +0100716 replaceSymbol, RegExpReplace,
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000717 searchSymbol, RegExpSearch,
718 splitSymbol, RegExpSplit,
719]);
720
721utils.InstallGetter(GlobalRegExp.prototype, 'global', RegExpGetGlobal);
722utils.InstallGetter(GlobalRegExp.prototype, 'ignoreCase', RegExpGetIgnoreCase);
723utils.InstallGetter(GlobalRegExp.prototype, 'multiline', RegExpGetMultiline);
724utils.InstallGetter(GlobalRegExp.prototype, 'source', RegExpGetSource);
725
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000726// The properties `input` and `$_` are aliases for each other. When this
727// value is set the value it is set to is coerced to a string.
728// Getter and setter for the input.
729var RegExpGetInput = function() {
730 var regExpInput = LAST_INPUT(RegExpLastMatchInfo);
731 return IS_UNDEFINED(regExpInput) ? "" : regExpInput;
732};
733var RegExpSetInput = function(string) {
734 LAST_INPUT(RegExpLastMatchInfo) = TO_STRING(string);
735};
736
737%OptimizeObjectForAddingMultipleProperties(GlobalRegExp, 22);
738utils.InstallGetterSetter(GlobalRegExp, 'input', RegExpGetInput, RegExpSetInput,
739 DONT_DELETE);
740utils.InstallGetterSetter(GlobalRegExp, '$_', RegExpGetInput, RegExpSetInput,
741 DONT_ENUM | DONT_DELETE);
742
743
744var NoOpSetter = function(ignored) {};
745
746
747// Static properties set by a successful match.
748utils.InstallGetterSetter(GlobalRegExp, 'lastMatch', RegExpGetLastMatch,
749 NoOpSetter, DONT_DELETE);
750utils.InstallGetterSetter(GlobalRegExp, '$&', RegExpGetLastMatch, NoOpSetter,
751 DONT_ENUM | DONT_DELETE);
752utils.InstallGetterSetter(GlobalRegExp, 'lastParen', RegExpGetLastParen,
753 NoOpSetter, DONT_DELETE);
754utils.InstallGetterSetter(GlobalRegExp, '$+', RegExpGetLastParen, NoOpSetter,
755 DONT_ENUM | DONT_DELETE);
756utils.InstallGetterSetter(GlobalRegExp, 'leftContext', RegExpGetLeftContext,
757 NoOpSetter, DONT_DELETE);
758utils.InstallGetterSetter(GlobalRegExp, '$`', RegExpGetLeftContext, NoOpSetter,
759 DONT_ENUM | DONT_DELETE);
760utils.InstallGetterSetter(GlobalRegExp, 'rightContext', RegExpGetRightContext,
761 NoOpSetter, DONT_DELETE);
762utils.InstallGetterSetter(GlobalRegExp, "$'", RegExpGetRightContext, NoOpSetter,
763 DONT_ENUM | DONT_DELETE);
764
765for (var i = 1; i < 10; ++i) {
766 utils.InstallGetterSetter(GlobalRegExp, '$' + i, RegExpMakeCaptureGetter(i),
767 NoOpSetter, DONT_DELETE);
768}
769%ToFastProperties(GlobalRegExp);
770
771// -------------------------------------------------------------------
772// Exports
773
774utils.Export(function(to) {
775 to.RegExpExec = DoRegExpExec;
776 to.RegExpExecNoTests = RegExpExecNoTests;
777 to.RegExpLastMatchInfo = RegExpLastMatchInfo;
778 to.RegExpTest = RegExpTest;
779});
780
781})