OLD | NEW |
1 // Copyright 2012 the V8 project authors. All rights reserved. | 1 // Copyright 2012 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 259 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
270 %StringIndexOf(replace, '$', 0) < 0) { | 270 %StringIndexOf(replace, '$', 0) < 0) { |
271 // Searching by traversing a cons string tree and replace with cons of | 271 // Searching by traversing a cons string tree and replace with cons of |
272 // slices works only when the replaced string is a single character, being | 272 // slices works only when the replaced string is a single character, being |
273 // replaced by a simple string and only pays off for long strings. | 273 // replaced by a simple string and only pays off for long strings. |
274 return %StringReplaceOneCharWithString(subject, search, replace); | 274 return %StringReplaceOneCharWithString(subject, search, replace); |
275 } | 275 } |
276 var start = %StringIndexOf(subject, search, 0); | 276 var start = %StringIndexOf(subject, search, 0); |
277 if (start < 0) return subject; | 277 if (start < 0) return subject; |
278 var end = start + search.length; | 278 var end = start + search.length; |
279 | 279 |
280 var builder = new ReplaceResultBuilder(subject); | 280 var result = SubString(subject, 0, start); |
281 // prefix | |
282 builder.addSpecialSlice(0, start); | |
283 | 281 |
284 // Compute the string to replace with. | 282 // Compute the string to replace with. |
285 if (IS_SPEC_FUNCTION(replace)) { | 283 if (IS_SPEC_FUNCTION(replace)) { |
286 var receiver = %GetDefaultReceiver(replace); | 284 var receiver = %GetDefaultReceiver(replace); |
287 builder.add(%_CallFunction(receiver, | 285 result += %_CallFunction(receiver, search, start, subject, replace); |
288 search, | |
289 start, | |
290 subject, | |
291 replace)); | |
292 } else { | 286 } else { |
293 reusableMatchInfo[CAPTURE0] = start; | 287 reusableMatchInfo[CAPTURE0] = start; |
294 reusableMatchInfo[CAPTURE1] = end; | 288 reusableMatchInfo[CAPTURE1] = end; |
295 replace = TO_STRING_INLINE(replace); | 289 replace = TO_STRING_INLINE(replace); |
296 ExpandReplacement(replace, subject, reusableMatchInfo, builder); | 290 result = ExpandReplacement(replace, subject, reusableMatchInfo, result); |
297 } | 291 } |
298 | 292 |
299 // suffix | 293 return result + SubString(subject, end, subject.length); |
300 builder.addSpecialSlice(end, subject.length); | |
301 | |
302 return builder.generate(); | |
303 } | 294 } |
304 | 295 |
305 | 296 |
306 // Expand the $-expressions in the string and return a new string with | 297 // Expand the $-expressions in the string and return a new string with |
307 // the result. | 298 // the result. |
308 function ExpandReplacement(string, subject, matchInfo, builder) { | 299 function ExpandReplacement(string, subject, matchInfo, result) { |
309 var length = string.length; | 300 var length = string.length; |
310 var builder_elements = builder.elements; | |
311 var next = %StringIndexOf(string, '$', 0); | 301 var next = %StringIndexOf(string, '$', 0); |
312 if (next < 0) { | 302 if (next < 0) { |
313 if (length > 0) builder_elements.push(string); | 303 if (length > 0) result += string; |
314 return; | 304 return result; |
315 } | 305 } |
316 | 306 |
317 // Compute the number of captures; see ECMA-262, 15.5.4.11, p. 102. | 307 if (next > 0) result += SubString(string, 0, next); |
318 var m = NUMBER_OF_CAPTURES(matchInfo) >> 1; // Includes the match. | |
319 | |
320 if (next > 0) builder_elements.push(SubString(string, 0, next)); | |
321 | 308 |
322 while (true) { | 309 while (true) { |
323 var expansion = '$'; | 310 var expansion = '$'; |
324 var position = next + 1; | 311 var position = next + 1; |
325 if (position < length) { | 312 if (position < length) { |
326 var peek = %_StringCharCodeAt(string, position); | 313 var peek = %_StringCharCodeAt(string, position); |
327 if (peek == 36) { // $$ | 314 if (peek == 36) { // $$ |
328 ++position; | 315 ++position; |
329 builder_elements.push('$'); | 316 result += '$'; |
330 } else if (peek == 38) { // $& - match | 317 } else if (peek == 38) { // $& - match |
331 ++position; | 318 ++position; |
332 builder.addSpecialSlice(matchInfo[CAPTURE0], | 319 result += SubString(subject, matchInfo[CAPTURE0], matchInfo[CAPTURE1]); |
333 matchInfo[CAPTURE1]); | |
334 } else if (peek == 96) { // $` - prefix | 320 } else if (peek == 96) { // $` - prefix |
335 ++position; | 321 ++position; |
336 builder.addSpecialSlice(0, matchInfo[CAPTURE0]); | 322 result += SubString(subject, 0, matchInfo[CAPTURE0]); |
337 } else if (peek == 39) { // $' - suffix | 323 } else if (peek == 39) { // $' - suffix |
338 ++position; | 324 ++position; |
339 builder.addSpecialSlice(matchInfo[CAPTURE1], subject.length); | 325 result += SubString(subject, matchInfo[CAPTURE1], subject.length); |
340 } else if (peek >= 48 && peek <= 57) { // $n, 0 <= n <= 9 | |
341 ++position; | |
342 var n = peek - 48; | |
343 if (position < length) { | |
344 peek = %_StringCharCodeAt(string, position); | |
345 // $nn, 01 <= nn <= 99 | |
346 if (n != 0 && peek == 48 || peek >= 49 && peek <= 57) { | |
347 var nn = n * 10 + (peek - 48); | |
348 if (nn < m) { | |
349 // If the two digit capture reference is within range of | |
350 // the captures, we use it instead of the single digit | |
351 // one. Otherwise, we fall back to using the single | |
352 // digit reference. This matches the behavior of | |
353 // SpiderMonkey. | |
354 ++position; | |
355 n = nn; | |
356 } | |
357 } | |
358 } | |
359 if (0 < n && n < m) { | |
360 addCaptureString(builder, matchInfo, n); | |
361 } else { | |
362 // Because of the captures range check in the parsing of two | |
363 // digit capture references, we can only enter here when a | |
364 // single digit capture reference is outside the range of | |
365 // captures. | |
366 builder_elements.push('$'); | |
367 --position; | |
368 } | |
369 } else { | 326 } else { |
370 builder_elements.push('$'); | 327 result += '$'; |
371 } | 328 } |
372 } else { | 329 } else { |
373 builder_elements.push('$'); | 330 result += '$'; |
374 } | 331 } |
375 | 332 |
376 // Go the the next $ in the string. | 333 // Go the the next $ in the string. |
377 next = %StringIndexOf(string, '$', position); | 334 next = %StringIndexOf(string, '$', position); |
378 | 335 |
379 // Return if there are no more $ characters in the string. If we | 336 // Return if there are no more $ characters in the string. If we |
380 // haven't reached the end, we need to append the suffix. | 337 // haven't reached the end, we need to append the suffix. |
381 if (next < 0) { | 338 if (next < 0) { |
382 if (position < length) { | 339 if (position < length) { |
383 builder_elements.push(SubString(string, position, length)); | 340 result += SubString(string, position, length); |
384 } | 341 } |
385 return; | 342 return result; |
386 } | 343 } |
387 | 344 |
388 // Append substring between the previous and the next $ character. | 345 // Append substring between the previous and the next $ character. |
389 if (next > position) { | 346 if (next > position) { |
390 builder_elements.push(SubString(string, position, next)); | 347 result += SubString(string, position, next); |
391 } | 348 } |
392 } | 349 } |
| 350 return result; |
393 } | 351 } |
394 | 352 |
395 | 353 |
396 // Compute the string of a given regular expression capture. | 354 // Compute the string of a given regular expression capture. |
397 function CaptureString(string, lastCaptureInfo, index) { | 355 function CaptureString(string, lastCaptureInfo, index) { |
398 // Scale the index. | 356 // Scale the index. |
399 var scaled = index << 1; | 357 var scaled = index << 1; |
400 // Compute start and end. | 358 // Compute start and end. |
401 var start = lastCaptureInfo[CAPTURE(scaled)]; | 359 var start = lastCaptureInfo[CAPTURE(scaled)]; |
402 // If start isn't valid, return undefined. | 360 // If start isn't valid, return undefined. |
403 if (start < 0) return; | 361 if (start < 0) return; |
404 var end = lastCaptureInfo[CAPTURE(scaled + 1)]; | 362 var end = lastCaptureInfo[CAPTURE(scaled + 1)]; |
405 return SubString(string, start, end); | 363 return SubString(string, start, end); |
406 } | 364 } |
407 | 365 |
408 | 366 |
409 // Add the string of a given regular expression capture to the | |
410 // ReplaceResultBuilder | |
411 function addCaptureString(builder, matchInfo, index) { | |
412 // Scale the index. | |
413 var scaled = index << 1; | |
414 // Compute start and end. | |
415 var start = matchInfo[CAPTURE(scaled)]; | |
416 if (start < 0) return; | |
417 var end = matchInfo[CAPTURE(scaled + 1)]; | |
418 builder.addSpecialSlice(start, end); | |
419 } | |
420 | |
421 // TODO(lrn): This array will survive indefinitely if replace is never | 367 // TODO(lrn): This array will survive indefinitely if replace is never |
422 // called again. However, it will be empty, since the contents are cleared | 368 // called again. However, it will be empty, since the contents are cleared |
423 // in the finally block. | 369 // in the finally block. |
424 var reusableReplaceArray = new InternalArray(16); | 370 var reusableReplaceArray = new InternalArray(16); |
425 | 371 |
426 // Helper function for replacing regular expressions with the result of a | 372 // Helper function for replacing regular expressions with the result of a |
427 // function application in String.prototype.replace. | 373 // function application in String.prototype.replace. |
428 function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) { | 374 function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) { |
429 var resultArray = reusableReplaceArray; | 375 var resultArray = reusableReplaceArray; |
430 if (resultArray) { | 376 if (resultArray) { |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
499 var result = resultBuilder.generate(); | 445 var result = resultBuilder.generate(); |
500 resultArray.length = 0; | 446 resultArray.length = 0; |
501 reusableReplaceArray = resultArray; | 447 reusableReplaceArray = resultArray; |
502 return result; | 448 return result; |
503 } | 449 } |
504 | 450 |
505 | 451 |
506 function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) { | 452 function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) { |
507 var matchInfo = DoRegExpExec(regexp, subject, 0); | 453 var matchInfo = DoRegExpExec(regexp, subject, 0); |
508 if (IS_NULL(matchInfo)) return subject; | 454 if (IS_NULL(matchInfo)) return subject; |
509 var result = new ReplaceResultBuilder(subject); | |
510 var index = matchInfo[CAPTURE0]; | 455 var index = matchInfo[CAPTURE0]; |
511 result.addSpecialSlice(0, index); | 456 var result = SubString(subject, 0, index); |
512 var endOfMatch = matchInfo[CAPTURE1]; | 457 var endOfMatch = matchInfo[CAPTURE1]; |
513 // Compute the parameter list consisting of the match, captures, index, | 458 // Compute the parameter list consisting of the match, captures, index, |
514 // and subject for the replace function invocation. | 459 // and subject for the replace function invocation. |
515 // The number of captures plus one for the match. | 460 // The number of captures plus one for the match. |
516 var m = NUMBER_OF_CAPTURES(matchInfo) >> 1; | 461 var m = NUMBER_OF_CAPTURES(matchInfo) >> 1; |
517 var replacement; | 462 var replacement; |
518 var receiver = %GetDefaultReceiver(replace); | 463 var receiver = %GetDefaultReceiver(replace); |
519 if (m == 1) { | 464 if (m == 1) { |
520 // No captures, only the match, which is always valid. | 465 // No captures, only the match, which is always valid. |
521 var s = SubString(subject, index, endOfMatch); | 466 var s = SubString(subject, index, endOfMatch); |
522 // Don't call directly to avoid exposing the built-in global object. | 467 // Don't call directly to avoid exposing the built-in global object. |
523 replacement = %_CallFunction(receiver, s, index, subject, replace); | 468 replacement = %_CallFunction(receiver, s, index, subject, replace); |
524 } else { | 469 } else { |
525 var parameters = new InternalArray(m + 2); | 470 var parameters = new InternalArray(m + 2); |
526 for (var j = 0; j < m; j++) { | 471 for (var j = 0; j < m; j++) { |
527 parameters[j] = CaptureString(subject, matchInfo, j); | 472 parameters[j] = CaptureString(subject, matchInfo, j); |
528 } | 473 } |
529 parameters[j] = index; | 474 parameters[j] = index; |
530 parameters[j + 1] = subject; | 475 parameters[j + 1] = subject; |
531 | 476 |
532 replacement = %Apply(replace, receiver, parameters, 0, j + 2); | 477 replacement = %Apply(replace, receiver, parameters, 0, j + 2); |
533 } | 478 } |
534 | 479 |
535 result.add(replacement); // The add method converts to string if necessary. | 480 result += replacement; // The add method converts to string if necessary. |
536 // Can't use matchInfo any more from here, since the function could | 481 // Can't use matchInfo any more from here, since the function could |
537 // overwrite it. | 482 // overwrite it. |
538 result.addSpecialSlice(endOfMatch, subject.length); | 483 return result + SubString(subject, endOfMatch, subject.length); |
539 return result.generate(); | |
540 } | 484 } |
541 | 485 |
542 | 486 |
543 // ECMA-262 section 15.5.4.12 | 487 // ECMA-262 section 15.5.4.12 |
544 function StringSearch(re) { | 488 function StringSearch(re) { |
545 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { | 489 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { |
546 throw MakeTypeError("called_on_null_or_undefined", | 490 throw MakeTypeError("called_on_null_or_undefined", |
547 ["String.prototype.search"]); | 491 ["String.prototype.search"]); |
548 } | 492 } |
549 var regexp; | 493 var regexp; |
(...skipping 489 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1039 "fixed", StringFixed, | 983 "fixed", StringFixed, |
1040 "italics", StringItalics, | 984 "italics", StringItalics, |
1041 "small", StringSmall, | 985 "small", StringSmall, |
1042 "strike", StringStrike, | 986 "strike", StringStrike, |
1043 "sub", StringSub, | 987 "sub", StringSub, |
1044 "sup", StringSup | 988 "sup", StringSup |
1045 )); | 989 )); |
1046 } | 990 } |
1047 | 991 |
1048 SetUpString(); | 992 SetUpString(); |
OLD | NEW |