NIR Specification
The Nodora Intermediate Representation
NIR (Nodora Intermediate Representation) is the compiled, JSON-encoded form of a Nodora program. The compiler emits NIR; the evaluator consumes it. You can serialize NIR, store it, send it over the wire, and load it later in any host that has a Nodora evaluator (Go, WASM/JS, etc.).
NIR is not meant to be hand-written, but it is documented so you can inspect compiled output, build tooling around it, or implement another runtime.
Top-level shape
{
"meta": { "version": "..." },
"signals": {
"SignalName": { "params": [{ "name": "p1" }, { "name": "p2" }] }
},
"rules": {
"RuleName": {
"outputs": { "out_name": { "sym": <int> } },
"symslots": <int>,
"ops": [ /* Op[] */ ]
}
}
}meta— free-form metadata. Currently includesversion.signals— declared signals and their parameter names.rules— each rule keyed by name.
Rules
A compiled rule is a register machine:
symslots— number of value slots reserved for the rule. Slot0is always theinputobject; the rest are filled by ops.ops— a list of operations executed in order. Each op may read from any slot and write to itsoutslot.outputs— maps eachoutname to the slot that holds its value at the end of execution.
{
"outputs": { "is_adult": { "sym": 1 } },
"symslots": 2,
"ops": [
{
"op": "gte",
"args": [
{ "path": "age", "from": { "sym": 0 } },
{ "imm": 18 }
],
"out": 1
}
]
}Operations
op | Arity | Description |
|---|---|---|
cp | 1 | Copy args[0] into the output slot. |
add | 2 | Numeric or string +. |
sub | 2 | -. |
mul | 2 | *. |
div | 2 | /. Division by zero ⇒ undefined. |
mod | 2 | %. Modulo by zero ⇒ undefined. |
and | 2 | Boolean &&. |
or | 2 | Boolean ||. |
lt | 2 | <. |
lte | 2 | <=. |
gt | 2 | >. |
gte | 2 | >=. |
eq | 2 | Structural ==. |
neq | 2 | Structural !=. |
in | 2 | x in arr. |
not | 1 | Boolean !. |
select | 3 | Ternary: args[0] ? args[1] : args[2]. |
emit | 1 | Emit a signal. args[0] is a signal expression. |
The out field is the integer slot where the result is written. Ops
with no out (such as emit) omit it.
Expression nodes
Each args[i] is one of the following tagged JSON shapes. The shape is
discriminated by which key is present.
Immediate
A constant value baked into the program.
{ "imm": 18 }
{ "imm": "us" }
{ "imm": true }Symbol
A reference to a previously computed slot.
{ "sym": 0 } // the input object
{ "sym": 4 } // value produced by an earlier opArray literal
{ "arr": [ { "imm": 1 }, { "sym": 3 } ] }Object literal
{ "obj": { "user_id": { "sym": 0 }, "role": { "imm": "admin" } } }Selector
A path access like input.user.name. Path components are dot-separated.
A literal $ segment is a computed key, with its expression supplied in
the $ array.
{
"path": "user.tags",
"from": { "sym": 0 }
}{
"path": "user.$",
"from": { "sym": 0 },
"$": [ { "sym": 5 } ]
}Index
Array element access by integer expression.
{
"idx": { "imm": 0 },
"from": { "sym": 2 }
}Call
A built-in or registered function invocation.
{
"call": { "ns": "strings", "name": "upper" },
"args": [ { "path": "name", "from": { "sym": 0 } } ]
}ns is omitted for core functions.
Signal
Only valid inside an emit op.
{
"signal": "BlockAccount",
"args": [ { "path": "user_id", "from": { "sym": 0 } } ],
"when": { "sym": 3 }
}when is optional. If present, the signal is queued only when its
expression evaluates to true.
Lambda
A first-class function used by higher-order builtins like
some and arrays::group_by.
{
"params": [ { "sym": 4 } ],
"ops": [ /* nested Op[] */ ]
}Each entry in params declares which slot receives the corresponding
call argument. The lambda's return value is whatever the last op with
an out slot wrote.
Optimizations
The compiler runs the following passes after lowering:
- Constant folding & constant propagation — repeated up to ten times until a fixed point.
- Dead-code elimination — drops ops whose results are never read.
- Symbol remap — compacts slot indices so
symslotsis minimal.
Inspect the result by compiling an example file and reading the JSON output:
nodora compile -f examples/BasicRule.ruleset -o BasicRule.jsonVersioning
The shape above is stable across patch releases of the runtime. Field
keys are intentionally short (sym, imm, arr, obj, idx,
path, call, args, op, out) to keep serialized programs
compact.