TopchetoEU
eff076f6fe
All checks were successful
tagged-release / Tagged Release (push) Successful in 3m56s
|
||
---|---|---|
.github/workflows | ||
buildSrc | ||
common | ||
compilation | ||
lib | ||
repl | ||
runtime | ||
.gitattributes | ||
.gitignore | ||
build.gradle.kts | ||
gradle.properties | ||
LICENSE | ||
README.md | ||
settings.gradle.kts |
J2S (Java-JavaScript or Java to JavaScript)
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. 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.
How to use?
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.
var engine = new Engine();
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:
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:
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.all()) {
DebugContext.get(env).onFunctionLoad(el.body(), el.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:
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.