An ES5 interpreter for Java
Go to file
TopchetoEU 166e9c0470
All checks were successful
tagged-release / Tagged Release (push) Successful in 3m56s
bump
2025-01-24 03:26:14 +02:00
.github/workflows bump 2025-01-24 03:26:14 +02:00
buildSrc add package uploading 2025-01-24 02:45:26 +02:00
common move FunctionMap logic away from core 2025-01-24 00:21:51 +02:00
compilation add package uploading 2025-01-24 02:45:26 +02:00
lib some minor restructuring 2025-01-24 00:22:15 +02:00
repl move FunctionMap logic away from core 2025-01-24 00:21:51 +02:00
runtime add package uploading 2025-01-24 02:45:26 +02:00
.gitattributes fix: some config files cleanup 2023-12-24 14:31:33 +02:00
.gitignore build: split up into multiple projects, use kotlin DLS 2025-01-10 04:14:40 +02:00
build.gradle.kts fix: forEach is cringe 2025-01-22 20:21:32 +02:00
gradle.properties bump 2025-01-24 03:26:14 +02:00
LICENSE bruh 2023-08-26 12:19:26 +03:00
README.md fix: function mappings were registered incorrectly 2025-01-15 20:12:52 +02:00
settings.gradle.kts add package uploading 2025-01-24 02:45:26 +02:00

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.