OLD | NEW |
1 // Copyright 2012 the V8 project authors. All rights reserved. | 1 // 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 // This file relies on the fact that the following declaration has been made | 5 // This file relies on the fact that the following declaration has been made |
6 // in runtime.js: | 6 // in runtime.js: |
7 // var $Object = global.Object; | 7 // var $Object = global.Object; |
8 // var $Array = global.Array; | 8 // var $Array = global.Array; |
9 | 9 |
10 var $RegExp = global.RegExp; | 10 var $RegExp = global.RegExp; |
11 | 11 |
12 // ------------------------------------------------------------------- | 12 // ------------------------------------------------------------------- |
13 | 13 |
14 // A recursive descent parser for Patterns according to the grammar of | 14 // A recursive descent parser for Patterns according to the grammar of |
15 // ECMA-262 15.10.1, with deviations noted below. | 15 // ECMA-262 15.10.1, with deviations noted below. |
16 function DoConstructRegExp(object, pattern, flags) { | 16 function DoConstructRegExp(object, pattern, flags) { |
17 // RegExp : Called as constructor; see ECMA-262, section 15.10.4. | 17 // RegExp : Called as constructor; see ECMA-262, section 15.10.4. |
18 if (IS_REGEXP(pattern)) { | 18 if (IS_REGEXP(pattern)) { |
19 if (!IS_UNDEFINED(flags)) { | 19 if (!IS_UNDEFINED(flags)) { |
20 throw MakeTypeError('regexp_flags', []); | 20 throw MakeTypeError('regexp_flags', []); |
21 } | 21 } |
22 flags = (pattern.global ? 'g' : '') | 22 flags = (pattern.global ? 'g' : '') |
23 + (pattern.ignoreCase ? 'i' : '') | 23 + (pattern.ignoreCase ? 'i' : '') |
24 + (pattern.multiline ? 'm' : ''); | 24 + (pattern.multiline ? 'm' : ''); |
25 if (harmony_regexps) | |
26 flags += (pattern.sticky ? 'y' : ''); | |
27 pattern = pattern.source; | 25 pattern = pattern.source; |
28 } | 26 } |
29 | 27 |
30 pattern = IS_UNDEFINED(pattern) ? '' : ToString(pattern); | 28 pattern = IS_UNDEFINED(pattern) ? '' : ToString(pattern); |
31 flags = IS_UNDEFINED(flags) ? '' : ToString(flags); | 29 flags = IS_UNDEFINED(flags) ? '' : ToString(flags); |
32 | 30 |
33 var global = false; | 31 var global = false; |
34 var ignoreCase = false; | 32 var ignoreCase = false; |
35 var multiline = false; | 33 var multiline = false; |
36 var sticky = false; | |
37 for (var i = 0; i < flags.length; i++) { | 34 for (var i = 0; i < flags.length; i++) { |
38 var c = %_CallFunction(flags, i, StringCharAt); | 35 var c = %_CallFunction(flags, i, StringCharAt); |
39 switch (c) { | 36 switch (c) { |
40 case 'g': | 37 case 'g': |
41 if (global) { | 38 if (global) { |
42 throw MakeSyntaxError("invalid_regexp_flags", [flags]); | 39 throw MakeSyntaxError("invalid_regexp_flags", [flags]); |
43 } | 40 } |
44 global = true; | 41 global = true; |
45 break; | 42 break; |
46 case 'i': | 43 case 'i': |
47 if (ignoreCase) { | 44 if (ignoreCase) { |
48 throw MakeSyntaxError("invalid_regexp_flags", [flags]); | 45 throw MakeSyntaxError("invalid_regexp_flags", [flags]); |
49 } | 46 } |
50 ignoreCase = true; | 47 ignoreCase = true; |
51 break; | 48 break; |
52 case 'm': | 49 case 'm': |
53 if (multiline) { | 50 if (multiline) { |
54 throw MakeSyntaxError("invalid_regexp_flags", [flags]); | 51 throw MakeSyntaxError("invalid_regexp_flags", [flags]); |
55 } | 52 } |
56 multiline = true; | 53 multiline = true; |
57 break; | 54 break; |
58 case 'y': | |
59 if (!harmony_regexps || sticky) { | |
60 throw MakeSyntaxError("invalid_regexp_flags", [flags]); | |
61 } | |
62 sticky = true; | |
63 break; | |
64 default: | 55 default: |
65 throw MakeSyntaxError("invalid_regexp_flags", [flags]); | 56 throw MakeSyntaxError("invalid_regexp_flags", [flags]); |
66 } | 57 } |
67 } | 58 } |
68 | 59 |
69 %RegExpInitializeObject(object, pattern, global, ignoreCase, multiline, sticky
); | 60 %RegExpInitializeObject(object, pattern, global, ignoreCase, multiline); |
70 | 61 |
71 // Call internal function to compile the pattern. | 62 // Call internal function to compile the pattern. |
72 %RegExpCompile(object, pattern, flags); | 63 %RegExpCompile(object, pattern, flags); |
73 } | 64 } |
74 | 65 |
75 | 66 |
76 function RegExpConstructor(pattern, flags) { | 67 function RegExpConstructor(pattern, flags) { |
77 if (%_IsConstructCall()) { | 68 if (%_IsConstructCall()) { |
78 DoConstructRegExp(this, pattern, flags); | 69 DoConstructRegExp(this, pattern, flags); |
79 } else { | 70 } else { |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
161 ['RegExp.prototype.exec', this]); | 152 ['RegExp.prototype.exec', this]); |
162 } | 153 } |
163 | 154 |
164 string = TO_STRING_INLINE(string); | 155 string = TO_STRING_INLINE(string); |
165 var lastIndex = this.lastIndex; | 156 var lastIndex = this.lastIndex; |
166 | 157 |
167 // Conversion is required by the ES5 specification (RegExp.prototype.exec | 158 // Conversion is required by the ES5 specification (RegExp.prototype.exec |
168 // algorithm, step 5) even if the value is discarded for non-global RegExps. | 159 // algorithm, step 5) even if the value is discarded for non-global RegExps. |
169 var i = TO_INTEGER(lastIndex); | 160 var i = TO_INTEGER(lastIndex); |
170 | 161 |
171 var updateLastIndex = this.global || (harmony_regexps && this.sticky); | 162 var global = this.global; |
172 if (updateLastIndex) { | 163 if (global) { |
173 if (i < 0 || i > string.length) { | 164 if (i < 0 || i > string.length) { |
174 this.lastIndex = 0; | 165 this.lastIndex = 0; |
175 return null; | 166 return null; |
176 } | 167 } |
177 } else { | 168 } else { |
178 i = 0; | 169 i = 0; |
179 } | 170 } |
180 | 171 |
181 // matchIndices is either null or the lastMatchInfo array. | 172 // matchIndices is either null or the lastMatchInfo array. |
182 var matchIndices = %_RegExpExec(this, string, i, lastMatchInfo); | 173 var matchIndices = %_RegExpExec(this, string, i, lastMatchInfo); |
183 | 174 |
184 if (IS_NULL(matchIndices)) { | 175 if (IS_NULL(matchIndices)) { |
185 this.lastIndex = 0; | 176 this.lastIndex = 0; |
186 return null; | 177 return null; |
187 } | 178 } |
188 | 179 |
189 // Successful match. | 180 // Successful match. |
190 lastMatchInfoOverride = null; | 181 lastMatchInfoOverride = null; |
191 if (updateLastIndex) { | 182 if (global) { |
192 this.lastIndex = lastMatchInfo[CAPTURE1]; | 183 this.lastIndex = lastMatchInfo[CAPTURE1]; |
193 } | 184 } |
194 RETURN_NEW_RESULT_FROM_MATCH_INFO(matchIndices, string); | 185 RETURN_NEW_RESULT_FROM_MATCH_INFO(matchIndices, string); |
195 } | 186 } |
196 | 187 |
197 | 188 |
198 // One-element cache for the simplified test regexp. | 189 // One-element cache for the simplified test regexp. |
199 var regexp_key; | 190 var regexp_key; |
200 var regexp_val; | 191 var regexp_val; |
201 | 192 |
202 // Section 15.10.6.3 doesn't actually make sense, but the intention seems to be | 193 // Section 15.10.6.3 doesn't actually make sense, but the intention seems to be |
203 // that test is defined in terms of String.prototype.exec. However, it probably | 194 // that test is defined in terms of String.prototype.exec. However, it probably |
204 // means the original value of String.prototype.exec, which is what everybody | 195 // means the original value of String.prototype.exec, which is what everybody |
205 // else implements. | 196 // else implements. |
206 function RegExpTest(string) { | 197 function RegExpTest(string) { |
207 if (!IS_REGEXP(this)) { | 198 if (!IS_REGEXP(this)) { |
208 throw MakeTypeError('incompatible_method_receiver', | 199 throw MakeTypeError('incompatible_method_receiver', |
209 ['RegExp.prototype.test', this]); | 200 ['RegExp.prototype.test', this]); |
210 } | 201 } |
211 string = TO_STRING_INLINE(string); | 202 string = TO_STRING_INLINE(string); |
212 | 203 |
213 var lastIndex = this.lastIndex; | 204 var lastIndex = this.lastIndex; |
214 | 205 |
215 // Conversion is required by the ES5 specification (RegExp.prototype.exec | 206 // Conversion is required by the ES5 specification (RegExp.prototype.exec |
216 // algorithm, step 5) even if the value is discarded for non-global RegExps. | 207 // algorithm, step 5) even if the value is discarded for non-global RegExps. |
217 var i = TO_INTEGER(lastIndex); | 208 var i = TO_INTEGER(lastIndex); |
218 | 209 |
219 if (this.global || (harmony_regexps && this.sticky)) { | 210 if (this.global) { |
220 if (i < 0 || i > string.length) { | 211 if (i < 0 || i > string.length) { |
221 this.lastIndex = 0; | 212 this.lastIndex = 0; |
222 return false; | 213 return false; |
223 } | 214 } |
224 // matchIndices is either null or the lastMatchInfo array. | 215 // matchIndices is either null or the lastMatchInfo array. |
225 var matchIndices = %_RegExpExec(this, string, i, lastMatchInfo); | 216 var matchIndices = %_RegExpExec(this, string, i, lastMatchInfo); |
226 if (IS_NULL(matchIndices)) { | 217 if (IS_NULL(matchIndices)) { |
227 this.lastIndex = 0; | 218 this.lastIndex = 0; |
228 return false; | 219 return false; |
229 } | 220 } |
230 lastMatchInfoOverride = null; | 221 lastMatchInfoOverride = null; |
231 this.lastIndex = lastMatchInfo[CAPTURE1]; | 222 this.lastIndex = lastMatchInfo[CAPTURE1]; |
232 return true; | 223 return true; |
233 } else { | 224 } else { |
234 // Non-global, non-sticky regexp. | 225 // Non-global regexp. |
235 // Remove irrelevant preceeding '.*' in a test regexp. The expression | 226 // Remove irrelevant preceeding '.*' in a non-global test regexp. |
236 // checks whether this.source starts with '.*' and that the third char is | 227 // The expression checks whether this.source starts with '.*' and |
237 // not a '?'. But see https://code.google.com/p/v8/issues/detail?id=3560 | 228 // that the third char is not a '?'. |
238 var regexp = this; | 229 var regexp = this; |
239 if (regexp.source.length >= 3 && | 230 if (%_StringCharCodeAt(regexp.source, 0) == 46 && // '.' |
240 %_StringCharCodeAt(regexp.source, 0) == 46 && // '.' | |
241 %_StringCharCodeAt(regexp.source, 1) == 42 && // '*' | 231 %_StringCharCodeAt(regexp.source, 1) == 42 && // '*' |
242 %_StringCharCodeAt(regexp.source, 2) != 63) { // '?' | 232 %_StringCharCodeAt(regexp.source, 2) != 63) { // '?' |
243 regexp = TrimRegExp(regexp); | 233 regexp = TrimRegExp(regexp); |
244 } | 234 } |
245 // matchIndices is either null or the lastMatchInfo array. | 235 // matchIndices is either null or the lastMatchInfo array. |
246 var matchIndices = %_RegExpExec(regexp, string, 0, lastMatchInfo); | 236 var matchIndices = %_RegExpExec(regexp, string, 0, lastMatchInfo); |
247 if (IS_NULL(matchIndices)) { | 237 if (IS_NULL(matchIndices)) { |
248 this.lastIndex = 0; | 238 this.lastIndex = 0; |
249 return false; | 239 return false; |
250 } | 240 } |
(...skipping 16 matching lines...) Expand all Loading... |
267 | 257 |
268 function RegExpToString() { | 258 function RegExpToString() { |
269 if (!IS_REGEXP(this)) { | 259 if (!IS_REGEXP(this)) { |
270 throw MakeTypeError('incompatible_method_receiver', | 260 throw MakeTypeError('incompatible_method_receiver', |
271 ['RegExp.prototype.toString', this]); | 261 ['RegExp.prototype.toString', this]); |
272 } | 262 } |
273 var result = '/' + this.source + '/'; | 263 var result = '/' + this.source + '/'; |
274 if (this.global) result += 'g'; | 264 if (this.global) result += 'g'; |
275 if (this.ignoreCase) result += 'i'; | 265 if (this.ignoreCase) result += 'i'; |
276 if (this.multiline) result += 'm'; | 266 if (this.multiline) result += 'm'; |
277 if (harmony_regexps && this.sticky) result += 'y'; | |
278 return result; | 267 return result; |
279 } | 268 } |
280 | 269 |
281 | 270 |
282 // Getters for the static properties lastMatch, lastParen, leftContext, and | 271 // Getters for the static properties lastMatch, lastParen, leftContext, and |
283 // rightContext of the RegExp constructor. The properties are computed based | 272 // rightContext of the RegExp constructor. The properties are computed based |
284 // on the captures array of the last successful match and the subject string | 273 // on the captures array of the last successful match and the subject string |
285 // of the last successful match. | 274 // of the last successful match. |
286 function RegExpGetLastMatch() { | 275 function RegExpGetLastMatch() { |
287 if (lastMatchInfoOverride !== null) { | 276 if (lastMatchInfoOverride !== null) { |
(...skipping 178 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
466 | 455 |
467 for (var i = 1; i < 10; ++i) { | 456 for (var i = 1; i < 10; ++i) { |
468 %DefineAccessorPropertyUnchecked($RegExp, '$' + i, | 457 %DefineAccessorPropertyUnchecked($RegExp, '$' + i, |
469 RegExpMakeCaptureGetter(i), NoOpSetter, | 458 RegExpMakeCaptureGetter(i), NoOpSetter, |
470 DONT_DELETE); | 459 DONT_DELETE); |
471 } | 460 } |
472 %ToFastProperties($RegExp); | 461 %ToFastProperties($RegExp); |
473 } | 462 } |
474 | 463 |
475 SetUpRegExp(); | 464 SetUpRegExp(); |
OLD | NEW |