Core library reprogramming #5

Merged
TopchetoEU merged 23 commits from TopchetoEU/corelib-reprogramming into master 2023-10-04 05:50:26 +00:00
20 changed files with 741 additions and 636 deletions
Showing only changes of commit f2cd50726d - Show all commits

View File

@ -7,7 +7,8 @@ interface Environment {
interface Internals {
object: ObjectConstructor;
function: FunctionConstructor;
promise: typeof Promise;
array: ArrayConstructor;
promise: PromiseConstructor;
markSpecial(...funcs: Function[]): void;
getEnv(func: Function): Environment | undefined;
@ -35,32 +36,31 @@ interface Internals {
sort(arr: any[], comaprator: (a: any, b: any) => number): void;
constructor: {
log(...args: any[]): void;
}
log(...args: any[]): void;
}
var env: Environment = arguments[0], internals: Internals = arguments[1];
globalThis.log = internals.constructor.log;
var i = 0.0;
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;
env.setProto('object', Object.prototype);
env.setProto('function', Function.prototype);
env.setProto('array', Array.prototype);
(Object.prototype as any).__proto__ = null;
run('values/symbol');
internals.getEnv(run)?.setProto('array', Array.prototype);
globalThis.log = (...args) => internals.apply(internals.log, internals, args);
run('values/symbol');
run('values/errors');
run('values/string');
run('values/number');
run('values/boolean');
run('values/array');
run('map');
run('set');
run('regex');

View File

@ -8,7 +8,6 @@
"values/string.ts",
"values/number.ts",
"values/boolean.ts",
"values/array.ts",
"map.ts",
"set.ts",
"regex.ts",

View File

@ -7,7 +7,7 @@ function setProps<
}
>(target: TargetT, desc: DescT) {
var props = internals.keys(desc, false);
for (var i = 0; i < props.length; i++) {
for (var i = 0; i in props; i++) {
var key = props[i];
internals.defineField(
target, key, (desc as any)[key],

View File

@ -1,336 +0,0 @@
define("values/array", () => {
var Array = env.global.Array = function(len?: number) {
var res = [];
if (typeof len === 'number' && arguments.length === 1) {
if (len < 0) throw 'Invalid array length.';
res.length = len;
}
else {
for (var i = 0; i < arguments.length; i++) {
res[i] = arguments[i];
}
}
return res;
} as ArrayConstructor;
env.setProto('array', Array.prototype);
(Array.prototype as any)[env.global.Symbol.typeName] = "Array";
setConstr(Array.prototype, Array);
setProps(Array.prototype, {
[env.global.Symbol.iterator]: function() {
return this.values();
},
[env.global.Symbol.typeName]: "Array",
values() {
var i = 0;
return {
next: () => {
while (i < this.length) {
if (i++ in this) return { done: false, value: this[i - 1] };
}
return { done: true, value: undefined };
},
[env.global.Symbol.iterator]() { return this; }
};
},
keys() {
var i = 0;
return {
next: () => {
while (i < this.length) {
if (i++ in this) return { done: false, value: i - 1 };
}
return { done: true, value: undefined };
},
[env.global.Symbol.iterator]() { return this; }
};
},
entries() {
var i = 0;
return {
next: () => {
while (i < this.length) {
if (i++ in this) return { done: false, value: [i - 1, this[i - 1]] };
}
return { done: true, value: undefined };
},
[env.global.Symbol.iterator]() { return this; }
};
},
concat() {
var res = [] as any[];
res.push.apply(res, this);
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
if (arg instanceof Array) {
res.push.apply(res, arg);
}
else {
res.push(arg);
}
}
return res;
},
every(func, thisArg) {
if (typeof func !== 'function') throw new TypeError("Given argument not a function.");
func = func.bind(thisArg);
for (var i = 0; i < this.length; i++) {
if (!func(this[i], i, this)) return false;
}
return true;
},
some(func, thisArg) {
if (typeof func !== 'function') throw new TypeError("Given argument not a function.");
func = func.bind(thisArg);
for (var i = 0; i < this.length; i++) {
if (func(this[i], i, this)) return true;
}
return false;
},
fill(val, start, end) {
if (arguments.length < 3) end = this.length;
if (arguments.length < 2) start = 0;
start = clampI(this.length, wrapI(this.length + 1, start ?? 0));
end = clampI(this.length, wrapI(this.length + 1, end ?? this.length));
for (; start < end; start++) {
this[start] = val;
}
return this;
},
filter(func, thisArg) {
if (typeof func !== 'function') throw new TypeError("Given argument is not a function.");
var res = [];
for (var i = 0; i < this.length; i++) {
if (i in this && func.call(thisArg, this[i], i, this)) res.push(this[i]);
}
return res;
},
find(func, thisArg) {
if (typeof func !== 'function') throw new TypeError("Given argument is not a function.");
for (var i = 0; i < this.length; i++) {
if (i in this && func.call(thisArg, this[i], i, this)) return this[i];
}
return undefined;
},
findIndex(func, thisArg) {
if (typeof func !== 'function') throw new TypeError("Given argument is not a function.");
for (var i = 0; i < this.length; i++) {
if (i in this && func.call(thisArg, this[i], i, this)) return i;
}
return -1;
},
findLast(func, thisArg) {
if (typeof func !== 'function') throw new TypeError("Given argument is not a function.");
for (var i = this.length - 1; i >= 0; i--) {
if (i in this && func.call(thisArg, this[i], i, this)) return this[i];
}
return undefined;
},
findLastIndex(func, thisArg) {
if (typeof func !== 'function') throw new TypeError("Given argument is not a function.");
for (var i = this.length - 1; i >= 0; i--) {
if (i in this && func.call(thisArg, this[i], i, this)) return i;
}
return -1;
},
flat(depth) {
var res = [] as any[];
var buff = [];
res.push(...this);
for (var i = 0; i < (depth ?? 1); i++) {
var anyArrays = false;
for (var el of res) {
if (el instanceof Array) {
buff.push(...el);
anyArrays = true;
}
else buff.push(el);
}
res = buff;
buff = [];
if (!anyArrays) break;
}
return res;
},
flatMap(func, th) {
return this.map(func, th).flat();
},
forEach(func, thisArg) {
for (var i = 0; i < this.length; i++) {
if (i in this) func.call(thisArg, this[i], i, this);
}
},
map(func, thisArg) {
if (typeof func !== 'function') throw new TypeError("Given argument is not a function.");
var res = [];
for (var i = 0; i < this.length; i++) {
if (i in this) res[i] = func.call(thisArg, this[i], i, this);
}
return res;
},
pop() {
if (this.length === 0) return undefined;
var val = this[this.length - 1];
this.length--;
return val;
},
push() {
for (var i = 0; i < arguments.length; i++) {
this[this.length] = arguments[i];
}
return arguments.length;
},
shift() {
if (this.length === 0) return undefined;
var res = this[0];
for (var i = 0; i < this.length - 1; i++) {
this[i] = this[i + 1];
}
this.length--;
return res;
},
unshift() {
for (var i = this.length - 1; i >= 0; i--) {
this[i + arguments.length] = this[i];
}
for (var i = 0; i < arguments.length; i++) {
this[i] = arguments[i];
}
return arguments.length;
},
slice(start, end) {
start = clampI(this.length, wrapI(this.length + 1, start ?? 0));
end = clampI(this.length, wrapI(this.length + 1, end ?? this.length));
var res: any[] = [];
var n = end - start;
if (n <= 0) return res;
for (var i = 0; i < n; i++) {
res[i] = this[start + i];
}
return res;
},
toString() {
let res = '';
for (let i = 0; i < this.length; i++) {
if (i > 0) res += ',';
if (i in this && this[i] !== undefined && this[i] !== null) res += this[i];
}
return res;
},
indexOf(el, start) {
start = start! | 0;
for (var i = Math.max(0, start); i < this.length; i++) {
if (i in this && this[i] == el) return i;
}
return -1;
},
lastIndexOf(el, start) {
start = start! | 0;
for (var i = this.length; i >= start; i--) {
if (i in this && this[i] == el) return i;
}
return -1;
},
includes(el, start) {
return this.indexOf(el, start) >= 0;
},
join(val = ',') {
let res = '', first = true;
for (let i = 0; i < this.length; i++) {
if (!(i in this)) continue;
if (!first) res += val;
first = false;
res += this[i];
}
return res;
},
sort(func) {
func ??= (a, b) => {
const _a = a + '';
const _b = b + '';
if (_a > _b) return 1;
if (_a < _b) return -1;
return 0;
};
if (typeof func !== 'function') throw new TypeError('Expected func to be undefined or a function.');
internals.sort(this, func);
return this;
},
splice(start, deleteCount, ...items) {
start = clampI(this.length, wrapI(this.length, start ?? 0));
deleteCount = (deleteCount ?? Infinity | 0);
if (start + deleteCount >= this.length) deleteCount = this.length - start;
const res = this.slice(start, start + deleteCount);
const moveN = items.length - deleteCount;
const len = this.length;
if (moveN < 0) {
for (let i = start - moveN; i < len; i++) {
this[i + moveN] = this[i];
}
}
else if (moveN > 0) {
for (let i = len - 1; i >= start; i--) {
this[i + moveN] = this[i];
}
}
for (let i = 0; i < items.length; i++) {
this[i + start] = items[i];
}
this.length = len + moveN;
return res;
}
});
setProps(Array, {
isArray(val: any) { return internals.isArray(val); }
});
internals.markSpecial(Array);
});

View File

@ -15,7 +15,6 @@ import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.events.Observer;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.interop.NativeTypeRegister;
import me.topchetoeu.jscript.polyfills.Internals;
public class Main {
@ -63,9 +62,8 @@ public class Main {
var in = new BufferedReader(new InputStreamReader(System.in));
engine = new Engine();
// TODO: Replace type register with safer accessor
env = new Environment(null, new NativeTypeRegister(), null);
var builderEnv = new Environment(null, new NativeTypeRegister(), null);
env = new Environment(null, null, null);
var builderEnv = new Environment(null, null, null);
var exited = new boolean[1];
env.global.define("exit", ctx -> {
@ -83,7 +81,12 @@ public class Main {
}
});
engine.pushMsg(false, new Context(builderEnv, new MessageContext(engine)), "core.js", resourceToString("js/core.js"), null, env, new Internals()).toObservable().on(valuePrinter);
engine.pushMsg(
false,
new Context(builderEnv, new MessageContext(engine)),
"core.js", resourceToString("js/core.js"),
null, env, new Internals(env)
).toObservable().on(valuePrinter);
task = engine.start();
var reader = new Thread(() -> {

View File

@ -11,6 +11,7 @@ import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeSetter;
import me.topchetoeu.jscript.interop.NativeWrapperProvider;
public class Environment {
private HashMap<String, ObjectValue> prototypes = new HashMap<>();
@ -33,7 +34,8 @@ public class Environment {
}
@Native public Symbol symbol(String name) {
if (symbols.containsKey(name)) return symbols.get(name);
if (symbols.containsKey(name))
return symbols.get(name);
else {
var res = new Symbol(name);
symbols.put(name, res);
@ -79,14 +81,7 @@ public class Environment {
public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) {
if (compile == null) compile = new NativeFunction("compile", (ctx, thisArg, args) -> args.length == 0 ? "" : args[0]);
if (nativeConverter == null) nativeConverter = new WrappersProvider() {
public ObjectValue getConstr(Class<?> obj) {
throw EngineException.ofType("Java objects not passable to Javascript.");
}
public ObjectValue getProto(Class<?> obj) {
throw EngineException.ofType("Java objects not passable to Javascript.");
}
};
if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this);
if (global == null) global = new GlobalScope();
this.wrappersProvider = nativeConverter;

View File

@ -1,8 +1,9 @@
package me.topchetoeu.jscript.engine;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.ObjectValue;
public interface WrappersProvider {
public ObjectValue getProto(Class<?> obj);
public ObjectValue getConstr(Class<?> obj);
public FunctionValue getConstr(Class<?> obj);
}

View File

@ -268,6 +268,10 @@ public class CodeFrame {
if (res != Runners.NO_RETURN) return res;
}
}
catch (Throwable e) {
// e.printStackTrace();
throw e;
}
finally {
ctx.message.popFrame(this);
}

View File

@ -1,90 +1,128 @@
package me.topchetoeu.jscript.engine.values;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import me.topchetoeu.jscript.engine.Context;
public class ArrayValue extends ObjectValue {
private static final Object EMPTY = new Object();
private final ArrayList<Object> values = new ArrayList<>();
// TODO: Make methods generic
public class ArrayValue extends ObjectValue implements Iterable<Object> {
private static final Object UNDEFINED = new Object();
private Object[] values;
private int size;
public int size() { return values.size(); }
private void alloc(int index) {
if (index < values.length) return;
if (index < values.length * 2) index = values.length * 2;
var arr = new Object[index];
System.arraycopy(values, 0, arr, 0, values.length);
values = arr;
}
public int size() { return size; }
public boolean setSize(int val) {
if (val < 0) return false;
while (size() > val) {
values.remove(values.size() - 1);
}
while (size() < val) {
values.add(EMPTY);
if (size > val) shrink(size - val);
else {
alloc(val);
size = val;
}
return true;
}
public Object get(int i) {
if (i < 0 || i >= values.size()) return null;
var res = values.get(i);
if (res == EMPTY) return null;
if (i < 0 || i >= size) return null;
var res = values[i];
if (res == UNDEFINED) return null;
else return res;
}
public void set(Context ctx, int i, Object val) {
if (i < 0) return;
while (values.size() <= i) {
values.add(EMPTY);
}
alloc(i);
values.set(i, Values.normalize(ctx, val));
val = Values.normalize(ctx, val);
if (val == null) val = UNDEFINED;
values[i] = val;
if (i >= size) size = i + 1;
}
public boolean has(int i) {
return i >= 0 && i < values.size() && values.get(i) != EMPTY;
return i >= 0 && i < values.length && values[i] != null;
}
public void remove(int i) {
if (i < 0 || i >= values.size()) return;
values.set(i, EMPTY);
if (i < 0 || i >= values.length) return;
values[i] = null;
}
public void shrink(int n) {
if (n > values.size()) values.clear();
if (n >= values.length) {
values = new Object[16];
size = 0;
}
else {
for (int i = 0; i < n && values.size() > 0; i++) {
values.remove(values.size() - 1);
for (int i = 0; i < n; i++) {
values[--size] = null;
}
}
}
public Object[] toArray() {
Object[] res = new Object[size];
copyTo(res, 0, 0, size);
return res;
}
public void copyTo(Object[] arr, int sourceStart, int destStart, int count) {
for (var i = 0; i < count; i++) {
if (i + sourceStart < 0 || i + sourceStart >= size) arr[i + destStart] = null;
if (values[i + sourceStart] == UNDEFINED) arr[i + destStart] = null;
else arr[i + sourceStart] = values[i + destStart];
}
}
public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) {
// Iterate in reverse to reallocate at most once
for (var i = count - 1; i >= 0; i--) {
if (i + sourceStart < 0 || i + sourceStart >= size) arr.set(ctx, i + destStart, null);
if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null);
else arr.set(ctx, i + destStart, values[i + sourceStart]);
}
}
public void copyFrom(Context ctx, Object[] arr, int sourceStart, int destStart, int count) {
for (var i = 0; i < count; i++) {
set(ctx, i + destStart, arr[i + sourceStart]);
}
}
public void move(int srcI, int dstI, int n) {
alloc(dstI + n);
System.arraycopy(values, srcI, values, dstI, n);
if (dstI + n >= size) size = dstI + n;
}
public void sort(Comparator<Object> comparator) {
values.sort((a, b) -> {
Arrays.sort(values, 0, size, (a, b) -> {
var _a = 0;
var _b = 0;
if (a == null) _a = 1;
if (a == EMPTY) _a = 2;
if (a == UNDEFINED) _a = 1;
if (a == null) _a = 2;
if (b == null) _b = 1;
if (b == EMPTY) _b = 2;
if (b == UNDEFINED) _b = 1;
if (b == null) _b = 2;
if (Integer.compare(_a, _b) != 0) return Integer.compare(_a, _b);
if (_a != 0 || _b != 0) return Integer.compare(_a, _b);
return comparator.compare(a, b);
});
}
public Object[] toArray() {
Object[] res = new Object[values.size()];
for (var i = 0; i < values.size(); i++) {
if (values.get(i) == EMPTY) res[i] = null;
else res[i] = values.get(i);
}
return res;
}
@Override
protected Object getField(Context ctx, Object key) throws InterruptedException {
if (key.equals("length")) return values.size();
if (key instanceof Number) {
var i = ((Number)key).doubleValue();
if (i >= 0 && i - Math.floor(i) == 0) {
@ -96,9 +134,6 @@ public class ArrayValue extends ObjectValue {
}
@Override
protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException {
if (key.equals("length")) {
return setSize((int)Values.toNumber(ctx, val));
}
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
@ -111,7 +146,6 @@ public class ArrayValue extends ObjectValue {
}
@Override
protected boolean hasField(Context ctx, Object key) throws InterruptedException {
if (key.equals("length")) return true;
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
@ -140,18 +174,42 @@ public class ArrayValue extends ObjectValue {
for (var i = 0; i < size(); i++) {
if (has(i)) res.add(i);
}
if (includeNonEnumerable) res.add("length");
return res;
}
@Override
public Iterator<Object> iterator() {
return new Iterator<Object>() {
private int i = 0;
@Override
public boolean hasNext() {
return i < size();
}
@Override
public Object next() {
if (!hasNext()) return null;
return get(i++);
}
};
}
public ArrayValue() {
super(PlaceholderProto.ARRAY);
nonEnumerableSet.add("length");
nonConfigurableSet.add("length");
values = new Object[16];
size = 0;
}
public ArrayValue(int cap) {
super(PlaceholderProto.ARRAY);
values = new Object[cap];
size = 0;
}
public ArrayValue(Context ctx, Object ...values) {
this();
for (var i = 0; i < values.length; i++) this.values.add(Values.normalize(ctx, values[i]));
values = new Object[values.length];
size = values.length;
for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]);
}
public static ArrayValue of(Context ctx, Collection<Object> values) {

View File

@ -233,7 +233,7 @@ public class ObjectValue {
public final Object getMember(Context ctx, Object key, Object thisArg) throws InterruptedException {
key = Values.normalize(ctx, key);
if (key.equals("__proto__")) {
if ("__proto__".equals(key)) {
var res = getPrototype(ctx);
return res == null ? Values.NULL : res;
}

View File

@ -10,6 +10,6 @@ public final class Symbol {
@Override
public String toString() {
if (value == null) return "Symbol";
else return "Symbol(" + value + ")";
else return "@@" + value;
}
}

View File

@ -517,7 +517,7 @@ public class Values {
if (obj == null) return null;
if (clazz.isInstance(obj)) return (T)obj;
throw new ConvertException(type(obj), clazz.getName());
throw new ConvertException(type(obj), clazz.getSimpleName());
}
public static Iterable<Object> toJavaIterable(Context ctx, Object obj) throws InterruptedException {
@ -631,7 +631,7 @@ public class Values {
if (i != 0) System.out.print(", ");
else System.out.print(" ");
if (obj.has(i)) printValue(ctx, obj.get(i), passed, tab);
else System.out.print(", ");
else System.out.print("<empty>");
}
System.out.print(" ] ");
}

View File

@ -8,6 +8,6 @@ import java.lang.annotation.Target;
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface NativeGetter {
public String value();
public String value() default "";
public boolean thisArg() default false;
}

View File

@ -8,6 +8,6 @@ import java.lang.annotation.Target;
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface NativeSetter {
public String value();
public String value() default "";
public boolean thisArg() default false;
}

View File

@ -3,17 +3,19 @@ package me.topchetoeu.jscript.interop;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.WrappersProvider;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.exceptions.EngineException;
public class NativeTypeRegister implements WrappersProvider {
public class NativeWrapperProvider implements WrappersProvider {
private final HashMap<Class<?>, FunctionValue> constructors = new HashMap<>();
private final HashMap<Class<?>, ObjectValue> prototypes = new HashMap<>();
private final Environment env;
private static void applyMethods(boolean member, ObjectValue target, Class<?> clazz) {
private static void applyMethods(Environment env, boolean member, ObjectValue target, Class<?> clazz) {
for (var method : clazz.getDeclaredMethods()) {
var nat = method.getAnnotation(Native.class);
var get = method.getAnnotation(NativeGetter.class);
@ -23,11 +25,13 @@ public class NativeTypeRegister implements WrappersProvider {
if (nat != null) {
if (nat.thisArg() != member && memberMismatch) continue;
var name = nat.value();
Object name = nat.value();
if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
else if (name.equals("")) name = method.getName();
var val = target.values.get(name);
if (name.equals("")) name = method.getName();
if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name));
if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name.toString()));
((OverloadFunction)val).overloads.add(Overload.fromMethod(method, nat.thisArg()));
}
@ -35,7 +39,10 @@ public class NativeTypeRegister implements WrappersProvider {
if (get != null) {
if (get.thisArg() != member && memberMismatch) continue;
var name = get.value();
Object name = get.value();
if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
else if (name.equals("")) name = method.getName();
var prop = target.properties.get(name);
OverloadFunction getter = null;
var setter = prop == null ? null : prop.setter;
@ -49,7 +56,10 @@ public class NativeTypeRegister implements WrappersProvider {
if (set != null) {
if (set.thisArg() != member && memberMismatch) continue;
var name = set.value();
Object name = set.value();
if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
else if (name.equals("")) name = method.getName();
var prop = target.properties.get(name);
var getter = prop == null ? null : prop.getter;
OverloadFunction setter = null;
@ -63,28 +73,31 @@ public class NativeTypeRegister implements WrappersProvider {
}
}
}
private static void applyFields(boolean member, ObjectValue target, Class<?> clazz) {
private static void applyFields(Environment env, boolean member, ObjectValue target, Class<?> clazz) {
for (var field : clazz.getDeclaredFields()) {
if (!Modifier.isStatic(field.getModifiers()) != member) continue;
var nat = field.getAnnotation(Native.class);
if (nat != null) {
var name = nat.value();
if (name.equals("")) name = field.getName();
Object name = nat.value();
if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
else if (name.equals("")) name = field.getName();
var getter = new OverloadFunction("get " + name).add(Overload.getterFromField(field));
var setter = new OverloadFunction("set " + name).add(Overload.setterFromField(field));
target.defineProperty(null, name, getter, setter, true, false);
}
}
}
private static void applyClasses(boolean member, ObjectValue target, Class<?> clazz) {
private static void applyClasses(Environment env, boolean member, ObjectValue target, Class<?> clazz) {
for (var cl : clazz.getDeclaredClasses()) {
if (!Modifier.isStatic(cl.getModifiers()) != member) continue;
var nat = cl.getAnnotation(Native.class);
if (nat != null) {
var name = nat.value();
if (name.equals("")) name = cl.getSimpleName();
Object name = nat.value();
if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
else if (name.equals("")) name = cl.getName();
var getter = new NativeFunction("get " + name, (ctx, thisArg, args) -> cl);
@ -99,12 +112,12 @@ public class NativeTypeRegister implements WrappersProvider {
* All accessors and methods will expect the this argument to be a native wrapper of the given class type.
* @param clazz The class for which a prototype should be generated
*/
public static ObjectValue makeProto(Class<?> clazz) {
public static ObjectValue makeProto(Environment ctx, Class<?> clazz) {
var res = new ObjectValue();
applyMethods(true, res, clazz);
applyFields(true, res, clazz);
applyClasses(true, res, clazz);
applyMethods(ctx, true, res, clazz);
applyFields(ctx, true, res, clazz);
applyClasses(ctx, true, res, clazz);
return res;
}
@ -114,7 +127,7 @@ public class NativeTypeRegister implements WrappersProvider {
* When the function gets called, the underlying constructor will get called, unless the constructor is inaccessible.
* @param clazz The class for which a constructor should be generated
*/
public static FunctionValue makeConstructor(Class<?> clazz) {
public static FunctionValue makeConstructor(Environment ctx, Class<?> clazz) {
FunctionValue func = new OverloadFunction(clazz.getName());
for (var overload : clazz.getConstructors()) {
@ -132,9 +145,9 @@ public class NativeTypeRegister implements WrappersProvider {
func = new NativeFunction(clazz.getName(), (a, b, c) -> { throw EngineException.ofError("This constructor is not invokable."); });
}
applyMethods(false, func, clazz);
applyFields(false, func, clazz);
applyClasses(false, func, clazz);
applyMethods(ctx, false, func, clazz);
applyFields(ctx, false, func, clazz);
applyClasses(ctx, false, func, clazz);
func.special = true;
@ -143,15 +156,15 @@ public class NativeTypeRegister implements WrappersProvider {
/**
* Generates a namespace for the given class.
* The returned function will have appropriate wrappers for all static members.
* This method behaves almost like {@link NativeTypeRegister#makeConstructor}, but will return an object instead.
* This method behaves almost like {@link NativeWrapperProvider#makeConstructor}, but will return an object instead.
* @param clazz The class for which a constructor should be generated
*/
public static ObjectValue makeNamespace(Class<?> clazz) {
public static ObjectValue makeNamespace(Environment ctx, Class<?> clazz) {
ObjectValue res = new ObjectValue();
applyMethods(false, res, clazz);
applyFields(false, res, clazz);
applyClasses(false, res, clazz);
applyMethods(ctx, false, res, clazz);
applyFields(ctx, false, res, clazz);
applyClasses(ctx, false, res, clazz);
return res;
}
@ -173,8 +186,8 @@ public class NativeTypeRegister implements WrappersProvider {
clazz.isSynthetic()
) return;
if (constr == null) constr = makeConstructor(clazz);
if (proto == null) proto = makeProto(clazz);
if (constr == null) constr = makeConstructor(env, clazz);
if (proto == null) proto = makeProto(env, clazz);
proto.values.put("constructor", constr);
constr.values.put("prototype", proto);
@ -207,4 +220,8 @@ public class NativeTypeRegister implements WrappersProvider {
public void setConstr(Class<?> clazz, FunctionValue value) {
constructors.put(clazz, value);
}
public NativeWrapperProvider(Environment env) {
this.env = env;
}
}

View File

@ -59,7 +59,10 @@ public class OverloadFunction extends FunctionValue {
Object _this = thisArgType == null ? null : Values.convert(ctx, thisArg, thisArgType);
if (consumesEngine) newArgs[0] = ctx;
if (overload.passThis) newArgs[consumesEngine ? 1 : 0] = _this;
if (overload.passThis) {
newArgs[consumesEngine ? 1 : 0] = _this;
_this = null;
}
try {
return Values.normalize(ctx, overload.runner.run(ctx, _this, newArgs));

View File

@ -0,0 +1,350 @@
package me.topchetoeu.jscript.polyfills;
import java.util.Iterator;
import java.util.Stack;
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.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeSetter;
public class ArrayPolyfill {
@NativeGetter(thisArg = true) public static int length(Context ctx, ArrayValue thisArg) throws InterruptedException {
return thisArg.size();
}
@NativeSetter(thisArg = true) public static void length(Context ctx, ArrayValue thisArg, int len) throws InterruptedException {
thisArg.setSize(len);
}
@Native(thisArg = true) public static ObjectValue values(Context ctx, ArrayValue thisArg) throws InterruptedException {
return Values.fromJavaIterable(ctx, thisArg);
}
@Native(thisArg = true) public static ObjectValue keys(Context ctx, ArrayValue thisArg) throws InterruptedException {
return Values.fromJavaIterable(ctx, () -> new Iterator<Object>() {
private int i = 0;
@Override
public boolean hasNext() {
return i < thisArg.size();
}
@Override
public Object next() {
if (!hasNext()) return null;
return i++;
}
});
}
@Native(thisArg = true) public static ObjectValue entries(Context ctx, ArrayValue thisArg) throws InterruptedException {
return Values.fromJavaIterable(ctx, () -> new Iterator<Object>() {
private int i = 0;
@Override
public boolean hasNext() {
return i < thisArg.size();
}
@Override
public Object next() {
if (!hasNext()) return null;
return new ArrayValue(ctx, i, thisArg.get(i++));
}
});
}
@Native(value = "@@Symbol.iterator", thisArg = true)
public static ObjectValue iterator(Context ctx, ArrayValue thisArg) throws InterruptedException {
return values(ctx, thisArg);
}
@Native(value = "@@Symbol.asyncIterator", thisArg = true)
public static ObjectValue asyncIterator(Context ctx, ArrayValue thisArg) throws InterruptedException {
return values(ctx, thisArg);
}
@Native(thisArg = true) public static ArrayValue concat(Context ctx, ArrayValue thisArg, Object ...others) throws InterruptedException {
// TODO: Fully implement with non-array spreadable objects
var size = 0;
for (int i = 0; i < others.length; i++) {
if (others[i] instanceof ArrayValue) size += ((ArrayValue)others[i]).size();
else i++;
}
var res = new ArrayValue(size);
for (int i = 0, j = 0; i < others.length; i++) {
if (others[i] instanceof ArrayValue) {
int n = ((ArrayValue)others[i]).size();
((ArrayValue)others[i]).copyTo(ctx, res, 0, j, n);
j += n;
}
else {
res.set(ctx, j++, others[i]);
}
}
return res;
}
@Native(thisArg = true) public static void sort(Context ctx, ArrayValue arr, FunctionValue cmp) throws InterruptedException {
try {
arr.sort((a, b) -> {
try {
var res = Values.toNumber(ctx, cmp.call(ctx, null, a, b));
if (res < 0) return -1;
if (res > 0) return 1;
return 0;
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
catch (RuntimeException e) {
if (e.getCause() instanceof InterruptedException) throw (InterruptedException)e.getCause();
else throw e;
}
}
private static int normalizeI(int len, int i, boolean clamp) {
if (i < 0) i += len;
if (clamp) {
if (i < 0) i = 0;
if (i >= len) i = len;
}
return i;
}
@Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start, int end) throws InterruptedException {
start = normalizeI(arr.size(), start, true);
end = normalizeI(arr.size(), end, true);
for (; start < end; start++) {
arr.set(ctx, start, val);
}
return arr;
}
@Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start) throws InterruptedException {
return fill(ctx, arr, val, start, arr.size());
}
@Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val) throws InterruptedException {
return fill(ctx, arr, val, 0, arr.size());
}
@Native(thisArg = true) public static boolean every(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException {
for (var i = 0; i < arr.size(); i++) {
if (!Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return false;
}
return true;
}
@Native(thisArg = true) public static boolean some(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException {
for (var i = 0; i < arr.size(); i++) {
if (Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return true;
}
return false;
}
@Native(thisArg = true) public static ArrayValue filter(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException {
var res = new ArrayValue(arr.size());
for (int i = 0, j = 0; i < arr.size(); i++) {
if (arr.has(i) && Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) res.set(ctx, j++, arr.get(i));
}
return res;
}
@Native(thisArg = true) public static ArrayValue map(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException {
var res = new ArrayValue(arr.size());
for (int i = 0, j = 0; i < arr.size(); i++) {
if (arr.has(i)) res.set(ctx, j++, func.call(ctx, thisArg, arr.get(i), i, arr));
}
return res;
}
@Native(thisArg = true) public static void forEach(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException {
for (int i = 0; i < arr.size(); i++) {
if (arr.has(i)) func.call(ctx, thisArg, arr.get(i), i, arr);
}
}
@Native(thisArg = true) public static ArrayValue flat(Context ctx, ArrayValue arr, int depth) throws InterruptedException {
var res = new ArrayValue(arr.size());
var stack = new Stack<Object>();
var depths = new Stack<Integer>();
stack.push(arr);
depths.push(-1);
while (!stack.empty()) {
var el = stack.pop();
int d = depths.pop();
if (d <= depth && el instanceof ArrayValue) {
for (int i = ((ArrayValue)el).size() - 1; i >= 0; i--) {
stack.push(((ArrayValue)el).get(i));
depths.push(d + 1);
}
}
else res.set(ctx, depth, arr);
}
return res;
}
@Native(thisArg = true) public static ArrayValue flatMap(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException {
return flat(ctx, map(ctx, arr, cmp, thisArg), 1);
}
@Native(thisArg = true) public static Object find(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException {
for (int i = 0; i < arr.size(); i++) {
if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return arr.get(i);
}
return null;
}
@Native(thisArg = true) public static Object findLast(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException {
for (var i = arr.size() - 1; i >= 0; i--) {
if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return arr.get(i);
}
return null;
}
@Native(thisArg = true) public static int findIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException {
for (int i = 0; i < arr.size(); i++) {
if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return i;
}
return -1;
}
@Native(thisArg = true) public static int findLastIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException {
for (var i = arr.size() - 1; i >= 0; i--) {
if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return i;
}
return -1;
}
@Native(thisArg = true) public static int indexOf(Context ctx, ArrayValue arr, Object val, int start) throws InterruptedException {
start = normalizeI(arr.size(), start, true);
for (int i = 0; i < arr.size() && i < start; i++) {
if (Values.strictEquals(ctx, arr.get(i), val)) return i;
}
return -1;
}
@Native(thisArg = true) public static int lastIndexOf(Context ctx, ArrayValue arr, Object val, int start) throws InterruptedException {
start = normalizeI(arr.size(), start, true);
for (int i = arr.size(); i >= start; i--) {
if (Values.strictEquals(ctx, arr.get(i), val)) return i;
}
return -1;
}
@Native(thisArg = true) public static boolean includes(Context ctx, ArrayValue arr, Object el, int start) throws InterruptedException {
return indexOf(ctx, arr, el, start) >= 0;
}
@Native(thisArg = true) public static Object pop(Context ctx, ArrayValue arr) throws InterruptedException {
if (arr.size() == 0) return null;
var val = arr.get(arr.size() - 1);
arr.shrink(1);
return val;
}
@Native(thisArg = true) public static int push(Context ctx, ArrayValue arr, Object ...values) throws InterruptedException {
arr.copyFrom(ctx, values, 0, arr.size(), values.length);
return arr.size();
}
@Native(thisArg = true) public static Object shift(Context ctx, ArrayValue arr) throws InterruptedException {
if (arr.size() == 0) return null;
var val = arr.get(0);
arr.move(1, 0, arr.size());
arr.shrink(1);
return val;
}
@Native(thisArg = true) public static int unshift(Context ctx, ArrayValue arr, Object ...values) throws InterruptedException {
arr.move(0, values.length, arr.size());
arr.copyFrom(ctx, values, 0, 0, values.length);
return arr.size();
}
@Native(thisArg = true) public static ArrayValue slice(Context ctx, ArrayValue arr, int start, int end) {
start = normalizeI(arr.size(), start, true);
end = normalizeI(arr.size(), end, true);
var res = new ArrayValue(end - start);
arr.copyTo(ctx, res, start, 0, end - start);
return res;
}
@Native(thisArg = true) public static ArrayValue slice(Context ctx, ArrayValue arr, int start) {
return slice(ctx, arr, start, arr.size());
}
@Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start, int deleteCount, Object ...items) throws InterruptedException {
start = normalizeI(arr.size(), start, true);
deleteCount = normalizeI(arr.size(), deleteCount, true);
if (start + deleteCount >= arr.size()) deleteCount = arr.size() - start;
var size = arr.size() - deleteCount + items.length;
var res = new ArrayValue(deleteCount);
arr.copyTo(ctx, res, start, 0, deleteCount);
arr.move(start + deleteCount, start + items.length, arr.size() - start - deleteCount);
arr.copyFrom(ctx, items, 0, start, items.length);
arr.setSize(size);
return res;
}
@Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start) throws InterruptedException {
return splice(ctx, arr, start, arr.size() - start);
}
@Native(thisArg = true) public static String toString(Context ctx, ArrayValue arr) throws InterruptedException {
return join(ctx, arr, ",");
}
@Native(thisArg = true) public static String join(Context ctx, ArrayValue arr, String sep) throws InterruptedException {
var res = new StringBuilder();
var comma = true;
for (int i = 0; i < arr.size(); i++) {
if (!arr.has(i)) continue;
if (comma) res.append(sep);
comma = false;
var el = arr.get(i);
if (el == null || el == Values.NULL) continue;
res.append(Values.toString(ctx, el));
}
return res.toString();
}
@Native public static boolean isArray(Context ctx, Object val) { return val instanceof ArrayValue; }
@Native public static ArrayValue of(Context ctx, Object... args) {
var res = new ArrayValue(args.length);
res.copyFrom(ctx, args, 0, 0, args.length);
return res;
}
@NativeConstructor public static ArrayValue constructor(Context ctx, Object... args) {
ArrayValue res;
if (args.length == 1 && args[0] instanceof Number) {
int len = ((Number)args[0]).intValue();
res = new ArrayValue(len);
res.setSize(len);
}
else {
res = new ArrayValue(args.length);
res.copyFrom(ctx, args, 0, 0, args.length);
}
return res;
}
}

View File

@ -16,7 +16,7 @@ public class FunctionPolyfill {
return func.call(ctx, thisArg, args);
}
@Native(thisArg = true) public static Object bind(Context ctx, FunctionValue func, Object thisArg, Object... args) {
@Native(thisArg = true) public static FunctionValue bind(Context ctx, FunctionValue func, Object thisArg, Object... args) {
if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function.");
return new NativeFunction(func.name + " (bound)", (callCtx, _0, callArgs) -> {

View File

@ -12,9 +12,12 @@ import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.interop.Native;
public class Internals {
@Native public final Class<ObjectPolyfill> object = ObjectPolyfill.class;
@Native public final Class<FunctionPolyfill> function = FunctionPolyfill.class;
@Native public final Class<PromisePolyfill> promise = PromisePolyfill.class;
public final Environment targetEnv;
@Native public final FunctionValue object;
@Native public final FunctionValue function;
@Native public final FunctionValue promise;
@Native public final FunctionValue array;
@Native public void markSpecial(FunctionValue ...funcs) {
for (var func : funcs) {
@ -79,7 +82,7 @@ public class Internals {
return str.value;
}
@Native public static void log(Context ctx, Object ...args) throws InterruptedException {
@Native public void log(Context ctx, Object ...args) throws InterruptedException {
for (var arg : args) {
Values.printValue(ctx, arg);
}
@ -150,4 +153,12 @@ public class Internals {
}
});
}
public Internals(Environment targetEnv) {
this.targetEnv = targetEnv;
this.object = targetEnv.wrappersProvider.getConstr(ObjectPolyfill.class);
this.function = targetEnv.wrappersProvider.getConstr(FunctionPolyfill.class);
this.promise = targetEnv.wrappersProvider.getConstr(PromisePolyfill.class);
this.array = targetEnv.wrappersProvider.getConstr(ArrayPolyfill.class);
}
}

View File

@ -262,7 +262,7 @@ public class PromisePolyfill {
if (handles.size() == 0) {
ctx.message.engine.pushMsg(true, ctx.message, new NativeFunction((_ctx, _thisArg, _args) -> {
if (!handled) {
try { Values.printError(new EngineException(val), "(in promise)"); }
try { Values.printError(new EngineException(val).setContext(ctx), "(in promise)"); }
catch (InterruptedException ex) { }
}