blob: 719a08100e5a386e1bef672b64c189c72873d9db [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 Murdochda12d292016-06-02 14:46:10 +010013var GlobalArray = global.Array;
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000014var GlobalObject = global.Object;
15var GlobalRegExp = global.RegExp;
16var GlobalRegExpPrototype;
17var InternalArray = utils.InternalArray;
18var InternalPackedArray = utils.InternalPackedArray;
19var MakeTypeError;
Ben Murdochda12d292016-06-02 14:46:10 +010020var MaxSimple;
21var MinSimple;
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000022var matchSymbol = utils.ImportNow("match_symbol");
Ben Murdoch097c5b22016-05-18 11:27:45 +010023var replaceSymbol = utils.ImportNow("replace_symbol");
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000024var searchSymbol = utils.ImportNow("search_symbol");
25var splitSymbol = utils.ImportNow("split_symbol");
Ben Murdochda12d292016-06-02 14:46:10 +010026var SpeciesConstructor;
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000027
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000028utils.Import(function(from) {
Ben Murdoch097c5b22016-05-18 11:27:45 +010029 ExpandReplacement = from.ExpandReplacement;
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000030 MakeTypeError = from.MakeTypeError;
Ben Murdochda12d292016-06-02 14:46:10 +010031 MaxSimple = from.MaxSimple;
32 MinSimple = from.MinSimple;
33 SpeciesConstructor = from.SpeciesConstructor;
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000034});
35
36// -------------------------------------------------------------------
37
38// Property of the builtins object for recording the result of the last
39// regexp match. The property RegExpLastMatchInfo includes the matchIndices
40// array of the last successful regexp match (an array of start/end index
41// pairs for the match and all the captured substrings), the invariant is
42// that there are at least two capture indeces. The array also contains
43// the subject string for the last successful match.
44var RegExpLastMatchInfo = new InternalPackedArray(
45 2, // REGEXP_NUMBER_OF_CAPTURES
46 "", // Last subject.
47 UNDEFINED, // Last input - settable with RegExpSetInput.
48 0, // REGEXP_FIRST_CAPTURE + 0
49 0 // REGEXP_FIRST_CAPTURE + 1
50);
51
52// -------------------------------------------------------------------
53
Ben Murdochda12d292016-06-02 14:46:10 +010054// ES#sec-isregexp IsRegExp ( argument )
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000055function IsRegExp(o) {
56 if (!IS_RECEIVER(o)) return false;
57 var is_regexp = o[matchSymbol];
58 if (!IS_UNDEFINED(is_regexp)) return TO_BOOLEAN(is_regexp);
59 return IS_REGEXP(o);
60}
61
62
Ben Murdochda12d292016-06-02 14:46:10 +010063// ES#sec-regexpinitialize
64// Runtime Semantics: RegExpInitialize ( obj, pattern, flags )
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000065function RegExpInitialize(object, pattern, flags) {
66 pattern = IS_UNDEFINED(pattern) ? '' : TO_STRING(pattern);
67 flags = IS_UNDEFINED(flags) ? '' : TO_STRING(flags);
68 %RegExpInitializeAndCompile(object, pattern, flags);
69 return object;
70}
71
72
73function PatternFlags(pattern) {
74 return (REGEXP_GLOBAL(pattern) ? 'g' : '') +
75 (REGEXP_IGNORE_CASE(pattern) ? 'i' : '') +
76 (REGEXP_MULTILINE(pattern) ? 'm' : '') +
77 (REGEXP_UNICODE(pattern) ? 'u' : '') +
78 (REGEXP_STICKY(pattern) ? 'y' : '');
79}
80
81
Ben Murdochda12d292016-06-02 14:46:10 +010082// ES#sec-regexp-pattern-flags
83// RegExp ( pattern, flags )
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000084function RegExpConstructor(pattern, flags) {
85 var newtarget = new.target;
86 var pattern_is_regexp = IsRegExp(pattern);
87
88 if (IS_UNDEFINED(newtarget)) {
89 newtarget = GlobalRegExp;
90
91 // ES6 section 21.2.3.1 step 3.b
92 if (pattern_is_regexp && IS_UNDEFINED(flags) &&
93 pattern.constructor === newtarget) {
94 return pattern;
95 }
96 }
97
98 if (IS_REGEXP(pattern)) {
99 if (IS_UNDEFINED(flags)) flags = PatternFlags(pattern);
100 pattern = REGEXP_SOURCE(pattern);
101
102 } else if (pattern_is_regexp) {
103 var input_pattern = pattern;
104 pattern = pattern.source;
105 if (IS_UNDEFINED(flags)) flags = input_pattern.flags;
106 }
107
Ben Murdochda12d292016-06-02 14:46:10 +0100108 var object = %_NewObject(GlobalRegExp, newtarget);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000109 return RegExpInitialize(object, pattern, flags);
110}
111
112
Ben Murdochda12d292016-06-02 14:46:10 +0100113// ES#sec-regexp.prototype.compile RegExp.prototype.compile (pattern, flags)
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000114function RegExpCompileJS(pattern, flags) {
115 if (!IS_REGEXP(this)) {
116 throw MakeTypeError(kIncompatibleMethodReceiver,
117 "RegExp.prototype.compile", this);
118 }
119
120 if (IS_REGEXP(pattern)) {
121 if (!IS_UNDEFINED(flags)) throw MakeTypeError(kRegExpFlags);
122
123 flags = PatternFlags(pattern);
124 pattern = REGEXP_SOURCE(pattern);
125 }
126
127 RegExpInitialize(this, pattern, flags);
128
129 // Return undefined for compatibility with JSC.
130 // See http://crbug.com/585775 for web compat details.
131}
132
133
134function DoRegExpExec(regexp, string, index) {
135 return %_RegExpExec(regexp, string, index, RegExpLastMatchInfo);
136}
137
138
139// This is kind of performance sensitive, so we want to avoid unnecessary
140// type checks on inputs. But we also don't want to inline it several times
141// manually, so we use a macro :-)
142macro RETURN_NEW_RESULT_FROM_MATCH_INFO(MATCHINFO, STRING)
143 var numResults = NUMBER_OF_CAPTURES(MATCHINFO) >> 1;
144 var start = MATCHINFO[CAPTURE0];
145 var end = MATCHINFO[CAPTURE1];
146 // Calculate the substring of the first match before creating the result array
147 // to avoid an unnecessary write barrier storing the first result.
148 var first = %_SubString(STRING, start, end);
149 var result = %_RegExpConstructResult(numResults, start, STRING);
150 result[0] = first;
151 if (numResults == 1) return result;
152 var j = REGEXP_FIRST_CAPTURE + 2;
153 for (var i = 1; i < numResults; i++) {
154 start = MATCHINFO[j++];
155 if (start != -1) {
156 end = MATCHINFO[j];
157 result[i] = %_SubString(STRING, start, end);
158 }
159 j++;
160 }
161 return result;
162endmacro
163
164
165function RegExpExecNoTests(regexp, string, start) {
166 // Must be called with RegExp, string and positive integer as arguments.
167 var matchInfo = %_RegExpExec(regexp, string, start, RegExpLastMatchInfo);
168 if (matchInfo !== null) {
169 // ES6 21.2.5.2.2 step 18.
170 if (REGEXP_STICKY(regexp)) regexp.lastIndex = matchInfo[CAPTURE1];
171 RETURN_NEW_RESULT_FROM_MATCH_INFO(matchInfo, string);
172 }
173 regexp.lastIndex = 0;
174 return null;
175}
176
177
Ben Murdochda12d292016-06-02 14:46:10 +0100178// ES#sec-regexp.prototype.exec
179// RegExp.prototype.exec ( string )
180function RegExpSubclassExecJS(string) {
181 if (!IS_REGEXP(this)) {
182 throw MakeTypeError(kIncompatibleMethodReceiver,
183 'RegExp.prototype.exec', this);
184 }
185
186 string = TO_STRING(string);
187 var lastIndex = this.lastIndex;
188
189 // Conversion is required by the ES2015 specification (RegExpBuiltinExec
190 // algorithm, step 4) even if the value is discarded for non-global RegExps.
191 var i = TO_LENGTH(lastIndex);
192
193 var global = TO_BOOLEAN(REGEXP_GLOBAL(this));
194 var sticky = TO_BOOLEAN(REGEXP_STICKY(this));
195 var updateLastIndex = global || sticky;
196 if (updateLastIndex) {
197 if (i > string.length) {
198 this.lastIndex = 0;
199 return null;
200 }
201 } else {
202 i = 0;
203 }
204
205 // matchIndices is either null or the RegExpLastMatchInfo array.
206 // TODO(littledan): Whether a RegExp is sticky is compiled into the RegExp
207 // itself, but ES2015 allows monkey-patching this property to differ from
208 // the internal flags. If it differs, recompile a different RegExp?
209 var matchIndices = %_RegExpExec(this, string, i, RegExpLastMatchInfo);
210
211 if (IS_NULL(matchIndices)) {
212 this.lastIndex = 0;
213 return null;
214 }
215
216 // Successful match.
217 if (updateLastIndex) {
218 this.lastIndex = RegExpLastMatchInfo[CAPTURE1];
219 }
220 RETURN_NEW_RESULT_FROM_MATCH_INFO(matchIndices, string);
221}
222%FunctionRemovePrototype(RegExpSubclassExecJS);
223
224
225// Legacy implementation of RegExp.prototype.exec
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000226function RegExpExecJS(string) {
227 if (!IS_REGEXP(this)) {
228 throw MakeTypeError(kIncompatibleMethodReceiver,
229 'RegExp.prototype.exec', this);
230 }
231
232 string = TO_STRING(string);
233 var lastIndex = this.lastIndex;
234
235 // Conversion is required by the ES2015 specification (RegExpBuiltinExec
236 // algorithm, step 4) even if the value is discarded for non-global RegExps.
Ben Murdoch097c5b22016-05-18 11:27:45 +0100237 var i = TO_LENGTH(lastIndex);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000238
239 var updateLastIndex = REGEXP_GLOBAL(this) || REGEXP_STICKY(this);
240 if (updateLastIndex) {
241 if (i < 0 || i > string.length) {
242 this.lastIndex = 0;
243 return null;
244 }
245 } else {
246 i = 0;
247 }
248
249 // matchIndices is either null or the RegExpLastMatchInfo array.
250 var matchIndices = %_RegExpExec(this, string, i, RegExpLastMatchInfo);
251
252 if (IS_NULL(matchIndices)) {
253 this.lastIndex = 0;
254 return null;
255 }
256
257 // Successful match.
258 if (updateLastIndex) {
259 this.lastIndex = RegExpLastMatchInfo[CAPTURE1];
260 }
261 RETURN_NEW_RESULT_FROM_MATCH_INFO(matchIndices, string);
262}
263
264
Ben Murdochda12d292016-06-02 14:46:10 +0100265// ES#sec-regexpexec Runtime Semantics: RegExpExec ( R, S )
266// Also takes an optional exec method in case our caller
267// has already fetched exec.
268function RegExpSubclassExec(regexp, string, exec) {
269 if (IS_UNDEFINED(exec)) {
270 exec = regexp.exec;
271 }
272 if (IS_CALLABLE(exec)) {
273 var result = %_Call(exec, regexp, string);
274 if (!IS_RECEIVER(result) && !IS_NULL(result)) {
275 throw MakeTypeError(kInvalidRegExpExecResult);
276 }
277 return result;
278 }
279 return %_Call(RegExpExecJS, regexp, string);
280}
281%SetForceInlineFlag(RegExpSubclassExec);
282
283
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000284// One-element cache for the simplified test regexp.
285var regexp_key;
286var regexp_val;
287
Ben Murdochda12d292016-06-02 14:46:10 +0100288// Legacy implementation of RegExp.prototype.test
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000289// Section 15.10.6.3 doesn't actually make sense, but the intention seems to be
290// that test is defined in terms of String.prototype.exec. However, it probably
291// means the original value of String.prototype.exec, which is what everybody
292// else implements.
293function RegExpTest(string) {
294 if (!IS_REGEXP(this)) {
295 throw MakeTypeError(kIncompatibleMethodReceiver,
296 'RegExp.prototype.test', this);
297 }
298 string = TO_STRING(string);
299
300 var lastIndex = this.lastIndex;
301
302 // Conversion is required by the ES2015 specification (RegExpBuiltinExec
303 // algorithm, step 4) even if the value is discarded for non-global RegExps.
Ben Murdoch097c5b22016-05-18 11:27:45 +0100304 var i = TO_LENGTH(lastIndex);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000305
306 if (REGEXP_GLOBAL(this) || REGEXP_STICKY(this)) {
307 if (i < 0 || i > string.length) {
308 this.lastIndex = 0;
309 return false;
310 }
311 // matchIndices is either null or the RegExpLastMatchInfo array.
312 var matchIndices = %_RegExpExec(this, string, i, RegExpLastMatchInfo);
313 if (IS_NULL(matchIndices)) {
314 this.lastIndex = 0;
315 return false;
316 }
317 this.lastIndex = RegExpLastMatchInfo[CAPTURE1];
318 return true;
319 } else {
320 // Non-global, non-sticky regexp.
321 // Remove irrelevant preceeding '.*' in a test regexp. The expression
322 // checks whether this.source starts with '.*' and that the third char is
323 // not a '?'. But see https://code.google.com/p/v8/issues/detail?id=3560
324 var regexp = this;
325 var source = REGEXP_SOURCE(regexp);
326 if (regexp.length >= 3 &&
327 %_StringCharCodeAt(regexp, 0) == 46 && // '.'
328 %_StringCharCodeAt(regexp, 1) == 42 && // '*'
329 %_StringCharCodeAt(regexp, 2) != 63) { // '?'
330 regexp = TrimRegExp(regexp);
331 }
332 // matchIndices is either null or the RegExpLastMatchInfo array.
333 var matchIndices = %_RegExpExec(regexp, string, 0, RegExpLastMatchInfo);
334 if (IS_NULL(matchIndices)) {
335 this.lastIndex = 0;
336 return false;
337 }
338 return true;
339 }
340}
341
Ben Murdochda12d292016-06-02 14:46:10 +0100342
343// ES#sec-regexp.prototype.test RegExp.prototype.test ( S )
344function RegExpSubclassTest(string) {
345 if (!IS_RECEIVER(this)) {
346 throw MakeTypeError(kIncompatibleMethodReceiver,
347 'RegExp.prototype.test', this);
348 }
349 string = TO_STRING(string);
350 var match = RegExpSubclassExec(this, string);
351 return !IS_NULL(match);
352}
353%FunctionRemovePrototype(RegExpSubclassTest);
354
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000355function TrimRegExp(regexp) {
Ben Murdoch097c5b22016-05-18 11:27:45 +0100356 if (regexp_key !== regexp) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000357 regexp_key = regexp;
358 regexp_val =
359 new GlobalRegExp(
360 %_SubString(REGEXP_SOURCE(regexp), 2, REGEXP_SOURCE(regexp).length),
361 (REGEXP_IGNORE_CASE(regexp) ? REGEXP_MULTILINE(regexp) ? "im" : "i"
362 : REGEXP_MULTILINE(regexp) ? "m" : ""));
363 }
364 return regexp_val;
365}
366
367
368function RegExpToString() {
Ben Murdochda12d292016-06-02 14:46:10 +0100369 if (!IS_RECEIVER(this)) {
370 throw MakeTypeError(
371 kIncompatibleMethodReceiver, 'RegExp.prototype.toString', this);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000372 }
Ben Murdochda12d292016-06-02 14:46:10 +0100373 if (this === GlobalRegExpPrototype) {
374 %IncrementUseCounter(kRegExpPrototypeToString);
375 }
376 return '/' + TO_STRING(this.source) + '/' + TO_STRING(this.flags);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000377}
378
379
Ben Murdoch097c5b22016-05-18 11:27:45 +0100380function AtSurrogatePair(subject, index) {
381 if (index + 1 >= subject.length) return false;
382 var first = %_StringCharCodeAt(subject, index);
383 if (first < 0xD800 || first > 0xDBFF) return false;
384 var second = %_StringCharCodeAt(subject, index + 1);
385 return second >= 0xDC00 || second <= 0xDFFF;
386}
387
388
Ben Murdochda12d292016-06-02 14:46:10 +0100389// Legacy implementation of RegExp.prototype[Symbol.split] which
390// doesn't properly call the underlying exec, @@species methods
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000391function RegExpSplit(string, limit) {
392 // TODO(yangguo): allow non-regexp receivers.
393 if (!IS_REGEXP(this)) {
394 throw MakeTypeError(kIncompatibleMethodReceiver,
395 "RegExp.prototype.@@split", this);
396 }
397 var separator = this;
398 var subject = TO_STRING(string);
399
400 limit = (IS_UNDEFINED(limit)) ? kMaxUint32 : TO_UINT32(limit);
401 var length = subject.length;
402
403 if (limit === 0) return [];
404
405 if (length === 0) {
406 if (DoRegExpExec(separator, subject, 0, 0) !== null) return [];
407 return [subject];
408 }
409
410 var currentIndex = 0;
411 var startIndex = 0;
412 var startMatch = 0;
413 var result = new InternalArray();
414
415 outer_loop:
416 while (true) {
417 if (startIndex === length) {
418 result[result.length] = %_SubString(subject, currentIndex, length);
419 break;
420 }
421
422 var matchInfo = DoRegExpExec(separator, subject, startIndex);
423 if (matchInfo === null || length === (startMatch = matchInfo[CAPTURE0])) {
424 result[result.length] = %_SubString(subject, currentIndex, length);
425 break;
426 }
427 var endIndex = matchInfo[CAPTURE1];
428
429 // We ignore a zero-length match at the currentIndex.
430 if (startIndex === endIndex && endIndex === currentIndex) {
Ben Murdoch097c5b22016-05-18 11:27:45 +0100431 if (REGEXP_UNICODE(this) && AtSurrogatePair(subject, startIndex)) {
432 startIndex += 2;
433 } else {
434 startIndex++;
435 }
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000436 continue;
437 }
438
439 result[result.length] = %_SubString(subject, currentIndex, startMatch);
440
441 if (result.length === limit) break;
442
443 var matchinfo_len = NUMBER_OF_CAPTURES(matchInfo) + REGEXP_FIRST_CAPTURE;
444 for (var i = REGEXP_FIRST_CAPTURE + 2; i < matchinfo_len; ) {
445 var start = matchInfo[i++];
446 var end = matchInfo[i++];
447 if (end != -1) {
448 result[result.length] = %_SubString(subject, start, end);
449 } else {
450 result[result.length] = UNDEFINED;
451 }
452 if (result.length === limit) break outer_loop;
453 }
454
455 startIndex = currentIndex = endIndex;
456 }
457
458 var array_result = [];
459 %MoveArrayContents(result, array_result);
460 return array_result;
461}
462
463
Ben Murdochda12d292016-06-02 14:46:10 +0100464// ES#sec-regexp.prototype-@@split
465// RegExp.prototype [ @@split ] ( string, limit )
466function RegExpSubclassSplit(string, limit) {
467 if (!IS_RECEIVER(this)) {
468 throw MakeTypeError(kIncompatibleMethodReceiver,
469 "RegExp.prototype.@@split", this);
470 }
471 string = TO_STRING(string);
472 var constructor = SpeciesConstructor(this, GlobalRegExp);
473 var flags = TO_STRING(this.flags);
474
475 // TODO(adamk): this fast path is wrong with respect to this.global
476 // and this.sticky, but hopefully the spec will remove those gets
477 // and thus make the assumption of 'exec' having no side-effects
478 // more correct. Also, we doesn't ensure that 'exec' is actually
479 // a data property on RegExp.prototype.
480 var exec;
481 if (IS_REGEXP(this) && constructor === GlobalRegExp) {
482 exec = this.exec;
483 if (exec === RegExpSubclassExecJS) {
484 return %_Call(RegExpSplit, this, string, limit);
485 }
486 }
487
488 var unicode = %StringIndexOf(flags, 'u', 0) >= 0;
489 var sticky = %StringIndexOf(flags, 'y', 0) >= 0;
490 var newFlags = sticky ? flags : flags + "y";
491 var splitter = new constructor(this, newFlags);
492 var array = new GlobalArray();
493 var arrayIndex = 0;
494 var lim = (IS_UNDEFINED(limit)) ? kMaxUint32 : TO_UINT32(limit);
495 var size = string.length;
496 var prevStringIndex = 0;
497 if (lim === 0) return array;
498 var result;
499 if (size === 0) {
500 result = RegExpSubclassExec(splitter, string);
Ben Murdochc5610432016-08-08 18:44:38 +0100501 if (IS_NULL(result)) %AddElement(array, 0, string);
Ben Murdochda12d292016-06-02 14:46:10 +0100502 return array;
503 }
504 var stringIndex = prevStringIndex;
505 while (stringIndex < size) {
506 splitter.lastIndex = stringIndex;
507 result = RegExpSubclassExec(splitter, string, exec);
508 // Ensure exec will be read again on the next loop through.
509 exec = UNDEFINED;
510 if (IS_NULL(result)) {
511 stringIndex += AdvanceStringIndex(string, stringIndex, unicode);
512 } else {
513 var end = MinSimple(TO_LENGTH(splitter.lastIndex), size);
Ben Murdochc5610432016-08-08 18:44:38 +0100514 if (end === prevStringIndex) {
Ben Murdochda12d292016-06-02 14:46:10 +0100515 stringIndex += AdvanceStringIndex(string, stringIndex, unicode);
516 } else {
Ben Murdochc5610432016-08-08 18:44:38 +0100517 %AddElement(
Ben Murdochda12d292016-06-02 14:46:10 +0100518 array, arrayIndex,
519 %_SubString(string, prevStringIndex, stringIndex));
520 arrayIndex++;
521 if (arrayIndex === lim) return array;
522 prevStringIndex = end;
523 var numberOfCaptures = MaxSimple(TO_LENGTH(result.length), 0);
524 for (var i = 1; i < numberOfCaptures; i++) {
Ben Murdochc5610432016-08-08 18:44:38 +0100525 %AddElement(array, arrayIndex, result[i]);
Ben Murdochda12d292016-06-02 14:46:10 +0100526 arrayIndex++;
527 if (arrayIndex === lim) return array;
528 }
529 stringIndex = prevStringIndex;
530 }
531 }
532 }
Ben Murdochc5610432016-08-08 18:44:38 +0100533 %AddElement(array, arrayIndex,
Ben Murdochda12d292016-06-02 14:46:10 +0100534 %_SubString(string, prevStringIndex, size));
535 return array;
536}
537%FunctionRemovePrototype(RegExpSubclassSplit);
538
539
540// Legacy implementation of RegExp.prototype[Symbol.match] which
541// doesn't properly call the underlying exec method
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000542function RegExpMatch(string) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000543 if (!IS_REGEXP(this)) {
544 throw MakeTypeError(kIncompatibleMethodReceiver,
545 "RegExp.prototype.@@match", this);
546 }
547 var subject = TO_STRING(string);
548
549 if (!REGEXP_GLOBAL(this)) return RegExpExecNoTests(this, subject, 0);
550 this.lastIndex = 0;
551 var result = %StringMatch(subject, this, RegExpLastMatchInfo);
552 return result;
553}
554
555
Ben Murdochda12d292016-06-02 14:46:10 +0100556// ES#sec-regexp.prototype-@@match
557// RegExp.prototype [ @@match ] ( string )
558function RegExpSubclassMatch(string) {
559 if (!IS_RECEIVER(this)) {
560 throw MakeTypeError(kIncompatibleMethodReceiver,
561 "RegExp.prototype.@@match", this);
562 }
563 string = TO_STRING(string);
564 var global = this.global;
565 if (!global) return RegExpSubclassExec(this, string);
566 var unicode = this.unicode;
567 this.lastIndex = 0;
568 var array = new InternalArray();
569 var n = 0;
570 var result;
571 while (true) {
572 result = RegExpSubclassExec(this, string);
573 if (IS_NULL(result)) {
574 if (n === 0) return null;
575 break;
576 }
577 var matchStr = TO_STRING(result[0]);
578 array[n] = matchStr;
579 if (matchStr === "") SetAdvancedStringIndex(this, string, unicode);
580 n++;
581 }
582 var resultArray = [];
583 %MoveArrayContents(array, resultArray);
584 return resultArray;
585}
586%FunctionRemovePrototype(RegExpSubclassMatch);
587
588
589// Legacy implementation of RegExp.prototype[Symbol.replace] which
590// doesn't properly call the underlying exec method.
Ben Murdoch097c5b22016-05-18 11:27:45 +0100591
592// TODO(lrn): This array will survive indefinitely if replace is never
593// called again. However, it will be empty, since the contents are cleared
594// in the finally block.
595var reusableReplaceArray = new InternalArray(4);
596
597// Helper function for replacing regular expressions with the result of a
598// function application in String.prototype.replace.
599function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) {
600 var resultArray = reusableReplaceArray;
601 if (resultArray) {
602 reusableReplaceArray = null;
603 } else {
604 // Inside a nested replace (replace called from the replacement function
605 // of another replace) or we have failed to set the reusable array
606 // back due to an exception in a replacement function. Create a new
607 // array to use in the future, or until the original is written back.
608 resultArray = new InternalArray(16);
609 }
610 var res = %RegExpExecMultiple(regexp,
611 subject,
612 RegExpLastMatchInfo,
613 resultArray);
614 regexp.lastIndex = 0;
615 if (IS_NULL(res)) {
616 // No matches at all.
617 reusableReplaceArray = resultArray;
618 return subject;
619 }
620 var len = res.length;
621 if (NUMBER_OF_CAPTURES(RegExpLastMatchInfo) == 2) {
622 // If the number of captures is two then there are no explicit captures in
623 // the regexp, just the implicit capture that captures the whole match. In
624 // this case we can simplify quite a bit and end up with something faster.
625 // The builder will consist of some integers that indicate slices of the
626 // input string and some replacements that were returned from the replace
627 // function.
628 var match_start = 0;
629 for (var i = 0; i < len; i++) {
630 var elem = res[i];
631 if (%_IsSmi(elem)) {
632 // Integers represent slices of the original string.
633 if (elem > 0) {
634 match_start = (elem >> 11) + (elem & 0x7ff);
635 } else {
636 match_start = res[++i] - elem;
637 }
638 } else {
639 var func_result = replace(elem, match_start, subject);
640 // Overwrite the i'th element in the results with the string we got
641 // back from the callback function.
642 res[i] = TO_STRING(func_result);
643 match_start += elem.length;
644 }
645 }
646 } else {
647 for (var i = 0; i < len; i++) {
648 var elem = res[i];
649 if (!%_IsSmi(elem)) {
650 // elem must be an Array.
651 // Use the apply argument as backing for global RegExp properties.
Ben Murdochda12d292016-06-02 14:46:10 +0100652 var func_result = %reflect_apply(replace, UNDEFINED, elem);
Ben Murdoch097c5b22016-05-18 11:27:45 +0100653 // Overwrite the i'th element in the results with the string we got
654 // back from the callback function.
655 res[i] = TO_STRING(func_result);
656 }
657 }
658 }
659 var result = %StringBuilderConcat(res, len, subject);
660 resultArray.length = 0;
661 reusableReplaceArray = resultArray;
662 return result;
663}
664
665
666// Compute the string of a given regular expression capture.
667function CaptureString(string, lastCaptureInfo, index) {
668 // Scale the index.
669 var scaled = index << 1;
670 // Compute start and end.
671 var start = lastCaptureInfo[CAPTURE(scaled)];
672 // If start isn't valid, return undefined.
673 if (start < 0) return;
674 var end = lastCaptureInfo[CAPTURE(scaled + 1)];
675 return %_SubString(string, start, end);
676}
677
678
679function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) {
680 var matchInfo = DoRegExpExec(regexp, subject, 0);
681 if (IS_NULL(matchInfo)) {
682 regexp.lastIndex = 0;
683 return subject;
684 }
685 var index = matchInfo[CAPTURE0];
686 var result = %_SubString(subject, 0, index);
687 var endOfMatch = matchInfo[CAPTURE1];
688 // Compute the parameter list consisting of the match, captures, index,
689 // and subject for the replace function invocation.
690 // The number of captures plus one for the match.
691 var m = NUMBER_OF_CAPTURES(matchInfo) >> 1;
692 var replacement;
693 if (m == 1) {
694 // No captures, only the match, which is always valid.
695 var s = %_SubString(subject, index, endOfMatch);
696 // Don't call directly to avoid exposing the built-in global object.
697 replacement = replace(s, index, subject);
698 } else {
699 var parameters = new InternalArray(m + 2);
700 for (var j = 0; j < m; j++) {
701 parameters[j] = CaptureString(subject, matchInfo, j);
702 }
703 parameters[j] = index;
704 parameters[j + 1] = subject;
705
Ben Murdochda12d292016-06-02 14:46:10 +0100706 replacement = %reflect_apply(replace, UNDEFINED, parameters);
Ben Murdoch097c5b22016-05-18 11:27:45 +0100707 }
708
709 result += replacement; // The add method converts to string if necessary.
710 // Can't use matchInfo any more from here, since the function could
711 // overwrite it.
712 return result + %_SubString(subject, endOfMatch, subject.length);
713}
714
715
716function RegExpReplace(string, replace) {
Ben Murdoch097c5b22016-05-18 11:27:45 +0100717 if (!IS_REGEXP(this)) {
718 throw MakeTypeError(kIncompatibleMethodReceiver,
719 "RegExp.prototype.@@replace", this);
720 }
721 var subject = TO_STRING(string);
722 var search = this;
723
724 if (!IS_CALLABLE(replace)) {
725 replace = TO_STRING(replace);
726
727 if (!REGEXP_GLOBAL(search)) {
728 // Non-global regexp search, string replace.
729 var match = DoRegExpExec(search, subject, 0);
730 if (match == null) {
731 search.lastIndex = 0
732 return subject;
733 }
734 if (replace.length == 0) {
735 return %_SubString(subject, 0, match[CAPTURE0]) +
736 %_SubString(subject, match[CAPTURE1], subject.length)
737 }
738 return ExpandReplacement(replace, subject, RegExpLastMatchInfo,
739 %_SubString(subject, 0, match[CAPTURE0])) +
740 %_SubString(subject, match[CAPTURE1], subject.length);
741 }
742
743 // Global regexp search, string replace.
744 search.lastIndex = 0;
745 return %StringReplaceGlobalRegExpWithString(
746 subject, search, replace, RegExpLastMatchInfo);
747 }
748
749 if (REGEXP_GLOBAL(search)) {
750 // Global regexp search, function replace.
751 return StringReplaceGlobalRegExpWithFunction(subject, search, replace);
752 }
753 // Non-global regexp search, function replace.
754 return StringReplaceNonGlobalRegExpWithFunction(subject, search, replace);
755}
756
757
Ben Murdochda12d292016-06-02 14:46:10 +0100758// ES#sec-getsubstitution
759// GetSubstitution(matched, str, position, captures, replacement)
760// Expand the $-expressions in the string and return a new string with
761// the result.
762// TODO(littledan): Call this function from String.prototype.replace instead
763// of the very similar ExpandReplacement in src/js/string.js
764function GetSubstitution(matched, string, position, captures, replacement) {
765 var matchLength = matched.length;
766 var stringLength = string.length;
767 var capturesLength = captures.length;
768 var tailPos = position + matchLength;
769 var result = "";
770 var pos, expansion, peek, next, scaledIndex, advance, newScaledIndex;
771
772 var next = %StringIndexOf(replacement, '$', 0);
773 if (next < 0) {
774 result += replacement;
775 return result;
776 }
777
778 if (next > 0) result += %_SubString(replacement, 0, next);
779
780 while (true) {
781 expansion = '$';
782 pos = next + 1;
783 if (pos < replacement.length) {
784 peek = %_StringCharCodeAt(replacement, pos);
785 if (peek == 36) { // $$
786 ++pos;
787 result += '$';
788 } else if (peek == 38) { // $& - match
789 ++pos;
790 result += matched;
791 } else if (peek == 96) { // $` - prefix
792 ++pos;
793 result += %_SubString(string, 0, position);
794 } else if (peek == 39) { // $' - suffix
795 ++pos;
796 result += %_SubString(string, tailPos, stringLength);
797 } else if (peek >= 48 && peek <= 57) {
798 // Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99
799 scaledIndex = (peek - 48);
800 advance = 1;
801 if (pos + 1 < replacement.length) {
802 next = %_StringCharCodeAt(replacement, pos + 1);
803 if (next >= 48 && next <= 57) {
804 newScaledIndex = scaledIndex * 10 + ((next - 48));
805 if (newScaledIndex < capturesLength) {
806 scaledIndex = newScaledIndex;
807 advance = 2;
808 }
809 }
810 }
811 if (scaledIndex != 0 && scaledIndex < capturesLength) {
812 var capture = captures[scaledIndex];
813 if (!IS_UNDEFINED(capture)) result += capture;
814 pos += advance;
815 } else {
816 result += '$';
817 }
818 } else {
819 result += '$';
820 }
821 } else {
822 result += '$';
823 }
824
825 // Go the the next $ in the replacement.
826 next = %StringIndexOf(replacement, '$', pos);
827
828 // Return if there are no more $ characters in the replacement. If we
829 // haven't reached the end, we need to append the suffix.
830 if (next < 0) {
831 if (pos < replacement.length) {
832 result += %_SubString(replacement, pos, replacement.length);
833 }
834 return result;
835 }
836
837 // Append substring between the previous and the next $ character.
838 if (next > pos) {
839 result += %_SubString(replacement, pos, next);
840 }
841 }
842 return result;
843}
844
845
846// ES#sec-advancestringindex
847// AdvanceStringIndex ( S, index, unicode )
848function AdvanceStringIndex(string, index, unicode) {
849 var increment = 1;
850 if (unicode) {
851 var first = %_StringCharCodeAt(string, index);
852 if (first >= 0xD800 && first <= 0xDBFF && string.length > index + 1) {
853 var second = %_StringCharCodeAt(string, index + 1);
854 if (second >= 0xDC00 && second <= 0xDFFF) {
855 increment = 2;
856 }
857 }
858 }
859 return increment;
860}
861
862
863function SetAdvancedStringIndex(regexp, string, unicode) {
864 var lastIndex = regexp.lastIndex;
865 regexp.lastIndex = lastIndex +
866 AdvanceStringIndex(string, lastIndex, unicode);
867}
868
869
870// ES#sec-regexp.prototype-@@replace
871// RegExp.prototype [ @@replace ] ( string, replaceValue )
872function RegExpSubclassReplace(string, replace) {
873 if (!IS_RECEIVER(this)) {
874 throw MakeTypeError(kIncompatibleMethodReceiver,
875 "RegExp.prototype.@@replace", this);
876 }
877 string = TO_STRING(string);
878 var length = string.length;
879 var functionalReplace = IS_CALLABLE(replace);
880 if (!functionalReplace) replace = TO_STRING(replace);
881 var global = TO_BOOLEAN(this.global);
882 if (global) {
883 var unicode = TO_BOOLEAN(this.unicode);
884 this.lastIndex = 0;
885 }
886
887 // TODO(adamk): this fast path is wrong with respect to this.global
888 // and this.sticky, but hopefully the spec will remove those gets
889 // and thus make the assumption of 'exec' having no side-effects
890 // more correct. Also, we doesn't ensure that 'exec' is actually
891 // a data property on RegExp.prototype, nor does the fast path
892 // correctly handle lastIndex setting.
893 var exec;
894 if (IS_REGEXP(this)) {
895 exec = this.exec;
896 if (exec === RegExpSubclassExecJS) {
897 return %_Call(RegExpReplace, this, string, replace);
898 }
899 }
900
901 var results = new InternalArray();
902 var result, replacement;
903 while (true) {
904 result = RegExpSubclassExec(this, string, exec);
905 // Ensure exec will be read again on the next loop through.
906 exec = UNDEFINED;
907 if (IS_NULL(result)) {
908 break;
909 } else {
910 results.push(result);
911 if (!global) break;
912 var matchStr = TO_STRING(result[0]);
913 if (matchStr === "") SetAdvancedStringIndex(this, string, unicode);
914 }
915 }
916 var accumulatedResult = "";
917 var nextSourcePosition = 0;
918 for (var i = 0; i < results.length; i++) {
919 result = results[i];
920 var capturesLength = MaxSimple(TO_LENGTH(result.length), 0);
921 var matched = TO_STRING(result[0]);
922 var matchedLength = matched.length;
923 var position = MaxSimple(MinSimple(TO_INTEGER(result.index), length), 0);
924 var captures = new InternalArray();
925 for (var n = 0; n < capturesLength; n++) {
926 var capture = result[n];
927 if (!IS_UNDEFINED(capture)) capture = TO_STRING(capture);
928 captures[n] = capture;
929 }
930 if (functionalReplace) {
931 var parameters = new InternalArray(capturesLength + 2);
932 for (var j = 0; j < capturesLength; j++) {
933 parameters[j] = captures[j];
934 }
935 parameters[j] = position;
936 parameters[j + 1] = string;
937 replacement = %reflect_apply(replace, UNDEFINED, parameters, 0,
938 parameters.length);
939 } else {
940 replacement = GetSubstitution(matched, string, position, captures,
941 replace);
942 }
943 if (position >= nextSourcePosition) {
944 accumulatedResult +=
945 %_SubString(string, nextSourcePosition, position) + replacement;
946 nextSourcePosition = position + matchedLength;
947 }
948 }
949 if (nextSourcePosition >= length) return accumulatedResult;
950 return accumulatedResult + %_SubString(string, nextSourcePosition, length);
951}
952%FunctionRemovePrototype(RegExpSubclassReplace);
953
954
955// Legacy implementation of RegExp.prototype[Symbol.search] which
956// doesn't properly use the overridden exec method
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000957function RegExpSearch(string) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000958 if (!IS_REGEXP(this)) {
959 throw MakeTypeError(kIncompatibleMethodReceiver,
960 "RegExp.prototype.@@search", this);
961 }
962 var match = DoRegExpExec(this, TO_STRING(string), 0);
963 if (match) return match[CAPTURE0];
964 return -1;
965}
966
967
Ben Murdochda12d292016-06-02 14:46:10 +0100968// ES#sec-regexp.prototype-@@search
969// RegExp.prototype [ @@search ] ( string )
970function RegExpSubclassSearch(string) {
971 if (!IS_RECEIVER(this)) {
972 throw MakeTypeError(kIncompatibleMethodReceiver,
973 "RegExp.prototype.@@search", this);
974 }
975 string = TO_STRING(string);
976 var previousLastIndex = this.lastIndex;
977 this.lastIndex = 0;
978 var result = RegExpSubclassExec(this, string);
979 this.lastIndex = previousLastIndex;
980 if (IS_NULL(result)) return -1;
981 return result.index;
982}
983%FunctionRemovePrototype(RegExpSubclassSearch);
984
985
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000986// Getters for the static properties lastMatch, lastParen, leftContext, and
987// rightContext of the RegExp constructor. The properties are computed based
988// on the captures array of the last successful match and the subject string
989// of the last successful match.
990function RegExpGetLastMatch() {
991 var regExpSubject = LAST_SUBJECT(RegExpLastMatchInfo);
992 return %_SubString(regExpSubject,
993 RegExpLastMatchInfo[CAPTURE0],
994 RegExpLastMatchInfo[CAPTURE1]);
995}
996
997
998function RegExpGetLastParen() {
999 var length = NUMBER_OF_CAPTURES(RegExpLastMatchInfo);
1000 if (length <= 2) return ''; // There were no captures.
1001 // We match the SpiderMonkey behavior: return the substring defined by the
1002 // last pair (after the first pair) of elements of the capture array even if
1003 // it is empty.
1004 var regExpSubject = LAST_SUBJECT(RegExpLastMatchInfo);
1005 var start = RegExpLastMatchInfo[CAPTURE(length - 2)];
1006 var end = RegExpLastMatchInfo[CAPTURE(length - 1)];
1007 if (start != -1 && end != -1) {
1008 return %_SubString(regExpSubject, start, end);
1009 }
1010 return "";
1011}
1012
1013
1014function RegExpGetLeftContext() {
1015 var start_index;
1016 var subject;
1017 start_index = RegExpLastMatchInfo[CAPTURE0];
1018 subject = LAST_SUBJECT(RegExpLastMatchInfo);
1019 return %_SubString(subject, 0, start_index);
1020}
1021
1022
1023function RegExpGetRightContext() {
1024 var start_index;
1025 var subject;
1026 start_index = RegExpLastMatchInfo[CAPTURE1];
1027 subject = LAST_SUBJECT(RegExpLastMatchInfo);
1028 return %_SubString(subject, start_index, subject.length);
1029}
1030
1031
1032// The properties $1..$9 are the first nine capturing substrings of the last
1033// successful match, or ''. The function RegExpMakeCaptureGetter will be
1034// called with indices from 1 to 9.
1035function RegExpMakeCaptureGetter(n) {
1036 return function foo() {
1037 var index = n * 2;
1038 if (index >= NUMBER_OF_CAPTURES(RegExpLastMatchInfo)) return '';
1039 var matchStart = RegExpLastMatchInfo[CAPTURE(index)];
1040 var matchEnd = RegExpLastMatchInfo[CAPTURE(index + 1)];
1041 if (matchStart == -1 || matchEnd == -1) return '';
1042 return %_SubString(LAST_SUBJECT(RegExpLastMatchInfo), matchStart, matchEnd);
1043 };
1044}
1045
1046
Ben Murdochda12d292016-06-02 14:46:10 +01001047// ES6 21.2.5.3.
1048function RegExpGetFlags() {
1049 if (!IS_RECEIVER(this)) {
1050 throw MakeTypeError(
1051 kRegExpNonObject, "RegExp.prototype.flags", TO_STRING(this));
1052 }
1053 var result = '';
1054 if (this.global) result += 'g';
1055 if (this.ignoreCase) result += 'i';
1056 if (this.multiline) result += 'm';
1057 if (this.unicode) result += 'u';
1058 if (this.sticky) result += 'y';
1059 return result;
1060}
1061
1062
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001063// ES6 21.2.5.4.
1064function RegExpGetGlobal() {
1065 if (!IS_REGEXP(this)) {
1066 // TODO(littledan): Remove this RegExp compat workaround
1067 if (this === GlobalRegExpPrototype) {
Ben Murdochda12d292016-06-02 14:46:10 +01001068 %IncrementUseCounter(kRegExpPrototypeOldFlagGetter);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001069 return UNDEFINED;
1070 }
1071 throw MakeTypeError(kRegExpNonRegExp, "RegExp.prototype.global");
1072 }
Ben Murdochda12d292016-06-02 14:46:10 +01001073 return TO_BOOLEAN(REGEXP_GLOBAL(this));
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001074}
Ben Murdochda12d292016-06-02 14:46:10 +01001075%SetForceInlineFlag(RegExpGetGlobal);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001076
1077
1078// ES6 21.2.5.5.
1079function RegExpGetIgnoreCase() {
1080 if (!IS_REGEXP(this)) {
1081 // TODO(littledan): Remove this RegExp compat workaround
1082 if (this === GlobalRegExpPrototype) {
Ben Murdochda12d292016-06-02 14:46:10 +01001083 %IncrementUseCounter(kRegExpPrototypeOldFlagGetter);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001084 return UNDEFINED;
1085 }
1086 throw MakeTypeError(kRegExpNonRegExp, "RegExp.prototype.ignoreCase");
1087 }
Ben Murdochda12d292016-06-02 14:46:10 +01001088 return TO_BOOLEAN(REGEXP_IGNORE_CASE(this));
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001089}
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001090
1091
1092// ES6 21.2.5.7.
1093function RegExpGetMultiline() {
1094 if (!IS_REGEXP(this)) {
1095 // TODO(littledan): Remove this RegExp compat workaround
1096 if (this === GlobalRegExpPrototype) {
Ben Murdochda12d292016-06-02 14:46:10 +01001097 %IncrementUseCounter(kRegExpPrototypeOldFlagGetter);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001098 return UNDEFINED;
1099 }
1100 throw MakeTypeError(kRegExpNonRegExp, "RegExp.prototype.multiline");
1101 }
Ben Murdochda12d292016-06-02 14:46:10 +01001102 return TO_BOOLEAN(REGEXP_MULTILINE(this));
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001103}
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001104
1105
1106// ES6 21.2.5.10.
1107function RegExpGetSource() {
1108 if (!IS_REGEXP(this)) {
1109 // TODO(littledan): Remove this RegExp compat workaround
1110 if (this === GlobalRegExpPrototype) {
Ben Murdochda12d292016-06-02 14:46:10 +01001111 %IncrementUseCounter(kRegExpPrototypeSourceGetter);
1112 return "(?:)";
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001113 }
1114 throw MakeTypeError(kRegExpNonRegExp, "RegExp.prototype.source");
1115 }
1116 return REGEXP_SOURCE(this);
1117}
Ben Murdochda12d292016-06-02 14:46:10 +01001118
1119
1120// ES6 21.2.5.12.
1121function RegExpGetSticky() {
1122 if (!IS_REGEXP(this)) {
1123 // Compat fix: RegExp.prototype.sticky == undefined; UseCounter tracks it
1124 // TODO(littledan): Remove this workaround or standardize it
1125 if (this === GlobalRegExpPrototype) {
1126 %IncrementUseCounter(kRegExpPrototypeStickyGetter);
1127 return UNDEFINED;
1128 }
1129 throw MakeTypeError(kRegExpNonRegExp, "RegExp.prototype.sticky");
1130 }
1131 return TO_BOOLEAN(REGEXP_STICKY(this));
1132}
1133%SetForceInlineFlag(RegExpGetSticky);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001134
1135// -------------------------------------------------------------------
1136
1137%FunctionSetInstanceClassName(GlobalRegExp, 'RegExp');
1138GlobalRegExpPrototype = new GlobalObject();
1139%FunctionSetPrototype(GlobalRegExp, GlobalRegExpPrototype);
1140%AddNamedProperty(
1141 GlobalRegExp.prototype, 'constructor', GlobalRegExp, DONT_ENUM);
1142%SetCode(GlobalRegExp, RegExpConstructor);
1143
1144utils.InstallFunctions(GlobalRegExp.prototype, DONT_ENUM, [
1145 "exec", RegExpExecJS,
1146 "test", RegExpTest,
1147 "toString", RegExpToString,
1148 "compile", RegExpCompileJS,
1149 matchSymbol, RegExpMatch,
Ben Murdoch097c5b22016-05-18 11:27:45 +01001150 replaceSymbol, RegExpReplace,
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001151 searchSymbol, RegExpSearch,
1152 splitSymbol, RegExpSplit,
1153]);
1154
Ben Murdochda12d292016-06-02 14:46:10 +01001155utils.InstallGetter(GlobalRegExp.prototype, 'flags', RegExpGetFlags);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001156utils.InstallGetter(GlobalRegExp.prototype, 'global', RegExpGetGlobal);
1157utils.InstallGetter(GlobalRegExp.prototype, 'ignoreCase', RegExpGetIgnoreCase);
1158utils.InstallGetter(GlobalRegExp.prototype, 'multiline', RegExpGetMultiline);
1159utils.InstallGetter(GlobalRegExp.prototype, 'source', RegExpGetSource);
Ben Murdochda12d292016-06-02 14:46:10 +01001160utils.InstallGetter(GlobalRegExp.prototype, 'sticky', RegExpGetSticky);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001161
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001162// The properties `input` and `$_` are aliases for each other. When this
1163// value is set the value it is set to is coerced to a string.
1164// Getter and setter for the input.
1165var RegExpGetInput = function() {
1166 var regExpInput = LAST_INPUT(RegExpLastMatchInfo);
1167 return IS_UNDEFINED(regExpInput) ? "" : regExpInput;
1168};
1169var RegExpSetInput = function(string) {
1170 LAST_INPUT(RegExpLastMatchInfo) = TO_STRING(string);
1171};
1172
1173%OptimizeObjectForAddingMultipleProperties(GlobalRegExp, 22);
1174utils.InstallGetterSetter(GlobalRegExp, 'input', RegExpGetInput, RegExpSetInput,
1175 DONT_DELETE);
1176utils.InstallGetterSetter(GlobalRegExp, '$_', RegExpGetInput, RegExpSetInput,
1177 DONT_ENUM | DONT_DELETE);
1178
1179
1180var NoOpSetter = function(ignored) {};
1181
1182
1183// Static properties set by a successful match.
1184utils.InstallGetterSetter(GlobalRegExp, 'lastMatch', RegExpGetLastMatch,
1185 NoOpSetter, DONT_DELETE);
1186utils.InstallGetterSetter(GlobalRegExp, '$&', RegExpGetLastMatch, NoOpSetter,
1187 DONT_ENUM | DONT_DELETE);
1188utils.InstallGetterSetter(GlobalRegExp, 'lastParen', RegExpGetLastParen,
1189 NoOpSetter, DONT_DELETE);
1190utils.InstallGetterSetter(GlobalRegExp, '$+', RegExpGetLastParen, NoOpSetter,
1191 DONT_ENUM | DONT_DELETE);
1192utils.InstallGetterSetter(GlobalRegExp, 'leftContext', RegExpGetLeftContext,
1193 NoOpSetter, DONT_DELETE);
1194utils.InstallGetterSetter(GlobalRegExp, '$`', RegExpGetLeftContext, NoOpSetter,
1195 DONT_ENUM | DONT_DELETE);
1196utils.InstallGetterSetter(GlobalRegExp, 'rightContext', RegExpGetRightContext,
1197 NoOpSetter, DONT_DELETE);
1198utils.InstallGetterSetter(GlobalRegExp, "$'", RegExpGetRightContext, NoOpSetter,
1199 DONT_ENUM | DONT_DELETE);
1200
1201for (var i = 1; i < 10; ++i) {
1202 utils.InstallGetterSetter(GlobalRegExp, '$' + i, RegExpMakeCaptureGetter(i),
1203 NoOpSetter, DONT_DELETE);
1204}
1205%ToFastProperties(GlobalRegExp);
1206
1207// -------------------------------------------------------------------
Ben Murdochda12d292016-06-02 14:46:10 +01001208// Internal
1209
1210var InternalRegExpMatchInfo = new InternalPackedArray(2, "", UNDEFINED, 0, 0);
1211
1212function InternalRegExpMatch(regexp, subject) {
1213 var matchInfo = %_RegExpExec(regexp, subject, 0, InternalRegExpMatchInfo);
1214 if (!IS_NULL(matchInfo)) {
1215 RETURN_NEW_RESULT_FROM_MATCH_INFO(matchInfo, subject);
1216 }
1217 return null;
1218}
1219
1220function InternalRegExpReplace(regexp, subject, replacement) {
1221 return %StringReplaceGlobalRegExpWithString(
1222 subject, regexp, replacement, InternalRegExpMatchInfo);
1223}
1224
1225// -------------------------------------------------------------------
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001226// Exports
1227
1228utils.Export(function(to) {
Ben Murdochda12d292016-06-02 14:46:10 +01001229 to.InternalRegExpMatch = InternalRegExpMatch;
1230 to.InternalRegExpReplace = InternalRegExpReplace;
1231 to.IsRegExp = IsRegExp;
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001232 to.RegExpExec = DoRegExpExec;
Ben Murdochda12d292016-06-02 14:46:10 +01001233 to.RegExpInitialize = RegExpInitialize;
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001234 to.RegExpLastMatchInfo = RegExpLastMatchInfo;
Ben Murdochda12d292016-06-02 14:46:10 +01001235 to.RegExpSubclassExecJS = RegExpSubclassExecJS;
1236 to.RegExpSubclassMatch = RegExpSubclassMatch;
1237 to.RegExpSubclassReplace = RegExpSubclassReplace;
1238 to.RegExpSubclassSearch = RegExpSubclassSearch;
1239 to.RegExpSubclassSplit = RegExpSubclassSplit;
1240 to.RegExpSubclassTest = RegExpSubclassTest;
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001241 to.RegExpTest = RegExpTest;
1242});
1243
1244})