a lot of fixes

This commit is contained in:
TopchetoEU 2023-09-27 15:08:23 +03:00
parent c1b84689c4
commit 0dacaaeb4c
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
25 changed files with 452 additions and 378 deletions

View File

@ -17,7 +17,8 @@ interface Internals {
syntax: SyntaxErrorConstructor;
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);
}

View File

@ -2,7 +2,6 @@
"files": [
"lib.d.ts",
"modules.ts",
"utils.ts",
"regex.ts",
"timeout.ts",
"core.ts"

View File

@ -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
);
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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));

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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);

View File

@ -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();

View File

@ -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);

View File

@ -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

View File

@ -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 {

View File

@ -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;

View File

@ -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);
}
}

View File

@ -1,85 +1,85 @@
package me.topchetoeu.jscript.polyfills;
import java.util.HashSet;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.json.JSONElement;
import me.topchetoeu.jscript.json.JSONList;
import me.topchetoeu.jscript.json.JSONMap;
public class JSON {
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.isMap()) {
var res = new ObjectValue();
for (var el : val.map().entrySet()) {
res.defineProperty(null, el.getKey(), toJS(el.getValue()));
}
return res;
}
if (val.isNull()) return Values.NULL;
return null;
}
private static JSONElement toJSON(Context ctx, Object val, HashSet<Object> prev) throws InterruptedException {
if (val instanceof Boolean) return JSONElement.bool((boolean)val);
if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue());
if (val instanceof String) return JSONElement.string((String)val);
if (val == Values.NULL) return JSONElement.NULL;
if (val instanceof ObjectValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONMap();
for (var el : ((ObjectValue)val).keys(false)) {
var jsonEl = toJSON(ctx, ((ObjectValue)val).getMember(ctx, el), prev);
if (jsonEl == null) continue;
if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val instanceof ArrayValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONList();
for (var el : ((ArrayValue)val).toArray()) {
var jsonEl = toJSON(ctx, el, prev);
if (jsonEl == null) jsonEl = JSONElement.NULL;
res.add(jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val == null) return null;
return null;
}
@Native
public static Object parse(Context ctx, String val) throws InterruptedException {
try {
return toJS(me.topchetoeu.jscript.json.JSON.parse("<value>", val));
}
catch (SyntaxException e) {
throw EngineException.ofSyntax(e.msg);
}
}
@Native
public static String stringify(Context ctx, Object val) throws InterruptedException {
return me.topchetoeu.jscript.json.JSON.stringify(toJSON(ctx, val, new HashSet<>()));
}
}
package me.topchetoeu.jscript.polyfills;
import java.util.HashSet;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.json.JSONElement;
import me.topchetoeu.jscript.json.JSONList;
import me.topchetoeu.jscript.json.JSONMap;
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(JSONPolyfill::toJS).collect(Collectors.toList()));
if (val.isMap()) {
var res = new ObjectValue();
for (var el : val.map().entrySet()) {
res.defineProperty(null, el.getKey(), toJS(el.getValue()));
}
return res;
}
if (val.isNull()) return Values.NULL;
return null;
}
private static JSONElement toJSON(Context ctx, Object val, HashSet<Object> prev) throws InterruptedException {
if (val instanceof Boolean) return JSONElement.bool((boolean)val);
if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue());
if (val instanceof String) return JSONElement.string((String)val);
if (val == Values.NULL) return JSONElement.NULL;
if (val instanceof ObjectValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONMap();
for (var el : ((ObjectValue)val).keys(false)) {
var jsonEl = toJSON(ctx, ((ObjectValue)val).getMember(ctx, el), prev);
if (jsonEl == null) continue;
if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val instanceof ArrayValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONList();
for (var el : ((ArrayValue)val).toArray()) {
var jsonEl = toJSON(ctx, el, prev);
if (jsonEl == null) jsonEl = JSONElement.NULL;
res.add(jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val == null) return null;
return null;
}
@Native
public static Object parse(Context ctx, String val) throws InterruptedException {
try {
return toJS(me.topchetoeu.jscript.json.JSON.parse("<value>", val));
}
catch (SyntaxException e) {
throw EngineException.ofSyntax(e.msg);
}
}
@Native
public static String stringify(Context ctx, Object val) throws InterruptedException {
return me.topchetoeu.jscript.json.JSON.stringify(toJSON(ctx, val, new HashSet<>()));
}
}

View File

@ -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 {

View File

@ -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;

View File

@ -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)) {

View File

@ -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();

View File

@ -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); }
}

View 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); }
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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"); }