diff --git a/README.md b/README.md index 39ecaa0..589fb8c 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,76 @@ **WARNING: Currently, this code is undocumented. Proceed with caution and a psychiatrist.** -J2S is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes. Note that although the codebase has a Main class, this isn't meant to be a standalone program, but instead a library for running JavaScript code. +J2S is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes. A small REPL (`me.topchetoeu.j2s.repl.SimpleRepl`) library with an included simple debugger (`me.topchetoeu.j2s.repl.debug.SimpleDebugger`). These are more or less reference implementations. In the future, most of the primordials logic of `SimpleRepl` will be moved in the "lib" project, but for now, it will stay there. -## Example +## How to use? -The following is going to execute a simple javascript statement: +Since this is mostly targeted for integration into other applications, here, examples for invoking JS code from Java will be shown. In the future, a more comprehensive wiki will be made. + +### Setting up an event loop + +First of all, you will want to create an event loop. While not required, 99% of the times you will want to have one. ```java var engine = new Engine(); -// Initialize a standard environment, with implementations of most basic standard libraries (Object, Array, Symbol, etc.) -var env = Internals.apply(new Environment()); - -// Queue code to load internal libraries and start engine -var awaitable = engine.pushMsg(false, env, new Filename("tmp", "eval"), "10 + Math.sqrt(5 / 3)", null); -// Run the engine on the same thread, until the event loop runs empty -engine.run(true); - -// Get our result -System.out.println(awaitable.await()); +var thread = engine.start(); ``` + +Hooray! Now you have an event loop. The thread that was automatically created is a daemon thread, so it will harakiri when the rest of the application ends. If you don't want to use the built-in thread, you can instead run it with `engine.run(untilEmpty)`. If you pass true (which you most likely need), the event loop will be run blocking-ly until it is empty. Otherwise, it will be run forever. + +### Creating the execution environment + +This is one of the other crucial parts of J2S's architecture - the environment. It contains the global scope, a reference to the event loop, the global scope, the debugger, source mappings and a lot more. To run JS code, you must create an environment: + +```java +var env = Environment.empty(); +env.add(EventLoop.KEY, engine); // Gives +env.add(DebugContext.KEY, new DebugContext()); // For source mappings +``` + +As you can see, the environment is nothing more than a simple map of objects that may be of interest to the JS code. Although you can do much more with the environment, we will leave it at that. + +### Registering the compiler + +Since the compiler is a part of the runtime, you need to register it in the environment. You can use the following boilerplate, although a nicer API will be exposed later on: + +```java +env.add(Compiler.KEY, (_env, filename, raw, mapper) -> { + try { + // Invokes the compiler. Will return a CompilerResult, which, along other things, + // gives us all the compiled function bodies (aka function scaffoldings, that can be used to construct a function value) + var res = JavaScript.compile(env, filename, raw, true); + var body = res.body(); + + // We'll register the source and function source mappings for debugging + DebugContext.get(env).onSource(filename, raw); + for (var el : res.bodies()) { + DebugContext.get(env).onFunctionLoad(el, res.map(mapper)); + } + + // Finally, we will construct the function + // Few things to note: we need to pass the environment, the name of the function (the filename), + // and the last thing: the captures. Since we are compiling the main function, we don't have + // any captures, so we pass an empty array + return new CodeFunction(env, filename.toString(), body, new Value[0][]); + } + catch (SyntaxException e) { + // Convert the exception to an engine exception + var res = EngineException.ofSyntax(e.msg); + // Add the location of the error to its stack trace + res.add(env, e.loc.filename() + "", e.loc); + throw res; + } +}); +``` + +### Evaluating a piece of code on the event loop + +This is what you really want to do: run code! You can do that in the following way: + +```java +var result = engine.pushMsg(false, env, Filename.parse("my-app://test.js"), "return 10 + 5 / 3;", Value.UNDEFINED).get(); +System.out.println(result.toReadable(env)); +``` + +If all goes well, we will get "11.666..." as a result. diff --git a/compilation/src/main/java/me/topchetoeu/j2s/compilation/CompileResult.java b/compilation/src/main/java/me/topchetoeu/j2s/compilation/CompileResult.java index 89a6874..95dbcc7 100644 --- a/compilation/src/main/java/me/topchetoeu/j2s/compilation/CompileResult.java +++ b/compilation/src/main/java/me/topchetoeu/j2s/compilation/CompileResult.java @@ -2,6 +2,7 @@ package me.topchetoeu.j2s.compilation; import java.util.List; import java.util.Map; +import java.util.Stack; import java.util.function.Function; import me.topchetoeu.j2s.common.FunctionBody; @@ -18,6 +19,7 @@ import me.topchetoeu.j2s.compilation.scope.Variable; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedList; public final class CompileResult { @@ -64,9 +66,31 @@ public final class CompileResult { public void setLocation(Location type) { setLocation(instructions.size() - 1, type); } + public void setLocationAndDebug(Location loc, BreakpointType type) { setLocationAndDebug(instructions.size() - 1, loc, type); } + + public Iterable bodies() { + var stack = new Stack(); + stack.push(body()); + + return () -> new Iterator() { + @Override public FunctionBody next() { + if (stack.empty()) return null; + else { + var res = stack.pop(); + for (var el : res.children) { + stack.push(el); + } + return res; + } + } + @Override public boolean hasNext() { + return !stack.empty(); + } + }; + } public CompileResult addChild(FunctionNode node, CompileResult res) { this.children.add(res); diff --git a/repl/src/main/java/me/topchetoeu/j2s/repl/SimpleRepl.java b/repl/src/main/java/me/topchetoeu/j2s/repl/SimpleRepl.java index 52da439..a726ef5 100644 --- a/repl/src/main/java/me/topchetoeu/j2s/repl/SimpleRepl.java +++ b/repl/src/main/java/me/topchetoeu/j2s/repl/SimpleRepl.java @@ -13,11 +13,9 @@ import java.util.Optional; import java.util.WeakHashMap; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; -import java.util.function.Function; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; -import me.topchetoeu.j2s.common.FunctionBody; import me.topchetoeu.j2s.common.Metadata; import me.topchetoeu.j2s.common.Reading; import me.topchetoeu.j2s.common.SyntaxException; @@ -25,8 +23,6 @@ import me.topchetoeu.j2s.common.environment.Environment; import me.topchetoeu.j2s.common.environment.Key; import me.topchetoeu.j2s.common.json.JSON; import me.topchetoeu.j2s.common.parsing.Filename; -import me.topchetoeu.j2s.common.parsing.Location; -import me.topchetoeu.j2s.compilation.CompileResult; import me.topchetoeu.j2s.compilation.JavaScript; import me.topchetoeu.j2s.repl.debug.DebugServer; import me.topchetoeu.j2s.repl.debug.Debugger; @@ -63,8 +59,12 @@ public class SimpleRepl { try { var res = JavaScript.compile(env, filename, raw, true); var body = res.body(); + DebugContext.get(env).onSource(filename, raw); - registerFunc(env, body, res, mapper); + for (var el : res.bodies()) { + DebugContext.get(env).onFunctionLoad(el, res.map(mapper)); + } + return new CodeFunction(env, filename.toString(), body, new Value[0][]); } catch (SyntaxException e) { @@ -901,15 +901,7 @@ public class SimpleRepl { var tsCompiler = wrap(Compiler.get(environment), tsEnvironment, environment, tsCompilerFactory[0]); environment.add(Compiler.KEY, tsCompiler); } - private static void registerFunc(Environment env, FunctionBody body, CompileResult res, Function mapper) { - var map = res.map(mapper); - DebugContext.get(env).onFunctionLoad(body, map); - - for (var i = 0; i < body.children.length; i++) { - registerFunc(env, body.children[i], res.children.get(i), mapper); - } - } public static void main(String args[]) throws InterruptedException { SimpleRepl.args = args;