Create generators #3

Merged
TopchetoEU merged 5 commits from TopchetoEU/Generators into master 2023-08-25 21:44:39 +00:00
9 changed files with 166 additions and 37 deletions
Showing only changes of commit c5a4167c94 - Show all commits

View File

@ -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: <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;

View File

@ -365,5 +365,5 @@ setProps(Array.prototype, {
});
setProps(Array, {
isArray(val: any) { return (val instanceof Array); }
isArray(val: any) { return internals.isArr(val); }
});

View File

@ -14,7 +14,15 @@ interface FunctionConstructor extends Function {
(...args: string[]): (...args: any[]) => any;
new (...args: string[]): (...args: any[]) => any;
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 {
@ -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);
}
})

View File

@ -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];

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;

View 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;
}
}

View File

@ -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) {