1 /* 2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.nashorn.internal.objects; 27 28 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 29 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 30 31 import java.util.ArrayList; 32 import java.util.Arrays; 33 import java.util.List; 34 import jdk.nashorn.internal.objects.annotations.Attribute; 35 import jdk.nashorn.internal.objects.annotations.Constructor; 36 import jdk.nashorn.internal.objects.annotations.Function; 37 import jdk.nashorn.internal.objects.annotations.Getter; 38 import jdk.nashorn.internal.objects.annotations.Property; 39 import jdk.nashorn.internal.objects.annotations.ScriptClass; 40 import jdk.nashorn.internal.objects.annotations.SpecializedConstructor; 41 import jdk.nashorn.internal.parser.RegExp; 42 import jdk.nashorn.internal.runtime.BitVector; 43 import jdk.nashorn.internal.runtime.JSType; 44 import jdk.nashorn.internal.runtime.ParserException; 45 import jdk.nashorn.internal.runtime.RegExpMatch; 46 import jdk.nashorn.internal.runtime.ScriptFunction; 47 import jdk.nashorn.internal.runtime.ScriptObject; 48 import jdk.nashorn.internal.runtime.ScriptRuntime; 49 50 import jdk.nashorn.internal.joni.Matcher; 51 import jdk.nashorn.internal.joni.Option; 52 import jdk.nashorn.internal.joni.Regex; 53 import jdk.nashorn.internal.joni.Region; 54 55 /** 56 * ECMA 15.10 RegExp Objects. 57 */ 58 @ScriptClass("RegExp") 59 public final class NativeRegExp extends ScriptObject { 60 /** ECMA 15.10.7.5 lastIndex property */ 61 @Property(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE) 62 public Object lastIndex; 63 64 /** Pattern string. */ 65 private String source; 66 67 /** Input string. */ 68 private String input; 69 70 /** Input string as char array */ 71 private char[] inputChars; 72 73 /** Global search flag for this regexp. */ 74 private boolean global; 75 76 /** Case insensitive flag for this regexp */ 77 private boolean ignoreCase; 78 79 /** Multi-line flag for this regexp */ 80 private boolean multiline; 81 82 /** Joni regex pattern to use for match. We compile to one of these */ 83 private Regex regex; 84 85 private BitVector groupsInNegativeLookahead; 86 87 /* 88 public NativeRegExp() { 89 init(); 90 }*/ 91 92 NativeRegExp(final String input, final String flagString) { 93 RegExp regExp = null; 94 try { 95 regExp = new RegExp(input, flagString); 96 } catch (final ParserException e) { 97 // translate it as SyntaxError object and throw it 98 e.throwAsEcmaException(Global.instance()); 99 throw new AssertionError(); //guard against null warnings below 100 } 101 102 this.setLastIndex(0); 103 this.source = regExp.getSource(); 104 this.global = regExp.isGlobal(); 105 this.ignoreCase = regExp.isIgnoreCase(); 106 this.multiline = regExp.isMultiline(); 107 this.regex = regExp.getRegex(); 108 this.groupsInNegativeLookahead = regExp.getGroupsInNegativeLookahead(); 109 110 init(); 111 } 112 113 NativeRegExp(final String string) { 114 this(string, ""); 115 } 116 117 NativeRegExp(final NativeRegExp regExp) { 118 this.source = regExp.getSource(); 119 this.global = regExp.getGlobal(); 120 this.multiline = regExp.getMultiline(); 121 this.ignoreCase = regExp.getIgnoreCase(); 122 this.lastIndex = regExp.getLastIndexObject(); 123 this.regex = regExp.getRegex(); 124 this.groupsInNegativeLookahead = regExp.getGroupsInNegativeLookahead(); 125 126 init(); 127 } 128 129 /* NativeRegExp(final Pattern pattern) { 130 this.input = pattern.pattern(); 131 this.multiline = (pattern.flags() & Pattern.MULTILINE) != 0; 132 this.ignoreCase = (pattern.flags() & Pattern.CASE_INSENSITIVE) != 0; 133 this.lastIndex = 0; 134 this.pattern = pattern; 135 136 init(); 137 } */ 138 139 @Override 140 public String getClassName() { 141 return "RegExp"; 142 } 143 144 /** 145 * ECMA 15.10.4 146 * 147 * Constructor 148 * 149 * @param isNew is the new operator used for instantiating this regexp 150 * @param self self reference 151 * @param args arguments (optional: pattern and flags) 152 * @return new NativeRegExp 153 */ 154 @Constructor(arity = 2) 155 public static Object constructor(final boolean isNew, final Object self, final Object... args) { 156 if (args.length > 1) { 157 return newRegExp(args[0], args[1]); 158 } else if (args.length > 0) { 159 return newRegExp(args[0], UNDEFINED); 160 } 161 162 return newRegExp(UNDEFINED, UNDEFINED); 163 } 164 165 /** 166 * ECMA 15.10.4 167 * 168 * Constructor - specialized version, no args, empty regexp 169 * 170 * @param isNew is the new operator used for instantiating this regexp 171 * @param self self reference 172 * @return new NativeRegExp 173 */ 174 @SpecializedConstructor 175 public static Object constructor(final boolean isNew, final Object self) { 176 return new NativeRegExp("", ""); 177 } 178 179 /** 180 * ECMA 15.10.4 181 * 182 * Constructor - specialized version, pattern, no flags 183 * 184 * @param isNew is the new operator used for instantiating this regexp 185 * @param self self reference 186 * @param pattern pattern 187 * @return new NativeRegExp 188 */ 189 @SpecializedConstructor 190 public static Object constructor(final boolean isNew, final Object self, final Object pattern) { 191 return newRegExp(pattern, UNDEFINED); 192 } 193 194 /** 195 * ECMA 15.10.4 196 * 197 * Constructor - specialized version, pattern and flags 198 * 199 * @param isNew is the new operator used for instantiating this regexp 200 * @param self self reference 201 * @param pattern pattern 202 * @param flags flags 203 * @return new NativeRegExp 204 */ 205 @SpecializedConstructor 206 public static Object constructor(final boolean isNew, final Object self, final Object pattern, final Object flags) { 207 return newRegExp(pattern, flags); 208 } 209 210 /** 211 * External constructor used in generated code created by {@link jdk.nashorn.internal.codegen.CodeGenerator}, which 212 * explain the {@code public} access. 213 * 214 * @param regexp regexp 215 * @param flags flags 216 * @return new NativeRegExp 217 */ 218 public static NativeRegExp newRegExp(final Object regexp, final Object flags) { 219 String patternString = ""; 220 String flagString = ""; 221 boolean flagsDefined = false; 222 223 if (flags != UNDEFINED) { 224 flagsDefined = true; 225 flagString = JSType.toString(flags); 226 } 227 228 if (regexp != UNDEFINED) { 229 if (regexp instanceof NativeRegExp) { 230 if (!flagsDefined) { 231 return (NativeRegExp)regexp; // 15.10.3.1 - undefined flags and regexp as 232 } 233 typeError(Global.instance(), "regex.cant.supply.flags"); 234 } 235 patternString = JSType.toString(regexp); 236 } 237 238 return new NativeRegExp(patternString, flagString); 239 } 240 241 public static NativeRegExp flatRegExp(String string) { 242 // escape special characters 243 StringBuilder sb = new StringBuilder(string.length()); 244 for (int i = 0; i < string.length(); i++) { 245 final char c = string.charAt(i); 246 switch (c) { 247 case '^': 248 case '$': 249 case '\\': 250 case '.': 251 case '*': 252 case '+': 253 case '?': 254 case '(': 255 case ')': 256 case '[': 257 case '{': 258 case '|': 259 sb.append('\\'); 260 default: 261 sb.append(c); 262 } 263 } 264 return new NativeRegExp(sb.toString(), ""); 265 } 266 267 private String getFlagString() { 268 final StringBuilder sb = new StringBuilder(); 269 270 if (global) { 271 sb.append('g'); 272 } 273 if (ignoreCase) { 274 sb.append('i'); 275 } 276 if (multiline) { 277 sb.append('m'); 278 } 279 280 return sb.toString(); 281 } 282 283 @Override 284 public String safeToString() { 285 return "[RegExp " + toString() + "]"; 286 } 287 288 @Override 289 public String toString() { 290 return "/" + source + "/" + getFlagString(); 291 } 292 293 /** 294 * Nashorn extension: RegExp.prototype.compile - everybody implements this! 295 * 296 * @param self self reference 297 * @param pattern pattern 298 * @param flags flags 299 * @return new NativeRegExp 300 */ 301 @Function(attributes = Attribute.NOT_ENUMERABLE) 302 public static Object compile(final Object self, final Object pattern, final Object flags) { 303 final NativeRegExp regExp = checkRegExp(self); 304 final NativeRegExp compiled = newRegExp(pattern, flags); 305 // copy over fields to 'self' 306 regExp.setSource(compiled.getSource()); 307 regExp.setGlobal(compiled.getGlobal()); 308 regExp.setIgnoreCase(compiled.getIgnoreCase()); 309 regExp.setMultiline(compiled.getMultiline()); 310 regExp.setRegex(compiled.getRegex()); 311 regExp.setGroupsInNegativeLookahead(compiled.getGroupsInNegativeLookahead()); 312 313 // Some implementations return undefined. Some return 'self'. Since return 314 // value is most likely be ignored, we can play safe and return 'self'. 315 return regExp; 316 } 317 318 /** 319 * ECMA 15.10.6.2 RegExp.prototype.exec(string) 320 * 321 * @param self self reference 322 * @param string string to match against regexp 323 * @return array containing the matches or {@code null} if no match 324 */ 325 @Function(attributes = Attribute.NOT_ENUMERABLE) 326 public static Object exec(final Object self, final Object string) { 327 return checkRegExp(self).exec(JSType.toString(string)); 328 } 329 330 /** 331 * ECMA 15.10.6.3 RegExp.prototype.test(string) 332 * 333 * @param self self reference 334 * @param string string to test for matches against regexp 335 * @return true if matches found, false otherwise 336 */ 337 @Function(attributes = Attribute.NOT_ENUMERABLE) 338 public static Object test(final Object self, final Object string) { 339 return checkRegExp(self).test(JSType.toString(string)); 340 } 341 342 /** 343 * ECMA 15.10.6.4 RegExp.prototype.toString() 344 * 345 * @param self self reference 346 * @return string version of regexp 347 */ 348 @Function(attributes = Attribute.NOT_ENUMERABLE) 349 public static Object toString(final Object self) { 350 return checkRegExp(self).toString(); 351 } 352 353 /** 354 * ECMA 15.10.7.1 source 355 * 356 * @param self self reference 357 * @return the input string for the regexp 358 */ 359 @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE | Attribute.NOT_WRITABLE) 360 public static Object source(final Object self) { 361 return checkRegExp(self).source; 362 } 363 364 /** 365 * ECMA 15.10.7.2 global 366 * 367 * @param self self reference 368 * @return true if this regexp is flagged global, false otherwise 369 */ 370 @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE | Attribute.NOT_WRITABLE) 371 public static Object global(final Object self) { 372 return checkRegExp(self).global; 373 } 374 375 /** 376 * ECMA 15.10.7.3 ignoreCase 377 * 378 * @param self self reference 379 * @return true if this regexp if flagged to ignore case, false otherwise 380 */ 381 @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE | Attribute.NOT_WRITABLE) 382 public static Object ignoreCase(final Object self) { 383 return checkRegExp(self).ignoreCase; 384 } 385 386 /** 387 * ECMA 15.10.7.4 multiline 388 * 389 * @param self self reference 390 * @return true if this regexp is flagged to be multiline, false otherwise 391 */ 392 @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE | Attribute.NOT_WRITABLE) 393 public static Object multiline(final Object self) { 394 return checkRegExp(self).multiline; 395 } 396 397 private RegExpMatch execInner(final String string) { 398 if (this.regex == null) { 399 return null; // never matches or similar, e.g. a[] 400 } 401 402 setInput(string); 403 404 final Matcher matcher = regex.matcher(inputChars); 405 final int start = this.global ? getLastIndex() : 0; 406 407 if (start < 0 || start > string.length()) { 408 setLastIndex(0); 409 return null; 410 } 411 412 if (matcher.search(start, inputChars.length, Option.NONE) == -1) { 413 setLastIndex(0); 414 return null; 415 } 416 417 if (global) { 418 setLastIndex(matcher.getEnd()); 419 } 420 421 return new RegExpMatch(string, matcher.getBegin(), groups(matcher)); 422 } 423 424 /** 425 * Convert java.util.regex.Matcher groups to JavaScript groups. 426 * That is, replace null and groups that didn't match with undefined. 427 */ 428 private Object[] groups(final Matcher matcher) { 429 Region region = matcher.getRegion(); 430 final int groupCount = region == null ? 0 : region.numRegs - 1; 431 final Object[] groups = new Object[groupCount + 1]; 432 groups[0] = input.substring(matcher.getBegin(), matcher.getEnd()); 433 434 for (int i = 1, lastGroupStart = matcher.getBegin(); i <= groupCount; i++) { 435 final int groupStart = region.beg[i]; 436 if (lastGroupStart > groupStart 437 || (groupsInNegativeLookahead != null && groupsInNegativeLookahead.isSet(i))) { 438 // (1) ECMA 15.10.2.5 NOTE 3: need to clear Atom's captures each time Atom is repeated. 439 // (2) ECMA 15.10.2.8 NOTE 3: Backreferences to captures in (?!Disjunction) from elsewhere 440 // in the pattern always return undefined because the negative lookahead must fail. 441 groups[i] = UNDEFINED; 442 continue; 443 } 444 final int begin = region.beg[i]; 445 final int end = region.end[i]; 446 groups[i] = begin == -1 ? UNDEFINED : input.substring(begin, end); 447 lastGroupStart = groupStart; 448 } 449 return groups; 450 } 451 452 private void setInput(String input) { 453 if (!input.equals(this.input)) { 454 this.input = input; 455 this.inputChars = input.toCharArray(); 456 } 457 } 458 459 /** 460 * Executes a search for a match within a string based on a regular 461 * expression. It returns an array of information or null if no match is 462 * found. 463 * 464 * @param string String to match. 465 * @return NativeArray of matches, string or null. 466 */ 467 public Object exec(final String string) { 468 final RegExpMatch m = execInner(string); 469 // the input string 470 if (m == null) { 471 return null; 472 } 473 474 return new NativeRegExpExecResult(m); 475 } 476 477 /** 478 * Executes a search for a match within a string based on a regular 479 * expression. 480 * 481 * @param string String to match. 482 * @return True if a match is found. 483 */ 484 public Object test(final String string) { 485 return exec(string) != null; 486 } 487 488 /** 489 * Searches and replaces the regular expression portion (match) with the 490 * replaced text instead. For the "replacement text" parameter, you can use 491 * the keywords $1 to $2 to replace the original text with values from 492 * sub-patterns defined within the main pattern. 493 * 494 * @param string String to match. 495 * @param replacement Replacement string. 496 * @return String with substitutions. 497 */ 498 Object replace(final String string, final String replacement, final ScriptFunction function) { 499 setInput(string); 500 final Matcher matcher = regex.matcher(inputChars); 501 /* 502 * $$ -> $ 503 * $& -> the matched substring 504 * $` -> the portion of string that preceeds matched substring 505 * $' -> the portion of string that follows the matched substring 506 * $n -> the nth capture, where n is [1-9] and $n is NOT followed by a decimal digit 507 * $nn -> the nnth capture, where nn is a two digit decimal number [01-99]. 508 */ 509 String replace = replacement; 510 511 if (!global) { 512 if (matcher.search(0, inputChars.length, Option.NONE) == -1) { 513 return string; 514 } 515 516 final StringBuilder sb = new StringBuilder(); 517 if (function != null) { 518 replace = callReplaceValue(function, matcher, string); 519 } 520 appendReplacement(matcher, string, replace, sb, 0); 521 sb.append(string, matcher.getEnd(), string.length()); 522 return sb.toString(); 523 } 524 525 int end = 0; // a.k.a. lastAppendPosition 526 setLastIndex(0); 527 528 int found; 529 try { 530 found = matcher.search(end, inputChars.length, Option.NONE); 531 } catch (final IndexOutOfBoundsException e) { 532 found = -1; 533 } 534 535 if (found == -1) { 536 return string; 537 } 538 539 int thisIndex = 0; 540 int previousLastIndex = 0; 541 final StringBuilder sb = new StringBuilder(); 542 do { 543 if (function != null) { 544 replace = callReplaceValue(function, matcher, string); 545 } 546 appendReplacement(matcher, string, replace, sb, thisIndex); 547 end = matcher.getEnd(); 548 549 // ECMA 15.5.4.10 String.prototype.match(regexp) 550 thisIndex = end; 551 if (thisIndex == previousLastIndex) { 552 setLastIndex(thisIndex + 1); 553 previousLastIndex = thisIndex + 1; 554 end++; 555 } else { 556 previousLastIndex = thisIndex; 557 } 558 } while (matcher.search(end, inputChars.length, Option.NONE) > -1); 559 560 sb.append(string, thisIndex, string.length()); 561 562 return sb.toString(); 563 } 564 565 private void appendReplacement(final Matcher matcher, final String text, final String replacement, final StringBuilder sb, final int lastAppendPosition) { 566 // Process substitution string to replace group references with groups 567 int cursor = 0; 568 final StringBuilder result = new StringBuilder(); 569 Object[] groups = null; 570 571 while (cursor < replacement.length()) { 572 char nextChar = replacement.charAt(cursor); 573 if (nextChar == '$') { 574 // Skip past $ 575 cursor++; 576 nextChar = replacement.charAt(cursor); 577 final int firstDigit = nextChar - '0'; 578 Region region = matcher.getRegion(); 579 int groupCount = region == null ? 0 : region.numRegs - 1; 580 581 if (firstDigit >= 0 && firstDigit <= 9 && firstDigit <= groupCount) { 582 // $0 is not supported, but $01 is. implementation-defined: if n>m, ignore second digit. 583 int refNum = firstDigit; 584 cursor++; 585 if (cursor < replacement.length() && firstDigit < groupCount) { 586 final int secondDigit = replacement.charAt(cursor) - '0'; 587 if ((secondDigit >= 0) && (secondDigit <= 9)) { 588 final int newRefNum = (firstDigit * 10) + secondDigit; 589 if (newRefNum <= groupCount && newRefNum > 0) { 590 // $nn ($01-$99) 591 refNum = newRefNum; 592 cursor++; 593 } 594 } 595 } 596 if (refNum > 0) { 597 if (groups == null) { 598 groups = groups(matcher); 599 } 600 // Append group if matched. 601 if (groups[refNum] != UNDEFINED) { 602 result.append((String) groups[refNum]); 603 } 604 } else { // $0. ignore. 605 assert refNum == 0; 606 result.append("$0"); 607 } 608 } else if (nextChar == '$') { 609 result.append('$'); 610 cursor++; 611 } else if (nextChar == '&') { 612 result.append(text, matcher.getBegin(), matcher.getEnd()); 613 cursor++; 614 } else if (nextChar == '`') { 615 result.append(text, 0, matcher.getBegin()); 616 cursor++; 617 } else if (nextChar == '\'') { 618 result.append(text, matcher.getEnd(), text.length()); 619 cursor++; 620 } else { 621 // unknown substitution or $n with n>m. skip. 622 result.append('$'); 623 } 624 } else { 625 result.append(nextChar); 626 cursor++; 627 } 628 } 629 // Append the intervening text 630 sb.append(text, lastAppendPosition, matcher.getBegin()); 631 // Append the match substitution 632 sb.append(result); 633 } 634 635 private String callReplaceValue(final ScriptFunction function, final Matcher matcher, final String string) { 636 final Object[] groups = groups(matcher); 637 final Object[] args = Arrays.copyOf(groups, groups.length + 2); 638 639 args[groups.length] = matcher.getBegin(); 640 args[groups.length + 1] = string; 641 642 final Object self = function.isStrict() ? UNDEFINED : Global.instance(); 643 644 return JSType.toString(ScriptRuntime.apply(function, self, args)); 645 } 646 647 /** 648 * Breaks up a string into an array of substrings based on a regular 649 * expression or fixed string. 650 * 651 * @param string String to match. 652 * @param limit Split limit. 653 * @return Array of substrings. 654 */ 655 Object split(final String string, final long limit) { 656 return split(this, string, limit); 657 } 658 659 private static Object split(final NativeRegExp regexp0, final String input, final long limit) { 660 final List<Object> matches = new ArrayList<>(); 661 662 final NativeRegExp regexp = new NativeRegExp(regexp0); 663 regexp.setGlobal(true); 664 665 if (limit == 0L) { 666 return new NativeArray(); 667 } 668 669 RegExpMatch match; 670 final int inputLength = input.length(); 671 int lastLength = -1; 672 int lastLastIndex = 0; 673 674 while ((match = regexp.execInner(input)) != null) { 675 final int lastIndex = match.getIndex() + match.length(); 676 677 if (lastIndex > lastLastIndex) { 678 matches.add(input.substring(lastLastIndex, match.getIndex())); 679 if (match.getGroups().length > 1 && match.getIndex() < inputLength) { 680 matches.addAll(Arrays.asList(match.getGroups()).subList(1, match.getGroups().length)); 681 } 682 683 lastLength = match.length(); 684 lastLastIndex = lastIndex; 685 686 if (matches.size() >= limit) { 687 break; 688 } 689 } 690 691 // bump the index to avoid infinite loop 692 if (regexp.getLastIndex() == match.getIndex()) { 693 regexp.setLastIndex(match.getIndex() + 1); 694 } 695 } 696 697 if (matches.size() < limit) { 698 // check special case if we need to append an empty string at the 699 // end of the match 700 // if the lastIndex was the entire string 701 if (lastLastIndex == input.length()) { 702 if (lastLength > 0 || regexp.test("") == Boolean.FALSE) { 703 matches.add(""); 704 } 705 } else { 706 matches.add(input.substring(lastLastIndex, inputLength)); 707 } 708 } 709 710 return new NativeArray(matches.toArray()); 711 } 712 713 /** 714 * Tests for a match in a string. It returns the index of the match, or -1 715 * if not found. 716 * 717 * @param string String to match. 718 * @return Index of match. 719 */ 720 Object search(final String string) { 721 setInput(string); 722 723 final Matcher matcher = regex.matcher(inputChars); 724 725 int start = 0; 726 if (global) { 727 start = getLastIndex(); 728 } 729 730 start = matcher.search(start, inputChars.length, Option.NONE); 731 732 if (global) { 733 setLastIndex(matcher.getEnd()); 734 } 735 736 return start; 737 } 738 739 /** 740 * Fast lastIndex getter 741 * @return last index property as int 742 */ 743 public int getLastIndex() { 744 return JSType.toInt32(lastIndex); 745 } 746 747 /** 748 * Fast lastIndex getter 749 * @return last index property as boxed integer 750 */ 751 public Object getLastIndexObject() { 752 return lastIndex; 753 } 754 755 /** 756 * Fast lastIndex setter 757 * @param lastIndex lastIndex 758 */ 759 public void setLastIndex(final int lastIndex) { 760 this.lastIndex = JSType.toObject(lastIndex); 761 } 762 763 private void init() { 764 this.setProto(Global.instance().getRegExpPrototype()); 765 } 766 767 private static NativeRegExp checkRegExp(final Object self) { 768 Global.checkObjectCoercible(self); 769 if (self instanceof NativeRegExp) { 770 return (NativeRegExp)self; 771 } else if (self != null && self == Global.instance().getRegExpPrototype()) { 772 return Global.instance().DEFAULT_REGEXP; 773 } else { 774 typeError(Global.instance(), "not.a.regexp", ScriptRuntime.safeToString(self)); 775 return null; 776 } 777 } 778 779 private String getSource() { 780 return source; 781 } 782 783 private void setSource(final String source) { 784 this.source = source; 785 } 786 787 boolean getGlobal() { 788 return global; 789 } 790 791 private void setGlobal(final boolean global) { 792 this.global = global; 793 } 794 795 private boolean getIgnoreCase() { 796 return ignoreCase; 797 } 798 799 private void setIgnoreCase(final boolean ignoreCase) { 800 this.ignoreCase = ignoreCase; 801 } 802 803 private boolean getMultiline() { 804 return multiline; 805 } 806 807 private void setMultiline(final boolean multiline) { 808 this.multiline = multiline; 809 } 810 811 private Regex getRegex() { 812 return regex; 813 } 814 815 private void setRegex(final Regex regex) { 816 this.regex = regex; 817 } 818 819 private BitVector getGroupsInNegativeLookahead() { 820 return groupsInNegativeLookahead; 821 } 822 823 private void setGroupsInNegativeLookahead(final BitVector groupsInNegativeLookahead) { 824 this.groupsInNegativeLookahead = groupsInNegativeLookahead; 825 } 826 827 }