Core library reprogramming #5
20
lib/core.ts
20
lib/core.ts
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
|
@ -8,7 +8,6 @@
|
||||
"values/string.ts",
|
||||
"values/number.ts",
|
||||
"values/boolean.ts",
|
||||
"values/array.ts",
|
||||
"map.ts",
|
||||
"set.ts",
|
||||
"regex.ts",
|
||||
|
@ -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],
|
||||
|
@ -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);
|
||||
});
|
@ -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(() -> {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -10,6 +10,6 @@ public final class Symbol {
|
||||
@Override
|
||||
public String toString() {
|
||||
if (value == null) return "Symbol";
|
||||
else return "Symbol(" + value + ")";
|
||||
else return "@@" + value;
|
||||
}
|
||||
}
|
||||
|
@ -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(" ] ");
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
|
350
src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java
Normal file
350
src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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) -> {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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) { }
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user