diff --git a/lib/values/function.ts b/lib/values/function.ts index 872ce83..0bd101b 100644 --- a/lib/values/function.ts +++ b/lib/values/function.ts @@ -14,6 +14,7 @@ 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): Promise; } interface CallableFunction extends Function { @@ -75,4 +76,10 @@ setProps(Function.prototype, { toString() { return 'function (...) { ... }'; }, -}); \ No newline at end of file +}); +setProps(Function, { + async(func) { + if (typeof func !== 'function') throw new TypeError('Expected func to be function.'); + return internals.makeAsync(func); + } +}) \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index a13ad08..1edc5a7 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -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(() -> { @@ -68,7 +78,7 @@ public class Main { while (true) { try { var raw = in.readLine(); - + if (raw == null) break; engine.pushMsg(false, scope, Map.of(), "", raw, null).toObservable().once(valuePrinter); } diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 5264f8d..8548535 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -26,6 +26,7 @@ public class CodeFrame { public final Object thisArg; public final Object[] args; public final List stack = new ArrayList<>(); + public final List 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); - if (res != Runners.NO_RETURN) return res; - } - catch (EngineException e) { - throw e.add(function.name, instr.location); - } + while (true) { + var res = next(ctx); + if (res != Runners.NO_RETURN) return res; } - 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) { diff --git a/src/me/topchetoeu/jscript/engine/modules/ModuleManager.java b/src/me/topchetoeu/jscript/engine/modules/ModuleManager.java index e6e8645..4def5e1 100644 --- a/src/me/topchetoeu/jscript/engine/modules/ModuleManager.java +++ b/src/me/topchetoeu/jscript/engine/modules/ModuleManager.java @@ -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; } diff --git a/src/me/topchetoeu/jscript/engine/values/NativeFunction.java b/src/me/topchetoeu/jscript/engine/values/NativeFunction.java index 60c8691..c6a8be2 100644 --- a/src/me/topchetoeu/jscript/engine/values/NativeFunction.java +++ b/src/me/topchetoeu/jscript/engine/values/NativeFunction.java @@ -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; + } } diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index e5e8399..a5b6851 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -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; diff --git a/src/me/topchetoeu/jscript/js/bootstrap.js b/src/me/topchetoeu/jscript/js/bootstrap.js index 204a85a..2cf9d29 100644 --- a/src/me/topchetoeu/jscript/js/bootstrap.js +++ b/src/me/topchetoeu/jscript/js/bootstrap.js @@ -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; + } + ]; +}); diff --git a/src/me/topchetoeu/jscript/parsing/Parsing.java b/src/me/topchetoeu/jscript/parsing/Parsing.java index dd576b8..c817931 100644 --- a/src/me/topchetoeu/jscript/parsing/Parsing.java +++ b/src/me/topchetoeu/jscript/parsing/Parsing.java @@ -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"); diff --git a/src/me/topchetoeu/jscript/polyfills/AsyncFunction.java b/src/me/topchetoeu/jscript/polyfills/AsyncFunction.java new file mode 100644 index 0000000..32c4ab3 --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/AsyncFunction.java @@ -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; + } +} diff --git a/src/me/topchetoeu/jscript/polyfills/Internals.java b/src/me/topchetoeu/jscript/polyfills/Internals.java index 110f9c0..9aa7446 100644 --- a/src/me/topchetoeu/jscript/polyfills/Internals.java +++ b/src/me/topchetoeu/jscript/polyfills/Internals.java @@ -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(); diff --git a/src/me/topchetoeu/jscript/polyfills/Promise.java b/src/me/topchetoeu/jscript/polyfills/Promise.java index b973184..2795de1 100644 --- a/src/me/topchetoeu/jscript/polyfills/Promise.java +++ b/src/me/topchetoeu/jscript/polyfills/Promise.java @@ -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; }) ); diff --git a/src/me/topchetoeu/jscript/polyfills/TypescriptEngine.java b/src/me/topchetoeu/jscript/polyfills/TypescriptEngine.java index 31ec4c5..54b677e 100644 --- a/src/me/topchetoeu/jscript/polyfills/TypescriptEngine.java +++ b/src/me/topchetoeu/jscript/polyfills/TypescriptEngine.java @@ -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; }));