feat: add async function

This commit is contained in:
TopchetoEU 2023-08-23 11:47:50 +03:00
parent 6da7720c67
commit 79a93ef971
No known key found for this signature in database
GPG Key ID: 24E57B2E9C61AD19
12 changed files with 240 additions and 124 deletions

View File

@ -14,6 +14,7 @@ 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): Promise<RetT>;
}
interface CallableFunction extends Function {
@ -76,3 +77,9 @@ setProps(Function.prototype, {
return 'function (...) { ... }';
},
});
setProps(Function, {
async(func) {
if (typeof func !== 'function') throw new TypeError('Expected func to be function.');
return internals.makeAsync(func);
}
})

View File

@ -4,6 +4,8 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import me.topchetoeu.jscript.engine.Engine;
@ -12,7 +14,6 @@ import me.topchetoeu.jscript.events.Observer;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.polyfills.PolyfillEngine;
import me.topchetoeu.jscript.polyfills.TypescriptEngine;
public class Main {
static Thread task;
@ -52,7 +53,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];
@ -61,6 +62,15 @@ public class Main {
task.interrupt();
throw new InterruptedException();
});
scope.define("go", ctx -> {
try {
var func = engine.compile(scope, "do.js", new String(Files.readAllBytes(Path.of("do.js"))));
return func.call(ctx);
}
catch (IOException e) {
throw new EngineException("Couldn't open do.js");
}
});
task = engine.start();
var reader = new Thread(() -> {

View File

@ -26,6 +26,7 @@ public class CodeFrame {
public final Object thisArg;
public final Object[] args;
public final List<Object> stack = new ArrayList<>();
public final List<TryContext> tryCtxs = new ArrayList<>();
public final CodeFunction function;
public int codePtr = 0;
@ -47,7 +48,7 @@ public class CodeFrame {
stack.add(stack.size(), Values.normalize(val));
}
private void cleanup(CallContext ctx) {
public void cleanup(CallContext ctx) {
stack.clear();
codePtr = 0;
debugCmd = null;
@ -61,7 +62,7 @@ public class CodeFrame {
var debugState = ctx.getData(Engine.DEBUG_STATE_KEY);
if (debugCmd == null) {
if (ctx.getData(STACK_N_KEY, 0) >= ctx.addData(MAX_STACK_KEY, 1000))
if (ctx.getData(STACK_N_KEY, 0) >= ctx.addData(MAX_STACK_KEY, 100000))
throw EngineException.ofRange("Stack overflow!");
ctx.changeData(STACK_N_KEY);
@ -72,7 +73,10 @@ public class CodeFrame {
}
if (Thread.currentThread().isInterrupted()) throw new InterruptedException();
if (codePtr < 0 || codePtr >= function.body.length) return null;
var instr = function.body[codePtr];
var loc = instr.location;
if (loc != null) prevLoc = loc;
@ -92,73 +96,80 @@ public class CodeFrame {
}
try {
var res = Runners.exec(debugCmd, instr, this, ctx);
if (res != Runners.NO_RETURN) cleanup(ctx);
return res;
return Runners.exec(debugCmd, instr, this, ctx);
}
catch (EngineException e) {
cleanup(ctx);
throw e.add(function.name, prevLoc);
}
catch (RuntimeException e) {
cleanup(ctx);
throw e;
}
}
public Object run(CallContext ctx) throws InterruptedException {
var debugState = ctx.getData(Engine.DEBUG_STATE_KEY);
DebugCommand command = ctx.getData(STOP_AT_START_KEY, false) ? DebugCommand.STEP_OVER : DebugCommand.NORMAL;
if (ctx.getData(STACK_N_KEY, 0) >= ctx.addData(MAX_STACK_KEY, 200)) throw EngineException.ofRange("Stack overflow!");
ctx.changeData(STACK_N_KEY);
if (debugState != null) debugState.pushFrame(this);
Location loc = null;
try {
while (codePtr >= 0 && codePtr < function.body.length) {
var _loc = function.body[codePtr].location;
if (_loc != null) loc = _loc;
if (Thread.currentThread().isInterrupted()) throw new InterruptedException();
var instr = function.body[codePtr];
if (debugState != null && loc != null) {
if (
instr.type == Type.NOP && instr.match("debug") ||
(
(command == DebugCommand.STEP_INTO || command == DebugCommand.STEP_OVER) &&
ctx.getData(STEPPING_TROUGH_KEY, false)
) ||
debugState.breakpoints.contains(loc)
) {
ctx.setData(STEPPING_TROUGH_KEY, true);
debugState.breakpointNotifier.next(new BreakpointData(loc, ctx));
command = debugState.commandNotifier.toAwaitable().await();
if (command == DebugCommand.NORMAL) ctx.setData(STEPPING_TROUGH_KEY, false);
}
}
try {
var res = Runners.exec(command, instr, this, ctx);
while (true) {
var res = next(ctx);
if (res != Runners.NO_RETURN) return res;
}
catch (EngineException e) {
throw e.add(function.name, instr.location);
}
}
return null;
}
// catch (StackOverflowError e) {
// e.printStackTrace();
// throw EngineException.ofRange("Stack overflow!").add(function.name, loc);
// }
finally {
ctx.changeData(STACK_N_KEY, -1);
cleanup(ctx);
}
// var debugState = ctx.getData(Engine.DEBUG_STATE_KEY);
// DebugCommand command = ctx.getData(STOP_AT_START_KEY, false) ? DebugCommand.STEP_OVER : DebugCommand.NORMAL;
// if (ctx.getData(STACK_N_KEY, 0) >= ctx.addData(MAX_STACK_KEY, 200)) throw EngineException.ofRange("Stack overflow!");
// ctx.changeData(STACK_N_KEY);
// if (debugState != null) debugState.pushFrame(this);
// Location loc = null;
// Location loc = null;
// try {
// while (codePtr >= 0 && codePtr < function.body.length) {
// var _loc = function.body[codePtr].location;
// if (_loc != null) loc = _loc;
// if (Thread.currentThread().isInterrupted()) throw new InterruptedException();
// var instr = function.body[codePtr];
// if (debugState != null && loc != null) {
// if (
// instr.type == Type.NOP && instr.match("debug") ||
// (
// (command == DebugCommand.STEP_INTO || command == DebugCommand.STEP_OVER) &&
// ctx.getData(STEPPING_TROUGH_KEY, false)
// ) ||
// debugState.breakpoints.contains(loc)
// ) {
// ctx.setData(STEPPING_TROUGH_KEY, true);
// debugState.breakpointNotifier.next(new BreakpointData(loc, ctx));
// command = debugState.commandNotifier.toAwaitable().await();
// if (command == DebugCommand.NORMAL) ctx.setData(STEPPING_TROUGH_KEY, false);
// }
// }
// try {
// var res = Runners.exec(command, instr, this, ctx);
// if (res != Runners.NO_RETURN) return res;
// }
// catch (EngineException e) {
// throw e.add(function.name, instr.location);
// }
// }
// return null;
// }
// // catch (StackOverflowError e) {
// // e.printStackTrace();
// // throw EngineException.ofRange("Stack overflow!").add(function.name, loc);
// // }
// finally {
// ctx.changeData(STACK_N_KEY, -1);
// }
}
public CodeFrame(Object thisArg, Object[] args, CodeFunction func) {

View File

@ -56,7 +56,7 @@ public class ModuleManager {
if (realName == null) return null;
if (cache.containsKey(realName)) return cache.get(realName);
var mod = files.getModule(cwd, name);
// cache.put(mod.name(), mod);
cache.put(mod.name(), mod);
mod.execute(ctx);
return mod;
}
@ -66,7 +66,7 @@ public class ModuleManager {
if (realName == null) continue;
if (cache.containsKey(realName)) return cache.get(realName);
var mod = provider.getModule(cwd, name);
// cache.put(mod.name(), mod);
cache.put(mod.name(), mod);
mod.execute(ctx);
return mod;
}

View File

@ -4,7 +4,7 @@ import me.topchetoeu.jscript.engine.CallContext;
public class NativeFunction extends FunctionValue {
public static interface NativeFunctionRunner {
Object run(CallContext ctx, Object thisArg, Object[] values) throws InterruptedException;
Object run(CallContext ctx, Object thisArg, Object[] args) throws InterruptedException;
}
public final NativeFunctionRunner action;
@ -18,4 +18,8 @@ public class NativeFunction extends FunctionValue {
super(name, 0);
this.action = action;
}
public NativeFunction(NativeFunctionRunner action) {
super("", 0);
this.action = action;
}
}

View File

@ -391,19 +391,21 @@ public class Values {
}
if (obj instanceof ArrayValue) {
var raw = array(obj).toArray();
if (clazz.isAssignableFrom(ArrayList.class)) {
var raw = array(obj).toArray();
var res = new ArrayList<>();
for (var i = 0; i < raw.length; i++) res.add(convert(ctx, raw[i], Object.class));
return (T)new ArrayList<>(res);
}
if (clazz.isAssignableFrom(HashSet.class)) {
var raw = array(obj).toArray();
var res = new HashSet<>();
for (var i = 0; i < raw.length; i++) res.add(convert(ctx, raw[i], Object.class));
return (T)new HashSet<>(res);
}
if (clazz.isArray()) {
var raw = array(obj).toArray();
Object res = Array.newInstance(clazz.arrayType(), raw.length);
for (var i = 0; i < raw.length; i++) Array.set(res, i, convert(ctx, raw[i], Object.class));
return (T)res;

View File

@ -1,7 +1,8 @@
// TODO: load this in java
var ts = require('./ts__');
log("Loaded typescript!");
var src = '', lib = libs.join(''), decls = [], version = 0;
var src = '', lib = libs.join(''), decls = '', version = 0;
var libSnapshot = ts.ScriptSnapshot.fromString(lib);
var settings = {
@ -9,7 +10,7 @@ var settings = {
declarationDir: "/out",
target: ts.ScriptTarget.ES5,
lib: [ ],
module: ts.ModuleKind.CommonJS,
module: ts.ModuleKind.None,
declaration: true,
stripInternal: true,
downlevelIteration: true,
@ -20,41 +21,17 @@ var settings = {
var reg = ts.createDocumentRegistry();
var service = ts.createLanguageService({
getCanonicalFileName: function (fileName) { return fileName; },
useCaseSensitiveFileNames: function () { return true; },
getNewLine: function () { return "\n"; },
getEnvironmentVariable: function () { return ""; },
log: function() {
log.apply(undefined, arguments);
},
fileExists: function (fileName) {
return (
fileName === "/src.ts" ||
fileName === "/lib.d.ts" ||
fileName === "/glob.d.ts"
);
},
readFile: function (fileName) {
if (fileName === "/src.ts") return src;
if (fileName === "/lib.d.ts") return lib;
if (fileName === "/glob.d.ts") return decls.join('\n');
throw new Error("File '" + fileName + "' doesn't exist.");
},
writeFile: function (fileName, data) {
if (fileName.endsWith(".js")) res = data;
else if (fileName.endsWith(".d.ts")) decls.push(data);
else throw new Error("File '" + fileName + "' isn't writable.");
},
getCompilationSettings: function () {
return settings;
},
getCurrentDirectory: function() { return "/"; },
getDefaultLibFileName: function() { return "/lib_.d.ts"; },
getScriptFileNames: function() { return [ "/src.ts", "/lib.d.ts", "/glob.d.ts" ]; },
getCompilationSettings: function () { return settings; },
fileExists: function(filename) { return filename === "/lib.d.ts" || filename === "/src.ts" || filename === "/glob.d.ts"; },
getScriptSnapshot: function(filename) {
if (filename === "/lib.d.ts") return libSnapshot;
else return ts.ScriptSnapshot.fromString(this.readFile(filename));
if (filename === "/src.ts") return ts.ScriptSnapshot.fromString(src);
if (filename === "/glob.d.ts") return ts.ScriptSnapshot.fromString(decls);
throw new Error("File '" + filename + "' doesn't exist.");
},
getScriptVersion: function (filename) {
if (filename === "/lib.d.ts") return 0;
@ -65,16 +42,11 @@ var service = ts.createLanguageService({
service.getEmitOutput('/lib.d.ts');
log('Loaded libraries!');
function compile(code) {
src = code;
version++;
function compile(filename, code) {
src = code, version++;
var emit = service.getEmitOutput("/src.ts");
var res = emit.outputFiles[0].text;
var decl = emit.outputFiles[1].text;
var diagnostics = []
.concat(service.getCompilerOptionsDiagnostics())
.concat(service.getSyntacticDiagnostics("/src.ts"))
@ -83,7 +55,9 @@ function compile(code) {
var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
if (diagnostic.file) {
var pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
return diagnostic.file.fileName.substring(1) + ":" + (pos.line + 1) + ":" + (pos.character + 1) + ": " + message;
var file = diagnostic.file.fileName.substring(1);
if (file === "src.ts") file = filename;
return file + ":" + (pos.line + 1) + ":" + (pos.character + 1) + ": " + message;
}
else return "Error: " + message;
});
@ -92,17 +66,21 @@ function compile(code) {
throw new SyntaxError(diagnostics.join('\n'));
}
decls.push(decl);
return {
result: res,
diagnostics: diagnostics
result: emit.outputFiles[0].text,
declaration: emit.outputFiles[1].text
};
}
log("Loaded typescript!");
init(function (code) {
var res = compile(code);
return res.result;
});
init(function (filename, code) {
var res = compile(filename, code);
return [
res.result,
function(func, th, args) {
var val = func.apply(th, args);
decls += res.declaration;
return val;
}
];
});

View File

@ -68,7 +68,7 @@ public class Parsing {
reserved.add("yield");
// Although the standards allow it, these are keywords in newer ES, so we won't allow them
reserved.add("const");
reserved.add("await");
// reserved.add("await");
reserved.add("async");
// These are allowed too, however our parser considers them keywords
reserved.add("undefined");

View File

@ -0,0 +1,87 @@
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.ArrayValue;
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("", this::fulfill);
private final NativeFunction rejectFunc = new NativeFunction("", this::reject);
private Object reject(CallContext ctx, Object thisArg, Object[] args) throws InterruptedException {
if (args.length > 0) promise.reject(ctx, args[0]);
return null;
}
public Object fulfill(CallContext ctx, Object thisArg, Object[] args) throws InterruptedException {
if (args.length == 1) frame.push(args[0]);
while (true) {
awaiting = false;
awaited = null;
try {
var res = frame.next(ctx);
if (res != Runners.NO_RETURN) {
promise.fulfill(ctx, res);
return null;
}
}
catch (EngineException e) {
promise.reject(e);
return null;
}
if (!awaiting) continue;
frame.pop();
if (awaited instanceof Promise) ((Promise)awaited).then(ctx, fulfillFunc, rejectFunc);
else if (Values.isPrimitive(awaited)) frame.push(awaited);
try {
var res = Values.getMember(ctx, awaited, "then");
if (res instanceof FunctionValue) {
Values.function(res).call(ctx, awaited, fulfillFunc, rejectFunc);
return null;
}
else frame.push(awaited);
}
catch (EngineException e) {
promise.reject(e);
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();
handler.frame = new CodeFrame(thisArg, new Object[] { new NativeFunction("await", handler::await), new ArrayValue(args) }, body);
handler.fulfill(_ctx, null, new Object[0]);
return handler.promise;
}
public AsyncFunction(CodeFunction body) {
super(body.name, body.length);
this.body = body;
}
}

View File

@ -4,6 +4,7 @@ import java.util.HashMap;
import me.topchetoeu.jscript.engine.CallContext;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Symbol;
@ -235,6 +236,12 @@ public class Internals {
return res.exports();
}
@Native
public AsyncFunction makeAsync(FunctionValue 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.");
}
@NativeGetter("err")
public ObjectValue errProto(CallContext ctx) {
return ctx.engine.errorProto();

View File

@ -203,11 +203,11 @@ public class Promise {
public void fulfill(CallContext ctx, Object val) {
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(ctx,
new NativeFunction(null, (e, th, args) -> {
this.fulfill(args[0]);
this.fulfill(e, args[0]);
return null;
}),
new NativeFunction(null, (e, th, args) -> {
this.reject(args[0]);
this.reject(e, args[0]);
return null;
})
);
@ -231,11 +231,11 @@ public class Promise {
public void reject(CallContext ctx, Object val) {
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(ctx,
new NativeFunction(null, (e, th, args) -> {
this.reject(args[0]);
this.reject(e, args[0]);
return null;
}),
new NativeFunction(null, (e, th, args) -> {
this.reject(args[0]);
this.reject(e, args[0]);
return null;
})
);

View File

@ -6,16 +6,26 @@ import java.util.Map;
import me.topchetoeu.jscript.engine.scope.GlobalScope;
import me.topchetoeu.jscript.engine.values.ArrayValue;
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;
public class TypescriptEngine extends PolyfillEngine {
private FunctionValue ts;
@Override
public CodeFunction compile(GlobalScope scope, String filename, String raw) throws InterruptedException {
if (ts != null) raw = (String)ts.call(context(), null, raw);
public FunctionValue compile(GlobalScope scope, String filename, String raw) throws InterruptedException {
if (ts != null) {
var res = Values.array(ts.call(context(), null, filename, raw));
var src = Values.toString(context(), res.get(0));
var func = Values.function(res.get(1));
var compiled = super.compile(scope, filename, src);
return new NativeFunction(null, (ctx, thisArg, args) -> {
return func.call(context(), null, compiled, thisArg, new ArrayValue(args));
});
}
return super.compile(scope, filename, raw);
}
@ -42,7 +52,7 @@ public class TypescriptEngine extends PolyfillEngine {
scope.define("libs", true, ArrayValue.of(decls));
scope.define(true, new NativeFunction("init", (el, t, args) -> {
ts = (FunctionValue)args[0];
ts = Values.function(args[0]);
return null;
}));