feat: make generator function
This commit is contained in:
parent
91579bd3a0
commit
c5a4167c94
@ -74,8 +74,8 @@ interface MathObject {
|
|||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
declare const arguments: IArguments;
|
declare const arguments: IArguments;
|
||||||
declare const Math: MathObject;
|
declare const Math: MathObject;
|
||||||
declare const NaN: number;
|
const NaN = 0 / 0;
|
||||||
declare const Infinity: number;
|
const Infinity = 1 / 0;
|
||||||
|
|
||||||
declare var setTimeout: <T extends any[]>(handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number;
|
declare var setTimeout: <T extends any[]>(handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number;
|
||||||
declare var setInterval: <T extends any[]>(handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number;
|
declare var setInterval: <T extends any[]>(handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number;
|
||||||
|
@ -365,5 +365,5 @@ setProps(Array.prototype, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setProps(Array, {
|
setProps(Array, {
|
||||||
isArray(val: any) { return (val instanceof Array); }
|
isArray(val: any) { return internals.isArr(val); }
|
||||||
});
|
});
|
||||||
|
@ -14,7 +14,15 @@ interface FunctionConstructor extends Function {
|
|||||||
(...args: string[]): (...args: any[]) => any;
|
(...args: string[]): (...args: any[]) => any;
|
||||||
new (...args: string[]): (...args: any[]) => any;
|
new (...args: string[]): (...args: any[]) => any;
|
||||||
prototype: Function;
|
prototype: Function;
|
||||||
async<ArgsT extends any[], RetT>(func: (await: <T>(val: T) => Awaited<T>) => (...args: ArgsT) => RetT): (...args: ArgsT) => Promise<RetT>;
|
async<ArgsT extends any[], RetT>(
|
||||||
|
func: (await: <T>(val: T) => Awaited<T>) => (...args: ArgsT) => RetT
|
||||||
|
): (...args: ArgsT) => Promise<RetT>;
|
||||||
|
asyncGenerator<ArgsT extends any[], RetT>(
|
||||||
|
func: (await: <T>(val: T) => Awaited<T>, _yield: <T>(val: T) => void) => (...args: ArgsT) => RetT
|
||||||
|
): (...args: ArgsT) => AsyncGenerator<RetT>;
|
||||||
|
generator<ArgsT extends any[], T, RetT, TNext>(
|
||||||
|
func: (_yield: <T>(val: T) => TNext) => (...args: ArgsT) => RetT
|
||||||
|
): (...args: ArgsT) => Generator<T, RetT, TNext>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CallableFunction extends Function {
|
interface CallableFunction extends Function {
|
||||||
@ -43,12 +51,16 @@ setProps(Function.prototype, {
|
|||||||
apply(thisArg, args) {
|
apply(thisArg, args) {
|
||||||
if (typeof args !== 'object') throw 'Expected arguments to be an array-like object.';
|
if (typeof args !== 'object') throw 'Expected arguments to be an array-like object.';
|
||||||
var len = args.length - 0;
|
var len = args.length - 0;
|
||||||
var newArgs: any[] = [];
|
let newArgs: any[];
|
||||||
|
if (Array.isArray(args)) newArgs = args;
|
||||||
|
else {
|
||||||
|
newArgs = [];
|
||||||
|
|
||||||
while (len >= 0) {
|
while (len >= 0) {
|
||||||
len--;
|
len--;
|
||||||
newArgs[len] = args[len];
|
newArgs[len] = args[len];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return internals.apply(this, thisArg, newArgs);
|
return internals.apply(this, thisArg, newArgs);
|
||||||
},
|
},
|
||||||
@ -81,5 +93,9 @@ setProps(Function, {
|
|||||||
async(func) {
|
async(func) {
|
||||||
if (typeof func !== 'function') throw new TypeError('Expected func to be function.');
|
if (typeof func !== 'function') throw new TypeError('Expected func to be function.');
|
||||||
return internals.makeAsync(func);
|
return internals.makeAsync(func);
|
||||||
|
},
|
||||||
|
generator(func) {
|
||||||
|
if (typeof func !== 'function') throw new TypeError('Expected func to be function.');
|
||||||
|
return internals.makeGenerator(func);
|
||||||
}
|
}
|
||||||
})
|
})
|
@ -54,7 +54,7 @@ public class Main {
|
|||||||
|
|
||||||
public static void main(String args[]) {
|
public static void main(String args[]) {
|
||||||
var in = new BufferedReader(new InputStreamReader(System.in));
|
var in = new BufferedReader(new InputStreamReader(System.in));
|
||||||
engine = new TypescriptEngine(new File("."));
|
engine = new PolyfillEngine(new File("."));
|
||||||
var scope = engine.global().globalChild();
|
var scope = engine.global().globalChild();
|
||||||
var exited = new boolean[1];
|
var exited = new boolean[1];
|
||||||
|
|
||||||
|
@ -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;
|
TryCtx tryCtx = null;
|
||||||
var handled = prevError == null;
|
if (prevError != Runners.NO_RETURN) prevReturn = Runners.NO_RETURN;
|
||||||
|
|
||||||
while (!tryStack.isEmpty()) {
|
while (!tryStack.isEmpty()) {
|
||||||
var tmp = tryStack.get(tryStack.size() - 1);
|
var tmp = tryStack.get(tryStack.size() - 1);
|
||||||
var remove = false;
|
var remove = false;
|
||||||
|
|
||||||
|
if (prevError != Runners.NO_RETURN) {
|
||||||
|
remove = true;
|
||||||
if (tmp.state == TryCtx.STATE_TRY) {
|
if (tmp.state == TryCtx.STATE_TRY) {
|
||||||
if (prevError != null) {
|
|
||||||
tmp.jumpPtr = tmp.end;
|
tmp.jumpPtr = tmp.end;
|
||||||
|
|
||||||
if (tmp.hasCatch) {
|
if (tmp.hasCatch) {
|
||||||
tmp.state = TryCtx.STATE_CATCH;
|
tmp.state = TryCtx.STATE_CATCH;
|
||||||
scope.catchVars.add(new ValueVariable(false, prevError));
|
scope.catchVars.add(new ValueVariable(false, prevError));
|
||||||
|
prevError = Runners.NO_RETURN;
|
||||||
codePtr = tmp.catchStart;
|
codePtr = tmp.catchStart;
|
||||||
handled = true;
|
remove = false;
|
||||||
}
|
}
|
||||||
else if (tmp.hasFinally) {
|
else if (tmp.hasFinally) {
|
||||||
tmp.state = TryCtx.STATE_FINALLY_THREW;
|
tmp.state = TryCtx.STATE_FINALLY_THREW;
|
||||||
tmp.err = prevError;
|
tmp.err = new EngineException(prevError);
|
||||||
|
prevError = Runners.NO_RETURN;
|
||||||
codePtr = tmp.finallyStart;
|
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;
|
if (jumpFlag) tmp.jumpPtr = codePtr;
|
||||||
else tmp.jumpPtr = tmp.end;
|
else tmp.jumpPtr = tmp.end;
|
||||||
|
|
||||||
@ -218,7 +233,6 @@ public class CodeFrame {
|
|||||||
remove = true;
|
remove = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!handled) throw prevError;
|
|
||||||
if (remove) tryStack.remove(tryStack.size() - 1);
|
if (remove) tryStack.remove(tryStack.size() - 1);
|
||||||
else {
|
else {
|
||||||
tryCtx = tmp;
|
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);
|
if (tryCtx == null) return nextNoTry(ctx);
|
||||||
else if (tryCtx.state == TryCtx.STATE_TRY) {
|
else if (tryCtx.state == TryCtx.STATE_TRY) {
|
||||||
@ -279,15 +294,11 @@ public class CodeFrame {
|
|||||||
else return nextNoTry(ctx);
|
else return nextNoTry(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleReturn(Object value) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object run(CallContext ctx) throws InterruptedException {
|
public Object run(CallContext ctx) throws InterruptedException {
|
||||||
try {
|
try {
|
||||||
start(ctx);
|
start(ctx);
|
||||||
while (true) {
|
while (true) {
|
||||||
var res = next(ctx, null);
|
var res = next(ctx, Runners.NO_RETURN, Runners.NO_RETURN);
|
||||||
if (res != Runners.NO_RETURN) return res;
|
if (res != Runners.NO_RETURN) return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,8 +35,6 @@ public class Parsing {
|
|||||||
reserved.add("void");
|
reserved.add("void");
|
||||||
reserved.add("null");
|
reserved.add("null");
|
||||||
reserved.add("this");
|
reserved.add("this");
|
||||||
reserved.add("NaN");
|
|
||||||
reserved.add("Infinity");
|
|
||||||
reserved.add("if");
|
reserved.add("if");
|
||||||
reserved.add("else");
|
reserved.add("else");
|
||||||
reserved.add("try");
|
reserved.add("try");
|
||||||
@ -58,7 +56,6 @@ public class Parsing {
|
|||||||
reserved.add("break");
|
reserved.add("break");
|
||||||
reserved.add("continue");
|
reserved.add("continue");
|
||||||
reserved.add("debug");
|
reserved.add("debug");
|
||||||
reserved.add("let");
|
|
||||||
reserved.add("implements");
|
reserved.add("implements");
|
||||||
reserved.add("interface");
|
reserved.add("interface");
|
||||||
reserved.add("package");
|
reserved.add("package");
|
||||||
@ -66,17 +63,18 @@ public class Parsing {
|
|||||||
reserved.add("protected");
|
reserved.add("protected");
|
||||||
reserved.add("public");
|
reserved.add("public");
|
||||||
reserved.add("static");
|
reserved.add("static");
|
||||||
reserved.add("yield");
|
// Although ES5 allow these, we will comply to ES6 here
|
||||||
// Although the standards allow it, these are keywords in newer ES, so we won't allow them
|
|
||||||
reserved.add("const");
|
reserved.add("const");
|
||||||
// reserved.add("await");
|
reserved.add("let");
|
||||||
reserved.add("async");
|
reserved.add("async");
|
||||||
|
reserved.add("super");
|
||||||
// These are allowed too, however our parser considers them keywords
|
// These are allowed too, however our parser considers them keywords
|
||||||
reserved.add("undefined");
|
reserved.add("undefined");
|
||||||
reserved.add("arguments");
|
reserved.add("arguments");
|
||||||
reserved.add("globalThis");
|
reserved.add("globalThis");
|
||||||
reserved.add("window");
|
reserved.add("window");
|
||||||
reserved.add("self");
|
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")) {
|
if (id.result.equals("null")) {
|
||||||
return ParseRes.res(new ConstantStatement(loc, Values.NULL), 1);
|
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")) {
|
if (id.result.equals("this")) {
|
||||||
return ParseRes.res(new VariableIndexStatement(loc, 0), 1);
|
return ParseRes.res(new VariableIndexStatement(loc, 0), 1);
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,8 @@ public class AsyncFunction extends FunctionValue {
|
|||||||
awaited = null;
|
awaited = null;
|
||||||
|
|
||||||
try {
|
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) {
|
if (res != Runners.NO_RETURN) {
|
||||||
promise.fulfill(ctx, res);
|
promise.fulfill(ctx, res);
|
||||||
break;
|
break;
|
||||||
|
99
src/me/topchetoeu/jscript/polyfills/GeneratorFunction.java
Normal file
99
src/me/topchetoeu/jscript/polyfills/GeneratorFunction.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,11 @@ public class Internals {
|
|||||||
return Values.toNumber(ctx, val);
|
return Values.toNumber(ctx, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Native
|
||||||
|
public boolean isArr(Object val) {
|
||||||
|
return val instanceof ArrayValue;
|
||||||
|
}
|
||||||
|
|
||||||
@NativeGetter("symbolProto")
|
@NativeGetter("symbolProto")
|
||||||
public ObjectValue symbolProto(CallContext ctx) {
|
public ObjectValue symbolProto(CallContext ctx) {
|
||||||
return ctx.engine().symbolProto();
|
return ctx.engine().symbolProto();
|
||||||
@ -241,6 +246,11 @@ public class Internals {
|
|||||||
if (func instanceof CodeFunction) return new AsyncFunction((CodeFunction)func);
|
if (func instanceof CodeFunction) return new AsyncFunction((CodeFunction)func);
|
||||||
else throw EngineException.ofType("Can't create an async function with a non-js function.");
|
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")
|
@NativeGetter("err")
|
||||||
public ObjectValue errProto(CallContext ctx) {
|
public ObjectValue errProto(CallContext ctx) {
|
||||||
|
Loading…
Reference in New Issue
Block a user