Core library reprogramming #5
72
lib/core.ts
72
lib/core.ts
@ -18,6 +18,7 @@ interface Internals {
|
||||
type: TypeErrorConstructor;
|
||||
range: RangeErrorConstructor;
|
||||
|
||||
regexp: typeof RegExp;
|
||||
map: typeof Map;
|
||||
set: typeof Set;
|
||||
|
||||
@ -50,44 +51,51 @@ interface Internals {
|
||||
log(...args: any[]): void;
|
||||
}
|
||||
|
||||
var env: Environment = arguments[0], internals: Internals = arguments[1];
|
||||
|
||||
try {
|
||||
var env: Environment = arguments[0], internals: Internals = arguments[1];
|
||||
|
||||
const Object = env.global.Object = internals.object;
|
||||
const Function = env.global.Function = internals.function;
|
||||
const Array = env.global.Array = internals.array;
|
||||
const Promise = env.global.Promise = internals.promise;
|
||||
const Boolean = env.global.Boolean = internals.bool;
|
||||
const Number = env.global.Number = internals.number;
|
||||
const String = env.global.String = internals.string;
|
||||
const Symbol = env.global.Symbol = internals.symbol;
|
||||
const Error = env.global.Error = internals.error;
|
||||
const SyntaxError = env.global.SyntaxError = internals.syntax;
|
||||
const TypeError = env.global.TypeError = internals.type;
|
||||
const RangeError = env.global.RangeError = internals.range;
|
||||
const values = {
|
||||
Object: env.global.Object = internals.object,
|
||||
Function: env.global.Function = internals.function,
|
||||
Array: env.global.Array = internals.array,
|
||||
Promise: env.global.Promise = internals.promise,
|
||||
Boolean: env.global.Boolean = internals.bool,
|
||||
Number: env.global.Number = internals.number,
|
||||
String: env.global.String = internals.string,
|
||||
Symbol: env.global.Symbol = internals.symbol,
|
||||
Error: env.global.Error = internals.error,
|
||||
SyntaxError: env.global.SyntaxError = internals.syntax,
|
||||
TypeError: env.global.TypeError = internals.type,
|
||||
RangeError: env.global.RangeError = internals.range,
|
||||
RegExp: env.global.RegExp = internals.regexp,
|
||||
Map: env.global.Map = internals.map,
|
||||
Set: env.global.Set = internals.set,
|
||||
}
|
||||
const Array = values.Array;
|
||||
|
||||
const Map = env.global.Map = internals.map;
|
||||
const Set = env.global.Set = internals.set;
|
||||
env.setProto('object', env.global.Object.prototype);
|
||||
env.setProto('function', env.global.Function.prototype);
|
||||
env.setProto('array', env.global.Array.prototype);
|
||||
env.setProto('number', env.global.Number.prototype);
|
||||
env.setProto('string', env.global.String.prototype);
|
||||
env.setProto('symbol', env.global.Symbol.prototype);
|
||||
env.setProto('bool', env.global.Boolean.prototype);
|
||||
|
||||
env.setProto('object', Object.prototype);
|
||||
env.setProto('function', Function.prototype);
|
||||
env.setProto('array', Array.prototype);
|
||||
env.setProto('number', Number.prototype);
|
||||
env.setProto('string', String.prototype);
|
||||
env.setProto('symbol', Symbol.prototype);
|
||||
env.setProto('bool', Boolean.prototype);
|
||||
|
||||
env.setProto('error', Error.prototype);
|
||||
env.setProto('rangeErr', RangeError.prototype);
|
||||
env.setProto('typeErr', TypeError.prototype);
|
||||
env.setProto('syntaxErr', SyntaxError.prototype);
|
||||
|
||||
(Object.prototype as any).__proto__ = null;
|
||||
env.setProto('error', env.global.Error.prototype);
|
||||
env.setProto('rangeErr', env.global.RangeError.prototype);
|
||||
env.setProto('typeErr', env.global.TypeError.prototype);
|
||||
env.setProto('syntaxErr', env.global.SyntaxError.prototype);
|
||||
(env.global.Object.prototype as any).__proto__ = null;
|
||||
|
||||
internals.getEnv(run)?.setProto('array', Array.prototype);
|
||||
globalThis.log = (...args) => internals.apply(internals.log, internals, args);
|
||||
|
||||
run('regex');
|
||||
for (const key in values) {
|
||||
(values as any)[key].prototype[env.symbol('Symbol.typeName')] = key;
|
||||
log();
|
||||
}
|
||||
|
||||
run('timeout');
|
||||
|
||||
env.global.log = log;
|
||||
@ -103,7 +111,7 @@ catch (e: any) {
|
||||
if ('name' in e) err += e.name + ": " + e.message;
|
||||
else err += 'Error: ' + e.message;
|
||||
}
|
||||
else err += e;
|
||||
else err += "[unknown]";
|
||||
|
||||
log(e);
|
||||
internals.log(err);
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
"files": [
|
||||
"lib.d.ts",
|
||||
"modules.ts",
|
||||
"utils.ts",
|
||||
"regex.ts",
|
||||
"timeout.ts",
|
||||
"core.ts"
|
||||
|
27
lib/utils.ts
27
lib/utils.ts
@ -1,27 +0,0 @@
|
||||
function setProps<
|
||||
TargetT extends object,
|
||||
DescT extends {
|
||||
[x in Exclude<keyof TargetT, 'constructor'> ]?: TargetT[x] extends ((...args: infer ArgsT) => infer RetT) ?
|
||||
((this: TargetT, ...args: ArgsT) => RetT) :
|
||||
TargetT[x]
|
||||
}
|
||||
>(target: TargetT, desc: DescT) {
|
||||
var props = internals.keys(desc, false);
|
||||
for (var i = 0; i in props; i++) {
|
||||
var key = props[i];
|
||||
internals.defineField(
|
||||
target, key, (desc as any)[key],
|
||||
true, // writable
|
||||
false, // enumerable
|
||||
true // configurable
|
||||
);
|
||||
}
|
||||
}
|
||||
function setConstr(target: object, constr: Function) {
|
||||
internals.defineField(
|
||||
target, 'constructor', constr,
|
||||
true, // writable
|
||||
false, // enumerable
|
||||
true // configurable
|
||||
);
|
||||
}
|
@ -22,7 +22,7 @@ public class Environment {
|
||||
|
||||
@Native public FunctionValue compile;
|
||||
@Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> {
|
||||
throw EngineException.ofError("Regular expressions not supported.");
|
||||
throw EngineException.ofError("Regular expressions not supported.").setContext(ctx);
|
||||
});
|
||||
@Native public ObjectValue proto(String name) {
|
||||
return prototypes.get(name);
|
||||
|
@ -24,7 +24,7 @@ public class CodeFrame {
|
||||
public final int tryStart, catchStart, finallyStart, end;
|
||||
public int state;
|
||||
public Object retVal;
|
||||
public Object err;
|
||||
public EngineException err;
|
||||
public int jumpPtr;
|
||||
|
||||
public TryCtx(int tryStart, int tryN, int catchN, int finallyN) {
|
||||
@ -93,10 +93,11 @@ public class CodeFrame {
|
||||
stack[stackPtr++] = Values.normalize(ctx, val);
|
||||
}
|
||||
|
||||
private void setCause(Context ctx, Object err, Object cause) throws InterruptedException {
|
||||
if (err instanceof ObjectValue) {
|
||||
private void setCause(Context ctx, EngineException err, EngineException cause) throws InterruptedException {
|
||||
if (err.value instanceof ObjectValue) {
|
||||
Values.setMember(ctx, err, ctx.env.symbol("Symbol.cause"), cause);
|
||||
}
|
||||
err.cause = cause;
|
||||
}
|
||||
private Object nextNoTry(Context ctx) throws InterruptedException {
|
||||
if (Thread.currentThread().isInterrupted()) throw new InterruptedException();
|
||||
@ -116,12 +117,12 @@ public class CodeFrame {
|
||||
}
|
||||
}
|
||||
|
||||
public Object next(Context ctx, Object value, Object returnValue, Object error) throws InterruptedException {
|
||||
public Object next(Context ctx, Object value, Object returnValue, EngineException error) throws InterruptedException {
|
||||
if (value != Runners.NO_RETURN) push(ctx, value);
|
||||
|
||||
if (returnValue == Runners.NO_RETURN && error == Runners.NO_RETURN) {
|
||||
if (returnValue == Runners.NO_RETURN && error == null) {
|
||||
try { returnValue = nextNoTry(ctx); }
|
||||
catch (EngineException e) { error = e.value; }
|
||||
catch (EngineException e) { error = e; }
|
||||
}
|
||||
|
||||
while (!tryStack.empty()) {
|
||||
@ -130,7 +131,7 @@ public class CodeFrame {
|
||||
|
||||
switch (tryCtx.state) {
|
||||
case TryCtx.STATE_TRY:
|
||||
if (error != Runners.NO_RETURN) {
|
||||
if (error != null) {
|
||||
if (tryCtx.hasCatch) {
|
||||
tryCtx.err = error;
|
||||
newState = TryCtx.STATE_CATCH;
|
||||
@ -158,7 +159,7 @@ public class CodeFrame {
|
||||
else codePtr = tryCtx.end;
|
||||
break;
|
||||
case TryCtx.STATE_CATCH:
|
||||
if (error != Runners.NO_RETURN) {
|
||||
if (error != null) {
|
||||
if (tryCtx.hasFinally) {
|
||||
tryCtx.err = error;
|
||||
newState = TryCtx.STATE_FINALLY_THREW;
|
||||
@ -167,7 +168,7 @@ public class CodeFrame {
|
||||
}
|
||||
else if (returnValue != Runners.NO_RETURN) {
|
||||
if (tryCtx.hasFinally) {
|
||||
tryCtx.retVal = error;
|
||||
tryCtx.retVal = returnValue;
|
||||
newState = TryCtx.STATE_FINALLY_RETURNED;
|
||||
}
|
||||
break;
|
||||
@ -182,7 +183,7 @@ public class CodeFrame {
|
||||
else codePtr = tryCtx.end;
|
||||
break;
|
||||
case TryCtx.STATE_FINALLY_THREW:
|
||||
if (error != Runners.NO_RETURN) setCause(ctx, error, tryCtx.err);
|
||||
if (error != null) setCause(ctx, error, tryCtx.err);
|
||||
else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) error = tryCtx.err;
|
||||
else return Runners.NO_RETURN;
|
||||
break;
|
||||
@ -211,7 +212,7 @@ public class CodeFrame {
|
||||
tryCtx.state = newState;
|
||||
switch (newState) {
|
||||
case TryCtx.STATE_CATCH:
|
||||
scope.catchVars.add(new ValueVariable(false, tryCtx.err));
|
||||
scope.catchVars.add(new ValueVariable(false, tryCtx.err.value));
|
||||
codePtr = tryCtx.catchStart;
|
||||
break;
|
||||
default:
|
||||
@ -221,7 +222,7 @@ public class CodeFrame {
|
||||
return Runners.NO_RETURN;
|
||||
}
|
||||
|
||||
if (error != Runners.NO_RETURN) throw new EngineException(error);
|
||||
if (error != null) throw error.setContext(ctx);
|
||||
if (returnValue != Runners.NO_RETURN) return returnValue;
|
||||
return Runners.NO_RETURN;
|
||||
}
|
||||
@ -230,7 +231,7 @@ public class CodeFrame {
|
||||
try {
|
||||
ctx.message.pushFrame(ctx, this);
|
||||
while (true) {
|
||||
var res = next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN);
|
||||
var res = next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null);
|
||||
if (res != Runners.NO_RETURN) return res;
|
||||
}
|
||||
}
|
||||
|
@ -685,7 +685,7 @@ public class Values {
|
||||
printValue(ctx, val, new HashSet<>(), 0);
|
||||
}
|
||||
public static void printError(RuntimeException err, String prefix) throws InterruptedException {
|
||||
prefix = prefix == null ? "Uncauthg" : "Uncaught " + prefix;
|
||||
prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix;
|
||||
try {
|
||||
if (err instanceof EngineException) {
|
||||
System.out.println(prefix + " " + ((EngineException)err).toString(((EngineException)err).ctx));
|
||||
|
@ -685,7 +685,7 @@ public class Parsing {
|
||||
}
|
||||
|
||||
}
|
||||
public static ParseRes<NewStatement> parseRegex(String filename, List<Token> tokens, int i) {
|
||||
public static ParseRes<RegexStatement> parseRegex(String filename, List<Token> tokens, int i) {
|
||||
var loc = getLoc(filename, tokens, i);
|
||||
try {
|
||||
if (tokens.get(i).isRegex()) {
|
||||
@ -693,11 +693,7 @@ public class Parsing {
|
||||
var index = val.lastIndexOf('/');
|
||||
var first = val.substring(1, index);
|
||||
var second = val.substring(index + 1);
|
||||
return ParseRes.res(new NewStatement(loc,
|
||||
new VariableStatement(null, "RegExp"),
|
||||
new ConstantStatement(loc, first),
|
||||
new ConstantStatement(loc, second)
|
||||
), 1);
|
||||
return ParseRes.res(new RegexStatement(loc, first, second), 1);
|
||||
}
|
||||
else return ParseRes.failed();
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
import me.topchetoeu.jscript.interop.NativeSetter;
|
||||
|
||||
public class ArrayPolyfill {
|
||||
@Native("@@Symbol.typeName") public final String name = "AsyncFunction";
|
||||
@NativeGetter(thisArg = true) public static int length(Context ctx, ArrayValue thisArg) throws InterruptedException {
|
||||
return thisArg.size();
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ public class AsyncFunctionPolyfill extends FunctionValue {
|
||||
awaiting = false;
|
||||
while (!awaiting) {
|
||||
try {
|
||||
res = frame.next(ctx, inducedValue, Runners.NO_RETURN, inducedError);
|
||||
res = frame.next(ctx, inducedValue, Runners.NO_RETURN, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError));
|
||||
inducedValue = inducedError = Runners.NO_RETURN;
|
||||
if (res != Runners.NO_RETURN) {
|
||||
promise.fulfill(ctx, res);
|
||||
|
@ -39,7 +39,7 @@ public class AsyncGeneratorPolyfill extends FunctionValue {
|
||||
|
||||
while (state == 0) {
|
||||
try {
|
||||
res = frame.next(ctx, inducedValue, inducedReturn, inducedError);
|
||||
res = frame.next(ctx, inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError));
|
||||
inducedValue = inducedReturn = inducedError = Runners.NO_RETURN;
|
||||
if (res != Runners.NO_RETURN) {
|
||||
var obj = new ObjectValue();
|
||||
|
@ -7,8 +7,6 @@ import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
|
||||
public class BooleanPolyfill {
|
||||
@Native("@@Symbol.typeName") public final String name = "Boolean";
|
||||
|
||||
public static final BooleanPolyfill TRUE = new BooleanPolyfill(true);
|
||||
public static final BooleanPolyfill FALSE = new BooleanPolyfill(false);
|
||||
|
||||
|
@ -30,7 +30,7 @@ public class ErrorPolyfill {
|
||||
}
|
||||
}
|
||||
|
||||
private static String toString(Context ctx, Object name, Object message, ArrayValue stack) throws InterruptedException {
|
||||
private static String toString(Context ctx, Object cause, Object name, Object message, ArrayValue stack) throws InterruptedException {
|
||||
if (name == null) name = "";
|
||||
else name = Values.toString(ctx, name).trim();
|
||||
if (message == null) message = "";
|
||||
@ -39,7 +39,7 @@ public class ErrorPolyfill {
|
||||
|
||||
if (!name.equals("")) res.append(name);
|
||||
if (!message.equals("") && !name.equals("")) res.append(": ");
|
||||
if (!name.equals("")) res.append(message);
|
||||
if (!message.equals("")) res.append(message);
|
||||
|
||||
if (stack != null) {
|
||||
for (var el : stack) {
|
||||
@ -48,6 +48,8 @@ public class ErrorPolyfill {
|
||||
}
|
||||
}
|
||||
|
||||
if (cause instanceof ObjectValue) res.append(toString(ctx, cause));
|
||||
|
||||
return res.toString();
|
||||
}
|
||||
|
||||
@ -56,6 +58,7 @@ public class ErrorPolyfill {
|
||||
var stack = Values.getMember(ctx, thisArg, "stack");
|
||||
if (!(stack instanceof ArrayValue)) stack = null;
|
||||
return toString(ctx,
|
||||
Values.getMember(ctx, thisArg, "cause"),
|
||||
Values.getMember(ctx, thisArg, "name"),
|
||||
Values.getMember(ctx, thisArg, "message"),
|
||||
(ArrayValue)stack
|
||||
|
@ -8,9 +8,7 @@ import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
|
||||
public class FunctionPolyfill {
|
||||
@Native("@@Symbol.typeName") public final String name = "Function";
|
||||
|
||||
@Native(thisArg = true) public static Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) throws InterruptedException {
|
||||
@Native(thisArg = true) public static Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) throws InterruptedException {
|
||||
return func.call(ctx, thisArg, args.toArray());
|
||||
}
|
||||
@Native(thisArg = true) public static Object call(Context ctx, FunctionValue func, Object thisArg, Object... args) throws InterruptedException {
|
||||
|
@ -35,7 +35,7 @@ public class GeneratorPolyfill extends FunctionValue {
|
||||
|
||||
while (!yielding) {
|
||||
try {
|
||||
res = frame.next(ctx, inducedValue, inducedReturn, inducedError);
|
||||
res = frame.next(ctx, inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError));
|
||||
inducedReturn = inducedError = Runners.NO_RETURN;
|
||||
if (res != Runners.NO_RETURN) {
|
||||
done = true;
|
||||
|
@ -20,7 +20,7 @@ public class Internals {
|
||||
@Native public final FunctionValue
|
||||
object, function, array,
|
||||
bool, number, string, symbol,
|
||||
promise, map, set,
|
||||
promise, map, set, regexp,
|
||||
error, syntax, type, range;
|
||||
|
||||
@Native public void markSpecial(FunctionValue ...funcs) {
|
||||
@ -174,5 +174,6 @@ public class Internals {
|
||||
this.syntax = targetEnv.wrappersProvider.getConstr(SyntaxErrorPolyfill.class);
|
||||
this.type = targetEnv.wrappersProvider.getConstr(TypeErrorPolyfill.class);
|
||||
this.range = targetEnv.wrappersProvider.getConstr(RangeErrorPolyfill.class);
|
||||
this.regexp = targetEnv.wrappersProvider.getConstr(RegExpPolyfill.class);
|
||||
}
|
||||
}
|
||||
|
@ -14,12 +14,12 @@ import me.topchetoeu.jscript.json.JSONElement;
|
||||
import me.topchetoeu.jscript.json.JSONList;
|
||||
import me.topchetoeu.jscript.json.JSONMap;
|
||||
|
||||
public class JSON {
|
||||
public class JSONPolyfill {
|
||||
private static Object toJS(JSONElement val) {
|
||||
if (val.isBoolean()) return val.bool();
|
||||
if (val.isString()) return val.string();
|
||||
if (val.isNumber()) return val.number();
|
||||
if (val.isList()) return ArrayValue.of(null, val.list().stream().map(JSON::toJS).collect(Collectors.toList()));
|
||||
if (val.isList()) return ArrayValue.of(null, val.list().stream().map(JSONPolyfill::toJS).collect(Collectors.toList()));
|
||||
if (val.isMap()) {
|
||||
var res = new ObjectValue();
|
||||
for (var el : val.map().entrySet()) {
|
@ -13,7 +13,6 @@ import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
|
||||
public class MapPolyfill {
|
||||
@Native("@@Symbol.typeName") public final String name = "Map";
|
||||
private LinkedHashMap<Object, Object> map = new LinkedHashMap<>();
|
||||
|
||||
@Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) throws InterruptedException {
|
||||
|
@ -7,8 +7,6 @@ import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
|
||||
public class NumberPolyfill {
|
||||
@Native("@@Symbol.typeName") public final String name = "Number";
|
||||
|
||||
@Native public static final double EPSILON = java.lang.Math.ulp(1.0);
|
||||
@Native public static final double MAX_SAFE_INTEGER = 9007199254740991.;
|
||||
@Native public static final double MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER;
|
||||
|
@ -11,8 +11,6 @@ import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
|
||||
public class ObjectPolyfill {
|
||||
@Native("@@Symbol.typeName") public final String name = "Object";
|
||||
|
||||
@Native public static ObjectValue assign(Context ctx, ObjectValue dst, Object... src) throws InterruptedException {
|
||||
for (var obj : src) {
|
||||
for (var key : Values.getMembers(ctx, obj, true, true)) {
|
||||
|
@ -28,8 +28,6 @@ public class PromisePolyfill {
|
||||
}
|
||||
}
|
||||
|
||||
@Native("@@Symbol.typeName") public final String name = "Promise";
|
||||
|
||||
@Native("resolve")
|
||||
public static PromisePolyfill ofResolved(Context ctx, Object val) throws InterruptedException {
|
||||
var res = new PromisePolyfill();
|
||||
|
@ -1,187 +0,0 @@
|
||||
package me.topchetoeu.jscript.polyfills;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeWrapper;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
import me.topchetoeu.jscript.interop.NativeSetter;
|
||||
|
||||
public class RegExp {
|
||||
// I used Regex to analyze Regex
|
||||
private static final Pattern NAMED_PATTERN = Pattern.compile("\\(\\?<([^=!].*?)>", Pattern.DOTALL);
|
||||
private static final Pattern ESCAPE_PATTERN = Pattern.compile("[/\\-\\\\^$*+?.()|\\[\\]{}]");
|
||||
|
||||
private static String cleanupPattern(Context ctx, Object val) throws InterruptedException {
|
||||
if (val == null) return "(?:)";
|
||||
if (val instanceof RegExp) return ((RegExp)val).source;
|
||||
if (val instanceof NativeWrapper && ((NativeWrapper)val).wrapped instanceof RegExp) {
|
||||
return ((RegExp)((NativeWrapper)val).wrapped).source;
|
||||
}
|
||||
var res = Values.toString(ctx, val);
|
||||
if (res.equals("")) return "(?:)";
|
||||
return res;
|
||||
}
|
||||
private static String cleanupFlags(Context ctx, Object val) throws InterruptedException {
|
||||
if (val == null) return "";
|
||||
return Values.toString(ctx, val);
|
||||
}
|
||||
|
||||
private static boolean checkEscaped(String s, int pos) {
|
||||
int n = 0;
|
||||
|
||||
while (true) {
|
||||
if (pos <= 0) break;
|
||||
if (s.charAt(pos) != '\\') break;
|
||||
n++;
|
||||
pos--;
|
||||
}
|
||||
|
||||
return (n % 2) != 0;
|
||||
}
|
||||
|
||||
@Native
|
||||
public static RegExp escape(Context ctx, Object raw, Object flags) throws InterruptedException {
|
||||
return escape(Values.toString(ctx, raw), cleanupFlags(ctx, flags));
|
||||
}
|
||||
public static RegExp escape(String raw, String flags) {
|
||||
return new RegExp(ESCAPE_PATTERN.matcher(raw).replaceAll("\\\\$0"), flags);
|
||||
}
|
||||
|
||||
private Pattern pattern;
|
||||
private String[] namedGroups;
|
||||
private int flags;
|
||||
private int lastI = 0;
|
||||
|
||||
@Native
|
||||
public final String source;
|
||||
@Native
|
||||
public final boolean hasIndices;
|
||||
@Native
|
||||
public final boolean global;
|
||||
@Native
|
||||
public final boolean sticky;
|
||||
|
||||
@NativeGetter("ignoreCase")
|
||||
public boolean ignoreCase() { return (flags & Pattern.CASE_INSENSITIVE) != 0; }
|
||||
@NativeGetter("multiline")
|
||||
public boolean multiline() { return (flags & Pattern.MULTILINE) != 0; }
|
||||
@NativeGetter("unicode")
|
||||
public boolean unicode() { return (flags & Pattern.UNICODE_CHARACTER_CLASS) != 0; }
|
||||
@NativeGetter("unicode")
|
||||
public boolean dotAll() { return (flags & Pattern.DOTALL) != 0; }
|
||||
|
||||
@NativeGetter("lastIndex")
|
||||
public int lastIndex() { return lastI; }
|
||||
@NativeSetter("lastIndex")
|
||||
public void setLastIndex(Context ctx, Object i) throws InterruptedException {
|
||||
lastI = (int)Values.toNumber(ctx, i);
|
||||
}
|
||||
public void setLastIndex(int i) {
|
||||
lastI = i;
|
||||
}
|
||||
|
||||
@NativeGetter("flags")
|
||||
public final String flags() {
|
||||
String res = "";
|
||||
if (hasIndices) res += 'd';
|
||||
if (global) res += 'g';
|
||||
if (ignoreCase()) res += 'i';
|
||||
if (multiline()) res += 'm';
|
||||
if (dotAll()) res += 's';
|
||||
if (unicode()) res += 'u';
|
||||
if (sticky) res += 'y';
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native
|
||||
public Object exec(Context ctx, Object str) throws InterruptedException {
|
||||
return exec(Values.toString(ctx, str));
|
||||
}
|
||||
public Object exec(String str) {
|
||||
var matcher = pattern.matcher(str);
|
||||
if (lastI > str.length() || !matcher.find(lastI) || sticky && matcher.start() != lastI) {
|
||||
lastI = 0;
|
||||
return Values.NULL;
|
||||
}
|
||||
if (sticky || global) {
|
||||
lastI = matcher.end();
|
||||
if (matcher.end() == matcher.start()) lastI++;
|
||||
}
|
||||
|
||||
var obj = new ArrayValue();
|
||||
var groups = new ObjectValue();
|
||||
|
||||
for (var el : namedGroups) {
|
||||
try {
|
||||
groups.defineProperty(null, el, matcher.group(el));
|
||||
}
|
||||
catch (IllegalArgumentException e) { }
|
||||
}
|
||||
if (groups.values.size() == 0) groups = null;
|
||||
|
||||
|
||||
for (int i = 0; i < matcher.groupCount() + 1; i++) {
|
||||
obj.set(null, i, matcher.group(i));
|
||||
}
|
||||
obj.defineProperty(null, "groups", groups);
|
||||
obj.defineProperty(null, "index", matcher.start());
|
||||
obj.defineProperty(null, "input", str);
|
||||
|
||||
if (hasIndices) {
|
||||
var indices = new ArrayValue();
|
||||
for (int i = 0; i < matcher.groupCount() + 1; i++) {
|
||||
indices.set(null, i, new ArrayValue(null, matcher.start(i), matcher.end(i)));
|
||||
}
|
||||
var groupIndices = new ObjectValue();
|
||||
for (var el : namedGroups) {
|
||||
groupIndices.defineProperty(null, el, new ArrayValue(null, matcher.start(el), matcher.end(el)));
|
||||
}
|
||||
indices.defineProperty(null, "groups", groupIndices);
|
||||
obj.defineProperty(null, "indices", indices);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Native
|
||||
public RegExp(Context ctx, Object pattern, Object flags) throws InterruptedException {
|
||||
this(cleanupPattern(ctx, pattern), cleanupFlags(ctx, flags));
|
||||
}
|
||||
public RegExp(String pattern, String flags) {
|
||||
if (pattern == null || pattern.equals("")) pattern = "(?:)";
|
||||
if (flags == null || flags.equals("")) flags = "";
|
||||
|
||||
this.flags = 0;
|
||||
this.hasIndices = flags.contains("d");
|
||||
this.global = flags.contains("g");
|
||||
this.sticky = flags.contains("y");
|
||||
this.source = pattern;
|
||||
|
||||
if (flags.contains("i")) this.flags |= Pattern.CASE_INSENSITIVE;
|
||||
if (flags.contains("m")) this.flags |= Pattern.MULTILINE;
|
||||
if (flags.contains("s")) this.flags |= Pattern.DOTALL;
|
||||
if (flags.contains("u")) this.flags |= Pattern.UNICODE_CHARACTER_CLASS;
|
||||
|
||||
this.pattern = Pattern.compile(pattern.replace("\\d", "[0-9]"), this.flags);
|
||||
|
||||
var matcher = NAMED_PATTERN.matcher(source);
|
||||
var groups = new ArrayList<String>();
|
||||
|
||||
while (matcher.find()) {
|
||||
if (!checkEscaped(source, matcher.start() - 1)) {
|
||||
groups.add(matcher.group(1));
|
||||
}
|
||||
}
|
||||
|
||||
namedGroups = groups.toArray(String[]::new);
|
||||
}
|
||||
|
||||
public RegExp(String pattern) { this(pattern, null); }
|
||||
public RegExp() { this(null, null); }
|
||||
}
|
296
src/me/topchetoeu/jscript/polyfills/RegExpPolyfill.java
Normal file
296
src/me/topchetoeu/jscript/polyfills/RegExpPolyfill.java
Normal file
@ -0,0 +1,296 @@
|
||||
package me.topchetoeu.jscript.polyfills;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeWrapper;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
|
||||
public class RegExpPolyfill {
|
||||
// I used Regex to analyze Regex
|
||||
private static final Pattern NAMED_PATTERN = Pattern.compile("\\(\\?<([^=!].*?)>", Pattern.DOTALL);
|
||||
private static final Pattern ESCAPE_PATTERN = Pattern.compile("[/\\-\\\\^$*+?.()|\\[\\]{}]");
|
||||
|
||||
private static String cleanupPattern(Context ctx, Object val) throws InterruptedException {
|
||||
if (val == null) return "(?:)";
|
||||
if (val instanceof RegExpPolyfill) return ((RegExpPolyfill)val).source;
|
||||
if (val instanceof NativeWrapper && ((NativeWrapper)val).wrapped instanceof RegExpPolyfill) {
|
||||
return ((RegExpPolyfill)((NativeWrapper)val).wrapped).source;
|
||||
}
|
||||
var res = Values.toString(ctx, val);
|
||||
if (res.equals("")) return "(?:)";
|
||||
return res;
|
||||
}
|
||||
private static String cleanupFlags(Context ctx, Object val) throws InterruptedException {
|
||||
if (val == null) return "";
|
||||
return Values.toString(ctx, val);
|
||||
}
|
||||
|
||||
private static boolean checkEscaped(String s, int pos) {
|
||||
int n = 0;
|
||||
|
||||
while (true) {
|
||||
if (pos <= 0) break;
|
||||
if (s.charAt(pos) != '\\') break;
|
||||
n++;
|
||||
pos--;
|
||||
}
|
||||
|
||||
return (n % 2) != 0;
|
||||
}
|
||||
|
||||
@Native
|
||||
public static RegExpPolyfill escape(Context ctx, Object raw, Object flags) throws InterruptedException {
|
||||
return escape(Values.toString(ctx, raw), cleanupFlags(ctx, flags));
|
||||
}
|
||||
public static RegExpPolyfill escape(String raw, String flags) {
|
||||
return new RegExpPolyfill(ESCAPE_PATTERN.matcher(raw).replaceAll("\\\\$0"), flags);
|
||||
}
|
||||
|
||||
private Pattern pattern;
|
||||
private String[] namedGroups;
|
||||
private int flags;
|
||||
|
||||
@Native public int lastI = 0;
|
||||
@Native public final String source;
|
||||
@Native public final boolean hasIndices;
|
||||
@Native public final boolean global;
|
||||
@Native public final boolean sticky;
|
||||
|
||||
@NativeGetter public boolean ignoreCase() { return (flags & Pattern.CASE_INSENSITIVE) != 0; }
|
||||
@NativeGetter public boolean multiline() { return (flags & Pattern.MULTILINE) != 0; }
|
||||
@NativeGetter public boolean unicode() { return (flags & Pattern.UNICODE_CHARACTER_CLASS) != 0; }
|
||||
@NativeGetter public boolean dotAll() { return (flags & Pattern.DOTALL) != 0; }
|
||||
|
||||
@NativeGetter("flags") public final String flags() {
|
||||
String res = "";
|
||||
if (hasIndices) res += 'd';
|
||||
if (global) res += 'g';
|
||||
if (ignoreCase()) res += 'i';
|
||||
if (multiline()) res += 'm';
|
||||
if (dotAll()) res += 's';
|
||||
if (unicode()) res += 'u';
|
||||
if (sticky) res += 'y';
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@Native public Object exec(String str) {
|
||||
var matcher = pattern.matcher(str);
|
||||
if (lastI > str.length() || !matcher.find(lastI) || sticky && matcher.start() != lastI) {
|
||||
lastI = 0;
|
||||
return Values.NULL;
|
||||
}
|
||||
if (sticky || global) {
|
||||
lastI = matcher.end();
|
||||
if (matcher.end() == matcher.start()) lastI++;
|
||||
}
|
||||
|
||||
var obj = new ArrayValue();
|
||||
var groups = new ObjectValue();
|
||||
|
||||
for (var el : namedGroups) {
|
||||
try {
|
||||
groups.defineProperty(null, el, matcher.group(el));
|
||||
}
|
||||
catch (IllegalArgumentException e) { }
|
||||
}
|
||||
if (groups.values.size() == 0) groups = null;
|
||||
|
||||
|
||||
for (int i = 0; i < matcher.groupCount() + 1; i++) {
|
||||
obj.set(null, i, matcher.group(i));
|
||||
}
|
||||
obj.defineProperty(null, "groups", groups);
|
||||
obj.defineProperty(null, "index", matcher.start());
|
||||
obj.defineProperty(null, "input", str);
|
||||
|
||||
if (hasIndices) {
|
||||
var indices = new ArrayValue();
|
||||
for (int i = 0; i < matcher.groupCount() + 1; i++) {
|
||||
indices.set(null, i, new ArrayValue(null, matcher.start(i), matcher.end(i)));
|
||||
}
|
||||
var groupIndices = new ObjectValue();
|
||||
for (var el : namedGroups) {
|
||||
groupIndices.defineProperty(null, el, new ArrayValue(null, matcher.start(el), matcher.end(el)));
|
||||
}
|
||||
indices.defineProperty(null, "groups", groupIndices);
|
||||
obj.defineProperty(null, "indices", indices);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Native public boolean test(String str) {
|
||||
return this.exec(str) != Values.NULL;
|
||||
}
|
||||
@Native public String toString() {
|
||||
return "/" + source + "/" + flags();
|
||||
}
|
||||
|
||||
@Native("@@Symvol.match") public Object match(Context ctx, String target) throws InterruptedException {
|
||||
if (this.global) {
|
||||
var res = new ArrayValue();
|
||||
Object val;
|
||||
while ((val = this.exec(target)) != Values.NULL) {
|
||||
res.set(ctx, res.size(), Values.getMember(ctx, val, 0));
|
||||
}
|
||||
lastI = 0;
|
||||
return res;
|
||||
}
|
||||
else {
|
||||
var res = this.exec(target);
|
||||
if (!this.sticky) this.lastI = 0;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@Native("@@Symvol.matchAll") public Object matchAll(Context ctx, String target) throws InterruptedException {
|
||||
var pattern = new RegExpPolyfill(this.source, this.flags() + "g");
|
||||
|
||||
return Values.fromJavaIterator(ctx, new Iterator<Object>() {
|
||||
private Object val = null;
|
||||
private boolean updated = false;
|
||||
|
||||
private void update() {
|
||||
if (!updated) val = pattern.exec(target);
|
||||
}
|
||||
@Override public boolean hasNext() {
|
||||
update();
|
||||
return val != Values.NULL;
|
||||
}
|
||||
@Override public Object next() {
|
||||
update();
|
||||
updated = false;
|
||||
return val;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Native("@@Symvol.split") public ArrayValue split(Context ctx, String target, Object limit, boolean sensible) throws InterruptedException {
|
||||
var pattern = new RegExpPolyfill(this.source, this.flags() + "g");
|
||||
Object match;
|
||||
int lastEnd = 0;
|
||||
var res = new ArrayValue();
|
||||
var lim = limit == null ? 0 : Values.toNumber(ctx, limit);
|
||||
|
||||
while ((match = pattern.exec(target)) != Values.NULL) {
|
||||
var added = new ArrayList<String>();
|
||||
var arrMatch = (ArrayValue)match;
|
||||
int index = (int)Values.toNumber(ctx, Values.getMember(ctx, match, "index"));
|
||||
var matchVal = (String)arrMatch.get(0);
|
||||
|
||||
if (index >= target.length()) break;
|
||||
|
||||
if (matchVal.length() == 0 || index - lastEnd > 0) {
|
||||
added.add(target.substring(lastEnd, pattern.lastI));
|
||||
if (pattern.lastI < target.length()) {
|
||||
for (var i = 1; i < arrMatch.size(); i++) added.add((String)arrMatch.get(i));
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (var i = 1; i < arrMatch.size(); i++) added.add((String)arrMatch.get(i));
|
||||
}
|
||||
|
||||
if (sensible) {
|
||||
if (limit != null && res.size() + added.size() >= lim) break;
|
||||
else for (var i = 0; i < added.size(); i++) res.set(ctx, res.size(), added.get(i));
|
||||
}
|
||||
else {
|
||||
for (var i = 0; i < added.size(); i++) {
|
||||
if (limit != null && res.size() >= lim) return res;
|
||||
else res.set(ctx, res.size(), added.get(i));
|
||||
}
|
||||
}
|
||||
lastEnd = pattern.lastI;
|
||||
}
|
||||
if (lastEnd < target.length()) {
|
||||
res.set(ctx, res.size(), target.substring(lastEnd));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
// [Symbol.replace](target, replacement) {
|
||||
// const pattern = new this.constructor(this, this.flags + "d") as RegExp;
|
||||
// let match: RegExpResult | null;
|
||||
// let lastEnd = 0;
|
||||
// const res: string[] = [];
|
||||
// // log(pattern.toString());
|
||||
// while ((match = pattern.exec(target)) !== null) {
|
||||
// const indices = match.indices![0];
|
||||
// res.push(target.substring(lastEnd, indices[0]));
|
||||
// if (replacement instanceof Function) {
|
||||
// res.push(replacement(target.substring(indices[0], indices[1]), ...match.slice(1), indices[0], target));
|
||||
// }
|
||||
// else {
|
||||
// res.push(replacement);
|
||||
// }
|
||||
// lastEnd = indices[1];
|
||||
// if (!pattern.global) break;
|
||||
// }
|
||||
// if (lastEnd < target.length) {
|
||||
// res.push(target.substring(lastEnd));
|
||||
// }
|
||||
// return res.join('');
|
||||
// },
|
||||
// [Symbol.search](target, reverse, start) {
|
||||
// const pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp;
|
||||
// if (!reverse) {
|
||||
// pattern.lastIndex = (start as any) | 0;
|
||||
// const res = pattern.exec(target);
|
||||
// if (res) return res.index;
|
||||
// else return -1;
|
||||
// }
|
||||
// else {
|
||||
// start ??= target.length;
|
||||
// start |= 0;
|
||||
// let res: RegExpResult | null = null;
|
||||
// while (true) {
|
||||
// const tmp = pattern.exec(target);
|
||||
// if (tmp === null || tmp.index > start) break;
|
||||
// res = tmp;
|
||||
// }
|
||||
// if (res && res.index <= start) return res.index;
|
||||
// else return -1;
|
||||
// }
|
||||
// },
|
||||
@Native public RegExpPolyfill(Context ctx, Object pattern, Object flags) throws InterruptedException {
|
||||
this(cleanupPattern(ctx, pattern), cleanupFlags(ctx, flags));
|
||||
}
|
||||
public RegExpPolyfill(String pattern, String flags) {
|
||||
if (pattern == null || pattern.equals("")) pattern = "(?:)";
|
||||
if (flags == null || flags.equals("")) flags = "";
|
||||
|
||||
this.flags = 0;
|
||||
this.hasIndices = flags.contains("d");
|
||||
this.global = flags.contains("g");
|
||||
this.sticky = flags.contains("y");
|
||||
this.source = pattern;
|
||||
|
||||
if (flags.contains("i")) this.flags |= Pattern.CASE_INSENSITIVE;
|
||||
if (flags.contains("m")) this.flags |= Pattern.MULTILINE;
|
||||
if (flags.contains("s")) this.flags |= Pattern.DOTALL;
|
||||
if (flags.contains("u")) this.flags |= Pattern.UNICODE_CHARACTER_CLASS;
|
||||
|
||||
this.pattern = Pattern.compile(pattern.replace("\\d", "[0-9]"), this.flags);
|
||||
|
||||
var matcher = NAMED_PATTERN.matcher(source);
|
||||
var groups = new ArrayList<String>();
|
||||
|
||||
while (matcher.find()) {
|
||||
if (!checkEscaped(source, matcher.start() - 1)) {
|
||||
groups.add(matcher.group(1));
|
||||
}
|
||||
}
|
||||
|
||||
namedGroups = groups.toArray(String[]::new);
|
||||
}
|
||||
|
||||
public RegExpPolyfill(String pattern) { this(pattern, null); }
|
||||
public RegExpPolyfill() { this(null, null); }
|
||||
}
|
@ -13,7 +13,6 @@ import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
|
||||
public class SetPolyfill {
|
||||
@Native("@@Symbol.typeName") public final String name = "Set";
|
||||
private LinkedHashSet<Object> set = new LinkedHashSet<>();
|
||||
|
||||
@Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) throws InterruptedException {
|
||||
|
@ -14,8 +14,6 @@ import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
|
||||
// TODO: implement index wrapping properly
|
||||
public class StringPolyfill {
|
||||
@Native("@@Symbol.typeName") public final String name = "String";
|
||||
|
||||
public final String value;
|
||||
|
||||
private static String passThis(Context ctx, String funcName, Object val) throws InterruptedException {
|
||||
|
@ -14,7 +14,6 @@ import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
|
||||
public class SymbolPolyfill {
|
||||
private static final Map<String, Symbol> symbols = new HashMap<>();
|
||||
@Native("@@Symbol.typeName") public final String name = "Symbol";
|
||||
|
||||
@NativeGetter public static Symbol typeName(Context ctx) { return ctx.env.symbol("Symbol.typeName"); }
|
||||
@NativeGetter public static Symbol replace(Context ctx) { return ctx.env.symbol("Symbol.replace"); }
|
||||
|
Loading…
Reference in New Issue
Block a user