NomiScript Language Reference
Table of Contents
Language features
Basic syntax
The language is based on S-expressions being a kind of domain-specific LISP dialect. The approach is chosen to simplify the processing of lists, provide lower complexity and flattened learning curve.
Comments
Comments start with a semicolon (;) and extend to the end of the line:
; This is a comment (defun square (x) ; inline comment (* x x))
Comments are ignored by the parser and do not affect program execution.
Annotations
Annotations are special comments that start with ; @ followed by an
annotation name and an optional S-expression value:
; @test (= (count 'defun) 5) ; @version 1 ; @author "John Doe"
Annotations are parsed and stored separately from program expressions, making them available for tooling, testing, and metadata processing. The annotation value can be any valid S-expression.
Types
Nil
The nil literal represents the absence of a value and the empty list.
It is falsy in boolean contexts.
Booleans
Boolean literals are #t for true and #f for false:
#t ; true #f ; false
Numbers
The only supported number primitive is a fraction. Every numeric input is
converted to appropriate fraction (i.e. 10 becomes (/ 10 1)).
Decimal input is supported as syntax sugar (i.e. 0.1 becomes (/ 1 10)) but
all the internal processing is only done in fractions.
Negative numbers are supported with a leading minus sign: -42, -0.5.
Commodity values
A commodity-bearing value (printed as (:commodity <ratio> :id
"<uuid>")) is a fraction tagged with the originating commodity's
entity id. account-balance returns one; the commodity-aware
arithmetic helpers consume them.
Type discipline: a Commodity is not a Ratio. The compiler refuses
(+ <commodity> <ratio>) and the equivalent for the other binary
ops; the runtime traps when two Commodity values carry different
commodity ids. The only bridge between strata is
(convert-commodity <amount> "<target-uuid>"), which goes through
the Prices table.
Three numeric strata:
| Strata | Examples | Role |
|---|---|---|
| I32 | (account-count) |
Cardinalities, flags. No mixing with Ratio. |
| Ratio | 1/2, 10, 0.05, (* 0.1 ratio) |
Dimensionless scalars, percentages, rates. |
| Commodity | (account-balance "...") |
Money / asset amounts. Tagged with commodity. |
Arithmetic dispatch (refuse = compile-time error, trap = runtime trap on commodity-id mismatch):
| Op | Ratio × Ratio | Commodity × Ratio | Ratio × Commodity | Commodity × Commodity (same) | Commodity × Commodity (diff) |
|---|---|---|---|---|---|
+ |
Ratio | refuse | refuse | Commodity | trap |
- |
Ratio | refuse | refuse | Commodity | trap |
* |
Ratio | Commodity | Commodity | refuse (no Commodity²) | refuse |
/ |
Ratio | Commodity | refuse | Ratio (dimensionless rate) | trap |
| comp | bool | refuse | refuse | bool | trap |
Note: the value-level term "commodity" refers to the rational +
commodity-id tagged scalar described here. The entity-level
finance::commodity::Commodity row (a currency or asset definition
in the Commodities table) is the target of the id field — the two
share a name by intent, since every commodity-bearing value is
anchored to exactly one Commodity entity.
Strings
UTF-8 strings can be defined via:
"character:"Hello"is a string, escaping with\is supported"""sequence for long and multi-line strings and texts
Supported escape sequences: \n (newline), \t (tab), \r (carriage return),
\\ (backslash), \" (quote).
Symbols
Symbols are literal names of language objects that can contain any non-space characters, they can also be internalized from the Strings.
Symbols cannot start with digits and cannot contain parentheses, quotes, or whitespace.
Cons cells (pairs)
Cons cells are the fundamental building block for compound data. A cons cell
contains two values: car (first) and cdr (rest). Dotted pair notation
creates cons cells directly:
(a . b) ; cons cell with car=a, cdr=b (1 . (2 . nil)) ; chain forming a proper list (1 2) (1 2 . 3) ; improper list ending in 3
Proper lists (where the final cdr is nil) are displayed without dots:
(a . (b . (c . nil))) ; displays as (a b c)
Lists
Lists are sequences of expressions enclosed in parentheses:
(1 2 3) (+ a b) (defun foo (x) (* x x))
Lists are syntactic sugar - internally they may be represented as chains of cons cells or as vectors depending on context. They are used for both data and code (function calls, special forms).
Quoted expressions
The quote character (') prevents evaluation of the following expression:
#+beginsrc lisp
'foo ; the symbol foo, not its value
'(1 2 3) ; a list, not a function call
'(+ 1 2) ; the list (+ 1 2), not 3
Functions
Functions are parts of code having arguments (0 or more) and return value (0 or 1).
In-place functions
There's a lambda keyword for declaring function right in place
(lambda () 1)
Named functions
Traditional defun allows to define a function with a name
(defun sum (a b) "Sums `A' and `B' numbers" (+ a b))
Entity API
The entity API provides access to the binary input/output format defined in the script format specification.
Available entity functions
Entity-related functions registered in symbol/builtins.rs (the
P3a-34 split moved register_entity_accessors out of the legacy
symbol.rs — fields with ACCOUNT-*, COMMODITY-*, … and
the new NODE-* accessors for the typed report tree from
P4 A6.b live here):
entity-countcontext-typeprimary-entity-typeprimary-entity-idxentity-typeentity-parent-idxtransaction-split-counttransaction-tag-counttransaction-is-multi-currencytransaction-post-datetransaction-enter-datesplit-value-numsplit-value-denomsplit-valuesplit-reconcile-statesplit-reconcile-datesplit-account-namecreate-tagtag-nametag-valuedelete-entityaccount-idaccount-nameaccount-parentcommodity-idcommodity-symbolcommodity-nametransaction-idtransaction-notesplit-idsplit-account-idsplit-commodity-idsplit-amounttag-entity-idtag-entity-nametag-entity-valueprice-idprice-commodity-idprice-currency-idprice-valueprice-datessh-key-idssh-key-fingerprintssh-key-namenode-idnode-labelnode-depthnode-amountnode-children
Entity type constants
Extracted from symbol/builtins.rs and scripting/format/src/lib.rs:
+ENTITY-TRANSACTION+= 0+ENTITY-SPLIT+= 1+ENTITY-TAG+= 2+ENTITY-ACCOUNT+= 3+ENTITY-COMMODITY+= 4
Context type constants
+CONTEXT-CREATE+= 0+CONTEXT-UPDATE+= 1+CONTEXT-DELETE+= 2+CONTEXT-BATCH+= 3
Financial struct definitions
Struct fields from symbol/stdlib.rs (P3a-34 split moved
load_financial_structs out of the registry file), registered via
DEFSTRUCT:
- TRANSACTION:
idparent-idxpost-dateenter-datesplit-counttag-countis-multi-currency - SPLIT:
idparent-idxaccount-idcommodity-idvalue-numvalue-denomreconcile-statereconcile-date - TAG:
idparent-idxnamevalue - ACCOUNT:
idparent-idxparent-account-idnamepathtag-count - COMMODITY:
idparent-idxsymbolnametag-count
Output functions
(create-tag parent-idx name value) ; create a tag entity in output (delete-entity entity-idx) ; mark entity for deletion in output
Typed-entity accessor surface
P4 A2 / A5 introduced WasmType::EntityRef(EntityKind) — a typed
GC-managed struct ref per server entity (Account, Commodity,
Transaction, Split, Tag, Price, SshKey, ReportNode). Each entity
exposes per-field accessor natives generated by the
nomi_entity_accessors! proc macro in supp_macro/: passing the
wrong kind into an accessor is a compile-time type error rather
than a silent field-misread. The hierarchical report tree from
balance-report (P4 A6.b) rides EntityRef(ReportNode) with a
recursive node-children accessor returning pair<report_node>,
so (dolist (n (node-children r)) ...) walks the report tree
without leaving the typed lattice.
The full registry — every typed native + its wasm import +
parameter signature + return type — autogenerates into
nativereference.org
via cargo run -p rpc --bin emit-bindings. Re-run after adding /
changing a HostFnSpec; a drift detector in rpc/tests/ fails
CI if the committed file diverges from the live registry.
The design rationale (host-allocated Rooted<StructRef> /
Rooted<ArrayRef> over plist strings, retirement of the
wraps_ratio / wraps_commodity / self_captures flags) lives
in ADR-0013.
Trigger function
Define should-apply to control when a script runs:
(defun should-apply ()
(= (primary-entity-type) +entity-transaction+))
If not defined, the script always runs (default returns 1).