From 63b04019cf50a936b79c4f095be6b9ca492a71dc Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Thu, 28 Sep 2023 10:25:32 +0300 Subject: [PATCH] feat: implement timers in java --- lib/core.ts | 56 +++---- lib/modules.ts | 13 -- lib/regex.ts | 143 ------------------ lib/timeout.ts | 38 ----- lib/tsconfig.json | 3 - .../jscript/polyfills/FunctionPolyfill.java | 11 +- .../jscript/polyfills/GeneratorPolyfill.java | 1 - .../jscript/polyfills/Internals.java | 5 + .../jscript/polyfills/TimerPolyfills.java | 60 ++++++++ 9 files changed, 102 insertions(+), 228 deletions(-) delete mode 100644 lib/modules.ts delete mode 100644 lib/regex.ts delete mode 100644 lib/timeout.ts create mode 100644 src/me/topchetoeu/jscript/polyfills/TimerPolyfills.java diff --git a/lib/core.ts b/lib/core.ts index d1795dd..da94bb6 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -17,15 +17,23 @@ interface Internals { syntax: SyntaxErrorConstructor; type: TypeErrorConstructor; range: RangeErrorConstructor; - + regexp: typeof RegExp; map: typeof Map; set: typeof Set; + timers: { + setTimeout: (handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number, + setInterval: (handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number, + clearTimeout: (id: number) => void, + clearInterval: (id: number) => void, + } + markSpecial(...funcs: Function[]): void; getEnv(func: Function): Environment | undefined; setEnv(func: T, env: Environment): T; apply(func: Function, thisArg: any, args: any[], env?: Environment): any; + bind(func: Function, thisArg: any): any; delay(timeout: number, callback: Function): () => void; pushMessage(micro: boolean, func: Function, thisArg: any, args: any[]): void; @@ -54,24 +62,26 @@ interface Internals { var env: Environment = arguments[0], internals: Internals = arguments[1]; try { - const values = { - Object: env.global.Object = internals.object, - Function: env.global.Function = internals.function, - Array: env.global.Array = internals.array, - Promise: env.global.Promise = internals.promise, - Boolean: env.global.Boolean = internals.bool, - Number: env.global.Number = internals.number, - String: env.global.String = internals.string, - Symbol: env.global.Symbol = internals.symbol, - Error: env.global.Error = internals.error, - SyntaxError: env.global.SyntaxError = internals.syntax, - TypeError: env.global.TypeError = internals.type, - RangeError: env.global.RangeError = internals.range, - RegExp: env.global.RegExp = internals.regexp, - Map: env.global.Map = internals.map, - Set: env.global.Set = internals.set, - } - const Array = values.Array; + const Array = env.global.Array = internals.array; + env.global.Object = internals.object; + env.global.Function = internals.function; + env.global.Promise = internals.promise; + env.global.Boolean = internals.bool; + env.global.Number = internals.number; + env.global.String = internals.string; + env.global.Symbol = internals.symbol; + env.global.Error = internals.error; + env.global.SyntaxError = internals.syntax; + env.global.TypeError = internals.type; + env.global.RangeError = internals.range; + env.global.RegExp = internals.regexp; + env.global.Map = internals.map; + env.global.Set = internals.set; + env.global.setInterval = internals.bind(internals.timers.setInterval, internals.timers); + env.global.setTimeout = internals.bind(internals.timers.setTimeout, internals.timers); + env.global.clearInterval = internals.bind(internals.timers.clearInterval, internals.timers); + env.global.clearTimeout = internals.bind(internals.timers.clearTimeout, internals.timers); + const log = env.global.log = internals.bind(internals.log, internals); env.setProto('object', env.global.Object.prototype); env.setProto('function', env.global.Function.prototype); @@ -88,14 +98,6 @@ try { (env.global.Object.prototype as any).__proto__ = null; - internals.getEnv(run)?.setProto('array', Array.prototype); - globalThis.log = (...args) => internals.apply(internals.log, internals, args); - - run('timeout'); - - env.global.log = log; - env.global.NewObject = internals.object; - log('Loaded polyfills!'); } catch (e: any) { diff --git a/lib/modules.ts b/lib/modules.ts deleted file mode 100644 index c15e496..0000000 --- a/lib/modules.ts +++ /dev/null @@ -1,13 +0,0 @@ -var { define, run } = (() => { - const modules: Record = {}; - - function define(name: string, func: Function) { - modules[name] = func; - } - function run(name: string) { - if (typeof modules[name] === 'function') return modules[name](); - else throw "The module '" + name + "' doesn't exist."; - } - - return { define, run }; -})(); diff --git a/lib/regex.ts b/lib/regex.ts deleted file mode 100644 index ed5ce84..0000000 --- a/lib/regex.ts +++ /dev/null @@ -1,143 +0,0 @@ -define("regex", () => { - // var RegExp = env.global.RegExp = env.internals.RegExp; - - // setProps(RegExp.prototype as RegExp, env, { - // [Symbol.typeName]: 'RegExp', - - // test(val) { - // return !!this.exec(val); - // }, - // toString() { - // return '/' + this.source + '/' + this.flags; - // }, - - // [Symbol.match](target) { - // if (this.global) { - // const res: string[] = []; - // let val; - // while (val = this.exec(target)) { - // res.push(val[0]); - // } - // this.lastIndex = 0; - // return res; - // } - // else { - // const res = this.exec(target); - // if (!this.sticky) this.lastIndex = 0; - // return res; - // } - // }, - // [Symbol.matchAll](target) { - // let pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp; - - // return { - // next: (): IteratorResult => { - // const val = pattern?.exec(target); - - // if (val === null || val === undefined) { - // pattern = undefined; - // return { done: true }; - // } - // else return { value: val }; - // }, - // [Symbol.iterator]() { return this; } - // } - // }, - // [Symbol.split](target, limit, sensible) { - // const pattern = new this.constructor(this, this.flags + "g") as RegExp; - // let match: RegExpResult | null; - // let lastEnd = 0; - // const res: string[] = []; - - // while ((match = pattern.exec(target)) !== null) { - // let added: string[] = []; - - // if (match.index >= target.length) break; - - // if (match[0].length === 0) { - // added = [ target.substring(lastEnd, pattern.lastIndex), ]; - // if (pattern.lastIndex < target.length) added.push(...match.slice(1)); - // } - // else if (match.index - lastEnd > 0) { - // added = [ target.substring(lastEnd, match.index), ...match.slice(1) ]; - // } - // else { - // for (let i = 1; i < match.length; i++) { - // res[res.length - match.length + i] = match[i]; - // } - // } - - // if (sensible) { - // if (limit !== undefined && res.length + added.length >= limit) break; - // else res.push(...added); - // } - // else { - // for (let i = 0; i < added.length; i++) { - // if (limit !== undefined && res.length >= limit) return res; - // else res.push(added[i]); - // } - // } - - // lastEnd = pattern.lastIndex; - // } - - // if (lastEnd < target.length) { - // res.push(target.substring(lastEnd)); - // } - - // return res; - // }, - // [Symbol.replace](target, replacement) { - // const pattern = new this.constructor(this, this.flags + "d") as RegExp; - // let match: RegExpResult | null; - // let lastEnd = 0; - // const res: string[] = []; - - // // log(pattern.toString()); - - // while ((match = pattern.exec(target)) !== null) { - // const indices = match.indices![0]; - // res.push(target.substring(lastEnd, indices[0])); - // if (replacement instanceof Function) { - // res.push(replacement(target.substring(indices[0], indices[1]), ...match.slice(1), indices[0], target)); - // } - // else { - // res.push(replacement); - // } - // lastEnd = indices[1]; - // if (!pattern.global) break; - // } - - // if (lastEnd < target.length) { - // res.push(target.substring(lastEnd)); - // } - - // return res.join(''); - // }, - // [Symbol.search](target, reverse, start) { - // const pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp; - - - // if (!reverse) { - // pattern.lastIndex = (start as any) | 0; - // const res = pattern.exec(target); - // if (res) return res.index; - // else return -1; - // } - // else { - // start ??= target.length; - // start |= 0; - // let res: RegExpResult | null = null; - - // while (true) { - // const tmp = pattern.exec(target); - // if (tmp === null || tmp.index > start) break; - // res = tmp; - // } - - // if (res && res.index <= start) return res.index; - // else return -1; - // } - // }, - // }); -}); \ No newline at end of file diff --git a/lib/timeout.ts b/lib/timeout.ts deleted file mode 100644 index e1f167a..0000000 --- a/lib/timeout.ts +++ /dev/null @@ -1,38 +0,0 @@ -define("timeout", () => { - const timeouts: Record void> = { }; - const intervals: Record void> = { }; - let timeoutI = 0, intervalI = 0; - - env.global.setTimeout = (func, delay, ...args) => { - if (typeof func !== 'function') throw new env.global.TypeError("func must be a function."); - delay = (delay ?? 0) - 0; - const cancelFunc = internals.delay(delay, () => internals.apply(func, undefined, args)); - timeouts[++timeoutI] = cancelFunc; - return timeoutI; - }; - env.global.setInterval = (func, delay, ...args) => { - if (typeof func !== 'function') throw new env.global.TypeError("func must be a function."); - delay = (delay ?? 0) - 0; - - const i = ++intervalI; - intervals[i] = internals.delay(delay, callback); - - return i; - - function callback() { - internals.apply(func, undefined, args); - intervals[i] = internals.delay(delay!, callback); - } - }; - - env.global.clearTimeout = (id) => { - const func = timeouts[id]; - if (func) func(); - timeouts[id] = undefined!; - }; - env.global.clearInterval = (id) => { - const func = intervals[id]; - if (func) func(); - intervals[id] = undefined!; - }; -}); \ No newline at end of file diff --git a/lib/tsconfig.json b/lib/tsconfig.json index 326c2bd..64939d9 100644 --- a/lib/tsconfig.json +++ b/lib/tsconfig.json @@ -1,9 +1,6 @@ { "files": [ "lib.d.ts", - "modules.ts", - "regex.ts", - "timeout.ts", "core.ts" ], "compilerOptions": { diff --git a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java index 9d5a625..2294773 100644 --- a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java @@ -24,9 +24,14 @@ public class FunctionPolyfill { if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function."); return new NativeFunction(func.name + " (bound)", (callCtx, _0, callArgs) -> { - var resArgs = new Object[args.length + callArgs.length]; - System.arraycopy(args, 0, resArgs, 0, args.length); - System.arraycopy(callArgs, 0, resArgs, args.length, callArgs.length); + Object[] resArgs; + + if (args.length == 0) resArgs = callArgs; + else { + resArgs = new Object[args.length + callArgs.length]; + System.arraycopy(args, 0, resArgs, 0, args.length); + System.arraycopy(callArgs, 0, resArgs, args.length, callArgs.length); + } return func.call(ctx, thisArg, resArgs); }); diff --git a/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java b/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java index cf4b817..db176ed 100644 --- a/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java @@ -1,7 +1,6 @@ package me.topchetoeu.jscript.polyfills; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.values.CodeFunction; diff --git a/src/me/topchetoeu/jscript/polyfills/Internals.java b/src/me/topchetoeu/jscript/polyfills/Internals.java index dded47a..3e1827f 100644 --- a/src/me/topchetoeu/jscript/polyfills/Internals.java +++ b/src/me/topchetoeu/jscript/polyfills/Internals.java @@ -20,6 +20,8 @@ public class Internals { promise, map, set, regexp, error, syntax, type, range; + @Native public final TimerPolyfills timers = new TimerPolyfills(); + @Native public void markSpecial(FunctionValue ...funcs) { for (var func : funcs) { func.special = true; @@ -37,6 +39,9 @@ public class Internals { if (env != null) ctx = new Context(env, ctx.message); return func.call(ctx, thisArg, args.toArray()); } + @Native public FunctionValue bind(Context ctx, FunctionValue func, Object thisArg) throws InterruptedException { + return FunctionPolyfill.bind(ctx, func, thisArg); + } @Native public FunctionValue delay(Context ctx, double delay, FunctionValue callback) throws InterruptedException { var thread = new Thread((Runnable)() -> { var ms = (long)delay; diff --git a/src/me/topchetoeu/jscript/polyfills/TimerPolyfills.java b/src/me/topchetoeu/jscript/polyfills/TimerPolyfills.java new file mode 100644 index 0000000..a0dd02e --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/TimerPolyfills.java @@ -0,0 +1,60 @@ +package me.topchetoeu.jscript.polyfills; + +import java.util.HashMap; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.values.FunctionValue; +import me.topchetoeu.jscript.interop.Native; + +public class TimerPolyfills { + private HashMap threads = new HashMap<>(); + + private int i = 0; + + @Native public int setTimeout(Context ctx, FunctionValue func, int delay, Object ...args) { + var thread = new Thread(() -> { + var ms = (long)delay; + var ns = (int)((delay - ms) * 10000000); + + try { + Thread.sleep(ms, ns); + } + catch (InterruptedException e) { return; } + + ctx.message.engine.pushMsg(false, ctx.message, func, null, args); + }); + thread.start(); + + threads.put(++i, thread); + + return i; + } + @Native public int setInterval(Context ctx, FunctionValue func, int delay, Object ...args) { + var thread = new Thread(() -> { + var ms = (long)delay; + var ns = (int)((delay - ms) * 10000000); + + while (true) { + try { + Thread.sleep(ms, ns); + } + catch (InterruptedException e) { return; } + + ctx.message.engine.pushMsg(false, ctx.message, func, null, args); + } + }); + thread.start(); + + threads.put(++i, thread); + + return i; + } + + @Native public void clearTimeout(Context ctx, int i) { + var thread = threads.remove(i); + if (thread != null) thread.interrupt(); + } + @Native public void clearInterval(Context ctx, int i) { + clearTimeout(ctx, i); + } +}