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.

  • Defining and converting

    There are usual intern, quote, and eval for conversions.

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-count
  • context-type
  • primary-entity-type
  • primary-entity-idx
  • entity-type
  • entity-parent-idx
  • transaction-split-count
  • transaction-tag-count
  • transaction-is-multi-currency
  • transaction-post-date
  • transaction-enter-date
  • split-value-num
  • split-value-denom
  • split-value
  • split-reconcile-state
  • split-reconcile-date
  • split-account-name
  • create-tag
  • tag-name
  • tag-value
  • delete-entity
  • account-id
  • account-name
  • account-parent
  • commodity-id
  • commodity-symbol
  • commodity-name
  • transaction-id
  • transaction-note
  • split-id
  • split-account-id
  • split-commodity-id
  • split-amount
  • tag-entity-id
  • tag-entity-name
  • tag-entity-value
  • price-id
  • price-commodity-id
  • price-currency-id
  • price-value
  • price-date
  • ssh-key-id
  • ssh-key-fingerprint
  • ssh-key-name
  • node-id
  • node-label
  • node-depth
  • node-amount
  • node-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: id parent-idx post-date enter-date split-count tag-count is-multi-currency
  • SPLIT: id parent-idx account-id commodity-id value-num value-denom reconcile-state reconcile-date
  • TAG: id parent-idx name value
  • ACCOUNT: id parent-idx parent-account-id name path tag-count
  • COMMODITY: id parent-idx symbol name tag-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).