High-speed MoLang compiler and executor designed with per-frame execution in mind. This implementation compiles MoLang expressions to Java bytecode for optimal runtime performance.
See MOLANG_COMPLIANCE.md for detailed compatibility information.
For official MoLang documentation, see:
There are two main ways to use this in your application. If you are writing a Minecraft Mod with NeoForge or Fabric, install Veil which already has the library shadowed. If you don't want to add another library, you can just manually shadow this library into your mod.
plugins {
id 'com.github.johnrengelman.shadow' version "8.1.1"
}
configurations {
shade
}
repositories {
maven {
name = "Jared's maven"
url = "https://maven.blamejared.com/"
}
}
dependencies {
implementation "gg.moonflower:molang-compiler:version"
shade "gg.moonflower:molang-compiler:version"
}
shadowJar {
configurations = [project.configurations.shade]
relocate 'gg.moonflower.molangcompiler', 'your.project.lib.molangcompiler'
}When using this library with a regular java program, you can use GlobalMolangCompiler to retrieve new instances of the standard compiler. The compiler is designed this way to allow the garbage collector to delete old expressions if they aren't needed anymore.
For example
public class Main {
public static void main(String[] args) {
MolangCompiler compiler = GlobalMolangCompiler.get();
}
}When in an environment like NeoForge or Fabric a custom molang compiler instance must be created as a child of the mod class loader. If using Veil, this step is already handled.
@Mod("modid")
public class NeoForgeMod {
public NeoForgeMod() {
MolangCompiler compiler = MolangCompiler.create(CompilerFlags.DEFAULT, NeoForgeMod.class.getClassLoader());
}
}- Java function calls: Execute custom Java functions from MoLang expressions
custom_lib.javaFunction(param1, param2, param3) - Custom libraries: Register your own MoLang libraries with custom functions
Note: When writing MoLang expressions that need to be compatible with Minecraft Bedrock Edition, avoid using these extensions.
This compiler supports various flags to customize compilation behavior. Compiler flags use an enum-based system instead of integer bit flags.
import gg.moonflower.molangcompiler.api.CompilerFlag;
import gg.moonflower.molangcompiler.api.CompilerFlags;
// Use default flags (includes OPTIMIZE)
MolangCompiler compiler = MolangCompiler.create(CompilerFlags.DEFAULT);
// Create custom flag configuration
CompilerFlags customFlags = CompilerFlags.of(CompilerFlag.OPTIMIZE, CompilerFlag.PRINT_CLASSES);
MolangCompiler compilerWithCustomFlags = MolangCompiler.create(customFlags);
// Add flags to existing configuration
CompilerFlags extendedFlags = CompilerFlags.DEFAULT.add(CompilerFlag.WRITE_CLASSES);Available compiler flags:
| Flag | Description |
|---|---|
OPTIMIZE |
Reduces constant expressions at compile time (e.g., 4 * 4 + 2 becomes 18) |
WRITE_CLASSES |
Writes generated bytecode to .class files for debugging |
PRINT_CLASSES |
Prints bytecode information to console for debugging |
Recommended: Always use CompilerFlags.DEFAULT unless you need specific debugging features.
Compiler supports multiple MoLang versions. This allows you to target different MoLang specifications while maintaining backward compatibility.
import gg.moonflower.molangcompiler.api.MolangVersion;
MolangCompiler compiler = GlobalMolangCompiler.get();
// Compile with the latest MoLang version (default)
MolangExpression latest = compiler.compile("math.pow(2, 8)");
// Explicitly specify version
MolangExpression v12 = compiler.compile("math.pow(2, 8)", MolangVersion.LATEST);
// Get a specific version
MolangVersion version = MolangVersion.get(12);
MolangExpression expr = compiler.compile("q.foo * 2", version);Compiling and using expressions:
public class Example {
private final MolangExpression speed;
private final MolangExpression time;
public Example(MolangExpression speed, MolangExpression time) {
this.speed = speed;
this.time = time;
}
// MolangEnvironment#resolve(MolangExpression) allows the caller to handle errors created while resolving
// The most common reason for an error is a variable being used that isn't defined in the environment
// Returns a MolangValue which can be converted to float with asFloat()
public float getSpeed(MolangEnvironment environment) throws MolangRuntimeException {
return environment.resolve(this.speed).asFloat();
}
// Alternatively MolangEnvironment#safeResolve(MolangExpression) can be used to print the stack trace and return 0 on errors
// Returns a MolangValue which can be converted to float with asFloat()
public float getTime(MolangEnvironment environment) {
return environment.safeResolve(this.time).asFloat();
}
public static @Nullable Example deserialize(String speedInput, String timeInput) {
try {
// Note: this cannot be used in a modded environment.
// The compiler used should be a global instance
// created like the NeoForgeMod example
MolangCompiler compiler = GlobalMolangCompiler.get();
// Expressions can be compiled from a valid MoLang string
// Note that compilation is relatively expensive(~15ms sometimes) so it should be done once and cached
MolangExpression speed = compiler.compile(speedInput);
MolangExpression time = compiler.compile(timeInput);
return new Example(speed, time);
} catch (MolangSyntaxException e) {
// The exception gives a message similar to Minecraft commands
// indicating exactly what token was invalid
e.printStackTrace();
return null;
}
}
}Using variables:
public class Foo {
public void run() throws MolangException {
// A runtime is the base implementation of an environment.
// The provided builder can add variables, queries, globals, and extra libraries
MolangEnvironment environment = MolangRuntime.runtime()
.setQuery("foo", 4.0f)
.setQuery("bar", 12.0f)
.create();
Example example = Example.deserialize("(q.foo - q.bar) > 0", "q.foo * q.bar");
// The environment will use the values specified in the builder as replacements when calculating the expression
// In this example, the result will become 4 * 12 = 48
float time = example.getTime(environment);
// See the documentation for more details on adding java functions and variables
}
}Adding Custom MoLang Libraries
public class BarLibrary extends MolangLibrary {
@Override
protected void populate(BiConsumer<String, MolangExpression> consumer) {
// Add all expressions that should be registered under "libname"
// For example, this becomes "libname.secret" in MoLang code
consumer.accept("secret", MolangExpression.of(42));
// You can also add functions that execute Java code
consumer.accept("greet", MolangExpression.function(1, (runtime, params) -> {
String name = params[0].asString();
return MolangValue.of("Hello, " + name + "!");
}));
}
// This name is used for printing and identification.
// The actual namespace of the library is defined when adding it to a MolangEnvironment.
@Override
protected String getName() {
return "libname";
}
public static MolangEnvironment createEnvironmentWithLibrary() {
// Use the builder pattern to create an environment with the custom library
return MolangRuntime.runtime()
.loadLibrary("libname", new BarLibrary())
.setQuery("loadedBar", 1.0f)
.create();
}
public static void addToExistingEnvironment(MolangEnvironment environment) {
// Environments can be immutable and will throw an exception if they are tried to be modified
if (!environment.canEdit()) {
throw new UnsupportedOperationException("Environment is immutable");
}
// Edit the environment and add the library
environment.edit()
.loadLibrary("libname", new BarLibrary())
.setQuery("loadedBar", 1.0f)
.create(); // This returns the same environment instance
// Now MoLang expressions can resolve "libname.secret" and "libname.greet()"
}
}Buddy for writing the Java bytecode generation and class loader. https://twitter.com/BuddyYuz