Skip to main content

A thin macro expander from Nock Assembly to canonical Nock 4K

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

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.0.0.tar.gz (210.3 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.0.0-py3-none-any.whl (10.0 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for nockasm-1.0.0.tar.gz
Algorithm Hash digest
SHA256 830cc6aa56ec22a6e6be9b9062f6ac7e14e783f4e87d792cad209db75a5ddbd7
MD5 5556ca5e7d08784274d8386749dfef0f
BLAKE2b-256 6c9b9bea90a01b81faeb6122ca4ed4e80a35694012bb6c7bd197514512bba758

See more details on using hashes here.

File details

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

File metadata

  • Download URL: nockasm-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 10.0 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.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 150c19367811ccaac5b370033d5c4f7d8f36e6f8bc293242e08051d25c9340c1
MD5 ebe3dafd9dba9216099c8a4ed56d4938
BLAKE2b-256 c54e330f7edb302dc6578ce268812a475c4d9b6b0eb73b412b083ea354ce7042

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