Skip to main content

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:

  1. Write .nasm in a regular Python cell (or text editor).
  2. Run expand(src) in a Python notebook to get canonical Nock.
  3. Paste the result into a :formula cell 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nockasm-1.1.1.tar.gz (214.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nockasm-1.1.1-py3-none-any.whl (14.1 kB view details)

Uploaded Python 3

File details

Details for the file nockasm-1.1.1.tar.gz.

File metadata

  • Download URL: nockasm-1.1.1.tar.gz
  • Upload date:
  • Size: 214.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.7

File hashes

Hashes for nockasm-1.1.1.tar.gz
Algorithm Hash digest
SHA256 54c321db21cc235844cfbaedacc949592b39d87d3ad8fc81df619fb1527143dc
MD5 9ef6921ac94a51a0adfa9ea3df99e17b
BLAKE2b-256 ff0c9ae81c7080a60bcc172c9aec3953474c0e8a31210070c2ec99fb02a29707

See more details on using hashes here.

File details

Details for the file nockasm-1.1.1-py3-none-any.whl.

File metadata

  • Download URL: nockasm-1.1.1-py3-none-any.whl
  • Upload date:
  • Size: 14.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.7

File hashes

Hashes for nockasm-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 478d012dafac1bd0ab28a56270248e20ebfaf257f1c78349d0fee7c0d94fc91f
MD5 81ef22e1a429c05a7077feb128d75407
BLAKE2b-256 fe794fed4a19b6b48b432fed9308b07f36983879fb6d454f8ee19c34961815df

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page