Nodora

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 includes version.
  • 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. Slot 0 is always the input object; the rest are filled by ops.
  • ops — a list of operations executed in order. Each op may read from any slot and write to its out slot.
  • outputs — maps each out name 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

opArityDescription
cp1Copy args[0] into the output slot.
add2Numeric or string +.
sub2-.
mul2*.
div2/. Division by zero ⇒ undefined.
mod2%. Modulo by zero ⇒ undefined.
and2Boolean &&.
or2Boolean ||.
lt2<.
lte2<=.
gt2>.
gte2>=.
eq2Structural ==.
neq2Structural !=.
in2x in arr.
not1Boolean !.
select3Ternary: args[0] ? args[1] : args[2].
emit1Emit 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 op

Array 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:

  1. Constant folding & constant propagation — repeated up to ten times until a fixed point.
  2. Dead-code elimination — drops ops whose results are never read.
  3. Symbol remap — compacts slot indices so symslots is minimal.

Inspect the result by compiling an example file and reading the JSON output:

nodora compile -f examples/BasicRule.ruleset -o BasicRule.json

Versioning

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.

On this page