Skip to content

CraftersMC/molang-compiler

 
 

Repository files navigation

Jenkins

Molang Compiler

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.

MoLang Compliance

See MOLANG_COMPLIANCE.md for detailed compatibility information.

For official MoLang documentation, see:

How to add to your workspace

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'
}

Usage

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 Integration

  • 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.

Compiler Configuration

Compiler Flags

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.

MoLang Version Support

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);

Examples

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()"
    }
}

Credit

Buddy for writing the Java bytecode generation and class loader. https://twitter.com/BuddyYuz

About

⚡ High-speed compiler and executor for the MoLang language by Mojang

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 100.0%