From c5a4167c942f9d3093101ecd34647bebe5b6c227 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Fri, 25 Aug 2023 23:48:40 +0300 Subject: [PATCH] feat: make generator function --- lib/core.ts | 4 +- lib/values/array.ts | 2 +- lib/values/function.ts | 26 ++++- src/me/topchetoeu/jscript/Main.java | 2 +- .../jscript/engine/frame/CodeFrame.java | 41 +++++--- .../topchetoeu/jscript/parsing/Parsing.java | 16 +-- .../jscript/polyfills/AsyncFunction.java | 3 +- .../jscript/polyfills/GeneratorFunction.java | 99 +++++++++++++++++++ .../jscript/polyfills/Internals.java | 10 ++ 9 files changed, 166 insertions(+), 37 deletions(-) create mode 100644 src/me/topchetoeu/jscript/polyfills/GeneratorFunction.java diff --git a/lib/core.ts b/lib/core.ts index e3e39d1..c1d51a2 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -74,8 +74,8 @@ interface MathObject { //@ts-ignore declare const arguments: IArguments; declare const Math: MathObject; -declare const NaN: number; -declare const Infinity: number; +const NaN = 0 / 0; +const Infinity = 1 / 0; declare var setTimeout: (handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number; declare var setInterval: (handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number; diff --git a/lib/values/array.ts b/lib/values/array.ts index 5144630..dc3eceb 100644 --- a/lib/values/array.ts +++ b/lib/values/array.ts @@ -365,5 +365,5 @@ setProps(Array.prototype, { }); setProps(Array, { - isArray(val: any) { return (val instanceof Array); } + isArray(val: any) { return internals.isArr(val); } }); diff --git a/lib/values/function.ts b/lib/values/function.ts index 81e4be0..b77b475 100644 --- a/lib/values/function.ts +++ b/lib/values/function.ts @@ -14,7 +14,15 @@ interface FunctionConstructor extends Function { (...args: string[]): (...args: any[]) => any; new (...args: string[]): (...args: any[]) => any; prototype: Function; - async(func: (await: (val: T) => Awaited) => (...args: ArgsT) => RetT): (...args: ArgsT) => Promise; + async( + func: (await: (val: T) => Awaited) => (...args: ArgsT) => RetT + ): (...args: ArgsT) => Promise; + asyncGenerator( + func: (await: (val: T) => Awaited, _yield: (val: T) => void) => (...args: ArgsT) => RetT + ): (...args: ArgsT) => AsyncGenerator; + generator( + func: (_yield: (val: T) => TNext) => (...args: ArgsT) => RetT + ): (...args: ArgsT) => Generator; } interface CallableFunction extends Function { @@ -43,11 +51,15 @@ setProps(Function.prototype, { apply(thisArg, args) { if (typeof args !== 'object') throw 'Expected arguments to be an array-like object.'; var len = args.length - 0; - var newArgs: any[] = []; + let newArgs: any[]; + if (Array.isArray(args)) newArgs = args; + else { + newArgs = []; - while (len >= 0) { - len--; - newArgs[len] = args[len]; + while (len >= 0) { + len--; + newArgs[len] = args[len]; + } } return internals.apply(this, thisArg, newArgs); @@ -81,5 +93,9 @@ setProps(Function, { async(func) { if (typeof func !== 'function') throw new TypeError('Expected func to be function.'); return internals.makeAsync(func); + }, + generator(func) { + if (typeof func !== 'function') throw new TypeError('Expected func to be function.'); + return internals.makeGenerator(func); } }) \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 34a2426..aa92604 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -54,7 +54,7 @@ public class Main { public static void main(String args[]) { var in = new BufferedReader(new InputStreamReader(System.in)); - engine = new TypescriptEngine(new File(".")); + engine = new PolyfillEngine(new File(".")); var scope = engine.global().globalChild(); var exited = new boolean[1]; diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 92a8a4d..1101c25 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -157,32 +157,47 @@ public class CodeFrame { } } - public Object next(CallContext ctx, EngineException prevError) throws InterruptedException { + public Object next(CallContext ctx, Object prevReturn, Object prevError) throws InterruptedException { TryCtx tryCtx = null; - var handled = prevError == null; + if (prevError != Runners.NO_RETURN) prevReturn = Runners.NO_RETURN; while (!tryStack.isEmpty()) { var tmp = tryStack.get(tryStack.size() - 1); var remove = false; - if (tmp.state == TryCtx.STATE_TRY) { - if (prevError != null) { + if (prevError != Runners.NO_RETURN) { + remove = true; + if (tmp.state == TryCtx.STATE_TRY) { tmp.jumpPtr = tmp.end; if (tmp.hasCatch) { tmp.state = TryCtx.STATE_CATCH; scope.catchVars.add(new ValueVariable(false, prevError)); + prevError = Runners.NO_RETURN; codePtr = tmp.catchStart; - handled = true; + remove = false; } else if (tmp.hasFinally) { tmp.state = TryCtx.STATE_FINALLY_THREW; - tmp.err = prevError; + tmp.err = new EngineException(prevError); + prevError = Runners.NO_RETURN; codePtr = tmp.finallyStart; - handled = true; + remove = false; } } - else if (codePtr < tmp.tryStart || codePtr >= tmp.catchStart) { + } + else if (prevReturn != Runners.NO_RETURN) { + remove = true; + if (tmp.hasFinally && tmp.state <= TryCtx.STATE_CATCH) { + tmp.state = TryCtx.STATE_FINALLY_RETURNED; + tmp.retVal = prevReturn; + prevReturn = Runners.NO_RETURN; + codePtr = tmp.finallyStart; + remove = false; + } + } + else if (tmp.state == TryCtx.STATE_TRY) { + if (codePtr < tmp.tryStart || codePtr >= tmp.catchStart) { if (jumpFlag) tmp.jumpPtr = codePtr; else tmp.jumpPtr = tmp.end; @@ -218,7 +233,6 @@ public class CodeFrame { remove = true; } - if (!handled) throw prevError; if (remove) tryStack.remove(tryStack.size() - 1); else { tryCtx = tmp; @@ -226,7 +240,8 @@ public class CodeFrame { } } - if (!handled) throw prevError; + if (prevError != Runners.NO_RETURN) throw new EngineException(prevError); + if (prevReturn != Runners.NO_RETURN) return prevReturn; if (tryCtx == null) return nextNoTry(ctx); else if (tryCtx.state == TryCtx.STATE_TRY) { @@ -279,15 +294,11 @@ public class CodeFrame { else return nextNoTry(ctx); } - public void handleReturn(Object value) { - - } - public Object run(CallContext ctx) throws InterruptedException { try { start(ctx); while (true) { - var res = next(ctx, null); + var res = next(ctx, Runners.NO_RETURN, Runners.NO_RETURN); if (res != Runners.NO_RETURN) return res; } } diff --git a/src/me/topchetoeu/jscript/parsing/Parsing.java b/src/me/topchetoeu/jscript/parsing/Parsing.java index 27804db..6c816dc 100644 --- a/src/me/topchetoeu/jscript/parsing/Parsing.java +++ b/src/me/topchetoeu/jscript/parsing/Parsing.java @@ -35,8 +35,6 @@ public class Parsing { reserved.add("void"); reserved.add("null"); reserved.add("this"); - reserved.add("NaN"); - reserved.add("Infinity"); reserved.add("if"); reserved.add("else"); reserved.add("try"); @@ -58,7 +56,6 @@ public class Parsing { reserved.add("break"); reserved.add("continue"); reserved.add("debug"); - reserved.add("let"); reserved.add("implements"); reserved.add("interface"); reserved.add("package"); @@ -66,17 +63,18 @@ public class Parsing { reserved.add("protected"); reserved.add("public"); reserved.add("static"); - reserved.add("yield"); - // Although the standards allow it, these are keywords in newer ES, so we won't allow them + // Although ES5 allow these, we will comply to ES6 here reserved.add("const"); - // reserved.add("await"); + reserved.add("let"); reserved.add("async"); + reserved.add("super"); // These are allowed too, however our parser considers them keywords reserved.add("undefined"); reserved.add("arguments"); reserved.add("globalThis"); reserved.add("window"); reserved.add("self"); + // We allow yield and await, because they're part of the custom async and generator functions } @@ -1040,12 +1038,6 @@ public class Parsing { if (id.result.equals("null")) { return ParseRes.res(new ConstantStatement(loc, Values.NULL), 1); } - if (id.result.equals("NaN")) { - return ParseRes.res(new ConstantStatement(loc, Double.NaN), 1); - } - if (id.result.equals("Infinity")) { - return ParseRes.res(new ConstantStatement(loc, Double.POSITIVE_INFINITY), 1); - } if (id.result.equals("this")) { return ParseRes.res(new VariableIndexStatement(loc, 0), 1); } diff --git a/src/me/topchetoeu/jscript/polyfills/AsyncFunction.java b/src/me/topchetoeu/jscript/polyfills/AsyncFunction.java index 627ff18..df4afc7 100644 --- a/src/me/topchetoeu/jscript/polyfills/AsyncFunction.java +++ b/src/me/topchetoeu/jscript/polyfills/AsyncFunction.java @@ -40,7 +40,8 @@ public class AsyncFunction extends FunctionValue { awaited = null; try { - var res = frame.next(ctx, err == Runners.NO_RETURN ? null : new EngineException(err)); + var res = frame.next(ctx, val, err); + err = Runners.NO_RETURN; if (res != Runners.NO_RETURN) { promise.fulfill(ctx, res); break; diff --git a/src/me/topchetoeu/jscript/polyfills/GeneratorFunction.java b/src/me/topchetoeu/jscript/polyfills/GeneratorFunction.java new file mode 100644 index 0000000..b59940b --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/GeneratorFunction.java @@ -0,0 +1,99 @@ +package me.topchetoeu.jscript.polyfills; + +import me.topchetoeu.jscript.engine.CallContext; +import me.topchetoeu.jscript.engine.frame.CodeFrame; +import me.topchetoeu.jscript.engine.frame.Runners; +import me.topchetoeu.jscript.engine.values.CodeFunction; +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; +import me.topchetoeu.jscript.interop.Native; + +public class GeneratorFunction extends FunctionValue { + public final CodeFunction factory; + + public static class Generator { + private boolean yielding = true; + private boolean done = false; + public CodeFrame frame; + + private ObjectValue next(CallContext ctx, Object inducedValue, Object inducedReturn, Object inducedError) throws InterruptedException { + if (done) { + if (inducedError != Runners.NO_RETURN) throw new EngineException(inducedError); + var res = new ObjectValue(); + res.defineProperty("done", true); + res.defineProperty("value", inducedReturn == Runners.NO_RETURN ? null : inducedReturn); + return res; + } + + Object res = null; + if (inducedValue != Runners.NO_RETURN) frame.push(inducedValue); + frame.start(ctx); + yielding = false; + while (!yielding) { + try { + res = frame.next(ctx, inducedReturn, inducedError); + inducedReturn = inducedError = Runners.NO_RETURN; + if (res != Runners.NO_RETURN) { + done = true; + break; + } + } + catch (EngineException e) { + done = true; + throw e; + } + } + + frame.end(ctx); + if (done) frame = null; + else res = frame.pop(); + + var obj = new ObjectValue(); + obj.defineProperty("done", done); + obj.defineProperty("value", res); + return obj; + } + + @Native + public ObjectValue next(CallContext ctx, Object... args) throws InterruptedException { + if (args.length == 0) return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN); + else return next(ctx, args[0], Runners.NO_RETURN, Runners.NO_RETURN); + } + @Native("throw") + public ObjectValue _throw(CallContext ctx, Object error) throws InterruptedException { + return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error); + } + @Native("return") + public ObjectValue _return(CallContext ctx, Object value) throws InterruptedException { + return next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN); + } + + @Override + public String toString() { + if (done) return "Generator [closed]"; + if (yielding) return "Generator [suspended]"; + return "Generator " + (done ? "[closed]" : "[suspended]"); + } + + public Object yield(CallContext ctx, Object thisArg, Object[] args) { + this.yielding = true; + return args.length > 0 ? args[0] : null; + } + } + + @Override + public Object call(CallContext _ctx, Object thisArg, Object... args) throws InterruptedException { + var handler = new Generator(); + var func = factory.call(_ctx, thisArg, new NativeFunction("yield", handler::yield)); + if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function."); + handler.frame = new CodeFrame(thisArg, args, (CodeFunction)func); + return handler; + } + + public GeneratorFunction(CodeFunction factory) { + super(factory.name, factory.length); + this.factory = factory; + } +} diff --git a/src/me/topchetoeu/jscript/polyfills/Internals.java b/src/me/topchetoeu/jscript/polyfills/Internals.java index 9aa7446..9a5d42e 100644 --- a/src/me/topchetoeu/jscript/polyfills/Internals.java +++ b/src/me/topchetoeu/jscript/polyfills/Internals.java @@ -24,6 +24,11 @@ public class Internals { return Values.toNumber(ctx, val); } + @Native + public boolean isArr(Object val) { + return val instanceof ArrayValue; + } + @NativeGetter("symbolProto") public ObjectValue symbolProto(CallContext ctx) { return ctx.engine().symbolProto(); @@ -241,6 +246,11 @@ public class Internals { if (func instanceof CodeFunction) return new AsyncFunction((CodeFunction)func); else throw EngineException.ofType("Can't create an async function with a non-js function."); } + @Native + public GeneratorFunction makeGenerator(FunctionValue func) { + if (func instanceof CodeFunction) return new GeneratorFunction((CodeFunction)func); + else throw EngineException.ofType("Can't create a generator with a non-js function."); + } @NativeGetter("err") public ObjectValue errProto(CallContext ctx) {