feat: implement map and set polyfills in java

This commit is contained in:
TopchetoEU 2023-09-26 08:31:27 +03:00
parent cf36b7adc5
commit e16c0fedb1
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
21 changed files with 208 additions and 201 deletions

View File

@ -12,6 +12,8 @@ interface Internals {
bool: BooleanConstructor;
number: NumberConstructor;
string: StringConstructor;
map: typeof Map;
set: typeof Set;
markSpecial(...funcs: Function[]): void;
getEnv(func: Function): Environment | undefined;
@ -45,19 +47,23 @@ interface Internals {
try {
var env: Environment = arguments[0], internals: Internals = arguments[1];
var Object = env.global.Object = internals.object;
var Function = env.global.Function = internals.function;
var Array = env.global.Array = internals.array;
var Promise = env.global.Promise = internals.promise;
var Boolean = env.global.Boolean = internals.bool;
var Number = env.global.Number = internals.number;
var String = env.global.String = internals.string;
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 Map = env.global.Map = internals.map;
const Set = env.global.Set = internals.set;
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('bool', Boolean.prototype);
(Object.prototype as any).__proto__ = null;
@ -66,8 +72,6 @@ try {
run('values/symbol');
run('values/errors');
run('map');
run('set');
run('regex');
run('timeout');

View File

@ -1,93 +0,0 @@
define("map", () => {
const syms = { values: internals.symbol('Map.values') } as { readonly values: unique symbol };
const Object = env.global.Object;
class Map<KeyT, ValueT> {
[syms.values]: any = {};
public [env.global.Symbol.iterator](): IterableIterator<[KeyT, ValueT]> {
return this.entries();
}
public clear() {
this[syms.values] = {};
}
public delete(key: KeyT) {
if ((key as any) in this[syms.values]) {
delete this[syms.values];
return true;
}
else return false;
}
public entries(): IterableIterator<[KeyT, ValueT]> {
const keys = internals.ownPropKeys(this[syms.values]);
let i = 0;
return {
next: () => {
if (i >= keys.length) return { done: true };
else return { done: false, value: [ keys[i], this[syms.values][keys[i++]] ] }
},
[env.global.Symbol.iterator]() { return this; }
}
}
public keys(): IterableIterator<KeyT> {
const keys = internals.ownPropKeys(this[syms.values]);
let i = 0;
return {
next: () => {
if (i >= keys.length) return { done: true };
else return { done: false, value: keys[i] }
},
[env.global.Symbol.iterator]() { return this; }
}
}
public values(): IterableIterator<ValueT> {
const keys = internals.ownPropKeys(this[syms.values]);
let i = 0;
return {
next: () => {
if (i >= keys.length) return { done: true };
else return { done: false, value: this[syms.values][keys[i++]] }
},
[env.global.Symbol.iterator]() { return this; }
}
}
public get(key: KeyT) {
return this[syms.values][key];
}
public set(key: KeyT, val: ValueT) {
this[syms.values][key] = val;
return this;
}
public has(key: KeyT) {
return (key as any) in this[syms.values][key];
}
public get size() {
return internals.ownPropKeys(this[syms.values]).length;
}
public forEach(func: (key: KeyT, val: ValueT, map: Map<KeyT, ValueT>) => void, thisArg?: any) {
const keys = internals.ownPropKeys(this[syms.values]);
for (let i = 0; i < keys.length; i++) {
func(keys[i], this[syms.values][keys[i]], this);
}
}
public constructor(iterable: Iterable<[KeyT, ValueT]>) {
const it = iterable[env.global.Symbol.iterator]();
for (let el = it.next(); !el.done; el = it.next()) {
this[syms.values][el.value[0]] = el.value[1];
}
}
}
env.global.Map = Map;
});

View File

@ -1,81 +0,0 @@
define("set", () => {
const syms = { values: internals.symbol('Map.values') } as { readonly values: unique symbol };
const Object = env.global.Object;
class Set<T> {
[syms.values]: any = {};
public [env.global.Symbol.iterator](): IterableIterator<[T, T]> {
return this.entries();
}
public clear() {
this[syms.values] = {};
}
public delete(key: T) {
if ((key as any) in this[syms.values]) {
delete this[syms.values];
return true;
}
else return false;
}
public entries(): IterableIterator<[T, T]> {
const keys = internals.ownPropKeys(this[syms.values]);
let i = 0;
return {
next: () => {
if (i >= keys.length) return { done: true };
else return { done: false, value: [ keys[i], keys[i] ] }
},
[env.global.Symbol.iterator]() { return this; }
}
}
public keys(): IterableIterator<T> {
const keys = internals.ownPropKeys(this[syms.values]);
let i = 0;
return {
next: () => {
if (i >= keys.length) return { done: true };
else return { done: false, value: keys[i] }
},
[env.global.Symbol.iterator]() { return this; }
}
}
public values(): IterableIterator<T> {
return this.keys();
}
public add(val: T) {
this[syms.values][val] = undefined;
return this;
}
public has(key: T) {
return (key as any) in this[syms.values][key];
}
public get size() {
return internals.ownPropKeys(this[syms.values]).length;
}
public forEach(func: (key: T, val: T, map: Set<T>) => void, thisArg?: any) {
const keys = internals.ownPropKeys(this[syms.values]);
for (let i = 0; i < keys.length; i++) {
func(keys[i], this[syms.values][keys[i]], this);
}
}
public constructor(iterable: Iterable<T>) {
const it = iterable[env.global.Symbol.iterator]();
for (let el = it.next(); !el.done; el = it.next()) {
this[syms.values][el.value] = undefined;
}
}
}
env.global.Set = Set;
});

View File

@ -31,6 +31,6 @@ define("values/symbol", () => {
asyncIterator: env.symbol('Symbol.asyncIterator') as any,
});
internals.defineField(env.global.Object.prototype, Symbol.typeName, 'Object', false, false, false);
internals.defineField(env.global, Symbol.typeName, 'Window', false, false, false);
// internals.defineField(env.global.Object.prototype, Symbol.typeName, 'Object', false, false, false);
// internals.defineField(env.global, Symbol.typeName, 'Window', false, false, false);
});

View File

@ -206,7 +206,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
}
public ArrayValue(Context ctx, Object ...values) {
this();
values = new Object[values.length];
this.values = new Object[values.length];
size = values.length;
for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]);

View File

@ -519,7 +519,7 @@ public class Values {
throw new ConvertException(type(obj), clazz.getSimpleName());
}
public static Iterable<Object> toJavaIterable(Context ctx, Object obj) throws InterruptedException {
public static Iterable<Object> toJavaIterable(Context ctx, Object obj) {
return () -> {
try {
var symbol = ctx.env.symbol("Symbol.iterator");
@ -527,7 +527,7 @@ public class Values {
var iteratorFunc = getMember(ctx, obj, symbol);
if (!isFunction(iteratorFunc)) return Collections.emptyIterator();
var iterator = iteratorFunc instanceof FunctionValue ?
((FunctionValue)iteratorFunc).call(ctx, iteratorFunc, obj) :
((FunctionValue)iteratorFunc).call(ctx, obj, obj) :
iteratorFunc;
var nextFunc = getMember(ctx, call(ctx, iteratorFunc, obj), "next");
@ -536,7 +536,7 @@ public class Values {
return new Iterator<Object>() {
private Object value = null;
public boolean consumed = true;
private FunctionValue next = function(iterator);
private FunctionValue next = (FunctionValue)nextFunc;
private void loadNext() throws InterruptedException {
if (next == null) value = null;
@ -588,9 +588,8 @@ public class Values {
};
}
public static ObjectValue fromJavaIterable(Context ctx, Iterable<?> iterable) throws InterruptedException {
public static ObjectValue fromJavaIterator(Context ctx, Iterator<?> it) throws InterruptedException {
var res = new ObjectValue();
var it = iterable.iterator();
try {
var key = getMember(ctx, getMember(ctx, ctx.env.proto("symbol"), "constructor"), "iterator");
@ -606,6 +605,10 @@ public class Values {
return res;
}
public static ObjectValue fromJavaIterable(Context ctx, Iterable<?> it) throws InterruptedException {
return fromJavaIterator(ctx, it.iterator());
}
private static void printValue(Context ctx, Object val, HashSet<Object> passed, int tab) throws InterruptedException {
if (passed.contains(val)) {
System.out.print("[circular]");

View File

@ -33,7 +33,7 @@ public class Overload {
return new Overload(
(ctx, th, args) -> method.newInstance(args),
method.isVarArgs(), passThis,
Modifier.isStatic(method.getModifiers()) ? null : method.getDeclaringClass(),
null,
method.getParameterTypes()
);
}

View File

@ -5,6 +5,7 @@ import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.Values;
@ -56,7 +57,15 @@ public class OverloadFunction extends FunctionValue {
}
var thisArgType = overload.passThis ? overload.params[consumesEngine ? 1 : 0] : overload.thisArg;
Object _this = thisArgType == null ? null : Values.convert(ctx, thisArg, thisArgType);
Object _this;
try {
_this = thisArgType == null ? null : Values.convert(ctx, thisArg, thisArgType);
}
catch (ConvertException e) {
if (overloads.size() > 1) continue loop;
else throw EngineException.ofType(String.format("This argument can't be converted from %s to %s", e.source, e.target));
}
if (consumesEngine) newArgs[0] = ctx;
if (overload.passThis) {
@ -74,15 +83,19 @@ public class OverloadFunction extends FunctionValue {
continue;
}
catch (InvocationTargetException e) {
var loc = new Location(0, 0, "<internal>");
if (e.getTargetException() instanceof EngineException) {
throw ((EngineException)e.getTargetException());
throw ((EngineException)e.getTargetException()).add(name, loc);
}
else if (e.getTargetException() instanceof NullPointerException) {
throw EngineException.ofType("Unexpected value of 'undefined'.").add(name, loc);
}
else {
throw EngineException.ofError(e.getTargetException().getMessage());
throw EngineException.ofError(e.getTargetException().getMessage()).add(name, loc);
}
}
catch (ReflectiveOperationException e) {
throw EngineException.ofError(e.getMessage());
throw EngineException.ofError(e.getMessage()).add(name, new Location(0, 0, "<internal>"));
}
catch (Exception e) {
throw e;

View File

@ -14,6 +14,7 @@ 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

@ -7,11 +7,13 @@ import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native;
public class AsyncFunctionPolyfill extends FunctionValue {
public final FunctionValue factory;
public static class AsyncHelper {
@Native("@@Symbol.typeName") public final String name = "AsyncFunction";
public PromisePolyfill promise = new PromisePolyfill();
public CodeFrame frame;

View File

@ -16,6 +16,7 @@ public class AsyncGeneratorPolyfill extends FunctionValue {
public final FunctionValue factory;
public static class AsyncGenerator {
@Native("@@Symbol.typeName") public final String name = "AsyncGenerator";
private int state = 0;
private boolean done = false;
private PromisePolyfill currPromise;

View File

@ -7,6 +7,8 @@ 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

@ -8,7 +8,9 @@ import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native;
public class FunctionPolyfill {
@Native(thisArg = true) public static Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) throws InterruptedException {
@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 {
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

@ -18,6 +18,8 @@ public class GeneratorPolyfill extends FunctionValue {
private boolean done = false;
public CodeFrame frame;
@Native("@@Symbol.typeName") public final String name = "Generator";
private ObjectValue next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) throws InterruptedException {
if (done) {
if (inducedError != Runners.NO_RETURN) throw new EngineException(inducedError);

View File

@ -14,7 +14,7 @@ import me.topchetoeu.jscript.interop.Native;
public class Internals {
public final Environment targetEnv;
@Native public final FunctionValue object, function, promise, array, bool, number, string;
@Native public final FunctionValue object, function, promise, array, bool, number, string, map, set;
@Native public void markSpecial(FunctionValue ...funcs) {
for (var func : funcs) {
@ -160,5 +160,7 @@ public class Internals {
this.bool = targetEnv.wrappersProvider.getConstr(BooleanPolyfill.class);
this.number = targetEnv.wrappersProvider.getConstr(NumberPolyfill.class);
this.string = targetEnv.wrappersProvider.getConstr(StringPolyfill.class);
this.map = targetEnv.wrappersProvider.getConstr(MapPolyfill.class);
this.set = targetEnv.wrappersProvider.getConstr(SetPolyfill.class);
}
}

View File

@ -0,0 +1,78 @@
package me.topchetoeu.jscript.polyfills;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue;
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 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 {
return this.entries(ctx);
}
@Native public void clear() {
map.clear();
}
@Native public boolean delete(Object key) {
if (map.containsKey(key)) {
map.remove(key);
return true;
}
return false;
}
@Native public ObjectValue entries(Context ctx) throws InterruptedException {
var res = map.entrySet().stream().map(v -> {
return new ArrayValue(ctx, v.getKey(), v.getValue());
}).collect(Collectors.toList());
return Values.fromJavaIterator(ctx, res.iterator());
}
@Native public ObjectValue keys(Context ctx) throws InterruptedException {
var res = new ArrayList<>(map.keySet());
return Values.fromJavaIterator(ctx, res.iterator());
}
@Native public ObjectValue values(Context ctx) throws InterruptedException {
var res = new ArrayList<>(map.values());
return Values.fromJavaIterator(ctx, res.iterator());
}
@Native public Object get(Object key) {
return map.get(key);
}
@Native public MapPolyfill set(Object key, Object val) {
map.put(key, val);
return this;
}
@Native public boolean has(Object key) {
return map.containsKey(key);
}
@NativeGetter public int size() {
return map.size();
}
@NativeGetter public void forEach(Context ctx, FunctionValue func, Object thisArg) throws InterruptedException {
var keys = new ArrayList<>(map.keySet());
for (var el : keys) func.call(ctx, thisArg, el, map.get(el), this);
}
@Native public MapPolyfill(Context ctx, Object iterable) throws InterruptedException {
for (var el : Values.toJavaIterable(ctx, iterable)) {
try {
set(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1));
}
catch (IllegalArgumentException e) { }
}
}
}

View File

@ -7,6 +7,8 @@ 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,6 +11,8 @@ 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)) {
@ -204,9 +206,9 @@ public class ObjectPolyfill {
@NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object arg) throws InterruptedException {
if (arg == null || arg == Values.NULL) return new ObjectValue();
else if (arg instanceof Boolean) return BooleanPolyfill.constructor(ctx, thisArg, arg);
else if (arg instanceof Number) return Values.callNew(ctx, ctx.env.global.get(ctx, "Number"), arg);
else if (arg instanceof String) return Values.callNew(ctx, ctx.env.global.get(ctx, "String"), arg);
else if (arg instanceof Symbol) return Values.callNew(ctx, ctx.env.global.get(ctx, "Symbol"), arg);
else if (arg instanceof Number) return NumberPolyfill.constructor(ctx, thisArg, arg);
else if (arg instanceof String) return StringPolyfill.constructor(ctx, thisArg, arg);
// else if (arg instanceof Symbol) return SymbolPolyfill.constructor(ctx, thisArg, arg);
else return arg;
}
}

View File

@ -28,6 +28,8 @@ 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

@ -0,0 +1,63 @@
package me.topchetoeu.jscript.polyfills;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue;
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 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 {
return this.values(ctx);
}
@Native public ObjectValue entries(Context ctx) throws InterruptedException {
var res = set.stream().map(v -> new ArrayValue(ctx, v, v)).collect(Collectors.toList());
return Values.fromJavaIterator(ctx, res.iterator());
}
@Native public ObjectValue keys(Context ctx) throws InterruptedException {
var res = new ArrayList<>(set);
return Values.fromJavaIterator(ctx, res.iterator());
}
@Native public ObjectValue values(Context ctx) throws InterruptedException {
var res = new ArrayList<>(set);
return Values.fromJavaIterator(ctx, res.iterator());
}
@Native public Object add(Object key) {
return set.add(key);
}
@Native public boolean delete(Object key) {
return set.remove(key);
}
@Native public boolean has(Object key) {
return set.contains(key);
}
@Native public void clear() {
set.clear();
}
@NativeGetter public int size() {
return set.size();
}
@NativeGetter public void forEach(Context ctx, FunctionValue func, Object thisArg) throws InterruptedException {
var keys = new ArrayList<>(set);
for (var el : keys) func.call(ctx, thisArg, el, el, this);
}
@Native public SetPolyfill(Context ctx, Object iterable) throws InterruptedException {
for (var el : Values.toJavaIterable(ctx, iterable)) add(el);
}
}

View File

@ -14,6 +14,8 @@ 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(String funcName, Object val) {