506 * @return last regexp input
507 */
508 @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$8")
509 public static Object getGroup8(Object self) {
510 final RegExpResult match = Global.instance().getLastRegExpResult();
511 return match == null ? "" : match.getGroup(8);
512 }
513
514 /**
515 * Getter for non-standard RegExp.$9 property.
516 * @param self self object
517 * @return last regexp input
518 */
519 @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$9")
520 public static Object getGroup9(Object self) {
521 final RegExpResult match = Global.instance().getLastRegExpResult();
522 return match == null ? "" : match.getGroup(9);
523 }
524
525 private RegExpResult execInner(final String string) {
526 int start = getLastIndex();
527 if (! regexp.isGlobal()) {
528 start = 0;
529 }
530
531 if (start < 0 || start > string.length()) {
532 setLastIndex(0);
533 return null;
534 }
535
536 final RegExpMatcher matcher = regexp.match(string);
537 if (matcher == null || !matcher.search(start)) {
538 setLastIndex(0);
539 return null;
540 }
541
542 if (regexp.isGlobal()) {
543 setLastIndex(matcher.end());
544 }
545
546 final RegExpResult match = new RegExpResult(string, matcher.start(), groups(matcher));
547 globalObject.setLastRegExpResult(match);
548 return match;
549 }
550
551 /**
552 * Convert java.util.regex.Matcher groups to JavaScript groups.
553 * That is, replace null and groups that didn't match with undefined.
554 */
555 private Object[] groups(final RegExpMatcher matcher) {
556 final int groupCount = matcher.groupCount();
557 final Object[] groups = new Object[groupCount + 1];
558 final BitVector groupsInNegativeLookahead = regexp.getGroupsInNegativeLookahead();
559
560 for (int i = 0, lastGroupStart = matcher.start(); i <= groupCount; i++) {
561 final int groupStart = matcher.start(i);
562 if (lastGroupStart > groupStart
563 || (groupsInNegativeLookahead != null && groupsInNegativeLookahead.isSet(i))) {
564 // (1) ECMA 15.10.2.5 NOTE 3: need to clear Atom's captures each time Atom is repeated.
565 // (2) ECMA 15.10.2.8 NOTE 3: Backreferences to captures in (?!Disjunction) from elsewhere
566 // in the pattern always return undefined because the negative lookahead must fail.
567 groups[i] = UNDEFINED;
568 continue;
569 }
570 final String group = matcher.group(i);
583 * @return NativeArray of matches, string or null.
584 */
585 public Object exec(final String string) {
586 final RegExpResult match = execInner(string);
587
588 if (match == null) {
589 return null;
590 }
591
592 return new NativeRegExpExecResult(match);
593 }
594
595 /**
596 * Executes a search for a match within a string based on a regular
597 * expression.
598 *
599 * @param string String to match.
600 * @return True if a match is found.
601 */
602 public Object test(final String string) {
603 return exec(string) != null;
604 }
605
606 /**
607 * Searches and replaces the regular expression portion (match) with the
608 * replaced text instead. For the "replacement text" parameter, you can use
609 * the keywords $1 to $2 to replace the original text with values from
610 * sub-patterns defined within the main pattern.
611 *
612 * @param string String to match.
613 * @param replacement Replacement string.
614 * @return String with substitutions.
615 */
616 Object replace(final String string, final String replacement, final ScriptFunction function) {
617 final RegExpMatcher matcher = regexp.match(string);
618
619 if (matcher == null) {
620 return string;
621 }
622
623 /*
748 final Object[] groups = groups(matcher);
749 final Object[] args = Arrays.copyOf(groups, groups.length + 2);
750
751 args[groups.length] = matcher.start();
752 args[groups.length + 1] = string;
753
754 final Object self = function.isStrict() ? UNDEFINED : Global.instance();
755
756 return JSType.toString(ScriptRuntime.apply(function, self, args));
757 }
758
759 /**
760 * Breaks up a string into an array of substrings based on a regular
761 * expression or fixed string.
762 *
763 * @param string String to match.
764 * @param limit Split limit.
765 * @return Array of substrings.
766 */
767 Object split(final String string, final long limit) {
768 return split(this, string, limit);
769 }
770
771 private static Object split(final NativeRegExp regexp0, final String input, final long limit) {
772 final List<Object> matches = new ArrayList<>();
773
774 final NativeRegExp regexp = new NativeRegExp(regexp0);
775 regexp.setGlobal(true);
776
777 if (limit == 0L) {
778 return new NativeArray();
779 }
780
781 RegExpResult match;
782 final int inputLength = input.length();
783 int lastLength = -1;
784 int lastLastIndex = 0;
785
786 while ((match = regexp.execInner(input)) != null) {
787 final int lastIndex = match.getIndex() + match.length();
788
789 if (lastIndex > lastLastIndex) {
790 matches.add(input.substring(lastLastIndex, match.getIndex()));
791 if (match.getGroups().length > 1 && match.getIndex() < inputLength) {
792 matches.addAll(Arrays.asList(match.getGroups()).subList(1, match.getGroups().length));
793 }
794
795 lastLength = match.length();
796 lastLastIndex = lastIndex;
797
798 if (matches.size() >= limit) {
799 break;
800 }
801 }
802
803 // bump the index to avoid infinite loop
804 if (regexp.getLastIndex() == match.getIndex()) {
805 regexp.setLastIndex(match.getIndex() + 1);
806 }
807 }
808
809 if (matches.size() < limit) {
810 // check special case if we need to append an empty string at the
811 // end of the match
812 // if the lastIndex was the entire string
813 if (lastLastIndex == input.length()) {
814 if (lastLength > 0 || regexp.test("") == Boolean.FALSE) {
815 matches.add("");
816 }
817 } else {
818 matches.add(input.substring(lastLastIndex, inputLength));
819 }
820 }
821
822 return new NativeArray(matches.toArray());
823 }
824
825 /**
826 * Tests for a match in a string. It returns the index of the match, or -1
827 * if not found.
828 *
829 * @param string String to match.
830 * @return Index of match.
831 */
832 Object search(final String string) {
833 final RegExpResult match = execInner(string);
834
835 if (match == null) {
836 return -1;
837 }
838
|
506 * @return last regexp input
507 */
508 @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$8")
509 public static Object getGroup8(Object self) {
510 final RegExpResult match = Global.instance().getLastRegExpResult();
511 return match == null ? "" : match.getGroup(8);
512 }
513
514 /**
515 * Getter for non-standard RegExp.$9 property.
516 * @param self self object
517 * @return last regexp input
518 */
519 @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$9")
520 public static Object getGroup9(Object self) {
521 final RegExpResult match = Global.instance().getLastRegExpResult();
522 return match == null ? "" : match.getGroup(9);
523 }
524
525 private RegExpResult execInner(final String string) {
526 final boolean isGlobal = regexp.isGlobal();
527 int start = getLastIndex();
528 if (!isGlobal) {
529 start = 0;
530 }
531
532 if (start < 0 || start > string.length()) {
533 if (isGlobal) {
534 setLastIndex(0);
535 }
536 return null;
537 }
538
539 final RegExpMatcher matcher = regexp.match(string);
540 if (matcher == null || !matcher.search(start)) {
541 if (isGlobal) {
542 setLastIndex(0);
543 }
544 return null;
545 }
546
547 if (isGlobal) {
548 setLastIndex(matcher.end());
549 }
550
551 final RegExpResult match = new RegExpResult(string, matcher.start(), groups(matcher));
552 globalObject.setLastRegExpResult(match);
553 return match;
554 }
555
556 // String.prototype.split method ignores the global flag and should not update lastIndex property.
557 private RegExpResult execSplit(final String string, int start) {
558 if (start < 0 || start > string.length()) {
559 return null;
560 }
561
562 final RegExpMatcher matcher = regexp.match(string);
563 if (matcher == null || !matcher.search(start)) {
564 return null;
565 }
566
567 final RegExpResult match = new RegExpResult(string, matcher.start(), groups(matcher));
568 globalObject.setLastRegExpResult(match);
569 return match;
570 }
571
572 /**
573 * Convert java.util.regex.Matcher groups to JavaScript groups.
574 * That is, replace null and groups that didn't match with undefined.
575 */
576 private Object[] groups(final RegExpMatcher matcher) {
577 final int groupCount = matcher.groupCount();
578 final Object[] groups = new Object[groupCount + 1];
579 final BitVector groupsInNegativeLookahead = regexp.getGroupsInNegativeLookahead();
580
581 for (int i = 0, lastGroupStart = matcher.start(); i <= groupCount; i++) {
582 final int groupStart = matcher.start(i);
583 if (lastGroupStart > groupStart
584 || (groupsInNegativeLookahead != null && groupsInNegativeLookahead.isSet(i))) {
585 // (1) ECMA 15.10.2.5 NOTE 3: need to clear Atom's captures each time Atom is repeated.
586 // (2) ECMA 15.10.2.8 NOTE 3: Backreferences to captures in (?!Disjunction) from elsewhere
587 // in the pattern always return undefined because the negative lookahead must fail.
588 groups[i] = UNDEFINED;
589 continue;
590 }
591 final String group = matcher.group(i);
604 * @return NativeArray of matches, string or null.
605 */
606 public Object exec(final String string) {
607 final RegExpResult match = execInner(string);
608
609 if (match == null) {
610 return null;
611 }
612
613 return new NativeRegExpExecResult(match);
614 }
615
616 /**
617 * Executes a search for a match within a string based on a regular
618 * expression.
619 *
620 * @param string String to match.
621 * @return True if a match is found.
622 */
623 public Object test(final String string) {
624 return execInner(string) != null;
625 }
626
627 /**
628 * Searches and replaces the regular expression portion (match) with the
629 * replaced text instead. For the "replacement text" parameter, you can use
630 * the keywords $1 to $2 to replace the original text with values from
631 * sub-patterns defined within the main pattern.
632 *
633 * @param string String to match.
634 * @param replacement Replacement string.
635 * @return String with substitutions.
636 */
637 Object replace(final String string, final String replacement, final ScriptFunction function) {
638 final RegExpMatcher matcher = regexp.match(string);
639
640 if (matcher == null) {
641 return string;
642 }
643
644 /*
769 final Object[] groups = groups(matcher);
770 final Object[] args = Arrays.copyOf(groups, groups.length + 2);
771
772 args[groups.length] = matcher.start();
773 args[groups.length + 1] = string;
774
775 final Object self = function.isStrict() ? UNDEFINED : Global.instance();
776
777 return JSType.toString(ScriptRuntime.apply(function, self, args));
778 }
779
780 /**
781 * Breaks up a string into an array of substrings based on a regular
782 * expression or fixed string.
783 *
784 * @param string String to match.
785 * @param limit Split limit.
786 * @return Array of substrings.
787 */
788 Object split(final String string, final long limit) {
789 if (limit == 0L) {
790 return new NativeArray();
791 }
792
793 final List<Object> matches = new ArrayList<>();
794
795 RegExpResult match;
796 final int inputLength = string.length();
797 int lastLength = -1;
798 int lastIndex = 0;
799 int lastLastIndex = 0;
800
801 while ((match = execSplit(string, lastIndex)) != null) {
802 lastIndex = match.getIndex() + match.length();
803
804 if (lastIndex > lastLastIndex) {
805 matches.add(string.substring(lastLastIndex, match.getIndex()));
806 final Object[] groups = match.getGroups();
807 if (groups.length > 1 && match.getIndex() < inputLength) {
808 for (int index = 1; index < groups.length && matches.size() < limit; index++) {
809 matches.add(groups[index]);
810 }
811 }
812
813 lastLength = match.length();
814
815 if (matches.size() >= limit) {
816 break;
817 }
818 }
819
820 // bump the index to avoid infinite loop
821 if (lastIndex == lastLastIndex) {
822 lastIndex++;
823 } else {
824 lastLastIndex = lastIndex;
825 }
826 }
827
828 if (matches.size() < limit) {
829 // check special case if we need to append an empty string at the
830 // end of the match
831 // if the lastIndex was the entire string
832 if (lastLastIndex == string.length()) {
833 if (lastLength > 0 || execSplit("", 0) == null) {
834 matches.add("");
835 }
836 } else {
837 matches.add(string.substring(lastLastIndex, inputLength));
838 }
839 }
840
841 return new NativeArray(matches.toArray());
842 }
843
844 /**
845 * Tests for a match in a string. It returns the index of the match, or -1
846 * if not found.
847 *
848 * @param string String to match.
849 * @return Index of match.
850 */
851 Object search(final String string) {
852 final RegExpResult match = execInner(string);
853
854 if (match == null) {
855 return -1;
856 }
857
|