Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Side by Side Diff: src/regexp.js

Issue 3778004: Restructure RegExp exec cache code. (Closed)
Patch Set: Addressed review comments. Created 10 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | src/string.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2006-2009 the V8 project authors. All rights reserved. 1 // Copyright 2006-2009 the V8 project authors. All rights reserved.
2 // Redistribution and use in source and binary forms, with or without 2 // Redistribution and use in source and binary forms, with or without
3 // modification, are permitted provided that the following conditions are 3 // modification, are permitted provided that the following conditions are
4 // met: 4 // met:
5 // 5 //
6 // * Redistributions of source code must retain the above copyright 6 // * Redistributions of source code must retain the above copyright
7 // notice, this list of conditions and the following disclaimer. 7 // notice, this list of conditions and the following disclaimer.
8 // * Redistributions in binary form must reproduce the above 8 // * Redistributions in binary form must reproduce the above
9 // copyright notice, this list of conditions and the following 9 // copyright notice, this list of conditions and the following
10 // disclaimer in the documentation and/or other materials provided 10 // disclaimer in the documentation and/or other materials provided
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after
119 if (result !== null) lastMatchInfoOverride = null; 119 if (result !== null) lastMatchInfoOverride = null;
120 return result; 120 return result;
121 } 121 }
122 122
123 123
124 function RegExpCache() { 124 function RegExpCache() {
125 this.type = 'none'; 125 this.type = 'none';
126 this.regExp = 0; 126 this.regExp = 0;
127 this.subject = 0; 127 this.subject = 0;
128 this.replaceString = 0; 128 this.replaceString = 0;
129 this.lastIndex = 0; // Also used for splitLimit when type is "split"
130 this.answer = 0; 129 this.answer = 0;
131 // answerSaved marks whether the contents of answer is valid for a cache 130 // answerSaved marks whether the contents of answer is valid for a cache
132 // hit in RegExpExec, StringMatch and StringSplit. 131 // hit in RegExpExec, StringMatch and StringSplit.
133 this.answerSaved = false; 132 this.answerSaved = false;
133 this.splitLimit = 0; // Used only when type is "split".
134 } 134 }
135 135
136 136
137 var regExpCache = new RegExpCache(); 137 var regExpCache = new RegExpCache();
138 138
139 139
140 function BuildResultFromMatchInfo(lastMatchInfo, s) { 140 function BuildResultFromMatchInfo(lastMatchInfo, s) {
141 var numResults = NUMBER_OF_CAPTURES(lastMatchInfo) >> 1; 141 var numResults = NUMBER_OF_CAPTURES(lastMatchInfo) >> 1;
142 var result = %_RegExpConstructResult(numResults, lastMatchInfo[CAPTURE0], s); 142 var result = %_RegExpConstructResult(numResults, lastMatchInfo[CAPTURE0], s);
143 if (numResults === 1) { 143 if (numResults === 1) {
(...skipping 30 matching lines...) Expand all
174 174
175 function RegExpExec(string) { 175 function RegExpExec(string) {
176 if (!IS_REGEXP(this)) { 176 if (!IS_REGEXP(this)) {
177 throw MakeTypeError('incompatible_method_receiver', 177 throw MakeTypeError('incompatible_method_receiver',
178 ['RegExp.prototype.exec', this]); 178 ['RegExp.prototype.exec', this]);
179 } 179 }
180 180
181 var cache = regExpCache; 181 var cache = regExpCache;
182 var saveAnswer = false; 182 var saveAnswer = false;
183 183
184 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).
184 if (%_ObjectEquals(cache.type, 'exec') && 192 if (%_ObjectEquals(cache.type, 'exec') &&
185 %_ObjectEquals(cache.lastIndex, this.lastIndex) && 193 %_ObjectEquals(0, lastIndex) &&
186 %_IsRegExpEquivalent(cache.regExp, this) && 194 %_IsRegExpEquivalent(cache.regExp, this) &&
187 %_ObjectEquals(cache.subject, string)) { 195 %_ObjectEquals(cache.subject, string)) {
188 if (cache.answerSaved) { 196 if (cache.answerSaved) {
189 // If this regexp is global, cache.lastIndex is zero, so we only get 197 // The regexp.lastIndex value must be 0 for non-global RegExps, and for
190 // here if this.lastIndex is zero, and resulting this.lastIndex 198 // global RegExps we only cache negative results, which gives a lastIndex
191 // must be zero too, so no change is necessary. 199 // of zero as well.
192 if (!this.global) this.lastIndex = lastMatchInfo[CAPTURE1]; 200 this.lastIndex = 0;
193 return %_RegExpCloneResult(cache.answer); 201 return %_RegExpCloneResult(cache.answer);
194 } else { 202 } else {
195 saveAnswer = true; 203 saveAnswer = true;
196 } 204 }
197 } 205 }
198 206
199 if (%_ArgumentsLength() == 0) { 207 if (%_ArgumentsLength() === 0) {
200 var regExpInput = LAST_INPUT(lastMatchInfo); 208 var regExpInput = LAST_INPUT(lastMatchInfo);
201 if (IS_UNDEFINED(regExpInput)) { 209 if (IS_UNDEFINED(regExpInput)) {
202 throw MakeError('no_input_to_regexp', [this]); 210 throw MakeError('no_input_to_regexp', [this]);
203 } 211 }
204 string = regExpInput; 212 string = regExpInput;
205 } 213 }
206 var s; 214 var s;
207 if (IS_STRING(string)) { 215 if (IS_STRING(string)) {
208 s = string; 216 s = string;
209 } else { 217 } else {
210 s = ToString(string); 218 s = ToString(string);
211 } 219 }
212 var lastIndex = this.lastIndex; 220 var global = this.global;
213 221
214 var i = this.global ? TO_INTEGER(lastIndex) : 0; 222 // Conversion is required by the ES5 specification (RegExp.prototype.exec
215 223 // algorithm, step 5) even if the value is discarded for non-global RegExps.
216 if (i < 0 || i > s.length) { 224 var i = TO_INTEGER(lastIndex);
217 this.lastIndex = 0; 225 if (global) {
218 return null; 226 if (i < 0 || i > s.length) {
227 this.lastIndex = 0;
228 return null;
229 }
230 } else {
231 i = 0;
219 } 232 }
220 233
221 %_Log('regexp', 'regexp-exec,%0r,%1S,%2i', [this, s, lastIndex]); 234 %_Log('regexp', 'regexp-exec,%0r,%1S,%2i', [this, s, lastIndex]);
222 // matchIndices is either null or the lastMatchInfo array. 235 // matchIndices is either null or the lastMatchInfo array.
223 var matchIndices = %_RegExpExec(this, s, i, lastMatchInfo); 236 var matchIndices = %_RegExpExec(this, s, i, lastMatchInfo);
224 237
225 if (matchIndices == null) { 238 if (matchIndices === null) {
226 if (this.global) { 239 if (global) {
240 // Cache negative result only if initial lastIndex was zero.
227 this.lastIndex = 0; 241 this.lastIndex = 0;
228 if (lastIndex != 0) return matchIndices; 242 if (lastIndex !== 0) return matchIndices;
229 } 243 }
230 cache.lastIndex = lastIndex;
231 cache.regExp = this; 244 cache.regExp = this;
232 cache.subject = s; 245 cache.subject = s; // Always a string.
233 cache.answer = matchIndices; // Null. 246 cache.answer = null;
234 cache.answerSaved = true; // Safe since no cloning is needed. 247 cache.answerSaved = true; // Safe since no cloning is needed.
235 cache.type = 'exec'; 248 cache.type = 'exec';
236 return matchIndices; // No match. 249 return matchIndices; // No match.
237 } 250 }
251
252 // Successful match.
238 lastMatchInfoOverride = null; 253 lastMatchInfoOverride = null;
239 var result = BuildResultFromMatchInfo(matchIndices, s); 254 var result = BuildResultFromMatchInfo(matchIndices, s);
240 255
241 if (this.global) { 256 if (global) {
257 // Don't cache positive results for global regexps.
242 this.lastIndex = lastMatchInfo[CAPTURE1]; 258 this.lastIndex = lastMatchInfo[CAPTURE1];
243 } else { 259 } else {
244 cache.regExp = this; 260 cache.regExp = this;
245 cache.subject = s; 261 cache.subject = s;
246 cache.lastIndex = lastIndex;
247 if (saveAnswer) cache.answer = %_RegExpCloneResult(result); 262 if (saveAnswer) cache.answer = %_RegExpCloneResult(result);
248 cache.answerSaved = saveAnswer; 263 cache.answerSaved = saveAnswer;
249 cache.type = 'exec'; 264 cache.type = 'exec';
250 } 265 }
251 return result; 266 return result;
252 267
253 } 268 }
254 269
255 270
256 // One-element cache for the simplified test regexp. 271 // One-element cache for the simplified test regexp.
257 var regexp_key; 272 var regexp_key;
258 var regexp_val; 273 var regexp_val;
259 274
260 // Section 15.10.6.3 doesn't actually make sense, but the intention seems to be 275 // Section 15.10.6.3 doesn't actually make sense, but the intention seems to be
261 // that test is defined in terms of String.prototype.exec. However, it probably 276 // that test is defined in terms of String.prototype.exec. However, it probably
262 // means the original value of String.prototype.exec, which is what everybody 277 // means the original value of String.prototype.exec, which is what everybody
263 // else implements. 278 // else implements.
264 function RegExpTest(string) { 279 function RegExpTest(string) {
265 if (!IS_REGEXP(this)) { 280 if (!IS_REGEXP(this)) {
266 throw MakeTypeError('incompatible_method_receiver', 281 throw MakeTypeError('incompatible_method_receiver',
267 ['RegExp.prototype.test', this]); 282 ['RegExp.prototype.test', this]);
268 } 283 }
269 if (%_ArgumentsLength() == 0) { 284 if (%_ArgumentsLength() == 0) {
270 var regExpInput = LAST_INPUT(lastMatchInfo); 285 var regExpInput = LAST_INPUT(lastMatchInfo);
271 if (IS_UNDEFINED(regExpInput)) { 286 if (IS_UNDEFINED(regExpInput)) {
272 throw MakeError('no_input_to_regexp', [this]); 287 throw MakeError('no_input_to_regexp', [this]);
273 } 288 }
274 string = regExpInput; 289 string = regExpInput;
275 } 290 }
291
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
276 var s; 306 var s;
277 if (IS_STRING(string)) { 307 if (IS_STRING(string)) {
278 s = string; 308 s = string;
279 } else { 309 } else {
280 s = ToString(string); 310 s = ToString(string);
281 } 311 }
312 var length = s.length;
282 313
283 var lastIndex = this.lastIndex; 314 // Conversion is required by the ES5 specification (RegExp.prototype.exec
284 var cache = regExpCache; 315 // algorithm, step 5) even if the value is discarded for non-global RegExps.
285 if (%_ObjectEquals(cache.type, 'test') && 316 var i = TO_INTEGER(lastIndex);
286 %_IsRegExpEquivalent(cache.regExp, this) && 317 if (global) {
287 %_ObjectEquals(cache.subject, string) && 318 if (i < 0 || i > length) {
288 %_ObjectEquals(cache.lastIndex, lastIndex)) { 319 this.lastIndex = 0;
289 // If this regexp is not global, cache.lastIndex is zero, so we only get 320 return false;
290 // here if this.lastIndex is zero, and resulting this.lastIndex 321 }
291 // must be zero too, so no change is necessary. 322 } else {
292 if (this.global) this.lastIndex = lastMatchInfo[CAPTURE1]; 323 i = 0;
293 return cache.answer;
294 } 324 }
295 325
326 var global = this.global;
327
296 // Remove irrelevant preceeding '.*' in a test regexp. The expression 328 // Remove irrelevant preceeding '.*' in a test regexp. The expression
297 // checks whether this.source starts with '.*' and that the third 329 // checks whether this.source starts with '.*' and that the third
298 // char is not a '?' 330 // char is not a '?'
299 if (%_StringCharCodeAt(this.source,0) == 46 && // '.' 331 if (%_StringCharCodeAt(this.source, 0) == 46 && // '.'
300 %_StringCharCodeAt(this.source,1) == 42 && // '*' 332 %_StringCharCodeAt(this.source, 1) == 42 && // '*'
301 %_StringCharCodeAt(this.source,2) != 63) { // '?' 333 %_StringCharCodeAt(this.source, 2) != 63) { // '?'
302 if (!%_ObjectEquals(regexp_key, this)) { 334 if (!%_ObjectEquals(regexp_key, this)) {
303 regexp_key = this; 335 regexp_key = this;
304 regexp_val = new $RegExp(this.source.substring(2, this.source.length), 336 regexp_val = new $RegExp(this.source.substring(2, this.source.length),
305 (this.global ? 'g' : '') 337 (this.global ? 'g' : '')
306 + (this.ignoreCase ? 'i' : '') 338 + (this.ignoreCase ? 'i' : '')
307 + (this.multiline ? 'm' : '')); 339 + (this.multiline ? 'm' : ''));
308 } 340 }
309 if (!regexp_val.test(s)) return false; 341 if (!regexp_val.test(s)) return false;
310 } 342 }
311 343
312 var length = s.length;
313 var i = this.global ? TO_INTEGER(lastIndex) : 0;
314
315 cache.type = 'test';
316 cache.regExp = this;
317 cache.subject = s;
318 cache.lastIndex = i;
319
320 if (i < 0 || i > length) {
321 this.lastIndex = 0;
322 cache.answer = false;
323 return false;
324 }
325
326 %_Log('regexp', 'regexp-exec,%0r,%1S,%2i', [this, s, lastIndex]); 344 %_Log('regexp', 'regexp-exec,%0r,%1S,%2i', [this, s, lastIndex]);
327 // matchIndices is either null or the lastMatchInfo array. 345 // matchIndices is either null or the lastMatchInfo array.
328 var matchIndices = %_RegExpExec(this, s, i, lastMatchInfo); 346 var matchIndices = %_RegExpExec(this, s, i, lastMatchInfo);
329 347
330 if (matchIndices == null) { 348 var result = (matchIndices !== null);
331 if (this.global) this.lastIndex = 0; 349 if (result) {
332 cache.answer = false; 350 lastMatchInfoOverride = null;
333 return false;
334 } 351 }
335 lastMatchInfoOverride = null; 352 if (global) {
336 if (this.global) this.lastIndex = lastMatchInfo[CAPTURE1]; 353 if (result) {
337 cache.answer = true; 354 this.lastIndex = lastMatchInfo[CAPTURE1];
338 return true; 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;
339 } 366 }
340 367
341 368
342 function RegExpToString() { 369 function RegExpToString() {
343 // If this.source is an empty string, output /(?:)/. 370 // If this.source is an empty string, output /(?:)/.
344 // http://bugzilla.mozilla.org/show_bug.cgi?id=225550 371 // http://bugzilla.mozilla.org/show_bug.cgi?id=225550
345 // ecma_2/RegExp/properties-001.js. 372 // ecma_2/RegExp/properties-001.js.
346 var src = this.source ? this.source : '(?:)'; 373 var src = this.source ? this.source : '(?:)';
347 var result = '/' + src + '/'; 374 var result = '/' + src + '/';
348 if (this.global) 375 if (this.global) result += 'g';
349 result += 'g'; 376 if (this.ignoreCase) result += 'i';
350 if (this.ignoreCase) 377 if (this.multiline) result += 'm';
351 result += 'i';
352 if (this.multiline)
353 result += 'm';
354 return result; 378 return result;
355 } 379 }
356 380
357 381
358 // Getters for the static properties lastMatch, lastParen, leftContext, and 382 // Getters for the static properties lastMatch, lastParen, leftContext, and
359 // rightContext of the RegExp constructor. The properties are computed based 383 // rightContext of the RegExp constructor. The properties are computed based
360 // on the captures array of the last successful match and the subject string 384 // on the captures array of the last successful match and the subject string
361 // of the last successful match. 385 // of the last successful match.
362 function RegExpGetLastMatch() { 386 function RegExpGetLastMatch() {
363 if (lastMatchInfoOverride !== null) { 387 if (lastMatchInfoOverride !== null) {
(...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after
537 %DefineAccessor($RegExp, "$'", SETTER, NoOpSetter, DONT_ENUM | DONT_DELETE); 561 %DefineAccessor($RegExp, "$'", SETTER, NoOpSetter, DONT_ENUM | DONT_DELETE);
538 562
539 for (var i = 1; i < 10; ++i) { 563 for (var i = 1; i < 10; ++i) {
540 %DefineAccessor($RegExp, '$' + i, GETTER, RegExpMakeCaptureGetter(i), DONT_D ELETE); 564 %DefineAccessor($RegExp, '$' + i, GETTER, RegExpMakeCaptureGetter(i), DONT_D ELETE);
541 %DefineAccessor($RegExp, '$' + i, SETTER, NoOpSetter, DONT_DELETE); 565 %DefineAccessor($RegExp, '$' + i, SETTER, NoOpSetter, DONT_DELETE);
542 } 566 }
543 } 567 }
544 568
545 569
546 SetupRegExp(); 570 SetupRegExp();
OLDNEW
« no previous file with comments | « no previous file | src/string.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698