nockasm 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(python *)",
5
+ "Bash(git add *)",
6
+ "Bash(git commit -m ' *)",
7
+ "Bash(git push *)"
8
+ ]
9
+ }
10
+ }
nockasm-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,204 @@
1
+ Metadata-Version: 2.4
2
+ Name: nockasm
3
+ Version: 1.0.0
4
+ Summary: A thin macro expander from Nock Assembly to canonical Nock 4K
5
+ Author-email: Sigilante <davisneale@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.8
8
+ Description-Content-Type: text/markdown
9
+
10
+ # Nock Assembly
11
+
12
+ ![](./img/hero.jpg)
13
+
14
+ Nock Assembly is a thin macro over [Nock ISA](https://nock.is) designed to make the language more legible for pedagogical purposes.
15
+
16
+ ## Design
17
+
18
+ | | |
19
+ |---|---|
20
+ | Named opcodes | `(%inc .x)` instead of `[4 0 2]`. Pure lexical. |
21
+ | Axis schemas | `:subject {.a .b .c}` resolves `.a` `.b` `.c` to axes 2, 6, 7. Right-leaning by Hoon convention. |
22
+ | `#let .name = E in B`| Opcode-8 push. Tracks subject shift via `+peg(3, n)` so old names still resolve in body. |
23
+ | `#match E { ... }` | Scrutinee lifted once via opcode 8. Nested opcode-6 dispatch on literal patterns. Required `_ =>` default. |
24
+ | `; comments` | And whitespace. |
25
+
26
+ ## Install / use
27
+
28
+ ```python
29
+ from nockasm import expand
30
+ print(expand("(%inc (%self))"))
31
+ # [4 0 1]
32
+
33
+ print(expand("""
34
+ :subject {.tag .data}
35
+ #match .tag {
36
+ 1 => (%inc .data)
37
+ 2 => .data
38
+ _ => 0
39
+ }
40
+ """))
41
+ # [8 [0 2] 6 [5 [1 1] 0 2] [4 0 7] 6 [5 [1 2] 0 2] [0 7] 1 0]
42
+ ```
43
+
44
+ End-to-end with [`pinochle`](https://github.com/sigilante/pinochle):
45
+
46
+ ```python
47
+ from pinochle import nock, parse_noun
48
+ from nockasm import expand
49
+
50
+ src = """
51
+ :subject {.before .target .after}
52
+ #let .next = (%inc .target) in
53
+ [.before .next .after]
54
+ """
55
+
56
+ formula = parse_noun(expand(src))
57
+ result = nock(parse_noun("[10 41 99]"), formula)
58
+ # result == [10 42 99]
59
+ ```
60
+
61
+ At the CLI:
62
+
63
+ ```bash
64
+ python -m nockasm program.nasm # canonical flat
65
+ python -m nockasm --pretty program.nasm # explicit binary cells
66
+ echo "(%inc (%self))" | python -m nockasm
67
+ ```
68
+
69
+ ## Integration with the Nock kernel
70
+
71
+ Pinochle ships `nock-kernel` for Jupyter (`Nock 4K` kernel). It accepts
72
+ canonical Nock in `:formula` cells. Workflow today:
73
+
74
+ 1. Write `.nasm` in a regular Python cell (or text editor).
75
+ 2. Run `expand(src)` in a Python notebook to get canonical Nock.
76
+ 3. Paste the result into a `:formula` cell in a Nock notebook.
77
+
78
+ A `:asm` cell magic for the Nock kernel that does this in one step is the
79
+ obvious next step. Roughly:
80
+
81
+ ```python
82
+ # in pinochle/packages/nock_kernel/kernel.py
83
+ if cell.startswith(':asm'):
84
+ from nockasm import expand
85
+ formula_src = expand(cell[len(':asm'):])
86
+ # then dispatch as if user had typed ':formula <formula_src>'
87
+ ```
88
+
89
+ ## Structural macros
90
+
91
+ ### `#let .name = VALUE in BODY`
92
+
93
+ Pushes `VALUE` onto the subject via opcode 8 and binds `.name` to axis 2 in
94
+ `BODY`. Any axes that were already in scope are shifted rightward via
95
+ `+peg(3, axis)`, so the old names still resolve in the body.
96
+
97
+ ```
98
+ :subject {.before .target .after}
99
+ #let .next = (%inc .target) in
100
+ [.before .next .after]
101
+ ; -> [8 [4 0 6] [0 6] [0 2] 0 15]
102
+ ; against [10 41 99] -> [10 42 99]
103
+ ```
104
+
105
+ `VALUE` and `BODY` are both formula positions (bare atoms lift). Shadowing
106
+ an existing schema name is a compile error.
107
+
108
+ ### `#match EXPR { PAT => BODY ... _ => DEFAULT }`
109
+
110
+ Pattern match on the value of `EXPR`. The scrutinee is evaluated once via
111
+ opcode 8 — i.e. lifted onto the subject — then each `PAT` is compared
112
+ against the lifted value via opcode 5 (eq), with opcode 6 (if) dispatching
113
+ to the matching `BODY`. The `_ =>` default is required.
114
+
115
+ ```
116
+ :subject {.tag .data}
117
+ #match .tag {
118
+ 1 => (%inc .data)
119
+ 2 => .data
120
+ _ => 0
121
+ }
122
+ ; -> [8 [0 2] 6 [5 [1 1] 0 2] [4 0 7] 6 [5 [1 2] 0 2] [0 7] 1 0]
123
+ ; against [1 41] -> 42
124
+ ; against [2 41] -> 41
125
+ ; against [9 41] -> 0
126
+ ```
127
+
128
+ `EXPR` and each `BODY` are formula positions. `PAT`s are *noun literals* —
129
+ they're compared against the scrutinee's runtime value, not against a
130
+ formula. Bare atoms in `PAT` position are not lifted: writing `1 => ...`
131
+ matches the atom `1`, not the formula `[1 1]`.
132
+
133
+ In the body of each arm (and the default), the scrutinee is at axis 2, and
134
+ the original schema axes are shifted rightward via `+peg(3, axis)` — same
135
+ shift rule as `#let`. That's why `.data` resolves to `[0 7]` (not `[0 3]`)
136
+ in the example above.
137
+
138
+ ## What lifts and what doesn't
139
+
140
+ Bare atoms get lifted to `[1 atom]` in formula positions. Not in noun-literal
141
+ positions (`%const` arg, hint tag) or axis positions (`%slot` arg, `%call`
142
+ arity arg, etc.). The per-opcode kinds:
143
+
144
+ | Opcode | Kinds | Notes |
145
+ |-----------|-------|-------|
146
+ | `%slot N` | a | axis literal |
147
+ | `%const X`| n | any noun, no lift |
148
+ | `%arm X` | n | synonym for `%const`; intent: callable formula |
149
+ | `%crash` | — | `[0 0]` — Nock crash idiom |
150
+ | `%self` | — | `[0 1]` — whole subject |
151
+ | `%battery`| — | `[0 2]` — standard core battery |
152
+ | `%payload`| — | `[0 3]` — standard core payload |
153
+ | `%sample` | — | `[0 6]` — standard gate sample |
154
+ | `%context`| — | `[0 7]` — standard gate context |
155
+ | `%eval` | ff | both formulas |
156
+ | `%isa` | f | |
157
+ | `%inc` | f | |
158
+ | `%eq` | ff | |
159
+ | `%if` | fff | branches lift |
160
+ | `%comp` | ff | |
161
+ | `%push` | ff | |
162
+ | `%call N F`| af | |
163
+ | `%edit N V F`| aff | |
164
+ | `%hint T F` | nf | tag is a noun literal |
165
+ | `%hintd T C F` | nff | clue is a formula — per 4K spec it's evaluated |
166
+
167
+ The intent-marking opcodes (`%arm`, `%crash`, and the axis aliases) all lower
168
+ to the same cells as their `%const` / `%slot` equivalents — they exist purely
169
+ to surface meaning at the source level. `%arm X` is `%const X` for cases
170
+ where `X` is a formula that will later be invoked via `%call`; `%self`
171
+ through `%context` name the standard Hoon core/gate axes.
172
+
173
+ `#let` value and body are formulas. `#match` scrutinee and arm bodies are
174
+ formulas. Match *patterns* are noun literals (compared against the
175
+ scrutinee's evaluated value).
176
+
177
+ Raw cells `[...]` are taken structurally: their elements are *not* lifted.
178
+ That gives you an escape hatch into raw Nock when you need it, and the
179
+ cons-formula distribution pattern works as expected:
180
+
181
+ ```
182
+ :subject {.a .b}
183
+ [(%inc .a) (%inc .b)]
184
+ ; -> [[4 0 2] [4 0 3]]
185
+ ; against [3 5] -> [4 6] via Nock distribution
186
+ ```
187
+
188
+ ## Tests
189
+
190
+ ```bash
191
+ python test_nockasm.py # unit tests, 55 cases
192
+ python test_e2e.py # end-to-end: expand -> pinochle -> verify, 19 cases
193
+ python test_benchmarks.py # urbit/benchmark equivalents, 5 cases (loaded from disk)
194
+ ```
195
+
196
+ `test_benchmarks.py` reads `benchmarks/tests.json` and `benchmarks/<name>.nasm`
197
+ from disk and runs each through pinochle. The five benchmarks present
198
+ (`dec`, `add`, `factorial`, `fibonacci`, `ackermann`) are faithful
199
+ transcriptions of `urbit/benchmark/desk/bar/<name>.nock` — each `.nasm`
200
+ expands to a noun bit-identical to the corresponding `.nock` formula.
201
+
202
+ ## License
203
+
204
+ MIT.
@@ -0,0 +1,195 @@
1
+ # Nock Assembly
2
+
3
+ ![](./img/hero.jpg)
4
+
5
+ Nock Assembly is a thin macro over [Nock ISA](https://nock.is) designed to make the language more legible for pedagogical purposes.
6
+
7
+ ## Design
8
+
9
+ | | |
10
+ |---|---|
11
+ | Named opcodes | `(%inc .x)` instead of `[4 0 2]`. Pure lexical. |
12
+ | Axis schemas | `:subject {.a .b .c}` resolves `.a` `.b` `.c` to axes 2, 6, 7. Right-leaning by Hoon convention. |
13
+ | `#let .name = E in B`| Opcode-8 push. Tracks subject shift via `+peg(3, n)` so old names still resolve in body. |
14
+ | `#match E { ... }` | Scrutinee lifted once via opcode 8. Nested opcode-6 dispatch on literal patterns. Required `_ =>` default. |
15
+ | `; comments` | And whitespace. |
16
+
17
+ ## Install / use
18
+
19
+ ```python
20
+ from nockasm import expand
21
+ print(expand("(%inc (%self))"))
22
+ # [4 0 1]
23
+
24
+ print(expand("""
25
+ :subject {.tag .data}
26
+ #match .tag {
27
+ 1 => (%inc .data)
28
+ 2 => .data
29
+ _ => 0
30
+ }
31
+ """))
32
+ # [8 [0 2] 6 [5 [1 1] 0 2] [4 0 7] 6 [5 [1 2] 0 2] [0 7] 1 0]
33
+ ```
34
+
35
+ End-to-end with [`pinochle`](https://github.com/sigilante/pinochle):
36
+
37
+ ```python
38
+ from pinochle import nock, parse_noun
39
+ from nockasm import expand
40
+
41
+ src = """
42
+ :subject {.before .target .after}
43
+ #let .next = (%inc .target) in
44
+ [.before .next .after]
45
+ """
46
+
47
+ formula = parse_noun(expand(src))
48
+ result = nock(parse_noun("[10 41 99]"), formula)
49
+ # result == [10 42 99]
50
+ ```
51
+
52
+ At the CLI:
53
+
54
+ ```bash
55
+ python -m nockasm program.nasm # canonical flat
56
+ python -m nockasm --pretty program.nasm # explicit binary cells
57
+ echo "(%inc (%self))" | python -m nockasm
58
+ ```
59
+
60
+ ## Integration with the Nock kernel
61
+
62
+ Pinochle ships `nock-kernel` for Jupyter (`Nock 4K` kernel). It accepts
63
+ canonical Nock in `:formula` cells. Workflow today:
64
+
65
+ 1. Write `.nasm` in a regular Python cell (or text editor).
66
+ 2. Run `expand(src)` in a Python notebook to get canonical Nock.
67
+ 3. Paste the result into a `:formula` cell in a Nock notebook.
68
+
69
+ A `:asm` cell magic for the Nock kernel that does this in one step is the
70
+ obvious next step. Roughly:
71
+
72
+ ```python
73
+ # in pinochle/packages/nock_kernel/kernel.py
74
+ if cell.startswith(':asm'):
75
+ from nockasm import expand
76
+ formula_src = expand(cell[len(':asm'):])
77
+ # then dispatch as if user had typed ':formula <formula_src>'
78
+ ```
79
+
80
+ ## Structural macros
81
+
82
+ ### `#let .name = VALUE in BODY`
83
+
84
+ Pushes `VALUE` onto the subject via opcode 8 and binds `.name` to axis 2 in
85
+ `BODY`. Any axes that were already in scope are shifted rightward via
86
+ `+peg(3, axis)`, so the old names still resolve in the body.
87
+
88
+ ```
89
+ :subject {.before .target .after}
90
+ #let .next = (%inc .target) in
91
+ [.before .next .after]
92
+ ; -> [8 [4 0 6] [0 6] [0 2] 0 15]
93
+ ; against [10 41 99] -> [10 42 99]
94
+ ```
95
+
96
+ `VALUE` and `BODY` are both formula positions (bare atoms lift). Shadowing
97
+ an existing schema name is a compile error.
98
+
99
+ ### `#match EXPR { PAT => BODY ... _ => DEFAULT }`
100
+
101
+ Pattern match on the value of `EXPR`. The scrutinee is evaluated once via
102
+ opcode 8 — i.e. lifted onto the subject — then each `PAT` is compared
103
+ against the lifted value via opcode 5 (eq), with opcode 6 (if) dispatching
104
+ to the matching `BODY`. The `_ =>` default is required.
105
+
106
+ ```
107
+ :subject {.tag .data}
108
+ #match .tag {
109
+ 1 => (%inc .data)
110
+ 2 => .data
111
+ _ => 0
112
+ }
113
+ ; -> [8 [0 2] 6 [5 [1 1] 0 2] [4 0 7] 6 [5 [1 2] 0 2] [0 7] 1 0]
114
+ ; against [1 41] -> 42
115
+ ; against [2 41] -> 41
116
+ ; against [9 41] -> 0
117
+ ```
118
+
119
+ `EXPR` and each `BODY` are formula positions. `PAT`s are *noun literals* —
120
+ they're compared against the scrutinee's runtime value, not against a
121
+ formula. Bare atoms in `PAT` position are not lifted: writing `1 => ...`
122
+ matches the atom `1`, not the formula `[1 1]`.
123
+
124
+ In the body of each arm (and the default), the scrutinee is at axis 2, and
125
+ the original schema axes are shifted rightward via `+peg(3, axis)` — same
126
+ shift rule as `#let`. That's why `.data` resolves to `[0 7]` (not `[0 3]`)
127
+ in the example above.
128
+
129
+ ## What lifts and what doesn't
130
+
131
+ Bare atoms get lifted to `[1 atom]` in formula positions. Not in noun-literal
132
+ positions (`%const` arg, hint tag) or axis positions (`%slot` arg, `%call`
133
+ arity arg, etc.). The per-opcode kinds:
134
+
135
+ | Opcode | Kinds | Notes |
136
+ |-----------|-------|-------|
137
+ | `%slot N` | a | axis literal |
138
+ | `%const X`| n | any noun, no lift |
139
+ | `%arm X` | n | synonym for `%const`; intent: callable formula |
140
+ | `%crash` | — | `[0 0]` — Nock crash idiom |
141
+ | `%self` | — | `[0 1]` — whole subject |
142
+ | `%battery`| — | `[0 2]` — standard core battery |
143
+ | `%payload`| — | `[0 3]` — standard core payload |
144
+ | `%sample` | — | `[0 6]` — standard gate sample |
145
+ | `%context`| — | `[0 7]` — standard gate context |
146
+ | `%eval` | ff | both formulas |
147
+ | `%isa` | f | |
148
+ | `%inc` | f | |
149
+ | `%eq` | ff | |
150
+ | `%if` | fff | branches lift |
151
+ | `%comp` | ff | |
152
+ | `%push` | ff | |
153
+ | `%call N F`| af | |
154
+ | `%edit N V F`| aff | |
155
+ | `%hint T F` | nf | tag is a noun literal |
156
+ | `%hintd T C F` | nff | clue is a formula — per 4K spec it's evaluated |
157
+
158
+ The intent-marking opcodes (`%arm`, `%crash`, and the axis aliases) all lower
159
+ to the same cells as their `%const` / `%slot` equivalents — they exist purely
160
+ to surface meaning at the source level. `%arm X` is `%const X` for cases
161
+ where `X` is a formula that will later be invoked via `%call`; `%self`
162
+ through `%context` name the standard Hoon core/gate axes.
163
+
164
+ `#let` value and body are formulas. `#match` scrutinee and arm bodies are
165
+ formulas. Match *patterns* are noun literals (compared against the
166
+ scrutinee's evaluated value).
167
+
168
+ Raw cells `[...]` are taken structurally: their elements are *not* lifted.
169
+ That gives you an escape hatch into raw Nock when you need it, and the
170
+ cons-formula distribution pattern works as expected:
171
+
172
+ ```
173
+ :subject {.a .b}
174
+ [(%inc .a) (%inc .b)]
175
+ ; -> [[4 0 2] [4 0 3]]
176
+ ; against [3 5] -> [4 6] via Nock distribution
177
+ ```
178
+
179
+ ## Tests
180
+
181
+ ```bash
182
+ python test_nockasm.py # unit tests, 55 cases
183
+ python test_e2e.py # end-to-end: expand -> pinochle -> verify, 19 cases
184
+ python test_benchmarks.py # urbit/benchmark equivalents, 5 cases (loaded from disk)
185
+ ```
186
+
187
+ `test_benchmarks.py` reads `benchmarks/tests.json` and `benchmarks/<name>.nasm`
188
+ from disk and runs each through pinochle. The five benchmarks present
189
+ (`dec`, `add`, `factorial`, `fibonacci`, `ackermann`) are faithful
190
+ transcriptions of `urbit/benchmark/desk/bar/<name>.nock` — each `.nasm`
191
+ expands to a noun bit-identical to the corresponding `.nock` formula.
192
+
193
+ ## License
194
+
195
+ MIT.
@@ -0,0 +1,57 @@
1
+ ; ackermann: ack(m, n) on a [m n] subject.
2
+ ;
3
+ ; Faithful transcription of urbit/benchmark/desk/bar/ackermann.nock,
4
+ ; drilled into named opcodes. The core has just two members: the
5
+ ; main arm (axis 4) — which self-recurses via %call 4 — and the +dec
6
+ ; arm wrapper (axis 5/10).
7
+ ;
8
+ ; Note: in this |^ trap the payload is [m n], so axis 6 = m and
9
+ ; axis 7 = n. The %sample and %context aliases line up by axis but
10
+ ; not by their usual gate-call semantics — they are just convenient
11
+ ; names for [0 6] and [0 7] here.
12
+ ;
13
+ ; Subject: a cell [m n].
14
+ ; Result : ack(m, n).
15
+
16
+ (%push
17
+ (%const
18
+ [ ; Main arm (axis 4 of [core m_n]). Standard Ackermann recursion.
19
+ (%if (%eq (%const 0) (%sample))
20
+ ; m == 0 -> +(n)
21
+ (%inc (%context))
22
+ (%if (%eq (%const 0) (%context))
23
+ ; n == 0 -> $(m (dec m), n 1)
24
+ (%call 4
25
+ (%edit 3
26
+ [ (%push (%call 5 (%self))
27
+ (%call 2 (%edit 6 (%slot 14) (%battery))))
28
+ (%const 1) ]
29
+ (%self)))
30
+ ; else -> $(m (dec m), n $(n (dec n)))
31
+ (%call 4
32
+ (%edit 3
33
+ [ (%push (%call 5 (%self))
34
+ (%call 2 (%edit 6 (%slot 14) (%battery))))
35
+ (%call 4
36
+ (%edit 7
37
+ (%push (%call 5 (%self))
38
+ (%call 2 (%edit 6 (%slot 15) (%battery))))
39
+ (%self))) ]
40
+ (%self)))))
41
+
42
+ ; +dec arm wrapper (axis 5 of [core m_n]). Same body as dec.nasm.
43
+ (%push (%const 0)
44
+ [ (%arm
45
+ (%if (%eq (%const 0) (%sample))
46
+ (%crash)
47
+ (%push (%const 0)
48
+ (%push (%arm
49
+ (%if (%eq (%slot 30) (%inc (%sample)))
50
+ (%sample)
51
+ (%call 2 (%edit 6
52
+ (%inc (%sample))
53
+ (%self)))))
54
+ (%call 2 (%self))))))
55
+ (%self) ]) ])
56
+
57
+ (%call 4 (%self)))
@@ -0,0 +1,48 @@
1
+ ; add: unsigned addition of [a b].
2
+ ;
3
+ ; Faithful transcription of urbit/benchmark/desk/bar/add.nock, drilled
4
+ ; fully into named opcodes.
5
+ ;
6
+ ; Subject: a cell [a b].
7
+ ; Result : a + b.
8
+
9
+ (%push
10
+ (%const
11
+ [ ; +add arm (axis 4 of [core a_b]). Pushes [0 0] as sample
12
+ ; placeholder, then autocons [add-body subject] to make a gate.
13
+ (%push (%const [0 0])
14
+ [ (%arm
15
+ ; +add body. Subject when entered: [body sample payload].
16
+ ; axis 12 = a, axis 13 = b, axis 28 = (axis 14 of payload).
17
+ (%if (%eq (%const 0) (%slot 12))
18
+ (%slot 13)
19
+ (%call 2
20
+ (%edit 6
21
+ [ (%push (%call 10 (%context))
22
+ (%call 2 (%edit 6 (%slot 28) (%battery))))
23
+ (%inc (%slot 13)) ]
24
+ (%self)))))
25
+ (%self) ])
26
+
27
+ ; +dec arm (axis 10 of [core a_b]). Same shape as dec.nasm.
28
+ (%push (%const 0)
29
+ [ (%arm
30
+ (%if (%eq (%const 0) (%sample))
31
+ (%crash)
32
+ (%push (%const 0)
33
+ (%push
34
+ (%arm
35
+ (%if (%eq (%slot 30) (%inc (%sample)))
36
+ (%sample)
37
+ (%call 2 (%edit 6
38
+ (%inc (%sample))
39
+ (%self)))))
40
+ (%call 2 (%self))))))
41
+ (%self) ])
42
+
43
+ ; Main body: push +add core, then call its arm with [m n] sample.
44
+ (%push (%call 4 (%self))
45
+ (%call 2 (%edit 6 [(%slot 14) (%slot 15)] (%battery)))) ])
46
+
47
+ ; Invoke main body at axis 11 of [core a_b].
48
+ (%call 11 (%self)))
@@ -0,0 +1,41 @@
1
+ ; dec: naive unsigned decrement.
2
+ ;
3
+ ; Faithful transcription of urbit/benchmark/desk/bar/dec.nock, drilled
4
+ ; fully into named opcodes. Uses %arm (intent-marked %const) for the
5
+ ; formulas later invoked via %call, %crash for [0 0], and the standard
6
+ ; axis aliases %self / %battery / %payload / %sample / %context.
7
+ ;
8
+ ; Subject: a single atom m (asserted nonzero).
9
+ ; Result : m - 1.
10
+
11
+ (%push
12
+ ; Push the |^ core onto the subject stack; new subject = [core m].
13
+ (%const
14
+ [ ; main arm at axis 4: build the dec sub-core via the core tail,
15
+ ; then call its first arm with m installed at sample.
16
+ (%push (%call 5 (%self))
17
+ (%call 2 (%edit 6 (%context) (%battery))))
18
+
19
+ ; core tail at axis 5: a formula that, when invoked, autocons
20
+ ; [dec-arm subject] to assemble a gate-shaped sub-core.
21
+ (%push (%const 0)
22
+ [ (%arm
23
+ ; +dec arm body. Subject when called is [arm payload]
24
+ ; with the argument at sample, original at axis 30.
25
+ (%if (%eq (%const 0) (%sample))
26
+ (%crash)
27
+ (%push (%const 0)
28
+ (%push
29
+ (%arm
30
+ ; inner |- loop on candidate b at sample:
31
+ ; if a == +(b) return b, else recurse b+1
32
+ (%if (%eq (%slot 30) (%inc (%sample)))
33
+ (%sample)
34
+ (%call 2 (%edit 6
35
+ (%inc (%sample))
36
+ (%self)))))
37
+ (%call 2 (%self))))))
38
+ (%self) ]) ])
39
+
40
+ ; Call the main arm at axis 4 of the new subject.
41
+ (%call 4 (%self)))
@@ -0,0 +1,96 @@
1
+ ; factorial: tail-recursive factorial of a single-atom subject.
2
+ ;
3
+ ; Faithful transcription of urbit/benchmark/desk/bar/factorial.nock,
4
+ ; drilled into named opcodes. The core has three explicit arms — +mul
5
+ ; (axis 4), +add (axis 20), +dec (axis 21) — and a main body (axis 11)
6
+ ; that builds an anonymous |= gate and invokes it on m.
7
+ ;
8
+ ; Subject: a single atom m.
9
+ ; Result : m! (use small m; pure Nock multiplication is slow).
10
+
11
+ (%push
12
+ (%const
13
+ [ ; +mul arm (axis 4 of [core m]). Standard `|: [a=`@`1 b=`@`1]`
14
+ ; pattern: push the [1 1] default sample, then autocons
15
+ ; [mul-body subject].
16
+ (%push (%const [1 1])
17
+ [ (%arm
18
+ ; +mul body. Push c=0 accumulator, then push the |- arm
19
+ ; and call it.
20
+ (%push (%const 0)
21
+ (%push (%arm
22
+ ; |- loop on a (axis 60) with c (axis 6).
23
+ ; if a == 0 -> return c
24
+ ; else recurse with a := dec(a), c := add(b, c)
25
+ (%if (%eq (%const 0) (%slot 60))
26
+ (%sample)
27
+ (%call 2
28
+ (%edit 60
29
+ (%push (%call 21 (%slot 31))
30
+ (%call 2 (%edit 6 (%slot 124) (%battery))))
31
+ (%edit 6
32
+ (%push (%call 20 (%slot 31))
33
+ (%call 2 (%edit 6 [(%slot 125) (%slot 14)] (%battery))))
34
+ (%self))))))
35
+ (%call 2 (%self)))))
36
+ (%self) ])
37
+
38
+ ; Arms cell at axis 5 of [core m]:
39
+ ; head (axis 10 of [core m]) = +add arm
40
+ ; tail (axis 11 of [core m]) starts with `8 [1 0] ...` — the
41
+ ; +dec arm wrapped as a noun-positioned formula.
42
+ ; NOTE: axis 11 is also where the main body lives — it shares a
43
+ ; prefix with the +dec wrapper, hence the structural mix here.
44
+ [ (%push (%const [0 0])
45
+ [ (%arm
46
+ ; +add body — uses +dec at axis 21.
47
+ (%if (%eq (%const 0) (%slot 12))
48
+ (%slot 13)
49
+ (%call 2
50
+ (%edit 6
51
+ [ (%push (%call 21 (%context))
52
+ (%call 2 (%edit 6 (%slot 28) (%battery))))
53
+ (%inc (%slot 13)) ]
54
+ (%self)))))
55
+ (%self) ])
56
+
57
+ ; +dec arm (axis 21 of [core m]) — identical body to dec.nasm.
58
+ (%push (%const 0)
59
+ [ (%arm
60
+ (%if (%eq (%const 0) (%sample))
61
+ (%crash)
62
+ (%push (%const 0)
63
+ (%push (%arm
64
+ (%if (%eq (%slot 30) (%inc (%sample)))
65
+ (%sample)
66
+ (%call 2 (%edit 6
67
+ (%inc (%sample))
68
+ (%self)))))
69
+ (%call 2 (%self))))))
70
+ (%self) ]) ]
71
+
72
+ ; Main body (also at axis 11): build the anonymous |= gate, then
73
+ ; call its battery with m installed at sample.
74
+ (%push (%push (%const 0)
75
+ [ (%arm
76
+ ; |= gate body: =/ t=1; |- ...
77
+ (%push (%const 1)
78
+ (%push (%arm
79
+ ; |- loop on n (axis 30), t (sample).
80
+ ; if n == 1 -> t
81
+ ; else recurse n := dec(n), t := mul(t, n)
82
+ (%if (%eq (%slot 30) (%const 1))
83
+ (%sample)
84
+ (%call 2
85
+ (%edit 30
86
+ (%push (%call 21 (%slot 31))
87
+ (%call 2 (%edit 6 (%slot 62) (%battery))))
88
+ (%edit 6
89
+ (%push (%call 4 (%slot 31))
90
+ (%call 2 (%edit 6 [(%slot 14) (%slot 62)] (%battery))))
91
+ (%self))))))
92
+ (%call 2 (%self)))))
93
+ (%self) ])
94
+ (%call 2 (%edit 6 (%context) (%battery)))) ])
95
+
96
+ (%call 11 (%self)))