feat: implement array polyfills in java

This commit is contained in:
TopchetoEU 2023-09-24 12:56:53 +03:00
parent d8071af480
commit f2cd50726d
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
20 changed files with 741 additions and 636 deletions

View File

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

View File

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

View File

@ -7,7 +7,7 @@ function setProps<
} }
>(target: TargetT, desc: DescT) { >(target: TargetT, desc: DescT) {
var props = internals.keys(desc, false); 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]; var key = props[i];
internals.defineField( internals.defineField(
target, key, (desc as any)[key], 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.events.Observer;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.interop.NativeTypeRegister;
import me.topchetoeu.jscript.polyfills.Internals; import me.topchetoeu.jscript.polyfills.Internals;
public class Main { public class Main {
@ -63,9 +62,8 @@ public class Main {
var in = new BufferedReader(new InputStreamReader(System.in)); var in = new BufferedReader(new InputStreamReader(System.in));
engine = new Engine(); engine = new Engine();
// TODO: Replace type register with safer accessor env = new Environment(null, null, null);
env = new Environment(null, new NativeTypeRegister(), null); var builderEnv = new Environment(null, null, null);
var builderEnv = new Environment(null, new NativeTypeRegister(), null);
var exited = new boolean[1]; var exited = new boolean[1];
env.global.define("exit", ctx -> { 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(); task = engine.start();
var reader = new Thread(() -> { 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.Native;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeSetter; import me.topchetoeu.jscript.interop.NativeSetter;
import me.topchetoeu.jscript.interop.NativeWrapperProvider;
public class Environment { public class Environment {
private HashMap<String, ObjectValue> prototypes = new HashMap<>(); private HashMap<String, ObjectValue> prototypes = new HashMap<>();
@ -33,7 +34,8 @@ public class Environment {
} }
@Native public Symbol symbol(String name) { @Native public Symbol symbol(String name) {
if (symbols.containsKey(name)) return symbols.get(name); if (symbols.containsKey(name))
return symbols.get(name);
else { else {
var res = new Symbol(name); var res = new Symbol(name);
symbols.put(name, res); symbols.put(name, res);
@ -79,14 +81,7 @@ public class Environment {
public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) { public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) {
if (compile == null) compile = new NativeFunction("compile", (ctx, thisArg, args) -> args.length == 0 ? "" : args[0]); if (compile == null) compile = new NativeFunction("compile", (ctx, thisArg, args) -> args.length == 0 ? "" : args[0]);
if (nativeConverter == null) nativeConverter = new WrappersProvider() { if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this);
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 (global == null) global = new GlobalScope(); if (global == null) global = new GlobalScope();
this.wrappersProvider = nativeConverter; this.wrappersProvider = nativeConverter;

View File

@ -1,8 +1,9 @@
package me.topchetoeu.jscript.engine; package me.topchetoeu.jscript.engine;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
public interface WrappersProvider { public interface WrappersProvider {
public ObjectValue getProto(Class<?> obj); 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; if (res != Runners.NO_RETURN) return res;
} }
} }
catch (Throwable e) {
// e.printStackTrace();
throw e;
}
finally { finally {
ctx.message.popFrame(this); ctx.message.popFrame(this);
} }

View File

@ -1,90 +1,128 @@
package me.topchetoeu.jscript.engine.values; package me.topchetoeu.jscript.engine.values;
import java.util.ArrayList; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.Iterator;
import java.util.List; import java.util.List;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
public class ArrayValue extends ObjectValue { // TODO: Make methods generic
private static final Object EMPTY = new Object(); public class ArrayValue extends ObjectValue implements Iterable<Object> {
private final ArrayList<Object> values = new ArrayList<>(); 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) { public boolean setSize(int val) {
if (val < 0) return false; if (val < 0) return false;
while (size() > val) { if (size > val) shrink(size - val);
values.remove(values.size() - 1); else {
} alloc(val);
while (size() < val) { size = val;
values.add(EMPTY);
} }
return true; return true;
} }
public Object get(int i) { public Object get(int i) {
if (i < 0 || i >= values.size()) return null; if (i < 0 || i >= size) return null;
var res = values.get(i); var res = values[i];
if (res == EMPTY) return null; if (res == UNDEFINED) return null;
else return res; else return res;
} }
public void set(Context ctx, int i, Object val) { public void set(Context ctx, int i, Object val) {
if (i < 0) return; if (i < 0) return;
while (values.size() <= i) { alloc(i);
values.add(EMPTY);
}
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) { 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) { public void remove(int i) {
if (i < 0 || i >= values.size()) return; if (i < 0 || i >= values.length) return;
values.set(i, EMPTY); values[i] = null;
} }
public void shrink(int n) { public void shrink(int n) {
if (n > values.size()) values.clear(); if (n >= values.length) {
values = new Object[16];
size = 0;
}
else { else {
for (int i = 0; i < n && values.size() > 0; i++) { for (int i = 0; i < n; i++) {
values.remove(values.size() - 1); 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) { public void sort(Comparator<Object> comparator) {
values.sort((a, b) -> { Arrays.sort(values, 0, size, (a, b) -> {
var _a = 0; var _a = 0;
var _b = 0; var _b = 0;
if (a == null) _a = 1; if (a == UNDEFINED) _a = 1;
if (a == EMPTY) _a = 2; if (a == null) _a = 2;
if (b == null) _b = 1; if (b == UNDEFINED) _b = 1;
if (b == EMPTY) _b = 2; 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); 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 @Override
protected Object getField(Context ctx, Object key) throws InterruptedException { protected Object getField(Context ctx, Object key) throws InterruptedException {
if (key.equals("length")) return values.size();
if (key instanceof Number) { if (key instanceof Number) {
var i = ((Number)key).doubleValue(); var i = ((Number)key).doubleValue();
if (i >= 0 && i - Math.floor(i) == 0) { if (i >= 0 && i - Math.floor(i) == 0) {
@ -96,9 +134,6 @@ public class ArrayValue extends ObjectValue {
} }
@Override @Override
protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException { 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) { if (key instanceof Number) {
var i = Values.number(key); var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) { if (i >= 0 && i - Math.floor(i) == 0) {
@ -111,7 +146,6 @@ public class ArrayValue extends ObjectValue {
} }
@Override @Override
protected boolean hasField(Context ctx, Object key) throws InterruptedException { protected boolean hasField(Context ctx, Object key) throws InterruptedException {
if (key.equals("length")) return true;
if (key instanceof Number) { if (key instanceof Number) {
var i = Values.number(key); var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) { if (i >= 0 && i - Math.floor(i) == 0) {
@ -140,18 +174,42 @@ public class ArrayValue extends ObjectValue {
for (var i = 0; i < size(); i++) { for (var i = 0; i < size(); i++) {
if (has(i)) res.add(i); if (has(i)) res.add(i);
} }
if (includeNonEnumerable) res.add("length");
return res; 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() { public ArrayValue() {
super(PlaceholderProto.ARRAY); super(PlaceholderProto.ARRAY);
nonEnumerableSet.add("length"); values = new Object[16];
nonConfigurableSet.add("length"); size = 0;
}
public ArrayValue(int cap) {
super(PlaceholderProto.ARRAY);
values = new Object[cap];
size = 0;
} }
public ArrayValue(Context ctx, Object ...values) { public ArrayValue(Context ctx, Object ...values) {
this(); 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) { 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 { public final Object getMember(Context ctx, Object key, Object thisArg) throws InterruptedException {
key = Values.normalize(ctx, key); key = Values.normalize(ctx, key);
if (key.equals("__proto__")) { if ("__proto__".equals(key)) {
var res = getPrototype(ctx); var res = getPrototype(ctx);
return res == null ? Values.NULL : res; return res == null ? Values.NULL : res;
} }

View File

@ -10,6 +10,6 @@ public final class Symbol {
@Override @Override
public String toString() { public String toString() {
if (value == null) return "Symbol"; 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 (obj == null) return null;
if (clazz.isInstance(obj)) return (T)obj; 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 { public static Iterable<Object> toJavaIterable(Context ctx, Object obj) throws InterruptedException {
@ -631,7 +631,7 @@ public class Values {
if (i != 0) System.out.print(", "); if (i != 0) System.out.print(", ");
else System.out.print(" "); else System.out.print(" ");
if (obj.has(i)) printValue(ctx, obj.get(i), passed, tab); if (obj.has(i)) printValue(ctx, obj.get(i), passed, tab);
else System.out.print(", "); else System.out.print("<empty>");
} }
System.out.print(" ] "); System.out.print(" ] ");
} }

View File

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

View File

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

View File

@ -1,210 +1,227 @@
package me.topchetoeu.jscript.interop; package me.topchetoeu.jscript.interop;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.HashMap; import java.util.HashMap;
import me.topchetoeu.jscript.engine.WrappersProvider; import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.WrappersProvider;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.exceptions.EngineException;
public class NativeTypeRegister implements WrappersProvider {
private final HashMap<Class<?>, FunctionValue> constructors = new HashMap<>(); public class NativeWrapperProvider implements WrappersProvider {
private final HashMap<Class<?>, ObjectValue> prototypes = new HashMap<>(); private final HashMap<Class<?>, FunctionValue> constructors = new HashMap<>();
private final HashMap<Class<?>, ObjectValue> prototypes = new HashMap<>();
private static void applyMethods(boolean member, ObjectValue target, Class<?> clazz) { private final Environment env;
for (var method : clazz.getDeclaredMethods()) {
var nat = method.getAnnotation(Native.class); private static void applyMethods(Environment env, boolean member, ObjectValue target, Class<?> clazz) {
var get = method.getAnnotation(NativeGetter.class); for (var method : clazz.getDeclaredMethods()) {
var set = method.getAnnotation(NativeSetter.class); var nat = method.getAnnotation(Native.class);
var memberMismatch = !Modifier.isStatic(method.getModifiers()) != member; var get = method.getAnnotation(NativeGetter.class);
var set = method.getAnnotation(NativeSetter.class);
if (nat != null) { var memberMismatch = !Modifier.isStatic(method.getModifiers()) != member;
if (nat.thisArg() != member && memberMismatch) continue;
if (nat != null) {
var name = nat.value(); if (nat.thisArg() != member && memberMismatch) continue;
var val = target.values.get(name);
Object name = nat.value();
if (name.equals("")) name = method.getName(); if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name)); else if (name.equals("")) name = method.getName();
((OverloadFunction)val).overloads.add(Overload.fromMethod(method, nat.thisArg())); var val = target.values.get(name);
}
else { if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name.toString()));
if (get != null) {
if (get.thisArg() != member && memberMismatch) continue; ((OverloadFunction)val).overloads.add(Overload.fromMethod(method, nat.thisArg()));
}
var name = get.value(); else {
var prop = target.properties.get(name); if (get != null) {
OverloadFunction getter = null; if (get.thisArg() != member && memberMismatch) continue;
var setter = prop == null ? null : prop.setter;
Object name = get.value();
if (prop != null && prop.getter instanceof OverloadFunction) getter = (OverloadFunction)prop.getter; if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
else getter = new OverloadFunction("get " + name); else if (name.equals("")) name = method.getName();
getter.overloads.add(Overload.fromMethod(method, get.thisArg())); var prop = target.properties.get(name);
target.defineProperty(null, name, getter, setter, true, true); OverloadFunction getter = null;
} var setter = prop == null ? null : prop.setter;
if (set != null) {
if (set.thisArg() != member && memberMismatch) continue; if (prop != null && prop.getter instanceof OverloadFunction) getter = (OverloadFunction)prop.getter;
else getter = new OverloadFunction("get " + name);
var name = set.value();
var prop = target.properties.get(name); getter.overloads.add(Overload.fromMethod(method, get.thisArg()));
var getter = prop == null ? null : prop.getter; target.defineProperty(null, name, getter, setter, true, true);
OverloadFunction setter = null; }
if (set != null) {
if (prop != null && prop.setter instanceof OverloadFunction) setter = (OverloadFunction)prop.setter; if (set.thisArg() != member && memberMismatch) continue;
else setter = new OverloadFunction("set " + name);
Object name = set.value();
setter.overloads.add(Overload.fromMethod(method, set.thisArg())); if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
target.defineProperty(null, name, getter, setter, true, true); else if (name.equals("")) name = method.getName();
}
} var prop = target.properties.get(name);
} var getter = prop == null ? null : prop.getter;
} OverloadFunction setter = null;
private static void applyFields(boolean member, ObjectValue target, Class<?> clazz) {
for (var field : clazz.getDeclaredFields()) { if (prop != null && prop.setter instanceof OverloadFunction) setter = (OverloadFunction)prop.setter;
if (!Modifier.isStatic(field.getModifiers()) != member) continue; else setter = new OverloadFunction("set " + name);
var nat = field.getAnnotation(Native.class);
setter.overloads.add(Overload.fromMethod(method, set.thisArg()));
if (nat != null) { target.defineProperty(null, name, getter, setter, true, true);
var name = nat.value(); }
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 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);
private static void applyClasses(boolean member, ObjectValue target, Class<?> clazz) {
for (var cl : clazz.getDeclaredClasses()) { if (nat != null) {
if (!Modifier.isStatic(cl.getModifiers()) != member) continue; Object name = nat.value();
var nat = cl.getAnnotation(Native.class); if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
else if (name.equals("")) name = field.getName();
if (nat != null) {
var name = nat.value(); var getter = new OverloadFunction("get " + name).add(Overload.getterFromField(field));
if (name.equals("")) name = cl.getSimpleName(); var setter = new OverloadFunction("set " + name).add(Overload.setterFromField(field));
target.defineProperty(null, name, getter, setter, true, false);
var getter = new NativeFunction("get " + name, (ctx, thisArg, args) -> cl); }
}
target.defineProperty(null, name, getter, null, true, false); }
} 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);
/**
* Generates a prototype for the given class. if (nat != null) {
* The returned object will have appropriate wrappers for all instance members. Object name = nat.value();
* All accessors and methods will expect the this argument to be a native wrapper of the given class type. if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
* @param clazz The class for which a prototype should be generated else if (name.equals("")) name = cl.getName();
*/
public static ObjectValue makeProto(Class<?> clazz) { var getter = new NativeFunction("get " + name, (ctx, thisArg, args) -> cl);
var res = new ObjectValue();
target.defineProperty(null, name, getter, null, true, false);
applyMethods(true, res, clazz); }
applyFields(true, res, clazz); }
applyClasses(true, res, clazz); }
return res; /**
} * Generates a prototype for the given class.
/** * The returned object will have appropriate wrappers for all instance members.
* Generates a constructor for the given class. * All accessors and methods will expect the this argument to be a native wrapper of the given class type.
* The returned function will have appropriate wrappers for all static members. * @param clazz The class for which a prototype should be generated
* 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 ObjectValue makeProto(Environment ctx, Class<?> clazz) {
*/ var res = new ObjectValue();
public static FunctionValue makeConstructor(Class<?> clazz) {
FunctionValue func = new OverloadFunction(clazz.getName()); applyMethods(ctx, true, res, clazz);
applyFields(ctx, true, res, clazz);
for (var overload : clazz.getConstructors()) { applyClasses(ctx, true, res, clazz);
var nat = overload.getAnnotation(Native.class);
if (nat == null) continue; return res;
((OverloadFunction)func).add(Overload.fromConstructor(overload, nat.thisArg())); }
} /**
for (var overload : clazz.getMethods()) { * Generates a constructor for the given class.
var constr = overload.getAnnotation(NativeConstructor.class); * The returned function will have appropriate wrappers for all static members.
if (constr == null) continue; * When the function gets called, the underlying constructor will get called, unless the constructor is inaccessible.
((OverloadFunction)func).add(Overload.fromMethod(overload, constr.thisArg())); * @param clazz The class for which a constructor should be generated
} */
public static FunctionValue makeConstructor(Environment ctx, Class<?> clazz) {
if (((OverloadFunction)func).overloads.size() == 0) { FunctionValue func = new OverloadFunction(clazz.getName());
func = new NativeFunction(clazz.getName(), (a, b, c) -> { throw EngineException.ofError("This constructor is not invokable."); });
} for (var overload : clazz.getConstructors()) {
var nat = overload.getAnnotation(Native.class);
applyMethods(false, func, clazz); if (nat == null) continue;
applyFields(false, func, clazz); ((OverloadFunction)func).add(Overload.fromConstructor(overload, nat.thisArg()));
applyClasses(false, func, clazz); }
for (var overload : clazz.getMethods()) {
func.special = true; var constr = overload.getAnnotation(NativeConstructor.class);
if (constr == null) continue;
return func; ((OverloadFunction)func).add(Overload.fromMethod(overload, constr.thisArg()));
} }
/**
* Generates a namespace for the given class. if (((OverloadFunction)func).overloads.size() == 0) {
* The returned function will have appropriate wrappers for all static members. func = new NativeFunction(clazz.getName(), (a, b, c) -> { throw EngineException.ofError("This constructor is not invokable."); });
* This method behaves almost like {@link NativeTypeRegister#makeConstructor}, but will return an object instead. }
* @param clazz The class for which a constructor should be generated
*/ applyMethods(ctx, false, func, clazz);
public static ObjectValue makeNamespace(Class<?> clazz) { applyFields(ctx, false, func, clazz);
ObjectValue res = new ObjectValue(); applyClasses(ctx, false, func, clazz);
applyMethods(false, res, clazz); func.special = true;
applyFields(false, res, clazz);
applyClasses(false, res, clazz); return func;
}
return res; /**
} * Generates a namespace for the given class.
* The returned function will have appropriate wrappers for all static members.
private void initType(Class<?> clazz, FunctionValue constr, ObjectValue proto) { * This method behaves almost like {@link NativeWrapperProvider#makeConstructor}, but will return an object instead.
if (constr != null && proto != null) return; * @param clazz The class for which a constructor should be generated
// i vomit */
if ( public static ObjectValue makeNamespace(Environment ctx, Class<?> clazz) {
clazz == Object.class || ObjectValue res = new ObjectValue();
clazz == Void.class ||
clazz == Number.class || clazz == Double.class || clazz == Float.class || applyMethods(ctx, false, res, clazz);
clazz == Long.class || clazz == Integer.class || clazz == Short.class || applyFields(ctx, false, res, clazz);
clazz == Character.class || clazz == Byte.class || clazz == Boolean.class || applyClasses(ctx, false, res, clazz);
clazz.isPrimitive() ||
clazz.isArray() || return res;
clazz.isAnonymousClass() || }
clazz.isEnum() ||
clazz.isInterface() || private void initType(Class<?> clazz, FunctionValue constr, ObjectValue proto) {
clazz.isSynthetic() if (constr != null && proto != null) return;
) return; // i vomit
if (
if (constr == null) constr = makeConstructor(clazz); clazz == Object.class ||
if (proto == null) proto = makeProto(clazz); clazz == Void.class ||
clazz == Number.class || clazz == Double.class || clazz == Float.class ||
proto.values.put("constructor", constr); clazz == Long.class || clazz == Integer.class || clazz == Short.class ||
constr.values.put("prototype", proto); clazz == Character.class || clazz == Byte.class || clazz == Boolean.class ||
clazz.isPrimitive() ||
prototypes.put(clazz, proto); clazz.isArray() ||
constructors.put(clazz, constr); clazz.isAnonymousClass() ||
clazz.isEnum() ||
var parent = clazz.getSuperclass(); clazz.isInterface() ||
if (parent == null) return; clazz.isSynthetic()
) return;
var parentProto = getProto(parent);
var parentConstr = getConstr(parent); if (constr == null) constr = makeConstructor(env, clazz);
if (proto == null) proto = makeProto(env, clazz);
if (parentProto != null) proto.setPrototype(null, parentProto);
if (parentConstr != null) constr.setPrototype(null, parentConstr); proto.values.put("constructor", constr);
} constr.values.put("prototype", proto);
public ObjectValue getProto(Class<?> clazz) { prototypes.put(clazz, proto);
initType(clazz, constructors.get(clazz), prototypes.get(clazz)); constructors.put(clazz, constr);
return prototypes.get(clazz);
} var parent = clazz.getSuperclass();
public FunctionValue getConstr(Class<?> clazz) { if (parent == null) return;
initType(clazz, constructors.get(clazz), prototypes.get(clazz));
return constructors.get(clazz); var parentProto = getProto(parent);
} var parentConstr = getConstr(parent);
public void setProto(Class<?> clazz, ObjectValue value) { if (parentProto != null) proto.setPrototype(null, parentProto);
prototypes.put(clazz, value); if (parentConstr != null) constr.setPrototype(null, parentConstr);
} }
public void setConstr(Class<?> clazz, FunctionValue value) {
constructors.put(clazz, value); public ObjectValue getProto(Class<?> clazz) {
} initType(clazz, constructors.get(clazz), prototypes.get(clazz));
} return prototypes.get(clazz);
}
public FunctionValue getConstr(Class<?> clazz) {
initType(clazz, constructors.get(clazz), prototypes.get(clazz));
return constructors.get(clazz);
}
public void setProto(Class<?> clazz, ObjectValue value) {
prototypes.put(clazz, value);
}
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); Object _this = thisArgType == null ? null : Values.convert(ctx, thisArg, thisArgType);
if (consumesEngine) newArgs[0] = ctx; if (consumesEngine) newArgs[0] = ctx;
if (overload.passThis) newArgs[consumesEngine ? 1 : 0] = _this; if (overload.passThis) {
newArgs[consumesEngine ? 1 : 0] = _this;
_this = null;
}
try { try {
return Values.normalize(ctx, overload.runner.run(ctx, _this, newArgs)); 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); 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."); if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function.");
return new NativeFunction(func.name + " (bound)", (callCtx, _0, callArgs) -> { 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; import me.topchetoeu.jscript.interop.Native;
public class Internals { public class Internals {
@Native public final Class<ObjectPolyfill> object = ObjectPolyfill.class; public final Environment targetEnv;
@Native public final Class<FunctionPolyfill> function = FunctionPolyfill.class;
@Native public final Class<PromisePolyfill> promise = PromisePolyfill.class; @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) { @Native public void markSpecial(FunctionValue ...funcs) {
for (var func : funcs) { for (var func : funcs) {
@ -79,7 +82,7 @@ public class Internals {
return str.value; 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) { for (var arg : args) {
Values.printValue(ctx, arg); 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) { if (handles.size() == 0) {
ctx.message.engine.pushMsg(true, ctx.message, new NativeFunction((_ctx, _thisArg, _args) -> { ctx.message.engine.pushMsg(true, ctx.message, new NativeFunction((_ctx, _thisArg, _args) -> {
if (!handled) { if (!handled) {
try { Values.printError(new EngineException(val), "(in promise)"); } try { Values.printError(new EngineException(val).setContext(ctx), "(in promise)"); }
catch (InterruptedException ex) { } catch (InterruptedException ex) { }
} }