From f2cd50726d28c48cf17c6defa03600741aefe641 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sun, 24 Sep 2023 12:56:53 +0300 Subject: [PATCH] feat: implement array polyfills in java --- lib/core.ts | 22 +- lib/tsconfig.json | 1 - lib/utils.ts | 2 +- lib/values/array.ts | 336 -------------- src/me/topchetoeu/jscript/Main.java | 13 +- .../jscript/engine/Environment.java | 13 +- .../jscript/engine/WrappersProvider.java | 3 +- .../jscript/engine/frame/CodeFrame.java | 4 + .../jscript/engine/values/ArrayValue.java | 156 +++++-- .../jscript/engine/values/ObjectValue.java | 2 +- .../jscript/engine/values/Symbol.java | 2 +- .../jscript/engine/values/Values.java | 4 +- .../jscript/interop/NativeGetter.java | 2 +- .../jscript/interop/NativeSetter.java | 2 +- ...gister.java => NativeWrapperProvider.java} | 437 +++++++++--------- .../jscript/interop/OverloadFunction.java | 5 +- .../jscript/polyfills/ArrayPolyfill.java | 350 ++++++++++++++ .../jscript/polyfills/FunctionPolyfill.java | 2 +- .../jscript/polyfills/Internals.java | 19 +- .../jscript/polyfills/PromisePolyfill.java | 2 +- 20 files changed, 741 insertions(+), 636 deletions(-) delete mode 100644 lib/values/array.ts rename src/me/topchetoeu/jscript/interop/{NativeTypeRegister.java => NativeWrapperProvider.java} (73%) create mode 100644 src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java diff --git a/lib/core.ts b/lib/core.ts index addd0b7..c1485fe 100644 --- a/lib/core.ts +++ b/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; - } + 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'); diff --git a/lib/tsconfig.json b/lib/tsconfig.json index e802b34..01d7996 100644 --- a/lib/tsconfig.json +++ b/lib/tsconfig.json @@ -8,7 +8,6 @@ "values/string.ts", "values/number.ts", "values/boolean.ts", - "values/array.ts", "map.ts", "set.ts", "regex.ts", diff --git a/lib/utils.ts b/lib/utils.ts index 6fdf694..fa3ca0a 100644 --- a/lib/utils.ts +++ b/lib/utils.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], diff --git a/lib/values/array.ts b/lib/values/array.ts deleted file mode 100644 index 12928ba..0000000 --- a/lib/values/array.ts +++ /dev/null @@ -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); -}); \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 6f580d1..309aeed 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -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(() -> { diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index 164112d..c9a4cbe 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -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 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; diff --git a/src/me/topchetoeu/jscript/engine/WrappersProvider.java b/src/me/topchetoeu/jscript/engine/WrappersProvider.java index 6205da8..dd27353 100644 --- a/src/me/topchetoeu/jscript/engine/WrappersProvider.java +++ b/src/me/topchetoeu/jscript/engine/WrappersProvider.java @@ -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); } diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 96b8350..818a4ae 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -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); } diff --git a/src/me/topchetoeu/jscript/engine/values/ArrayValue.java b/src/me/topchetoeu/jscript/engine/values/ArrayValue.java index 0bd59ea..8841810 100644 --- a/src/me/topchetoeu/jscript/engine/values/ArrayValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ArrayValue.java @@ -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 values = new ArrayList<>(); +// TODO: Make methods generic +public class ArrayValue extends ObjectValue implements Iterable { + 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 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 iterator() { + return new Iterator() { + 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 values) { diff --git a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java index 054ab3e..eae0dce 100644 --- a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java @@ -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; } diff --git a/src/me/topchetoeu/jscript/engine/values/Symbol.java b/src/me/topchetoeu/jscript/engine/values/Symbol.java index 5715430..116ae0b 100644 --- a/src/me/topchetoeu/jscript/engine/values/Symbol.java +++ b/src/me/topchetoeu/jscript/engine/values/Symbol.java @@ -10,6 +10,6 @@ public final class Symbol { @Override public String toString() { if (value == null) return "Symbol"; - else return "Symbol(" + value + ")"; + else return "@@" + value; } } diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index 839c6e3..03dffd9 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -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 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(""); } System.out.print(" ] "); } diff --git a/src/me/topchetoeu/jscript/interop/NativeGetter.java b/src/me/topchetoeu/jscript/interop/NativeGetter.java index 62c9061..7a625ad 100644 --- a/src/me/topchetoeu/jscript/interop/NativeGetter.java +++ b/src/me/topchetoeu/jscript/interop/NativeGetter.java @@ -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; } diff --git a/src/me/topchetoeu/jscript/interop/NativeSetter.java b/src/me/topchetoeu/jscript/interop/NativeSetter.java index 33b7d73..225eccb 100644 --- a/src/me/topchetoeu/jscript/interop/NativeSetter.java +++ b/src/me/topchetoeu/jscript/interop/NativeSetter.java @@ -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; } diff --git a/src/me/topchetoeu/jscript/interop/NativeTypeRegister.java b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java similarity index 73% rename from src/me/topchetoeu/jscript/interop/NativeTypeRegister.java rename to src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java index 3e90161..018a256 100644 --- a/src/me/topchetoeu/jscript/interop/NativeTypeRegister.java +++ b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java @@ -1,210 +1,227 @@ -package me.topchetoeu.jscript.interop; - -import java.lang.reflect.Modifier; -import java.util.HashMap; - -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 { - private final HashMap, FunctionValue> constructors = new HashMap<>(); - private final HashMap, ObjectValue> prototypes = new HashMap<>(); - - private static void applyMethods(boolean member, ObjectValue target, Class clazz) { - for (var method : clazz.getDeclaredMethods()) { - var nat = method.getAnnotation(Native.class); - var get = method.getAnnotation(NativeGetter.class); - var set = method.getAnnotation(NativeSetter.class); - var memberMismatch = !Modifier.isStatic(method.getModifiers()) != member; - - if (nat != null) { - if (nat.thisArg() != member && memberMismatch) continue; - - var name = nat.value(); - var val = target.values.get(name); - - if (name.equals("")) name = method.getName(); - if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name)); - - ((OverloadFunction)val).overloads.add(Overload.fromMethod(method, nat.thisArg())); - } - else { - if (get != null) { - if (get.thisArg() != member && memberMismatch) continue; - - var name = get.value(); - var prop = target.properties.get(name); - OverloadFunction getter = null; - var setter = prop == null ? null : prop.setter; - - if (prop != null && prop.getter instanceof OverloadFunction) getter = (OverloadFunction)prop.getter; - else getter = new OverloadFunction("get " + name); - - getter.overloads.add(Overload.fromMethod(method, get.thisArg())); - target.defineProperty(null, name, getter, setter, true, true); - } - if (set != null) { - if (set.thisArg() != member && memberMismatch) continue; - - var name = set.value(); - var prop = target.properties.get(name); - var getter = prop == null ? null : prop.getter; - OverloadFunction setter = null; - - if (prop != null && prop.setter instanceof OverloadFunction) setter = (OverloadFunction)prop.setter; - else setter = new OverloadFunction("set " + name); - - setter.overloads.add(Overload.fromMethod(method, set.thisArg())); - target.defineProperty(null, name, getter, setter, true, true); - } - } - } - } - private static void applyFields(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(); - 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) { - 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(); - - var getter = new NativeFunction("get " + name, (ctx, thisArg, args) -> cl); - - target.defineProperty(null, name, getter, null, true, false); - } - } - } - - /** - * Generates a prototype for the given class. - * The returned object will have appropriate wrappers for all instance members. - * 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) { - var res = new ObjectValue(); - - applyMethods(true, res, clazz); - applyFields(true, res, clazz); - applyClasses(true, res, clazz); - - return res; - } - /** - * Generates a constructor for the given class. - * The returned function will have appropriate wrappers for all static members. - * 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) { - FunctionValue func = new OverloadFunction(clazz.getName()); - - for (var overload : clazz.getConstructors()) { - var nat = overload.getAnnotation(Native.class); - if (nat == null) continue; - ((OverloadFunction)func).add(Overload.fromConstructor(overload, nat.thisArg())); - } - for (var overload : clazz.getMethods()) { - var constr = overload.getAnnotation(NativeConstructor.class); - if (constr == null) continue; - ((OverloadFunction)func).add(Overload.fromMethod(overload, constr.thisArg())); - } - - if (((OverloadFunction)func).overloads.size() == 0) { - 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); - - func.special = true; - - return func; - } - /** - * 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. - * @param clazz The class for which a constructor should be generated - */ - public static ObjectValue makeNamespace(Class clazz) { - ObjectValue res = new ObjectValue(); - - applyMethods(false, res, clazz); - applyFields(false, res, clazz); - applyClasses(false, res, clazz); - - return res; - } - - private void initType(Class clazz, FunctionValue constr, ObjectValue proto) { - if (constr != null && proto != null) return; - // i vomit - if ( - clazz == Object.class || - clazz == Void.class || - clazz == Number.class || clazz == Double.class || clazz == Float.class || - clazz == Long.class || clazz == Integer.class || clazz == Short.class || - clazz == Character.class || clazz == Byte.class || clazz == Boolean.class || - clazz.isPrimitive() || - clazz.isArray() || - clazz.isAnonymousClass() || - clazz.isEnum() || - clazz.isInterface() || - clazz.isSynthetic() - ) return; - - if (constr == null) constr = makeConstructor(clazz); - if (proto == null) proto = makeProto(clazz); - - proto.values.put("constructor", constr); - constr.values.put("prototype", proto); - - prototypes.put(clazz, proto); - constructors.put(clazz, constr); - - var parent = clazz.getSuperclass(); - if (parent == null) return; - - var parentProto = getProto(parent); - var parentConstr = getConstr(parent); - - if (parentProto != null) proto.setPrototype(null, parentProto); - if (parentConstr != null) constr.setPrototype(null, parentConstr); - } - - 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); - } -} +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 NativeWrapperProvider implements WrappersProvider { + private final HashMap, FunctionValue> constructors = new HashMap<>(); + private final HashMap, ObjectValue> prototypes = new HashMap<>(); + private final Environment env; + + 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); + var set = method.getAnnotation(NativeSetter.class); + var memberMismatch = !Modifier.isStatic(method.getModifiers()) != member; + + if (nat != null) { + if (nat.thisArg() != member && memberMismatch) continue; + + 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 (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name.toString())); + + ((OverloadFunction)val).overloads.add(Overload.fromMethod(method, nat.thisArg())); + } + else { + if (get != null) { + if (get.thisArg() != member && memberMismatch) continue; + + 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; + + if (prop != null && prop.getter instanceof OverloadFunction) getter = (OverloadFunction)prop.getter; + else getter = new OverloadFunction("get " + name); + + getter.overloads.add(Overload.fromMethod(method, get.thisArg())); + target.defineProperty(null, name, getter, setter, true, true); + } + if (set != null) { + if (set.thisArg() != member && memberMismatch) continue; + + 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; + + if (prop != null && prop.setter instanceof OverloadFunction) setter = (OverloadFunction)prop.setter; + else setter = new OverloadFunction("set " + name); + + setter.overloads.add(Overload.fromMethod(method, set.thisArg())); + target.defineProperty(null, name, getter, setter, true, true); + } + } + } + } + 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) { + 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(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) { + 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); + + target.defineProperty(null, name, getter, null, true, false); + } + } + } + + /** + * Generates a prototype for the given class. + * The returned object will have appropriate wrappers for all instance members. + * 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(Environment ctx, Class clazz) { + var res = new ObjectValue(); + + applyMethods(ctx, true, res, clazz); + applyFields(ctx, true, res, clazz); + applyClasses(ctx, true, res, clazz); + + return res; + } + /** + * Generates a constructor for the given class. + * The returned function will have appropriate wrappers for all static members. + * 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(Environment ctx, Class clazz) { + FunctionValue func = new OverloadFunction(clazz.getName()); + + for (var overload : clazz.getConstructors()) { + var nat = overload.getAnnotation(Native.class); + if (nat == null) continue; + ((OverloadFunction)func).add(Overload.fromConstructor(overload, nat.thisArg())); + } + for (var overload : clazz.getMethods()) { + var constr = overload.getAnnotation(NativeConstructor.class); + if (constr == null) continue; + ((OverloadFunction)func).add(Overload.fromMethod(overload, constr.thisArg())); + } + + if (((OverloadFunction)func).overloads.size() == 0) { + func = new NativeFunction(clazz.getName(), (a, b, c) -> { throw EngineException.ofError("This constructor is not invokable."); }); + } + + applyMethods(ctx, false, func, clazz); + applyFields(ctx, false, func, clazz); + applyClasses(ctx, false, func, clazz); + + func.special = true; + + return func; + } + /** + * Generates a namespace for the given class. + * The returned function will have appropriate wrappers for all static members. + * 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(Environment ctx, Class clazz) { + ObjectValue res = new ObjectValue(); + + applyMethods(ctx, false, res, clazz); + applyFields(ctx, false, res, clazz); + applyClasses(ctx, false, res, clazz); + + return res; + } + + private void initType(Class clazz, FunctionValue constr, ObjectValue proto) { + if (constr != null && proto != null) return; + // i vomit + if ( + clazz == Object.class || + clazz == Void.class || + clazz == Number.class || clazz == Double.class || clazz == Float.class || + clazz == Long.class || clazz == Integer.class || clazz == Short.class || + clazz == Character.class || clazz == Byte.class || clazz == Boolean.class || + clazz.isPrimitive() || + clazz.isArray() || + clazz.isAnonymousClass() || + clazz.isEnum() || + clazz.isInterface() || + clazz.isSynthetic() + ) return; + + if (constr == null) constr = makeConstructor(env, clazz); + if (proto == null) proto = makeProto(env, clazz); + + proto.values.put("constructor", constr); + constr.values.put("prototype", proto); + + prototypes.put(clazz, proto); + constructors.put(clazz, constr); + + var parent = clazz.getSuperclass(); + if (parent == null) return; + + var parentProto = getProto(parent); + var parentConstr = getConstr(parent); + + if (parentProto != null) proto.setPrototype(null, parentProto); + if (parentConstr != null) constr.setPrototype(null, parentConstr); + } + + 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; + } +} diff --git a/src/me/topchetoeu/jscript/interop/OverloadFunction.java b/src/me/topchetoeu/jscript/interop/OverloadFunction.java index 0954302..2ad62d2 100644 --- a/src/me/topchetoeu/jscript/interop/OverloadFunction.java +++ b/src/me/topchetoeu/jscript/interop/OverloadFunction.java @@ -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)); diff --git a/src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java b/src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java new file mode 100644 index 0000000..a2901a7 --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java @@ -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() { + 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() { + 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(); + var depths = new Stack(); + + 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; + } +} diff --git a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java index cec1917..439642e 100644 --- a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java @@ -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) -> { diff --git a/src/me/topchetoeu/jscript/polyfills/Internals.java b/src/me/topchetoeu/jscript/polyfills/Internals.java index e584be7..d09bb4b 100644 --- a/src/me/topchetoeu/jscript/polyfills/Internals.java +++ b/src/me/topchetoeu/jscript/polyfills/Internals.java @@ -12,9 +12,12 @@ import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.interop.Native; public class Internals { - @Native public final Class object = ObjectPolyfill.class; - @Native public final Class function = FunctionPolyfill.class; - @Native public final Class 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); + } } diff --git a/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java index dfffc22..741f052 100644 --- a/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java @@ -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) { } }