/* * 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.ScriptFunction; 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. */ @ScriptClass("RegExp") public final class NativeRegExp extends ScriptObject { /** ECMA 15.10.7.5 lastIndex property */ @Property(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE) public Object lastIndex; /** 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 * * @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 * * @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 * * @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 * * @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 * * @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 {@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! * * @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) * * @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) * * @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() * * @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 * * @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 * * @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 * * @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 * * @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. * @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. * @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. * @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. * @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. * @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. * @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. * @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. * @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. * @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. * @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. * @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. * @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. * @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. * @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. * @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. */ 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. * * @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. * * @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. * * @param string String to match. * @param replacement Replacement string. * @return String with substitutions. */ String replace(final String string, final String replacement, final ScriptFunction 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 = function.isStrict() ? 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 || function.isStrict() ? 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(); if (thisIndex == string.length() && matcher.start() == matcher.end()) { // Avoid getting empty match at end of string twice break; } // ECMA 15.5.4.10 String.prototype.match(regexp) if (thisIndex == previousLastIndex) { 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 preceeds 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 final MethodHandle getReplaceValueInvoker() { return Global.instance().getDynamicInvoker(REPLACE_VALUE, new Callable() { @Override public MethodHandle call() { return Bootstrap.createDynamicInvoker("dyn:call", String.class, ScriptFunction.class, Object.class, Object[].class); } }); } private String callReplaceValue(final MethodHandle invoker, final ScriptFunction 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. * * @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 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. * * @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 * @return last index property as int */ public int getLastIndex() { return JSType.toInteger(lastIndex); } /** * Fast lastIndex getter * @return last index property as boxed integer */ public Object getLastIndexObject() { return 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; } }