A parser and evaluator for Molang.
This library is a successor to our prior MQL library. Expressions are compiled into an efficient tree of specialized nodes, there is no runtime class loading so it is safe for native image.
- Basic operators (supported unless mentioned otherwise)
- Variables (persistent and temporary)
- Builtin math libraries
- Custom query objects
- Structs
- Arrays
- Cross-object accessors (arrow operator)
repositories {
mavenCentral()
}
dependencies {
implementation("dev.hollowcube:molang:<latest release>")
}double result = MolangProgram.compile("math.sqrt(16) + 1")
.eval(new MolangState()); // 5.0Programs are compiled once and evaluated many times, and variables persist in the state between evaluations:
var counter = MolangProgram.compile("v.x = (v.x ?? 0) + 1; v.x");
var state = new MolangState();
counter.eval(state); // 1.0
counter.eval(state); // 2.0Compile source into a MolangProgram against a MolangEnvironment, which describes the names expressions can read,
then evaluate it with a MolangState, which holds one entity's variables.
record Ctx(Entity entity, double animTime) {}
static final MolangEnvironment<Ctx> ENV = MolangEnvironment.<Ctx>builder()
.query(q -> q // query, also reachable as q
.number("anim_time", Ctx::animTime)
.bool("is_on_ground", ctx -> ctx.entity().isOnGround())
.stringFunction("is_item_equipped", (ctx, slot) -> ctx.entity().hasItem(slot) ? 1 : 0))
.build();
var program = ENV.compile("math.sin(q.anim_time * 90)");
// Scripts of several statements work the same way
var script = ENV.compile("""
temp.x = 1 + 2 + 3;
v.y = temp.x + 2;
""");
var state = new MolangState();
var result = program.eval(state, new Ctx(entity, time)); // Returns a double
var y = state.getVariable("y"); // 8.0
// Content errors from execution, eg "Division by zero at line 2, column 5"
var errors = state.getErrors();Environments and programs are immutable and can be shared between threads, so build the environment once, compile each
expression once (eg when loading a model) and keep a state per context (ie entity). Expressions that read no host values
can use MolangProgram.compile(source) and program.eval(state).
program.analysis() describes what a program depends on, worked out while compiling:
constant(): its value if it was folded entirely, eg to skip evaluating it.- Eg the first example would hold a constant of 5.0
hostNames(),dynamicNames()andunresolvedNames(): the names it reads, as canonical names (q.xis reported asquery.x). Unresolved names evaluate to null (and calling one is a content error), so a loader may want to warn about them.variablesRead(),variablesWritten(),usesTemps()andrandom().dependsOnlyOn(names): whether its result depends only on the given host names, eg to precompute the frames of an animation that reads nothing butquery.anim_time.
The analysis is conservative: a name in a branch that folding removed is still reported.
A namespace can hold:
number,boolandstringvalues of the context.functions of one to three numbers, astringFunctionof one string, andvariadicfunctions of any number of numbers. Arguments of the wrong type are content errors.purefunctions of zero to three numbers, which do not read the context, so calls with constant arguments fold, andconstantnumbers (a pure function of no arguments).valuefunctions taking and returning anyMolangValue, for what the typed forms cannot express.optionalNumbervalues, which are null when missing (so??replaces them).- nested
namespaces (egq.rider.yaw), anddynamicnamespaces whose names are only known at runtime. A namespace (or the top level, throughglobal) can also have adynamicfallback that answers every name it does not register.
variables binds read-only host values under variable (v), eg an entity's position as v.x; other variables stay
the state's own unless that namespace has a dynamic fallback. query (q) and context (c) have builder methods,
namespace registers any other top level name, and global registers names at the top level, eg a function called as
name(). Names are case-insensitive. math, variable and temp (and m, v and t) are built in, and return,
loop, for_each, break, continue and this are keywords, so none of them can be registered at the top level.
Host functions report problems by throwing MolangContentException, which becomes a content error that evaluates to 0; any
other exception is also reported as a content error.
Beyond Bedrock's Molang, this library accepts:
- Exponent literals, eg
3.27e-7or1E3. Bedrock has no exponent syntax, but numbers written by other tools (eg JavaScript'sString(1e-7)or JSON serializers) often use it, and a number directly followed byeis otherwise a syntax error, so no valid expression changes meaning. - Unary
+, eg2 * +3, which gives its operand as a number. mas an alias ofmath, alongside Bedrock'sq,v,tandc.- Top level functions, eg
name(), when the host registers them withglobal.
- A complex expression without
returnevaluates to its last statement, rather than 0. - A trailing
;is allowed in a simple expression. math.sign(0)is 0, rather than -1.
Contributions via PRs and issues are always welcome.
This project is licensed under the MIT License.