Merge pull request #3 from TopchetoEU/TopchetoEU/Generators

Create generators
This commit is contained in:
TopchetoEU 2023-08-26 00:44:38 +03:00 committed by GitHub
commit 7b19f7b399
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 253 additions and 149 deletions

View File

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

View File

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

View File

@ -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 = unknown, RetT = unknown, TNext = unknown>(
func: (_yield: <T>(val: T) => TNext) => (...args: ArgsT) => RetT
): (...args: ArgsT) => Generator<T, RetT, TNext>;
} }
interface CallableFunction extends Function { interface CallableFunction extends Function {
@ -43,11 +51,15 @@ 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);
@ -80,6 +92,90 @@ setProps(Function.prototype, {
setProps(Function, { 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 function (this: any) {
const args = arguments;
return new Promise((res, rej) => {
const gen = Function.generator(func as any).apply(this, args as any);
(function next(type: 'none' | 'err' | 'ret', val?: any) {
try {
let result;
switch (type) {
case 'err': result = gen.throw(val); break;
case 'ret': result = gen.next(val); break;
case 'none': result = gen.next(); break;
}
if (result.done) res(result.value);
else Promise.resolve(result.value).then(
v => next('ret', v),
v => next('err', v)
)
}
catch (e) {
rej(e);
}
})('none');
});
};
},
asyncGenerator(func) {
if (typeof func !== 'function') throw new TypeError('Expected func to be function.');
return function(this: any) {
const gen = Function.generator<any[], ['await' | 'yield', any]>((_yield) => func(
val => _yield(['await', val]) as any,
val => _yield(['yield', val])
)).apply(this, arguments as any);
const next = (resolve: Function, reject: Function, type: 'none' | 'val' | 'ret' | 'err', val?: any) => {
let res;
try {
switch (type) {
case 'val': res = gen.next(val); break;
case 'ret': res = gen.return(val); break;
case 'err': res = gen.throw(val); break;
default: res = gen.next(); break;
}
}
catch (e) { return reject(e); }
if (res.done) return { done: true, res: <any>res };
else if (res.value[0] === 'await') Promise.resolve(res.value[1]).then(
v => next(resolve, reject, 'val', v),
v => next(resolve, reject, 'err', v),
)
else resolve({ done: false, value: res.value[1] });
};
return {
next() {
const args = arguments;
if (arguments.length === 0) return new Promise((res, rej) => next(res, rej, 'none'));
else return new Promise((res, rej) => next(res, rej, 'val', args[0]));
},
return: (value) => new Promise((res, rej) => next(res, rej, 'ret', value)),
throw: (value) => new Promise((res, rej) => next(res, rej, 'err', value)),
[Symbol.asyncIterator]() { return this; }
}
}
},
generator(func) {
if (typeof func !== 'function') throw new TypeError('Expected func to be function.');
const gen = internals.makeGenerator(func);
return (...args: any[]) => {
const it = gen(args);
return {
next: it.next,
return: it.return,
throw: it.throw,
[Symbol.iterator]() { return this; }
}
}
} }
}) })

View File

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

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; 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 (tmp.state == TryCtx.STATE_TRY) { if (prevError != Runners.NO_RETURN) {
if (prevError != null) { remove = true;
if (tmp.state == TryCtx.STATE_TRY) {
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;
} }
} }

View File

@ -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
} }
@ -983,7 +981,14 @@ public class Parsing {
} }
@SuppressWarnings("all") @SuppressWarnings("all")
public static ParseRes<? extends Statement> parseSimple(String filename, List<Token> tokens, int i, boolean statement) { public static ParseRes<? extends Statement> parseSimple(String filename, List<Token> tokens, int i, boolean statement) {
var res = new ArrayList<>(List.of( var res = new ArrayList<>();
if (!statement) {
res.add(parseObject(filename, tokens, i));
res.add(parseFunction(filename, tokens, i, false));
}
res.addAll(List.of(
parseVariable(filename, tokens, i), parseVariable(filename, tokens, i),
parseLiteral(filename, tokens, i), parseLiteral(filename, tokens, i),
parseString(filename, tokens, i), parseString(filename, tokens, i),
@ -999,11 +1004,6 @@ public class Parsing {
parseDelete(filename, tokens, i) parseDelete(filename, tokens, i)
)); ));
if (!statement) {
res.add(parseObject(filename, tokens, i));
res.add(parseFunction(filename, tokens, i, false));
}
return ParseRes.any(res.toArray(ParseRes[]::new)); return ParseRes.any(res.toArray(ParseRes[]::new));
} }
@ -1040,12 +1040,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);
} }
@ -1803,7 +1797,6 @@ public class Parsing {
parseContinue(filename, tokens, i), parseContinue(filename, tokens, i),
parseBreak(filename, tokens, i), parseBreak(filename, tokens, i),
parseDebug(filename, tokens, i), parseDebug(filename, tokens, i),
parseValueStatement(filename, tokens, i),
parseIf(filename, tokens, i), parseIf(filename, tokens, i),
parseWhile(filename, tokens, i), parseWhile(filename, tokens, i),
parseSwitch(filename, tokens, i), parseSwitch(filename, tokens, i),
@ -1812,7 +1805,8 @@ public class Parsing {
parseDoWhile(filename, tokens, i), parseDoWhile(filename, tokens, i),
parseCatch(filename, tokens, i), parseCatch(filename, tokens, i),
parseCompound(filename, tokens, i), parseCompound(filename, tokens, i),
parseFunction(filename, tokens, i, true) parseFunction(filename, tokens, i, true),
parseValueStatement(filename, tokens, i)
); );
} }

View File

@ -1,101 +0,0 @@
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.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
public class AsyncFunction extends FunctionValue {
public final CodeFunction body;
private class CallHandler {
private boolean awaiting = false;
private Object awaited = null;
public final Promise promise = new Promise();
public CodeFrame frame;
private final NativeFunction fulfillFunc = new NativeFunction("", (ctx, thisArg, args) -> {
if (args.length == 0) exec(ctx, null, Runners.NO_RETURN);
else exec(ctx, args[0], Runners.NO_RETURN);
return null;
});
private final NativeFunction rejectFunc = new NativeFunction("", (ctx, thisArg, args) -> {
if (args.length == 0) exec(ctx, Runners.NO_RETURN, null);
else exec(ctx, Runners.NO_RETURN, args[0]);
return null;
});
public Object exec(CallContext ctx, Object val, Object err) throws InterruptedException {
if (val != Runners.NO_RETURN) frame.push(val);
frame.start(ctx);
while (true) {
awaiting = false;
awaited = null;
try {
var res = frame.next(ctx, err == Runners.NO_RETURN ? null : new EngineException(err));
if (res != Runners.NO_RETURN) {
promise.fulfill(ctx, res);
break;
}
}
catch (EngineException e) {
promise.reject(e.value);
break;
}
if (!awaiting) continue;
frame.pop();
if (awaited instanceof Promise) ((Promise)awaited).then(ctx, fulfillFunc, rejectFunc);
else if (Values.isPrimitive(awaited)) frame.push(awaited);
else {
try {
var res = Values.getMember(ctx, awaited, "then");
if (res instanceof FunctionValue) {
Values.function(res).call(ctx, awaited, fulfillFunc, rejectFunc);
break;
}
else frame.push(awaited);
}
catch (EngineException e) {
promise.reject(e);
break;
}
}
}
frame.end(ctx);
return null;
}
public Object await(CallContext ctx, Object thisArg, Object[] args) {
this.awaiting = true;
this.awaited = args[0];
return null;
}
}
@Override
public Object call(CallContext _ctx, Object thisArg, Object... args) throws InterruptedException {
var handler = new CallHandler();
var func = body.call(_ctx, thisArg, new NativeFunction("await", handler::await));
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
handler.frame = new CodeFrame(thisArg, args, (CodeFunction)func);
handler.exec(_ctx, Runners.NO_RETURN, Runners.NO_RETURN);
return handler.promise;
}
public AsyncFunction(CodeFunction body) {
super(body.name, body.length);
this.body = body;
}
}

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); 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();
@ -237,9 +242,9 @@ public class Internals {
} }
@Native @Native
public AsyncFunction makeAsync(FunctionValue func) { public GeneratorFunction makeGenerator(FunctionValue func) {
if (func instanceof CodeFunction) return new AsyncFunction((CodeFunction)func); if (func instanceof CodeFunction) return new GeneratorFunction((CodeFunction)func);
else throw EngineException.ofType("Can't create an async function with a non-js function."); else throw EngineException.ofType("Can't create a generator with a non-js function.");
} }
@NativeGetter("err") @NativeGetter("err")