/*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.nashorn.internal.objects;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
import java.lang.invoke.MethodHandle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import jdk.nashorn.internal.objects.annotations.Attribute;
import jdk.nashorn.internal.objects.annotations.Constructor;
import jdk.nashorn.internal.objects.annotations.Function;
import jdk.nashorn.internal.objects.annotations.Getter;
import jdk.nashorn.internal.objects.annotations.Property;
import jdk.nashorn.internal.objects.annotations.ScriptClass;
import jdk.nashorn.internal.objects.annotations.SpecializedFunction;
import jdk.nashorn.internal.objects.annotations.Where;
import jdk.nashorn.internal.runtime.BitVector;
import jdk.nashorn.internal.runtime.JSType;
import jdk.nashorn.internal.runtime.ParserException;
import jdk.nashorn.internal.runtime.PropertyMap;
import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.ScriptRuntime;
import jdk.nashorn.internal.runtime.linker.Bootstrap;
import jdk.nashorn.internal.runtime.regexp.RegExp;
import jdk.nashorn.internal.runtime.regexp.RegExpFactory;
import jdk.nashorn.internal.runtime.regexp.RegExpMatcher;
import jdk.nashorn.internal.runtime.regexp.RegExpResult;
ECMA 15.10 RegExp Objects.
/**
* ECMA 15.10 RegExp Objects.
*/
@ScriptClass("RegExp")
public final class NativeRegExp extends ScriptObject {
ECMA 15.10.7.5 lastIndex property /** ECMA 15.10.7.5 lastIndex property */
@Property(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE)
public Object lastIndex;
Compiled regexp /** Compiled regexp */
private RegExp regexp;
// Reference to global object needed to support static RegExp properties
private final Global globalObject;
// initialized by nasgen
private static PropertyMap $nasgenmap$;
private NativeRegExp(final Global global) {
super(global.getRegExpPrototype(), $nasgenmap$);
this.globalObject = global;
}
NativeRegExp(final String input, final String flagString, final Global global, final ScriptObject proto) {
super(proto, $nasgenmap$);
try {
this.regexp = RegExpFactory.create(input, flagString);
} catch (final ParserException e) {
// translate it as SyntaxError object and throw it
e.throwAsEcmaException();
throw new AssertionError(); //guard against null warnings below
}
this.globalObject = global;
this.setLastIndex(0);
}
NativeRegExp(final String input, final String flagString, final Global global) {
this(input, flagString, global, global.getRegExpPrototype());
}
NativeRegExp(final String input, final String flagString) {
this(input, flagString, Global.instance());
}
NativeRegExp(final String string, final Global global) {
this(string, "", global);
}
NativeRegExp(final String string) {
this(string, Global.instance());
}
NativeRegExp(final NativeRegExp regExp) {
this(Global.instance());
this.lastIndex = regExp.getLastIndexObject();
this.regexp = regExp.getRegExp();
}
@Override
public String getClassName() {
return "RegExp";
}
ECMA 15.10.4
Constructor
Params: - isNew – is the new operator used for instantiating this regexp
- self – self reference
- args – arguments (optional: pattern and flags)
Returns: new NativeRegExp
/**
* ECMA 15.10.4
*
* Constructor
*
* @param isNew is the new operator used for instantiating this regexp
* @param self self reference
* @param args arguments (optional: pattern and flags)
* @return new NativeRegExp
*/
@Constructor(arity = 2)
public static NativeRegExp constructor(final boolean isNew, final Object self, final Object... args) {
if (args.length > 1) {
return newRegExp(args[0], args[1]);
} else if (args.length > 0) {
return newRegExp(args[0], UNDEFINED);
}
return newRegExp(UNDEFINED, UNDEFINED);
}
ECMA 15.10.4
Constructor - specialized version, no args, empty regexp
Params: - isNew – is the new operator used for instantiating this regexp
- self – self reference
Returns: new NativeRegExp
/**
* ECMA 15.10.4
*
* Constructor - specialized version, no args, empty regexp
*
* @param isNew is the new operator used for instantiating this regexp
* @param self self reference
* @return new NativeRegExp
*/
@SpecializedFunction(isConstructor=true)
public static NativeRegExp constructor(final boolean isNew, final Object self) {
return new NativeRegExp("", "");
}
ECMA 15.10.4
Constructor - specialized version, pattern, no flags
Params: - isNew – is the new operator used for instantiating this regexp
- self – self reference
- pattern – pattern
Returns: new NativeRegExp
/**
* ECMA 15.10.4
*
* Constructor - specialized version, pattern, no flags
*
* @param isNew is the new operator used for instantiating this regexp
* @param self self reference
* @param pattern pattern
* @return new NativeRegExp
*/
@SpecializedFunction(isConstructor=true)
public static NativeRegExp constructor(final boolean isNew, final Object self, final Object pattern) {
return newRegExp(pattern, UNDEFINED);
}
ECMA 15.10.4
Constructor - specialized version, pattern and flags
Params: - isNew – is the new operator used for instantiating this regexp
- self – self reference
- pattern – pattern
- flags – flags
Returns: new NativeRegExp
/**
* ECMA 15.10.4
*
* Constructor - specialized version, pattern and flags
*
* @param isNew is the new operator used for instantiating this regexp
* @param self self reference
* @param pattern pattern
* @param flags flags
* @return new NativeRegExp
*/
@SpecializedFunction(isConstructor=true)
public static NativeRegExp constructor(final boolean isNew, final Object self, final Object pattern, final Object flags) {
return newRegExp(pattern, flags);
}
External constructor used in generated code, which explains the public access
Params: - regexp – regexp
- flags – flags
Returns: new NativeRegExp
/**
* External constructor used in generated code, which explains the public access
*
* @param regexp regexp
* @param flags flags
* @return new NativeRegExp
*/
public static NativeRegExp newRegExp(final Object regexp, final Object flags) {
String patternString = "";
String flagString = "";
if (regexp != UNDEFINED) {
if (regexp instanceof NativeRegExp) {
if (flags != UNDEFINED) {
throw typeError("regex.cant.supply.flags");
}
return (NativeRegExp)regexp; // 15.10.3.1 - undefined flags and regexp as
}
patternString = JSType.toString(regexp);
}
if (flags != UNDEFINED) {
flagString = JSType.toString(flags);
}
return new NativeRegExp(patternString, flagString);
}
Build a regexp that matches string
as-is. All meta-characters will be escaped. Params: - string – pattern string
Returns: flat regexp
/**
* Build a regexp that matches {@code string} as-is. All meta-characters will be escaped.
*
* @param string pattern string
* @return flat regexp
*/
static NativeRegExp flatRegExp(final String string) {
// escape special characters
StringBuilder sb = null;
final int length = string.length();
for (int i = 0; i < length; i++) {
final char c = string.charAt(i);
switch (c) {
case '^':
case '$':
case '\\':
case '.':
case '*':
case '+':
case '?':
case '(':
case ')':
case '[':
case '{':
case '|':
if (sb == null) {
sb = new StringBuilder(length * 2);
sb.append(string, 0, i);
}
sb.append('\\');
sb.append(c);
break;
default:
if (sb != null) {
sb.append(c);
}
break;
}
}
return new NativeRegExp(sb == null ? string : sb.toString(), "");
}
private String getFlagString() {
final StringBuilder sb = new StringBuilder(3);
if (regexp.isGlobal()) {
sb.append('g');
}
if (regexp.isIgnoreCase()) {
sb.append('i');
}
if (regexp.isMultiline()) {
sb.append('m');
}
return sb.toString();
}
@Override
public String safeToString() {
return "[RegExp " + toString() + "]";
}
@Override
public String toString() {
return "/" + regexp.getSource() + "/" + getFlagString();
}
Nashorn extension: RegExp.prototype.compile - everybody implements this!
Params: - self – self reference
- pattern – pattern
- flags – flags
Returns: new NativeRegExp
/**
* Nashorn extension: RegExp.prototype.compile - everybody implements this!
*
* @param self self reference
* @param pattern pattern
* @param flags flags
* @return new NativeRegExp
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static ScriptObject compile(final Object self, final Object pattern, final Object flags) {
final NativeRegExp regExp = checkRegExp(self);
final NativeRegExp compiled = newRegExp(pattern, flags);
// copy over regexp to 'self'
regExp.setRegExp(compiled.getRegExp());
// Some implementations return undefined. Some return 'self'. Since return
// value is most likely be ignored, we can play safe and return 'self'.
return regExp;
}
ECMA 15.10.6.2 RegExp.prototype.exec(string)
Params: - self – self reference
- string – string to match against regexp
Returns: array containing the matches or null
if no match
/**
* ECMA 15.10.6.2 RegExp.prototype.exec(string)
*
* @param self self reference
* @param string string to match against regexp
* @return array containing the matches or {@code null} if no match
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static ScriptObject exec(final Object self, final Object string) {
return checkRegExp(self).exec(JSType.toString(string));
}
ECMA 15.10.6.3 RegExp.prototype.test(string)
Params: - self – self reference
- string – string to test for matches against regexp
Returns: true if matches found, false otherwise
/**
* ECMA 15.10.6.3 RegExp.prototype.test(string)
*
* @param self self reference
* @param string string to test for matches against regexp
* @return true if matches found, false otherwise
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static boolean test(final Object self, final Object string) {
return checkRegExp(self).test(JSType.toString(string));
}
ECMA 15.10.6.4 RegExp.prototype.toString()
Params: - self – self reference
Returns: string version of regexp
/**
* ECMA 15.10.6.4 RegExp.prototype.toString()
*
* @param self self reference
* @return string version of regexp
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static String toString(final Object self) {
return checkRegExp(self).toString();
}
ECMA 15.10.7.1 source
Params: - self – self reference
Returns: the input string for the regexp
/**
* ECMA 15.10.7.1 source
*
* @param self self reference
* @return the input string for the regexp
*/
@Getter(attributes = Attribute.NON_ENUMERABLE_CONSTANT)
public static Object source(final Object self) {
return checkRegExp(self).getRegExp().getSource();
}
ECMA 15.10.7.2 global
Params: - self – self reference
Returns: true if this regexp is flagged global, false otherwise
/**
* ECMA 15.10.7.2 global
*
* @param self self reference
* @return true if this regexp is flagged global, false otherwise
*/
@Getter(attributes = Attribute.NON_ENUMERABLE_CONSTANT)
public static Object global(final Object self) {
return checkRegExp(self).getRegExp().isGlobal();
}
ECMA 15.10.7.3 ignoreCase
Params: - self – self reference
Returns: true if this regexp if flagged to ignore case, false otherwise
/**
* ECMA 15.10.7.3 ignoreCase
*
* @param self self reference
* @return true if this regexp if flagged to ignore case, false otherwise
*/
@Getter(attributes = Attribute.NON_ENUMERABLE_CONSTANT)
public static Object ignoreCase(final Object self) {
return checkRegExp(self).getRegExp().isIgnoreCase();
}
ECMA 15.10.7.4 multiline
Params: - self – self reference
Returns: true if this regexp is flagged to be multiline, false otherwise
/**
* ECMA 15.10.7.4 multiline
*
* @param self self reference
* @return true if this regexp is flagged to be multiline, false otherwise
*/
@Getter(attributes = Attribute.NON_ENUMERABLE_CONSTANT)
public static Object multiline(final Object self) {
return checkRegExp(self).getRegExp().isMultiline();
}
Getter for non-standard RegExp.input property.
Params: - self – self object
Returns: last regexp input
/**
* Getter for non-standard RegExp.input property.
* @param self self object
* @return last regexp input
*/
@Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "input")
public static Object getLastInput(final Object self) {
final RegExpResult match = Global.instance().getLastRegExpResult();
return match == null ? "" : match.getInput();
}
Getter for non-standard RegExp.multiline property.
Params: - self – self object
Returns: last regexp input
/**
* Getter for non-standard RegExp.multiline property.
* @param self self object
* @return last regexp input
*/
@Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "multiline")
public static Object getLastMultiline(final Object self) {
return false; // doesn't ever seem to become true and isn't documented anyhwere
}
Getter for non-standard RegExp.lastMatch property.
Params: - self – self object
Returns: last regexp input
/**
* Getter for non-standard RegExp.lastMatch property.
* @param self self object
* @return last regexp input
*/
@Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "lastMatch")
public static Object getLastMatch(final Object self) {
final RegExpResult match = Global.instance().getLastRegExpResult();
return match == null ? "" : match.getGroup(0);
}
Getter for non-standard RegExp.lastParen property.
Params: - self – self object
Returns: last regexp input
/**
* Getter for non-standard RegExp.lastParen property.
* @param self self object
* @return last regexp input
*/
@Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "lastParen")
public static Object getLastParen(final Object self) {
final RegExpResult match = Global.instance().getLastRegExpResult();
return match == null ? "" : match.getLastParen();
}
Getter for non-standard RegExp.leftContext property.
Params: - self – self object
Returns: last regexp input
/**
* Getter for non-standard RegExp.leftContext property.
* @param self self object
* @return last regexp input
*/
@Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "leftContext")
public static Object getLeftContext(final Object self) {
final RegExpResult match = Global.instance().getLastRegExpResult();
return match == null ? "" : match.getInput().substring(0, match.getIndex());
}
Getter for non-standard RegExp.rightContext property.
Params: - self – self object
Returns: last regexp input
/**
* Getter for non-standard RegExp.rightContext property.
* @param self self object
* @return last regexp input
*/
@Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "rightContext")
public static Object getRightContext(final Object self) {
final RegExpResult match = Global.instance().getLastRegExpResult();
return match == null ? "" : match.getInput().substring(match.getIndex() + match.length());
}
Getter for non-standard RegExp.$1 property.
Params: - self – self object
Returns: last regexp input
/**
* Getter for non-standard RegExp.$1 property.
* @param self self object
* @return last regexp input
*/
@Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$1")
public static Object getGroup1(final Object self) {
final RegExpResult match = Global.instance().getLastRegExpResult();
return match == null ? "" : match.getGroup(1);
}
Getter for non-standard RegExp.$2 property.
Params: - self – self object
Returns: last regexp input
/**
* Getter for non-standard RegExp.$2 property.
* @param self self object
* @return last regexp input
*/
@Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$2")
public static Object getGroup2(final Object self) {
final RegExpResult match = Global.instance().getLastRegExpResult();
return match == null ? "" : match.getGroup(2);
}
Getter for non-standard RegExp.$3 property.
Params: - self – self object
Returns: last regexp input
/**
* Getter for non-standard RegExp.$3 property.
* @param self self object
* @return last regexp input
*/
@Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$3")
public static Object getGroup3(final Object self) {
final RegExpResult match = Global.instance().getLastRegExpResult();
return match == null ? "" : match.getGroup(3);
}
Getter for non-standard RegExp.$4 property.
Params: - self – self object
Returns: last regexp input
/**
* Getter for non-standard RegExp.$4 property.
* @param self self object
* @return last regexp input
*/
@Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$4")
public static Object getGroup4(final Object self) {
final RegExpResult match = Global.instance().getLastRegExpResult();
return match == null ? "" : match.getGroup(4);
}
Getter for non-standard RegExp.$5 property.
Params: - self – self object
Returns: last regexp input
/**
* Getter for non-standard RegExp.$5 property.
* @param self self object
* @return last regexp input
*/
@Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$5")
public static Object getGroup5(final Object self) {
final RegExpResult match = Global.instance().getLastRegExpResult();
return match == null ? "" : match.getGroup(5);
}
Getter for non-standard RegExp.$6 property.
Params: - self – self object
Returns: last regexp input
/**
* Getter for non-standard RegExp.$6 property.
* @param self self object
* @return last regexp input
*/
@Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$6")
public static Object getGroup6(final Object self) {
final RegExpResult match = Global.instance().getLastRegExpResult();
return match == null ? "" : match.getGroup(6);
}
Getter for non-standard RegExp.$7 property.
Params: - self – self object
Returns: last regexp input
/**
* Getter for non-standard RegExp.$7 property.
* @param self self object
* @return last regexp input
*/
@Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$7")
public static Object getGroup7(final Object self) {
final RegExpResult match = Global.instance().getLastRegExpResult();
return match == null ? "" : match.getGroup(7);
}
Getter for non-standard RegExp.$8 property.
Params: - self – self object
Returns: last regexp input
/**
* Getter for non-standard RegExp.$8 property.
* @param self self object
* @return last regexp input
*/
@Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$8")
public static Object getGroup8(final Object self) {
final RegExpResult match = Global.instance().getLastRegExpResult();
return match == null ? "" : match.getGroup(8);
}
Getter for non-standard RegExp.$9 property.
Params: - self – self object
Returns: last regexp input
/**
* Getter for non-standard RegExp.$9 property.
* @param self self object
* @return last regexp input
*/
@Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$9")
public static Object getGroup9(final Object self) {
final RegExpResult match = Global.instance().getLastRegExpResult();
return match == null ? "" : match.getGroup(9);
}
private RegExpResult execInner(final String string) {
final boolean isGlobal = regexp.isGlobal();
int start = getLastIndex();
if (!isGlobal) {
start = 0;
}
if (start < 0 || start > string.length()) {
if (isGlobal) {
setLastIndex(0);
}
return null;
}
final RegExpMatcher matcher = regexp.match(string);
if (matcher == null || !matcher.search(start)) {
if (isGlobal) {
setLastIndex(0);
}
return null;
}
if (isGlobal) {
setLastIndex(matcher.end());
}
final RegExpResult match = new RegExpResult(string, matcher.start(), groups(matcher));
globalObject.setLastRegExpResult(match);
return match;
}
// String.prototype.split method ignores the global flag and should not update lastIndex property.
private RegExpResult execSplit(final String string, final int start) {
if (start < 0 || start > string.length()) {
return null;
}
final RegExpMatcher matcher = regexp.match(string);
if (matcher == null || !matcher.search(start)) {
return null;
}
final RegExpResult match = new RegExpResult(string, matcher.start(), groups(matcher));
globalObject.setLastRegExpResult(match);
return match;
}
Convert java.util.regex.Matcher groups to JavaScript groups.
That is, replace null and groups that didn't match with undefined.
/**
* Convert java.util.regex.Matcher groups to JavaScript groups.
* That is, replace null and groups that didn't match with undefined.
*/
private Object[] groups(final RegExpMatcher matcher) {
final int groupCount = matcher.groupCount();
final Object[] groups = new Object[groupCount + 1];
final BitVector groupsInNegativeLookahead = regexp.getGroupsInNegativeLookahead();
for (int i = 0, lastGroupStart = matcher.start(); i <= groupCount; i++) {
final int groupStart = matcher.start(i);
if (lastGroupStart > groupStart
|| groupsInNegativeLookahead != null && groupsInNegativeLookahead.isSet(i)) {
// (1) ECMA 15.10.2.5 NOTE 3: need to clear Atom's captures each time Atom is repeated.
// (2) ECMA 15.10.2.8 NOTE 3: Backreferences to captures in (?!Disjunction) from elsewhere
// in the pattern always return undefined because the negative lookahead must fail.
groups[i] = UNDEFINED;
continue;
}
final String group = matcher.group(i);
groups[i] = group == null ? UNDEFINED : group;
lastGroupStart = groupStart;
}
return groups;
}
Executes a search for a match within a string based on a regular
expression. It returns an array of information or null if no match is
found.
Params: - string – String to match.
Returns: NativeArray of matches, string or null.
/**
* Executes a search for a match within a string based on a regular
* expression. It returns an array of information or null if no match is
* found.
*
* @param string String to match.
* @return NativeArray of matches, string or null.
*/
public NativeRegExpExecResult exec(final String string) {
final RegExpResult match = execInner(string);
if (match == null) {
return null;
}
return new NativeRegExpExecResult(match, globalObject);
}
Executes a search for a match within a string based on a regular
expression.
Params: - string – String to match.
Returns: True if a match is found.
/**
* Executes a search for a match within a string based on a regular
* expression.
*
* @param string String to match.
* @return True if a match is found.
*/
public boolean test(final String string) {
return execInner(string) != null;
}
Searches and replaces the regular expression portion (match) with the
replaced text instead. For the "replacement text" parameter, you can use
the keywords $1 to $2 to replace the original text with values from
sub-patterns defined within the main pattern.
Params: - string – String to match.
- replacement – Replacement string.
Returns: String with substitutions.
/**
* Searches and replaces the regular expression portion (match) with the
* replaced text instead. For the "replacement text" parameter, you can use
* the keywords $1 to $2 to replace the original text with values from
* sub-patterns defined within the main pattern.
*
* @param string String to match.
* @param replacement Replacement string.
* @return String with substitutions.
*/
String replace(final String string, final String replacement, final Object function) throws Throwable {
final RegExpMatcher matcher = regexp.match(string);
if (matcher == null) {
return string;
}
if (!regexp.isGlobal()) {
if (!matcher.search(0)) {
return string;
}
final StringBuilder sb = new StringBuilder();
sb.append(string, 0, matcher.start());
if (function != null) {
final Object self = Bootstrap.isStrictCallable(function) ? UNDEFINED : Global.instance();
sb.append(callReplaceValue(getReplaceValueInvoker(), function, self, matcher, string));
} else {
appendReplacement(matcher, string, replacement, sb);
}
sb.append(string, matcher.end(), string.length());
return sb.toString();
}
setLastIndex(0);
if (!matcher.search(0)) {
return string;
}
int thisIndex = 0;
int previousLastIndex = 0;
final StringBuilder sb = new StringBuilder();
final MethodHandle invoker = function == null ? null : getReplaceValueInvoker();
final Object self = function == null || Bootstrap.isStrictCallable(function) ? UNDEFINED : Global.instance();
do {
sb.append(string, thisIndex, matcher.start());
if (function != null) {
sb.append(callReplaceValue(invoker, function, self, matcher, string));
} else {
appendReplacement(matcher, string, replacement, sb);
}
thisIndex = matcher.end();
// ECMA6 21.2.5.6 step 8.g.iv.5: If matchStr is empty advance index by one
if (matcher.start() == matcher.end()) {
setLastIndex(thisIndex + 1);
previousLastIndex = thisIndex + 1;
} else {
previousLastIndex = thisIndex;
}
} while (previousLastIndex <= string.length() && matcher.search(previousLastIndex));
sb.append(string, thisIndex, string.length());
return sb.toString();
}
private void appendReplacement(final RegExpMatcher matcher, final String text, final String replacement, final StringBuilder sb) {
/*
* Process substitution patterns:
*
* $$ -> $
* $& -> the matched substring
* $` -> the portion of string that precedes matched substring
* $' -> the portion of string that follows the matched substring
* $n -> the nth capture, where n is [1-9] and $n is NOT followed by a decimal digit
* $nn -> the nnth capture, where nn is a two digit decimal number [01-99].
*/
int cursor = 0;
Object[] groups = null;
while (cursor < replacement.length()) {
char nextChar = replacement.charAt(cursor);
if (nextChar == '$') {
// Skip past $
cursor++;
if (cursor == replacement.length()) {
// nothing after "$"
sb.append('$');
break;
}
nextChar = replacement.charAt(cursor);
final int firstDigit = nextChar - '0';
if (firstDigit >= 0 && firstDigit <= 9 && firstDigit <= matcher.groupCount()) {
// $0 is not supported, but $01 is. implementation-defined: if n>m, ignore second digit.
int refNum = firstDigit;
cursor++;
if (cursor < replacement.length() && firstDigit < matcher.groupCount()) {
final int secondDigit = replacement.charAt(cursor) - '0';
if (secondDigit >= 0 && secondDigit <= 9) {
final int newRefNum = firstDigit * 10 + secondDigit;
if (newRefNum <= matcher.groupCount() && newRefNum > 0) {
// $nn ($01-$99)
refNum = newRefNum;
cursor++;
}
}
}
if (refNum > 0) {
if (groups == null) {
groups = groups(matcher);
}
// Append group if matched.
if (groups[refNum] != UNDEFINED) {
sb.append((String) groups[refNum]);
}
} else { // $0. ignore.
assert refNum == 0;
sb.append("$0");
}
} else if (nextChar == '$') {
sb.append('$');
cursor++;
} else if (nextChar == '&') {
sb.append(matcher.group());
cursor++;
} else if (nextChar == '`') {
sb.append(text, 0, matcher.start());
cursor++;
} else if (nextChar == '\'') {
sb.append(text, matcher.end(), text.length());
cursor++;
} else {
// unknown substitution or $n with n>m. skip.
sb.append('$');
}
} else {
sb.append(nextChar);
cursor++;
}
}
}
private static final Object REPLACE_VALUE = new Object();
private static MethodHandle getReplaceValueInvoker() {
return Global.instance().getDynamicInvoker(REPLACE_VALUE,
new Callable<MethodHandle>() {
@Override
public MethodHandle call() {
return Bootstrap.createDynamicCallInvoker(String.class, Object.class, Object.class, Object[].class);
}
});
}
private String callReplaceValue(final MethodHandle invoker, final Object function, final Object self, final RegExpMatcher matcher, final String string) throws Throwable {
final Object[] groups = groups(matcher);
final Object[] args = Arrays.copyOf(groups, groups.length + 2);
args[groups.length] = matcher.start();
args[groups.length + 1] = string;
return (String)invoker.invokeExact(function, self, args);
}
Breaks up a string into an array of substrings based on a regular
expression or fixed string.
Params: - string – String to match.
- limit – Split limit.
Returns: Array of substrings.
/**
* Breaks up a string into an array of substrings based on a regular
* expression or fixed string.
*
* @param string String to match.
* @param limit Split limit.
* @return Array of substrings.
*/
NativeArray split(final String string, final long limit) {
if (limit == 0L) {
return new NativeArray();
}
final List<Object> matches = new ArrayList<>();
RegExpResult match;
final int inputLength = string.length();
int splitLastLength = -1;
int splitLastIndex = 0;
int splitLastLastIndex = 0;
while ((match = execSplit(string, splitLastIndex)) != null) {
splitLastIndex = match.getIndex() + match.length();
if (splitLastIndex > splitLastLastIndex) {
matches.add(string.substring(splitLastLastIndex, match.getIndex()));
final Object[] groups = match.getGroups();
if (groups.length > 1 && match.getIndex() < inputLength) {
for (int index = 1; index < groups.length && matches.size() < limit; index++) {
matches.add(groups[index]);
}
}
splitLastLength = match.length();
if (matches.size() >= limit) {
break;
}
}
// bump the index to avoid infinite loop
if (splitLastIndex == splitLastLastIndex) {
splitLastIndex++;
} else {
splitLastLastIndex = splitLastIndex;
}
}
if (matches.size() < limit) {
// check special case if we need to append an empty string at the
// end of the match
// if the lastIndex was the entire string
if (splitLastLastIndex == string.length()) {
if (splitLastLength > 0 || execSplit("", 0) == null) {
matches.add("");
}
} else {
matches.add(string.substring(splitLastLastIndex, inputLength));
}
}
return new NativeArray(matches.toArray());
}
Tests for a match in a string. It returns the index of the match, or -1
if not found.
Params: - string – String to match.
Returns: Index of match.
/**
* Tests for a match in a string. It returns the index of the match, or -1
* if not found.
*
* @param string String to match.
* @return Index of match.
*/
int search(final String string) {
final RegExpResult match = execInner(string);
if (match == null) {
return -1;
}
return match.getIndex();
}
Fast lastIndex getter
Returns: last index property as int
/**
* Fast lastIndex getter
* @return last index property as int
*/
public int getLastIndex() {
return JSType.toInteger(lastIndex);
}
Fast lastIndex getter
Returns: last index property as boxed integer
/**
* Fast lastIndex getter
* @return last index property as boxed integer
*/
public Object getLastIndexObject() {
return lastIndex;
}
Fast lastIndex setter
Params: - lastIndex – lastIndex
/**
* Fast lastIndex setter
* @param lastIndex lastIndex
*/
public void setLastIndex(final int lastIndex) {
this.lastIndex = JSType.toObject(lastIndex);
}
private static NativeRegExp checkRegExp(final Object self) {
if (self instanceof NativeRegExp) {
return (NativeRegExp)self;
} else if (self != null && self == Global.instance().getRegExpPrototype()) {
return Global.instance().getDefaultRegExp();
} else {
throw typeError("not.a.regexp", ScriptRuntime.safeToString(self));
}
}
boolean getGlobal() {
return regexp.isGlobal();
}
private RegExp getRegExp() {
return regexp;
}
private void setRegExp(final RegExp regexp) {
this.regexp = regexp;
}
}