A thin macro expander from Nock Assembly to canonical Nock 4K, with a Jupyter kernel
Project description
Nock Assembly
Nock Assembly is a thin macro over Nock ISA designed to make the language more legible for pedagogical purposes.
Design
| Named opcodes | (%inc .x) instead of [4 0 2]. Pure lexical. |
| Axis schemas | :subject {.a .b .c} resolves .a .b .c to axes 2, 6, 7. Right-leaning by Hoon convention. |
#let .name = E in B |
Opcode-8 push. Tracks subject shift via +peg(3, n) so old names still resolve in body. |
#match E { ... } |
Scrutinee lifted once via opcode 8. Nested opcode-6 dispatch on literal patterns. Required _ => default. |
; comments |
And whitespace. |
Install / use
pip install nockasm
from nockasm import expand
print(expand("(%inc (%self))"))
# [4 0 1]
print(expand("""
:subject {.tag .data}
#match .tag {
1 => (%inc .data)
2 => .data
_ => 0
}
"""))
# [8 [0 2] 6 [5 [1 1] 0 2] [4 0 7] 6 [5 [1 2] 0 2] [0 7] 1 0]
End-to-end with pinochle:
from pinochle import nock, parse_noun
from nockasm import expand
src = """
:subject {.before .target .after}
#let .next = (%inc .target) in
[.before .next .after]
"""
formula = parse_noun(expand(src))
result = nock(parse_noun("[10 41 99]"), formula)
# result == [10 42 99]
At the CLI:
python -m nockasm program.nasm # canonical flat
python -m nockasm --pretty program.nasm # explicit binary cells
echo "(%inc (%self))" | python -m nockasm
Integration with the Nock kernel
Pinochle ships nock-kernel for Jupyter (Nock 4K kernel). It accepts
canonical Nock in :formula cells. Workflow today:
- Write
.nasmin a regular Python cell (or text editor). - Run
expand(src)in a Python notebook to get canonical Nock. - Paste the result into a
:formulacell in a Nock notebook.
A :asm cell magic for the Nock kernel that does this in one step is the
obvious next step. Roughly:
# in pinochle/packages/nock_kernel/kernel.py
if cell.startswith(':asm'):
from nockasm import expand
formula_src = expand(cell[len(':asm'):])
# then dispatch as if user had typed ':formula <formula_src>'
Structural macros
#let .name = VALUE in BODY
Pushes VALUE onto the subject via opcode 8 and binds .name to axis 2 in
BODY. Any axes that were already in scope are shifted rightward via
+peg(3, axis), so the old names still resolve in the body.
:subject {.before .target .after}
#let .next = (%inc .target) in
[.before .next .after]
; -> [8 [4 0 6] [0 6] [0 2] 0 15]
; against [10 41 99] -> [10 42 99]
VALUE and BODY are both formula positions (bare atoms lift). Shadowing
an existing schema name is a compile error.
#match EXPR { PAT => BODY ... _ => DEFAULT }
Pattern match on the value of EXPR. The scrutinee is evaluated once via
opcode 8 — i.e. lifted onto the subject — then each PAT is compared
against the lifted value via opcode 5 (eq), with opcode 6 (if) dispatching
to the matching BODY. The _ => default is required.
:subject {.tag .data}
#match .tag {
1 => (%inc .data)
2 => .data
_ => 0
}
; -> [8 [0 2] 6 [5 [1 1] 0 2] [4 0 7] 6 [5 [1 2] 0 2] [0 7] 1 0]
; against [1 41] -> 42
; against [2 41] -> 41
; against [9 41] -> 0
EXPR and each BODY are formula positions. PATs are noun literals —
they're compared against the scrutinee's runtime value, not against a
formula. Bare atoms in PAT position are not lifted: writing 1 => ...
matches the atom 1, not the formula [1 1].
In the body of each arm (and the default), the scrutinee is at axis 2, and
the original schema axes are shifted rightward via +peg(3, axis) — same
shift rule as #let. That's why .data resolves to [0 7] (not [0 3])
in the example above.
What lifts and what doesn't
Bare atoms get lifted to [1 atom] in formula positions. Not in noun-literal
positions (%const arg, hint tag) or axis positions (%slot arg, %call
arity arg, etc.). The per-opcode kinds:
| Opcode | Kinds | Notes |
|---|---|---|
%slot N |
a | axis literal |
%const X |
n | any noun, no lift |
%arm X |
n | synonym for %const; intent: callable formula |
%crash |
— | [0 0] — Nock crash idiom |
%self |
— | [0 1] — whole subject |
%battery |
— | [0 2] — standard core battery |
%payload |
— | [0 3] — standard core payload |
%sample |
— | [0 6] — standard gate sample |
%context |
— | [0 7] — standard gate context |
%eval |
ff | both formulas |
%isa |
f | |
%inc |
f | |
%eq |
ff | |
%if |
fff | branches lift |
%comp |
ff | |
%push |
ff | |
%call N F |
af | |
%edit N V F |
aff | |
%hint T F |
nf | tag is a noun literal |
%hintd T C F |
nff | clue is a formula — per 4K spec it's evaluated |
The intent-marking opcodes (%arm, %crash, and the axis aliases) all lower
to the same cells as their %const / %slot equivalents — they exist purely
to surface meaning at the source level. %arm X is %const X for cases
where X is a formula that will later be invoked via %call; %self
through %context name the standard Hoon core/gate axes.
#let value and body are formulas. #match scrutinee and arm bodies are
formulas. Match patterns are noun literals (compared against the
scrutinee's evaluated value).
Raw cells [...] are taken structurally: their elements are not lifted.
That gives you an escape hatch into raw Nock when you need it, and the
cons-formula distribution pattern works as expected:
:subject {.a .b}
[(%inc .a) (%inc .b)]
; -> [[4 0 2] [4 0 3]]
; against [3 5] -> [4 6] via Nock distribution
Tests
python test_nockasm.py # unit tests, 55 cases
python test_e2e.py # end-to-end: expand -> pinochle -> verify, 19 cases
python test_benchmarks.py # urbit/benchmark equivalents, 5 cases (loaded from disk)
test_benchmarks.py reads benchmarks/tests.json and benchmarks/<name>.nasm
from disk and runs each through pinochle. The five benchmarks present
(dec, add, factorial, fibonacci, ackermann) are faithful
transcriptions of urbit/benchmark/desk/bar/<name>.nock — each .nasm
expands to a noun bit-identical to the corresponding .nock formula.
License
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nockasm-1.1.0.tar.gz.
File metadata
- Download URL: nockasm-1.1.0.tar.gz
- Upload date:
- Size: 213.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
439ce88406cce57b5a3c5c44b85c52ba968e4b7762abc2e2bd9695a8243e6c8b
|
|
| MD5 |
f21e1568186ae7deb78c51fede21c272
|
|
| BLAKE2b-256 |
d9ddcf74f53f120dfa7c04c361b49c1835c229d1816d5d99d6855d1ed9b05796
|
File details
Details for the file nockasm-1.1.0-py3-none-any.whl.
File metadata
- Download URL: nockasm-1.1.0-py3-none-any.whl
- Upload date:
- Size: 13.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dff68769fdf344e92808524192cb0f356ffa765bc07d228603592523d9f316db
|
|
| MD5 |
4ad7fb52a6bb700d090ee1d44ef8ec61
|
|
| BLAKE2b-256 |
213b2eab8ca5d84a4824320edd814613a14126cc326ef00ddb23bf83111ddbe7
|