/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.runtime.objects.text;
import static com.github.anba.es6draft.runtime.AbstractOperations.*;
import static com.github.anba.es6draft.runtime.internal.Properties.createProperties;
import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.github.anba.es6draft.parser.Characters;
import com.github.anba.es6draft.parser.ParserException;
import com.github.anba.es6draft.regexp.RegExpMatcher;
import com.github.anba.es6draft.regexp.RegExpParser;
import com.github.anba.es6draft.runtime.ExecutionContext;
import com.github.anba.es6draft.runtime.Realm;
import com.github.anba.es6draft.runtime.internal.CompatibilityOption;
import com.github.anba.es6draft.runtime.internal.Initializable;
import com.github.anba.es6draft.runtime.internal.Properties.Accessor;
import com.github.anba.es6draft.runtime.internal.Properties.Attributes;
import com.github.anba.es6draft.runtime.internal.Properties.CompatibilityExtension;
import com.github.anba.es6draft.runtime.internal.Properties.Prototype;
import com.github.anba.es6draft.runtime.internal.Properties.Value;
import com.github.anba.es6draft.runtime.types.BuiltinSymbol;
import com.github.anba.es6draft.runtime.types.Constructor;
import com.github.anba.es6draft.runtime.types.Intrinsics;
import com.github.anba.es6draft.runtime.types.Property;
import com.github.anba.es6draft.runtime.types.ScriptObject;
import com.github.anba.es6draft.runtime.types.Type;
import com.github.anba.es6draft.runtime.types.builtins.BuiltinConstructor;
/**
* <h1>21 Text Processing</h1><br>
* <h2>21.2 RegExp (Regular Expression) Objects</h2>
* <ul>
* <li>21.2.3 The RegExp Constructor
* <li>21.2.4 Properties of the RegExp Constructor
* </ul>
*/
public final class RegExpConstructor extends BuiltinConstructor implements Initializable {
/**
* Constructs a new RegExp constructor function.
*
* @param realm
* the realm object
*/
public RegExpConstructor(Realm realm) {
super(realm, "RegExp", 2);
}
@Override
public void initialize(Realm realm) {
createProperties(realm, this, Properties.class);
createProperties(realm, this, RegExpStatics.class);
}
@Override
public RegExpConstructor clone() {
return new RegExpConstructor(getRealm());
}
/**
* 21.2.3.1 RegExp(pattern, flags)
*/
@Override
public ScriptObject call(ExecutionContext callerContext, Object thisValue, Object... args) {
ExecutionContext calleeContext = calleeContext();
Object pattern = argument(args, 0);
Object flags = argument(args, 1);
/* steps 1-2 */
boolean patternIsRegExp = IsRegExp(calleeContext, pattern);
/* step 3 (not applicable) */
/* step 4 */
if (patternIsRegExp && Type.isUndefined(flags)) {
ScriptObject patternObject = Type.objectValue(pattern);
Object patternConstructor = Get(calleeContext, patternObject, "constructor");
if (this == patternConstructor) { // SameValue
return patternObject;
}
}
/* steps 5-10 */
return RegExpCreate(calleeContext, this, pattern, flags, patternIsRegExp);
}
/**
* 21.2.3.1 RegExp ( pattern, flags )
*/
@Override
public RegExpObject construct(ExecutionContext callerContext, Constructor newTarget,
Object... args) {
ExecutionContext calleeContext = calleeContext();
Object pattern = argument(args, 0);
Object flags = argument(args, 1);
/* steps 1-2 */
boolean patternIsRegExp = IsRegExp(calleeContext, pattern);
/* step 3 (omitted) */
/* step 4 (not applicable) */
/* steps 5-10 */
return RegExpCreate(calleeContext, newTarget, pattern, flags, patternIsRegExp);
}
private static RegExpObject RegExpCreate(ExecutionContext cx, Constructor newTarget,
Object pattern, Object flags, boolean patternIsRegExp) {
/* steps 5-7 */
Object p, f;
if (pattern instanceof RegExpObject) {
/* step 5 */
RegExpObject regexp = (RegExpObject) pattern;
/* step 5.a */
p = regexp.getOriginalSource();
/* steps 5.b-5.c */
f = Type.isUndefined(flags) ? regexp.getOriginalFlags() : flags;
} else if (patternIsRegExp) {
/* step 6 */
ScriptObject patternObject = Type.objectValue(pattern);
/* steps 6.a-b */
p = Get(cx, patternObject, "source");
/* steps 6.c-d */
f = Type.isUndefined(flags) ? Get(cx, patternObject, "flags") : flags;
} else {
/* step 7 */
p = pattern;
f = flags;
}
/* steps 8-9 */
RegExpObject obj = RegExpAlloc(cx, newTarget);
/* step 10 */
return RegExpInitialize(cx, obj, p, f);
}
static RegExpObject RegExpCreate(ExecutionContext cx, RegExpConstructor newTarget,
String pattern, String flags, boolean defaultMultiline) {
/* steps 5-7 (not applicable) */
/* steps 8-9 */
RegExpObject obj = RegExpAlloc(cx, newTarget);
/* step 10 */
return RegExpInitialize(cx, obj, pattern, flags, defaultMultiline);
}
/**
* 21.2.3.2 Abstract Operations for the RegExp Constructor<br>
* 21.2.3.2.1 Runtime Semantics: RegExpAlloc ( newTarget )
*
* @param cx
* the execution context
* @param newTarget
* the constructor function
* @return the new regular expression object
*/
public static RegExpObject RegExpAlloc(ExecutionContext cx, Constructor newTarget) {
/* steps 1-2 */
RegExpObject obj = OrdinaryCreateFromConstructor(cx, newTarget, Intrinsics.RegExpPrototype, RegExpObject::new);
/* steps 3-4 */
obj.infallibleDefineOwnProperty("lastIndex", new Property(0, true, false, false));
/* step 5 */
return obj;
}
/**
* 21.2.3.2 Abstract Operations for the RegExp Constructor<br>
* 21.2.3.2.2 Runtime Semantics: RegExpInitialize ( obj, pattern, flags )
*
* @param cx
* the execution context
* @param obj
* the RegExp object
* @param pattern
* the regular expression pattern
* @param flags
* the regular expression flags
* @return the RegExp object
*/
public static RegExpObject RegExpInitialize(ExecutionContext cx, RegExpObject obj,
Object pattern, Object flags) {
/* steps 1-3 */
String p = Type.isUndefined(pattern) ? "" : ToFlatString(cx, pattern);
/* steps 4-6 */
String f = Type.isUndefined(flags) ? "" : ToFlatString(cx, flags);
// RegExp statics extension
if (isDefaultMultiline(cx) && f.indexOf('m') == -1) {
f = f + 'm';
}
/* steps 7-16 */
return RegExpInitialize(cx, obj, p, f);
}
/**
* 21.2.3.2 Abstract Operations for the RegExp Constructor<br>
* 21.2.3.2.2 Runtime Semantics: RegExpInitialize ( obj, pattern, flags )
*
* @param cx
* the execution context
* @param obj
* the RegExp object
* @param pattern
* the regular expression pattern
* @param flags
* the regular expression flags
* @param defaultMultiline
* {@code true} to create multiline-mode pattern
* @return the RegExp object
*/
private static RegExpObject RegExpInitialize(ExecutionContext cx, RegExpObject obj,
String pattern, String flags, boolean defaultMultiline) {
/* steps 1-3 */
String p = pattern;
/* steps 4-6 */
String f = flags;
// RegExp statics extension
if (defaultMultiline && f.indexOf('m') == -1) {
f = f + 'm';
}
/* steps 7-16 */
return RegExpInitialize(cx, obj, p, f);
}
private static RegExpObject RegExpInitialize(ExecutionContext cx, RegExpObject obj, String p,
String f) {
/* steps 7-10 */
RegExpMatcher matcher;
try {
matcher = RegExpParser.parse(p, f, "<regexp>", 1, 1,
cx.getRealm().isEnabled(CompatibilityOption.WebRegularExpressions));
} catch (ParserException e) {
throw e.toScriptException(cx);
}
/* steps 11-13 */
obj.initialize(p, f, matcher);
/* steps 14-15 */
Set(cx, obj, "lastIndex", 0, true);
/* step 16 */
return obj;
}
/**
* 21.2.3.2 Abstract Operations for the RegExp Constructor<br>
* 21.2.3.2.3 Runtime Semantics: RegExpCreate ( P, F )
*
* @param cx
* the execution context
* @param pattern
* the regular expression pattern
* @param flags
* the regular expression flags
* @return the new RegExp object
*/
public static RegExpObject RegExpCreate(ExecutionContext cx, Object pattern, Object flags) {
/* steps 1-2 */
RegExpObject obj = RegExpAlloc(cx, (Constructor) cx.getIntrinsic(Intrinsics.RegExp));
/* step 3 */
return RegExpInitialize(cx, obj, pattern, flags);
}
/**
* 21.2.3.2 Abstract Operations for the RegExp Constructor<br>
* 21.2.3.2.4 Runtime Semantics: EscapeRegExpPattern ( P, F )
*
* @param realm
* the realm instance
* @param p
* the regular expression pattern
* @param f
* the regular expression flags
* @return the escaped regular expression pattern
*/
public static String EscapeRegExpPattern(Realm realm, String p, String f) {
if (p.isEmpty()) {
return "(?:)";
}
boolean inClass = false;
StringBuilder sb = new StringBuilder(p.length());
for (int i = 0, len = p.length(); i < len; ++i) {
char c = p.charAt(i);
if (c == '/' && !inClass) {
sb.append("\\/");
} else if (c == '[') {
inClass = true;
sb.append(c);
} else if (c == ']' && inClass) {
inClass = false;
sb.append(c);
} else if (c == '\\') {
assert i + 1 < len;
switch (c = p.charAt(++i)) {
case 0x0A:
sb.append("\\n");
break;
case 0x0D:
sb.append("\\r");
break;
case 0x2028:
sb.append("\\u2028");
break;
case 0x2029:
sb.append("\\u2029");
break;
default:
sb.append('\\').append(c);
break;
}
} else if (Characters.isLineTerminator(c)) {
switch (c) {
case 0x0A:
sb.append("\\n");
break;
case 0x0D:
sb.append("\\r");
break;
case 0x2028:
sb.append("\\u2028");
break;
case 0x2029:
sb.append("\\u2029");
break;
default:
throw new AssertionError();
}
} else {
sb.append(c);
}
}
return sb.toString();
}
/**
* 21.2.4 Properties of the RegExp Constructor
*/
public enum Properties {
;
@Prototype
public static final Intrinsics __proto__ = Intrinsics.FunctionPrototype;
@Value(name = "length", attributes = @Attributes(writable = false, enumerable = false,
configurable = true))
public static final int length = 2;
@Value(name = "name", attributes = @Attributes(writable = false, enumerable = false,
configurable = true))
public static final String name = "RegExp";
/**
* 21.2.4.1 RegExp.prototype
*/
@Value(name = "prototype", attributes = @Attributes(writable = false, enumerable = false,
configurable = false))
public static final Intrinsics prototype = Intrinsics.RegExpPrototype;
/**
* 21.2.4.2 get RegExp [ @@species ]
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the species object
*/
@Accessor(name = "get [Symbol.species]", symbol = BuiltinSymbol.species,
type = Accessor.Type.Getter)
public static Object species(ExecutionContext cx, Object thisValue) {
/* step 1 */
return thisValue;
}
}
/*
* RegExp statics extensions below this point
*/
private static final class RegExpStaticsHolder {
private static final MatchResult EMPTY_MATCH_RESULT;
static {
Matcher m = Pattern.compile("").matcher("");
m.matches();
EMPTY_MATCH_RESULT = m;
}
private boolean defaultMultiline = false;
private CharSequence lastInput = "";
private MatchResult lastMatchResult = EMPTY_MATCH_RESULT;
boolean isDefaultMultiline() {
return defaultMultiline;
}
void setDefaultMultiline(boolean defaultMultiline) {
this.defaultMultiline = defaultMultiline;
}
CharSequence getLastInput() {
return lastInput;
}
MatchResult getLastMatchResult() {
return lastMatchResult;
}
void storeLastMatchResult(CharSequence input, MatchResult matchResult) {
this.lastInput = input;
this.lastMatchResult = matchResult;
}
}
private RegExpStaticsHolder regExpStatics;
private static RegExpStaticsHolder getRegExpStatics(ExecutionContext cx) {
RegExpConstructor re = (RegExpConstructor) cx.getIntrinsic(Intrinsics.RegExp);
RegExpStaticsHolder statics = re.regExpStatics;
if (statics == null) {
re.regExpStatics = statics = new RegExpStaticsHolder();
}
return statics;
}
static void storeLastMatchResult(ExecutionContext cx, CharSequence input,
MatchResult matchResult) {
getRegExpStatics(cx).storeLastMatchResult(input, matchResult);
}
static boolean isDefaultMultiline(ExecutionContext cx) {
return getRegExpStatics(cx).isDefaultMultiline();
}
@CompatibilityExtension(CompatibilityOption.RegExpStatics)
public enum RegExpStatics {
;
private static String group(RegExpStaticsHolder statics, int groupIndex) {
assert groupIndex >= 0;
if (groupIndex == 0 || groupIndex > statics.getLastMatchResult().groupCount()) {
return "";
}
String[] groups = RegExpPrototype.groups(statics.getLastMatchResult());
String group = groups[groupIndex - 1];
return group != null ? group : "";
}
/**
* Extension: RegExp.$*
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the RegExp.multiline value
*/
@Accessor(name = "$*", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = false, configurable = true))
public static Object get_$multiline(ExecutionContext cx, Object thisValue) {
return get_multiline(cx, thisValue);
}
/**
* Extension: RegExp.$*
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param value
* the new multiline value
* @return the undefined value
*/
@Accessor(name = "$*", type = Accessor.Type.Setter, attributes = @Attributes(
writable = false, enumerable = false, configurable = true))
public static Object set_$multiline(ExecutionContext cx, Object thisValue, Object value) {
return set_multiline(cx, thisValue, value);
}
/**
* Extension: RegExp.multiline
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the RegExp.multiline value
*/
@Accessor(name = "multiline", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = true, configurable = true))
public static Object get_multiline(ExecutionContext cx, Object thisValue) {
return getRegExpStatics(cx).isDefaultMultiline();
}
/**
* Extension: RegExp.multiline
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param value
* the new multiline value
* @return the undefined value
*/
@Accessor(name = "multiline", type = Accessor.Type.Setter, attributes = @Attributes(
writable = false, enumerable = true, configurable = true))
public static Object set_multiline(ExecutionContext cx, Object thisValue, Object value) {
getRegExpStatics(cx).setDefaultMultiline(ToBoolean(value));
return UNDEFINED;
}
/**
* Extension: RegExp.$_
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the RegExp.input value
*/
@Accessor(name = "$_", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = false, configurable = true))
public static Object $input(ExecutionContext cx, Object thisValue) {
return input(cx, thisValue);
}
/**
* Extension: RegExp.input
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the RegExp.input value
*/
@Accessor(name = "input", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = true, configurable = true))
public static Object input(ExecutionContext cx, Object thisValue) {
return getRegExpStatics(cx).getLastInput();
}
/**
* Extension: RegExp.$&
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the RegExp.lastMatch value
*/
@Accessor(name = "$&", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = false, configurable = true))
public static Object $lastMatch(ExecutionContext cx, Object thisValue) {
return lastMatch(cx, thisValue);
}
/**
* Extension: RegExp.lastMatch
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the RegExp.lastMatch value
*/
@Accessor(name = "lastMatch", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = true, configurable = true))
public static Object lastMatch(ExecutionContext cx, Object thisValue) {
return getRegExpStatics(cx).getLastMatchResult().group();
}
/**
* Extension: RegExp.$+
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the RegExp.lastParen value
*/
@Accessor(name = "$+", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = false, configurable = true))
public static Object $lastParen(ExecutionContext cx, Object thisValue) {
return lastParen(cx, thisValue);
}
/**
* Extension: RegExp.lastParen
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the RegExp.lastParen value
*/
@Accessor(name = "lastParen", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = true, configurable = true))
public static Object lastParen(ExecutionContext cx, Object thisValue) {
RegExpStaticsHolder statics = getRegExpStatics(cx);
return group(statics, statics.getLastMatchResult().groupCount());
}
/**
* Extension: RegExp.$`
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the RegExp.leftContext value
*/
@Accessor(name = "$`", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = false, configurable = true))
public static Object $leftContext(ExecutionContext cx, Object thisValue) {
return leftContext(cx, thisValue);
}
/**
* Extension: RegExp.leftContext
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the RegExp.leftContext value
*/
@Accessor(name = "leftContext", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = true, configurable = true))
public static Object leftContext(ExecutionContext cx, Object thisValue) {
RegExpStaticsHolder statics = getRegExpStatics(cx);
return statics.getLastInput().toString()
.substring(0, statics.getLastMatchResult().start());
}
/**
* Extension: RegExp.$'
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the RegExp.rightContext value
*/
@Accessor(name = "$'", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = false, configurable = true))
public static Object $rightContext(ExecutionContext cx, Object thisValue) {
return rightContext(cx, thisValue);
}
/**
* Extension: RegExp.rightContext
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the RegExp.rightContext value
*/
@Accessor(name = "rightContext", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = true, configurable = true))
public static Object rightContext(ExecutionContext cx, Object thisValue) {
RegExpStaticsHolder statics = getRegExpStatics(cx);
return statics.getLastInput().toString().substring(statics.getLastMatchResult().end());
}
/**
* Extension: RegExp.$1
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the first matched group
*/
@Accessor(name = "$1", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = true, configurable = true))
public static Object $1(ExecutionContext cx, Object thisValue) {
return group(getRegExpStatics(cx), 1);
}
/**
* Extension: RegExp.$2
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the second matched group
*/
@Accessor(name = "$2", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = true, configurable = true))
public static Object $2(ExecutionContext cx, Object thisValue) {
return group(getRegExpStatics(cx), 2);
}
/**
* Extension: RegExp.$3
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the third matched group
*/
@Accessor(name = "$3", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = true, configurable = true))
public static Object $3(ExecutionContext cx, Object thisValue) {
return group(getRegExpStatics(cx), 3);
}
/**
* Extension: RegExp.$4
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the fourth matched group
*/
@Accessor(name = "$4", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = true, configurable = true))
public static Object $4(ExecutionContext cx, Object thisValue) {
return group(getRegExpStatics(cx), 4);
}
/**
* Extension: RegExp.$5
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the fifth matched group
*/
@Accessor(name = "$5", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = true, configurable = true))
public static Object $5(ExecutionContext cx, Object thisValue) {
return group(getRegExpStatics(cx), 5);
}
/**
* Extension: RegExp.$6
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the sixth matched group
*/
@Accessor(name = "$6", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = true, configurable = true))
public static Object $6(ExecutionContext cx, Object thisValue) {
return group(getRegExpStatics(cx), 6);
}
/**
* Extension: RegExp.$7
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the seventh matched group
*/
@Accessor(name = "$7", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = true, configurable = true))
public static Object $7(ExecutionContext cx, Object thisValue) {
return group(getRegExpStatics(cx), 7);
}
/**
* Extension: RegExp.$8
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the eighth matched group
*/
@Accessor(name = "$8", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = true, configurable = true))
public static Object $8(ExecutionContext cx, Object thisValue) {
return group(getRegExpStatics(cx), 8);
}
/**
* Extension: RegExp.$9
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the nineth matched group
*/
@Accessor(name = "$9", type = Accessor.Type.Getter, attributes = @Attributes(
writable = false, enumerable = true, configurable = true))
public static Object $9(ExecutionContext cx, Object thisValue) {
return group(getRegExpStatics(cx), 9);
}
}
}