Nockasm#
nockasm is a thin Python macro layer over canonical Nock, designed to make hand-written Nock more legible. It is not a separate language or a compiler; every nockasm expression expands to a plain Nock noun that you can paste directly into any Nock evaluator. The primary audience is people learning to write Nock by hand or verifying that a piece of Nock does what they think it does.
This notebook uses the Nockasm Jupyter kernel, which assembles each cell to canonical Nock 4K and evaluates the result via pinochle. Output shows the expanded formula on a ;-prefixed comment line, followed by the computed value.
Note
Install the kernel once before opening this notebook:
pip install "nockasm[kernel]"
nockasm-kernel-install
Then select Nockasm from the kernel menu.
Named opcodes#
Raw Nock opcodes are bare numbers. nockasm replaces them with named forms:
|
Canonical Nock |
Meaning |
|---|---|---|
|
|
address slot N in subject |
|
|
produce literal X |
|
|
evaluate F against G |
|
|
test cell/atom |
|
|
increment atom |
|
|
test equality |
|
|
conditional branch |
|
|
compose formulas |
|
|
push F onto subject, eval G |
|
|
call arm at slot N |
|
|
edit slot N of F with V |
|
|
static hint |
|
|
dynamic hint with clue |
Axis aliases name the standard Hoon core slots: (%self) → [0 1], (%battery) → [0 2], (%payload) → [0 3], (%sample) → [0 6], (%context) → [0 7].
; Increment the subject — default subject is atom 0, so result is 1
(%inc (%self))
; [4 0 1]
1
; Test equality of slots 2 and 3 — subject [0 0], result 0 (Nock true)
#subject [0 0]
(%eq (%slot 2) (%slot 3))
SyntaxError: unknown macro #subject
Traceback (most recent call last):
File "/opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages/nockasm_kernel/kernel.py", line 67, in do_execute
output = self._dispatch(code.strip())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages/nockasm_kernel/kernel.py", line 104, in _dispatch
return self._assemble_eval(code)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages/nockasm_kernel/kernel.py", line 185, in _assemble_eval
noun = expand_to_noun(code)
^^^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages/nockasm.py", line 597, in expand_to_noun
schema, expr = Parser(toks).parse_program()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages/nockasm.py", line 292, in parse_program
expr = self._parse_expr()
^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages/nockasm.py", line 355, in _parse_expr
return self._parse_macro()
^^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages/nockasm.py", line 433, in _parse_macro
raise SyntaxError(f"unknown macro {m.value}")
SyntaxError: unknown macro #subject
; Conditional: if slot 2 == 0 return 1, else return slot 2
; subject [0 99]: slot 2 is 0, true branch fires, returns 1
#subject [0 99]
(%if (%eq (%slot 2) (%const 0)) (%const 1) (%slot 2))
Axis schemas#
:subject {.a .b .c} binds names to the standard right-leaning binary tree addresses (axis 2, 6, 7 for a three-element subject, following Hoon convention). After the declaration, .a, .b, .c expand to [0 2], [0 6], [0 7] respectively.
The tree layout for a two-element subject [x y]:
1
/ \
2 3
(x) (y)
For three elements [x y z] (right-leaning: [x [y z]]):
1
/ \
2 3
(x) / \
6 7
(y) (z)
; Two-element subject: .x at axis 2, .y at axis 3
; [5 5] -> .x == .y, result 0 (Nock true)
#subject [5 5]
:subject {.x .y}
(%eq .x .y)
; Three-element subject: .target at axis 6 — increment it
#subject [10 41 99]
:subject {.before .target .after}
(%inc .target)
#let .name = VALUE in BODY#
Pushes VALUE onto the subject via opcode 8, binding .name to the new head (axis 2). Existing schema names shift rightward through peg(3, axis) so they remain valid in BODY.
This is the nockasm equivalent of a local let binding: it evaluates VALUE against the current subject and pushes the result as a new head, making it available under .name while the body runs.
; Bind .next to the incremented .target, then return all three values.
; subject [10 41 99] -> [10 42 99]
#subject [10 41 99]
:subject {.before .target .after}
#let .next = (%inc .target) in
[.before .next .after]
#match EXPR { PAT => BODY ... _ => DEFAULT }#
Evaluates EXPR once via opcode 8, then dispatches on its value using nested opcode-6 branches. The _ => default branch is required. Patterns are noun literals compared against the scrutinee at runtime.
Each pattern arm compiles to a nested [6 [5 [1 PAT] 0 2] BODY ...] chain, so a three-arm match with a default produces two nested if branches sharing a single evaluation of the scrutinee.
The same formula handles all three subjects below — the expansion (; line) is identical each time.
; tag=1: increment .data -> 42
#subject [1 41]
:subject {.tag .data}
#match .tag {
1 => (%inc .data)
2 => .data
_ => 0
}
; tag=2: return .data -> 41
#subject [2 41]
:subject {.tag .data}
#match .tag {
1 => (%inc .data)
2 => .data
_ => 0
}
; tag=9: default arm -> 0
#subject [9 41]
:subject {.tag .data}
#match .tag {
1 => (%inc .data)
2 => .data
_ => 0
}
Combining features#
The macros compose freely. Here is a small decrement-style program: given a subject [n acc], match on n and either return acc when n is zero or recurse (expressed as #let + #match) incrementing acc.
; Nested let + match: .a == .b (both 5), so equality is 0 (true),
; the 0-branch fires and increments .a -> 6
#subject [5 5]
:subject {.a .b}
#let .eq = (%eq .a .b) in
#match .eq {
0 => (%inc .a)
_ => .b
}
Relation to canonical Nock#
nockasm is a preprocessor, not a language. Every expression it accepts expands to a noun that is bit-identical to what you would write by hand. There is no runtime, no separate VM, and no overhead beyond the Python expansion step itself. The benchmark programs bundled with the library (dec, add, factorial, fibonacci, ackermann) each expand to nouns bit-identical to the corresponding reference formulas from the Urbit benchmark suite.
Note
See The Assembly Language Approach for background on the relationship between Nock and conventional assembly language. nockasm has a similar relationship to canonical Nock as assembly language has to machine code: it is a more legible way to express the same formulas, but it is not a separate language or runtime. But it’s not a machine-style assembly language.
CLI#
Outside of notebooks, nockasm can be driven from the shell:
python -m nockasm program.nasm # canonical flat Nock
python -m nockasm --pretty program.nasm # explicit binary cells
echo "(%inc (%self))" | python -m nockasm
The CLI reads .nasm source files or stdin and writes the expanded noun to stdout, making it easy to pipe into any Nock evaluator or use in a build pipeline.