on master help sign in
9 files changed+792−60
since splitsnapshots
unifiedsplit
nock/Cargo.lock+141−5 nock/Cargo.toml+7 nock/src/interpreter.rs+81−40 nock/src/jets.rs+171−2 nock/src/main.rs+35−3 nock/src/noun.rs+156 nock/test.hoon+3 nock/wock.hoon+198−10 wock.nock
nock/Cargo.lock+141−5
@@ -1,6 +1,6 @@
11# This file is automatically @generated by Cargo.
22# It is not intended for manual editing.
3−version = 3
3+version = 4
44
55[[package]]
66name = "anstream"
@@ -63,6 +63,12 @@ version = "0.8.0"
6363source = "registry+https://github.com/rust-lang/crates.io-index"
6464checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
6565
66+[[package]]
67+name = "bitflags"
68+version = "2.6.0"
69+source = "registry+https://github.com/rust-lang/crates.io-index"
70+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
71+
6672[[package]]
6773name = "bitter"
6874version = "0.7.0"
@@ -87,6 +93,12 @@ version = "3.16.0"
8793source = "registry+https://github.com/rust-lang/crates.io-index"
8894checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
8995
96+[[package]]
97+name = "bytecount"
98+version = "0.6.8"
99+source = "registry+https://github.com/rust-lang/crates.io-index"
100+checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce"
101+
90102[[package]]
91103name = "cc"
92104version = "1.1.31"
@@ -102,6 +114,12 @@ version = "1.0.0"
102114source = "registry+https://github.com/rust-lang/crates.io-index"
103115checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
104116
117+[[package]]
118+name = "cfg_aliases"
119+version = "0.2.1"
120+source = "registry+https://github.com/rust-lang/crates.io-index"
121+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
122+
105123[[package]]
106124name = "clap"
107125version = "4.5.20"
@@ -130,10 +148,10 @@ version = "4.5.18"
130148source = "registry+https://github.com/rust-lang/crates.io-index"
131149checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
132150dependencies = [
133− "heck",
151+ "heck 0.5.0",
134152 "proc-macro2",
135153 "quote",
136− "syn",
154+ "syn 2.0.85",
137155]
138156
139157[[package]]
@@ -158,6 +176,22 @@ dependencies = [
158176 "windows-sys 0.48.0",
159177]
160178
179+[[package]]
180+name = "ctrlc"
181+version = "3.4.5"
182+source = "registry+https://github.com/rust-lang/crates.io-index"
183+checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
184+dependencies = [
185+ "nix",
186+ "windows-sys 0.59.0",
187+]
188+
189+[[package]]
190+name = "fnv"
191+version = "1.0.7"
192+source = "registry+https://github.com/rust-lang/crates.io-index"
193+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
194+
161195[[package]]
162196name = "fplist"
163197version = "0.2.1"
@@ -170,6 +204,12 @@ version = "2.0.0"
170204source = "registry+https://github.com/rust-lang/crates.io-index"
171205checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
172206
207+[[package]]
208+name = "heck"
209+version = "0.4.1"
210+source = "registry+https://github.com/rust-lang/crates.io-index"
211+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
212+
173213[[package]]
174214name = "heck"
175215version = "0.5.0"
@@ -215,6 +255,18 @@ version = "0.5.2"
215255source = "registry+https://github.com/rust-lang/crates.io-index"
216256checksum = "9252111cf132ba0929b6f8e030cac2a24b507f3a4d6db6fb2896f27b354c714b"
217257
258+[[package]]
259+name = "nix"
260+version = "0.29.0"
261+source = "registry+https://github.com/rust-lang/crates.io-index"
262+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
263+dependencies = [
264+ "bitflags",
265+ "cfg-if",
266+ "cfg_aliases",
267+ "libc",
268+]
269+
218270[[package]]
219271name = "nock"
220272version = "0.1.0"
@@ -223,13 +275,16 @@ dependencies = [
223275 "bitter",
224276 "bitvec",
225277 "clap",
278+ "ctrlc",
226279 "fplist",
280+ "lazy_static",
227281 "murmur3",
228282 "num-bigint",
229283 "num-integer",
230284 "num-traits",
231285 "spinoff",
232286 "stacker",
287+ "tabled",
233288 "web-sys",
234289 "xxhash-rust",
235290]
@@ -268,12 +323,47 @@ version = "1.20.2"
268323source = "registry+https://github.com/rust-lang/crates.io-index"
269324checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
270325
326+[[package]]
327+name = "papergrid"
328+version = "0.12.0"
329+source = "registry+https://github.com/rust-lang/crates.io-index"
330+checksum = "c7419ad52a7de9b60d33e11085a0fe3df1fbd5926aa3f93d3dd53afbc9e86725"
331+dependencies = [
332+ "bytecount",
333+ "fnv",
334+ "unicode-width",
335+]
336+
271337[[package]]
272338name = "paste"
273339version = "1.0.15"
274340source = "registry+https://github.com/rust-lang/crates.io-index"
275341checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
276342
343+[[package]]
344+name = "proc-macro-error"
345+version = "1.0.4"
346+source = "registry+https://github.com/rust-lang/crates.io-index"
347+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
348+dependencies = [
349+ "proc-macro-error-attr",
350+ "proc-macro2",
351+ "quote",
352+ "syn 1.0.109",
353+ "version_check",
354+]
355+
356+[[package]]
357+name = "proc-macro-error-attr"
358+version = "1.0.4"
359+source = "registry+https://github.com/rust-lang/crates.io-index"
360+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
361+dependencies = [
362+ "proc-macro2",
363+ "quote",
364+ "version_check",
365+]
366+
277367[[package]]
278368name = "proc-macro2"
279369version = "1.0.89"
@@ -343,6 +433,17 @@ version = "0.11.1"
343433source = "registry+https://github.com/rust-lang/crates.io-index"
344434checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
345435
436+[[package]]
437+name = "syn"
438+version = "1.0.109"
439+source = "registry+https://github.com/rust-lang/crates.io-index"
440+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
441+dependencies = [
442+ "proc-macro2",
443+ "quote",
444+ "unicode-ident",
445+]
446+
346447[[package]]
347448name = "syn"
348449version = "2.0.85"
@@ -354,6 +455,29 @@ dependencies = [
354455 "unicode-ident",
355456]
356457
458+[[package]]
459+name = "tabled"
460+version = "0.16.0"
461+source = "registry+https://github.com/rust-lang/crates.io-index"
462+checksum = "77c9303ee60b9bedf722012ea29ae3711ba13a67c9b9ae28993838b63057cb1b"
463+dependencies = [
464+ "papergrid",
465+ "tabled_derive",
466+]
467+
468+[[package]]
469+name = "tabled_derive"
470+version = "0.8.0"
471+source = "registry+https://github.com/rust-lang/crates.io-index"
472+checksum = "bf0fb8bfdc709786c154e24a66777493fb63ae97e3036d914c8666774c477069"
473+dependencies = [
474+ "heck 0.4.1",
475+ "proc-macro-error",
476+ "proc-macro2",
477+ "quote",
478+ "syn 1.0.109",
479+]
480+
357481[[package]]
358482name = "tap"
359483version = "1.0.1"
@@ -366,12 +490,24 @@ version = "1.0.13"
366490source = "registry+https://github.com/rust-lang/crates.io-index"
367491checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
368492
493+[[package]]
494+name = "unicode-width"
495+version = "0.1.11"
496+source = "registry+https://github.com/rust-lang/crates.io-index"
497+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
498+
369499[[package]]
370500name = "utf8parse"
371501version = "0.2.2"
372502source = "registry+https://github.com/rust-lang/crates.io-index"
373503checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
374504
505+[[package]]
506+name = "version_check"
507+version = "0.9.5"
508+source = "registry+https://github.com/rust-lang/crates.io-index"
509+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
510+
375511[[package]]
376512name = "wasm-bindgen"
377513version = "0.2.95"
@@ -394,7 +530,7 @@ dependencies = [
394530 "once_cell",
395531 "proc-macro2",
396532 "quote",
397− "syn",
533+ "syn 2.0.85",
398534 "wasm-bindgen-shared",
399535]
400536
@@ -416,7 +552,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
416552dependencies = [
417553 "proc-macro2",
418554 "quote",
419− "syn",
555+ "syn 2.0.85",
420556 "wasm-bindgen-backend",
421557 "wasm-bindgen-shared",
422558]
nock/Cargo.toml+7
@@ -17,6 +17,9 @@ stacker = "0.1.17"
1717murmur3 = "0.5.2"
1818web-sys = "0.3.70"
1919fplist = "0.2.1"
20+ctrlc = "3.4.5"
21+lazy_static = "1.5.0"
22+tabled = "0.16.0"
2023
2124[dependencies.xxhash-rust]
2225version = "0.8.5"
@@ -26,6 +29,9 @@ features = ["xxh3"]
2629# Tell `rustc` to optimize for small code size.
2730opt-level = 3
2831
32+[features]
33+count-jets = []
34+
2935[[bin]]
3036name = "nock"
3137path = "src/main.rs"
@@ -33,3 +39,4 @@ path = "src/main.rs"
3339[[bin]]
3440name = "hash-gate"
3541path = "src/hash-gate.rs"
42+
nock/src/interpreter.rs+81−40
@@ -1,15 +1,23 @@
11use core::str;
22use std::borrow::Borrow;
33use std::collections::BTreeMap;
4+use std::collections::HashMap;
45use std::rc::Rc;
6+use std::sync::Mutex;
57
68use fplist::cons;
79use fplist::PersistentList;
10+use lazy_static::lazy_static;
811use num_bigint::BigUint;
912
1013use crate::jets::*;
1114use crate::noun::*;
1215
16+// #[cfg(feature = "count-jets")]
17+lazy_static! {
18+ pub static ref JET_COUNTER: Mutex<HashMap<Atom, u32>> = Mutex::new(HashMap::new());
19+}
20+
1321#[derive(Clone)]
1422pub struct Nouns {
1523 pub y: Rc<Noun>,
@@ -24,6 +32,7 @@ pub struct Nouns {
2432 pub memo: Rc<Noun>,
2533 pub mean: Rc<Noun>,
2634 pub slog: Rc<Noun>,
35+ pub fast: Rc<Noun>,
2736}
2837
2938#[derive(Clone)]
@@ -65,6 +74,7 @@ pub fn generate_interpreter_context() -> InterpreterContext {
6574 let memo = Rc::new(Noun::from_bytes(b"memo"));
6675 let mean = Rc::new(Noun::from_bytes(b"mean"));
6776 let slog = Rc::new(Noun::from_bytes(b"slog"));
77+ let fast = Rc::new(Noun::from_bytes(b"fast"));
6878 InterpreterContext {
6979 jets,
7080 double_jets,
@@ -81,6 +91,7 @@ pub fn generate_interpreter_context() -> InterpreterContext {
8191 memo,
8292 mean,
8393 slog,
94+ fast,
8495 },
8596 big_uints,
8697 memo: BTreeMap::new(),
@@ -193,7 +204,7 @@ fn hax(
193204
194205fn tar_u32<'a>(
195206 ctx: &mut InterpreterContext,
196− subj: Rc<Noun>,
207+ subj: &'a Rc<Noun>,
197208 op: u32,
198209 formula: &'a Rc<Noun>,
199210) -> Result<Rc<Noun>, TTanks> {
@@ -216,16 +227,16 @@ fn tar_u32<'a>(
216227 match ctx.jets.get(&hash) {
217228 Some(f) => f(ctx, sample),
218229 None => {
219− let foo = tar(ctx, subj.clone(), &b)?;
230+ let foo = tar(ctx, subj, &b)?;
220231 let bar = tar(ctx, subj, &c)?;
221− tar(ctx, foo, &bar)
232+ tar(ctx, &foo, &bar)
222233 }
223234 }
224235 }
225236 _ => {
226− let foo = tar(ctx, subj.clone(), &b)?;
237+ let foo = tar(ctx, subj, &b)?;
227238 let bar = tar(ctx, subj, &c)?;
228− tar(ctx, foo, &bar)
239+ tar(ctx, &foo, &bar)
229240 }
230241 }
231242 }
@@ -236,7 +247,7 @@ fn tar_u32<'a>(
236247 4 => Ok(Rc::new(lus(&*tar(ctx, subj, &formula)?)?)),
237248 5 => {
238249 let (b, c) = formula.as_cell().ok_or(TTanks::new())?;
239− let foo = tar(ctx, subj.clone(), &b)?;
250+ let foo = tar(ctx, subj, &b)?;
240251 let bar = tar(ctx, subj, &c)?;
241252 Ok(tis(ctx, &foo, &bar).clone())
242253 }
@@ -244,29 +255,29 @@ fn tar_u32<'a>(
244255 let (b, c) = formula.as_cell().ok_or(TTanks::new())?;
245256 let (c, d) = c.as_cell().ok_or(TTanks::new())?;
246257
247− let bar = tar_u32(ctx, subj.clone(), 4, &cell(&ctx.nouns.four.clone(), &b))?;
258+ let bar = tar_u32(ctx, subj, 4, &cell(&ctx.nouns.four.clone(), &b))?;
248259
249− let foo = tar_u32(ctx, ctx.nouns.two_three.clone(), 0, &bar)?;
250− let foo = tar_u32(ctx, cell(&c, &d), 0, &foo)?;
260+ let foo = tar_u32(ctx, &ctx.nouns.two_three.clone(), 0, &bar)?;
261+ let foo = tar_u32(ctx, &cell(&c, &d), 0, &foo)?;
251262
252263 tar(ctx, subj, &foo)
253264 }
254265 7 => {
255266 let (b, c) = formula.as_cell().ok_or(TTanks::new())?;
256267 let foo = tar(ctx, subj, &b)?;
257− tar(ctx, foo, &c)
268+ tar(ctx, &foo, &c)
258269 }
259270 8 => {
260271 let (b, c) = formula.as_cell().ok_or(TTanks::new())?;
261− let foo = tar(ctx, subj.clone(), &b)?;
262− tar(ctx, cell(&foo, &subj), &c)
272+ let foo = tar(ctx, subj, &b)?;
273+ tar(ctx, &cell(&foo, &subj), &c)
263274 }
264275 9 => {
265276 let (b, c) = formula.as_cell().ok_or(TTanks::new())?;
266277 let foo = tar(ctx, subj, &c)?;
267278 tar_u32(
268279 ctx,
269− foo,
280+ &foo,
270281 2,
271282 &cell(&ctx.nouns.sig_one, &cell(&ctx.nouns.sig, &b)),
272283 )
@@ -276,7 +287,7 @@ fn tar_u32<'a>(
276287 let (b, c) = b.as_cell().ok_or(TTanks::new())?;
277288 let b = b.as_atom().ok_or(TTanks::new())?;
278289
279− let foo = tar(ctx, subj.clone(), &c)?;
290+ let foo = tar(ctx, subj, &c)?;
280291 let bar = tar(ctx, subj, &d)?;
281292 hax(ctx, b, &foo, &bar)
282293 }
@@ -285,7 +296,7 @@ fn tar_u32<'a>(
285296 match b.borrow() {
286297 Noun::Atom(_) => tar(ctx, subj, &d),
287298 Noun::Cell { p, q, .. } => {
288− let eval = |ctx: &mut InterpreterContext| tar(ctx, subj.clone(), &d);
299+ let eval = |ctx: &mut InterpreterContext| tar(ctx, subj, &d);
289300 if *p == ctx.nouns.memo {
290301 let hash = cell(&subj, &d).hash();
291302 match ctx.memo.get(&hash) {
@@ -299,11 +310,33 @@ fn tar_u32<'a>(
299310 } else if *p == ctx.nouns.mean {
300311 with_trace((subj.clone(), q.clone()), eval(ctx))
301312 } else if *p == ctx.nouns.slog {
302− let noun = tar(ctx, subj.clone(), &q)?;
313+ let noun = tar(ctx, subj, &q)?;
303314 let (_prio, tank) = noun.as_cell().ok_or(TTanks::new())?;
304315
305316 (ctx.slog)(&ram(ctx, tank.clone()).unwrap_or(String::new()));
306317 eval(ctx)
318+ } else if *p == ctx.nouns.fast {
319+ if cfg!(feature = "count-jets") {
320+ let tag = q.as_cell().unwrap().1.as_cell().unwrap().0.as_atom();
321+ match tag {
322+ None => (),
323+ Some(tag) => {
324+ let mut counter = JET_COUNTER.lock().unwrap();
325+ match counter.get(tag) {
326+ None => {
327+ counter.insert(tag.clone(), 1);
328+ ()
329+ }
330+ Some(v) => {
331+ let v = *v;
332+ counter.insert(tag.clone(), v + 1);
333+ ()
334+ }
335+ }
336+ }
337+ }
338+ }
339+ eval(ctx)
307340 } else {
308341 eval(ctx)
309342 }
@@ -323,24 +356,22 @@ fn ram(ctx: &mut InterpreterContext, tank: Rc<Noun>) -> Option<String> {
323356 Noun::Cell { p, q, .. } => {
324357 let tag = p.as_atom()?.to_bytes_le();
325358 if tag == b"leaf" {
326− let mut bytes = crip(ctx, q.clone()).ok()?.as_atom()?.to_bytes_le();
359+ let mut bytes = crip(ctx, q.clone()).ok()?.as_bytes()?;
327360 Some(String::from(str::from_utf8_mut(&mut bytes).ok()?))
328361 } else if tag == b"palm" {
329− let (mid, foo) = q.as_cell()?;
330− let (open, foo) = foo.as_cell()?;
331− let (flat_open, foo) = foo.as_cell()?;
332− let (close, tanks) = foo.as_cell()?;
362+ let (spacers, tanks) = q.as_cell()?;
363+ let (mid, open, flat_open, close) = spacers.tuple_4()?;
333364
334− let mut mid = mid.as_bytes()?;
365+ let mut mid = crip(ctx, mid.clone()).ok()?.as_bytes()?;
335366 let mid = String::from(str::from_utf8_mut(&mut mid).ok()?);
336367
337− let mut open = open.as_bytes()?;
368+ let mut open = crip(ctx, open.clone()).ok()?.as_bytes()?;
338369 let open = String::from(str::from_utf8_mut(&mut open).ok()?);
339370
340− let mut flat_open = flat_open.as_bytes()?;
371+ let mut flat_open = crip(ctx, flat_open.clone()).ok()?.as_bytes()?;
341372 let flat_open = String::from(str::from_utf8_mut(&mut flat_open).ok()?);
342373
343− let mut close = close.as_bytes()?;
374+ let mut close = crip(ctx, close.clone()).ok()?.as_bytes()?;
344375 let close = String::from(str::from_utf8_mut(&mut close).ok()?);
345376
346377 let mut target = String::new();
@@ -363,17 +394,16 @@ fn ram(ctx: &mut InterpreterContext, tank: Rc<Noun>) -> Option<String> {
363394
364395 Some(target)
365396 } else if tag == b"rose" {
366− let (mid, foo) = q.as_cell()?;
367− let (open, foo) = foo.as_cell()?;
368− let (close, tanks) = foo.as_cell()?;
397+ let (spacers, tanks) = q.as_cell()?;
398+ let (mid, open, close) = spacers.tuple_3()?;
369399
370− let mut mid = mid.as_bytes()?;
400+ let mut mid = crip(ctx, mid.clone()).ok()?.as_bytes()?;
371401 let mid = String::from(str::from_utf8_mut(&mut mid).ok()?);
372402
373− let mut open = open.as_bytes()?;
403+ let mut open = crip(ctx, open.clone()).ok()?.as_bytes()?;
374404 let open = String::from(str::from_utf8_mut(&mut open).ok()?);
375405
376− let mut close = close.as_bytes()?;
406+ let mut close = crip(ctx, close.clone()).ok()?.as_bytes()?;
377407 let close = String::from(str::from_utf8_mut(&mut close).ok()?);
378408
379409 let mut target = String::new();
@@ -404,8 +434,15 @@ fn ram(ctx: &mut InterpreterContext, tank: Rc<Noun>) -> Option<String> {
404434pub fn ram_ttanks(ctx: &mut InterpreterContext, tanks: TTanks) -> String {
405435 let mut target = String::new();
406436
407− for tank in tanks.iter() {
437+ let mut first = true;
438+ for tank in tanks.iter().collect::<Vec<_>>().iter().cloned().rev() {
439+ if !first {
440+ target.push('\n');
441+ }
442+
408443 target.push_str(&*ram_ttank(ctx, tank.clone()));
444+
445+ first = false;
409446 }
410447
411448 target
@@ -414,12 +451,16 @@ pub fn ram_ttanks(ctx: &mut InterpreterContext, tanks: TTanks) -> String {
414451pub fn ram_ttank(ctx: &mut InterpreterContext, ttank: TTank) -> String {
415452 let (subj, q) = ttank;
416453
417− let tank = match tar(ctx, subj.clone(), &q) {
454+ let tank = match tar(ctx, &subj, &q) {
418455 Ok(gate) => match eval_pulled_gate(ctx, gate) {
419456 Ok(tank) => tank,
420− Err(_) => return String::new(),
457+ Err(_) => {
458+ return String::new();
459+ }
421460 },
422− Err(_) => return String::new(),
461+ Err(_) => {
462+ return String::new();
463+ }
423464 };
424465 ram(ctx, tank).unwrap_or(String::new())
425466}
@@ -443,13 +484,13 @@ pub const EMPTY_TANKS: TTanks = TTanks::new();
443484
444485pub fn tar<'a>(
445486 ctx: &mut InterpreterContext,
446− subj: Rc<Noun>,
487+ subj: &'a Rc<Noun>,
447488 formula: &'a Noun,
448489) -> Result<Rc<Noun>, TTanks> {
449490 let (op, formula) = formula.as_cell().ok_or(TTanks::new())?;
450491 match op.borrow() {
451492 Noun::Cell { .. } => {
452− let foo = tar(ctx, subj.clone(), &op)?;
493+ let foo = tar(ctx, subj, &op)?;
453494 let bar = tar(ctx, subj, &formula)?;
454495 Ok(cell(&foo, &bar))
455496 }
@@ -470,7 +511,7 @@ pub fn eval_gate(
470511 ctx: &mut InterpreterContext,
471512 gate_factory: &Rc<Noun>,
472513) -> Result<Rc<Noun>, TTanks> {
473− let pulled_gate = tar(ctx, ctx.nouns.sig.clone(), &gate_factory)?;
514+ let pulled_gate = tar(ctx, &ctx.nouns.sig.clone(), &gate_factory)?;
474515 eval_pulled_gate(ctx, pulled_gate)
475516}
476517
@@ -479,7 +520,7 @@ pub fn slam(
479520 gate_factory: &Rc<Noun>,
480521 sample: &Rc<Noun>,
481522) -> Result<Rc<Noun>, TTanks> {
482− let gate = tar(ctx, ctx.nouns.sig.clone(), &gate_factory)?;
523+ let gate = tar(ctx, &ctx.nouns.sig.clone(), &gate_factory)?;
483524
484525 eval_pulled_gate(ctx, replace_sample(&gate, sample)?)
485526}
@@ -489,7 +530,7 @@ pub fn slam(
489530pub fn eval_pulled_gate(ctx: &mut InterpreterContext, gate: Rc<Noun>) -> Result<Rc<Noun>, TTanks> {
490531 tar(
491532 ctx,
492− gate,
533+ &gate,
493534 &cell(
494535 &Rc::new(Noun::Atom(BigUint::new(vec![9]))),
495536 &cell(&ctx.nouns.two, &cell(&ctx.nouns.sig, &ctx.nouns.one)),
nock/src/jets.rs+171−2
@@ -2,14 +2,15 @@ use core::panic;
22use murmur3::murmur3_32;
33use num_integer::Integer;
44use std::{
5+ borrow::Borrow,
56 collections::BTreeMap,
67 iter::{once, repeat_n, zip},
78 rc::Rc,
89};
910
1011use crate::{
11− interpreter::{slam_pulled_gate, InterpreterContext, TTanks},
12− noun::{self, cell, Atom, Edge, Hair, Nail, Noun},
12+ interpreter::{slam_pulled_gate, InterpreterContext, TTanks, EMPTY_TANKS},
13+ noun::{self, cell, AsNoun, Atom, Edge, Hair, Nail, Noun},
1314};
1415
1516fn traverse_iter_result<E: Clone, T: Clone, I: Iterator<Item = Result<T, E>>>(
@@ -107,6 +108,11 @@ pub fn generate_jets() -> Jets {
107108 (124976064672625230427889101290245200507, TRIP),
108109 (4078279288073906183633157465551067635, MUK),
109110 (261900159748071234702685239353558471892, MUG),
111+ (198191257620924788695943358402651905954, AOR),
112+ (299422828284010240628877806738195667652, DOR),
113+ (166326796552601297695313176538120818632, GOR),
114+ (7942398240152905363972500667649380756, MOR),
115+ (315687984998079865981003916817293521765, LOOK),
110116 ])
111117}
112118
@@ -1007,6 +1013,169 @@ static MUK: Jet = |_ctx, n| {
10071013 Ok(Rc::new(Noun::from_u32(muk(seed, len, key))))
10081014};
10091015
1016+static AOR: Jet = |ctx, n| {
1017+ let (a, b) = n.as_cell().ok_or(EMPTY_TANKS)?;
1018+
1019+ if a == b {
1020+ Ok(ctx.nouns.y.clone())
1021+ } else {
1022+ match (a.borrow(), b.borrow()) {
1023+ (Noun::Cell { .. }, Noun::Atom(_)) => Ok(ctx.nouns.n.clone()),
1024+ (Noun::Cell { p: a_p, q: a_q, .. }, Noun::Cell { p: b_p, q: b_q, .. }) => {
1025+ if a_p == b_p {
1026+ AOR(ctx, &cell(a_q, b_q))
1027+ } else {
1028+ AOR(ctx, &cell(a_p, b_p))
1029+ }
1030+ }
1031+ (Noun::Atom(_), Noun::Cell { .. }) => Ok(ctx.nouns.y.clone()),
1032+ (Noun::Atom(a), Noun::Atom(b)) => {
1033+ let a = a.to_bytes_le();
1034+ let b = b.to_bytes_le();
1035+
1036+ let mut i = 0;
1037+ loop {
1038+ let c = a.get(i).unwrap_or(&0);
1039+ let d = b.get(i).unwrap_or(&0);
1040+
1041+ if a[0] == b[0] {
1042+ i += 1;
1043+ } else {
1044+ if c < d {
1045+ return Ok(ctx.nouns.y.clone());
1046+ } else {
1047+ return Ok(ctx.nouns.n.clone());
1048+ }
1049+ }
1050+ }
1051+ }
1052+ }
1053+ }
1054+};
1055+
1056+static DOR: Jet = |ctx, n| {
1057+ let (a, b) = n.as_cell().ok_or(EMPTY_TANKS)?;
1058+
1059+ if a == b {
1060+ Ok(ctx.nouns.y.clone())
1061+ } else {
1062+ match (a.borrow(), b.borrow()) {
1063+ (Noun::Cell { .. }, Noun::Atom(_)) => Ok(ctx.nouns.n.clone()),
1064+ (Noun::Cell { p: a_p, q: a_q, .. }, Noun::Cell { p: b_p, q: b_q, .. }) => {
1065+ if a_p == b_p {
1066+ DOR(ctx, &cell(a_q, b_q))
1067+ } else {
1068+ DOR(ctx, &cell(a_p, b_p))
1069+ }
1070+ }
1071+ (Noun::Atom(_), Noun::Cell { .. }) => Ok(ctx.nouns.y.clone()),
1072+ (Noun::Atom(a), Noun::Atom(b)) => {
1073+ if a < b {
1074+ Ok(ctx.nouns.y.clone())
1075+ } else {
1076+ Ok(ctx.nouns.n.clone())
1077+ }
1078+ }
1079+ }
1080+ }
1081+};
1082+
1083+static GOR: Jet = |ctx, n| {
1084+ let (a, b) = n.as_cell().ok_or(EMPTY_TANKS)?;
1085+ let c = a.mug();
1086+ let d = b.mug();
1087+
1088+ if c == d {
1089+ DOR(ctx, n)
1090+ } else {
1091+ if c < d {
1092+ Ok(ctx.nouns.y.clone())
1093+ } else {
1094+ Ok(ctx.nouns.n.clone())
1095+ }
1096+ }
1097+};
1098+
1099+static GOR_PAIR: fn(&mut InterpreterContext, &Rc<Noun>, &Rc<Noun>) -> Result<bool, TTanks> =
1100+ |ctx, a, b| {
1101+ let c = a.mug();
1102+ let d = b.mug();
1103+
1104+ if c == d {
1105+ Ok(DOR(ctx, &(a.clone(), b.clone()).as_noun())? == ctx.nouns.y)
1106+ } else {
1107+ Ok(c < d)
1108+ }
1109+ };
1110+
1111+static MOR: Jet = |ctx, n| {
1112+ let (a, b) = n.as_cell().ok_or(EMPTY_TANKS)?;
1113+ let c = Noun::from_u32(a.mug()).mug();
1114+ let d = Noun::from_u32(b.mug()).mug();
1115+
1116+ if c == d {
1117+ DOR(ctx, n)
1118+ } else {
1119+ if c < d {
1120+ Ok(ctx.nouns.y.clone())
1121+ } else {
1122+ Ok(ctx.nouns.n.clone())
1123+ }
1124+ }
1125+};
1126+
1127+static LOOK: Jet = |ctx, n| {
1128+ let (cog, dab) = n.as_cell().ok_or(EMPTY_TANKS)?;
1129+ LOOK_PAIR(ctx, Atom::new(vec![1]), cog, dab)
1130+};
1131+
1132+static LOOK_PAIR: fn(
1133+ &mut InterpreterContext,
1134+ Atom,
1135+ &Rc<Noun>,
1136+ &Rc<Noun>,
1137+) -> Result<Rc<Noun>, TTanks> = |ctx, axe, cog, dab| {
1138+ if dab.is_sig() {
1139+ Ok(ctx.nouns.sig.clone())
1140+ } else {
1141+ let (node, b, c) = dab.tuple_3().ok_or(EMPTY_TANKS)?;
1142+ let l = b;
1143+ let r = c;
1144+ let (p, q) = node.as_cell().ok_or(EMPTY_TANKS)?;
1145+ if b.is_sig() && c.is_sig() {
1146+ if cog == p {
1147+ Ok((ctx.nouns.sig.clone(), Noun::Atom(axe), q.clone()).as_noun())
1148+ } else {
1149+ Ok(ctx.nouns.sig.clone())
1150+ }
1151+ } else if b.is_sig() {
1152+ if cog == p {
1153+ Ok((ctx.nouns.sig.clone(), PEG_U32(&axe, 2), q.clone()).as_noun())
1154+ } else if GOR_PAIR(ctx, cog, p)? {
1155+ Ok(ctx.nouns.sig.clone())
1156+ } else {
1157+ LOOK_PAIR(ctx, PEG_U32(&axe, 3), cog, r)
1158+ }
1159+ } else if c.is_sig() {
1160+ if cog == p {
1161+ Ok((ctx.nouns.sig.clone(), PEG_U32(&axe, 2), q.clone()).as_noun())
1162+ } else if GOR_PAIR(ctx, cog, p)? {
1163+ LOOK_PAIR(ctx, PEG_U32(&axe, 3), cog, l)
1164+ } else {
1165+ Ok(ctx.nouns.sig.clone())
1166+ }
1167+ } else {
1168+ if cog == p {
1169+ Ok((ctx.nouns.sig.clone(), PEG_U32(&axe, 2), q.clone()).as_noun())
1170+ } else if GOR_PAIR(ctx, cog, p)? {
1171+ LOOK_PAIR(ctx, PEG_U32(&axe, 6), cog, l)
1172+ } else {
1173+ LOOK_PAIR(ctx, PEG_U32(&axe, 7), cog, r)
1174+ }
1175+ }
1176+ }
1177+};
1178+
10101179static JUST: DoubleJet = |ctx, n, m| {
10111180 let char = n.as_atom()?;
10121181 let nail = m.as_nail()?;
nock/src/main.rs+35−3
@@ -1,6 +1,8 @@
11use clap::{command, error::Result, Parser, Subcommand};
22use core::str;
3−use nock::interpreter::{ram_ttanks, slam_pulled_gate, tar, InterpreterContext, TTanks};
3+use nock::interpreter::{
4+ ram_ttanks, slam_pulled_gate, tar, InterpreterContext, TTanks, JET_COUNTER,
5+};
46use nock::{
57 cue::cue_bytes,
68 interpreter::{generate_interpreter_context, slam},
@@ -65,10 +67,36 @@ enum RunCommand {
6567 },
6668}
6769
70+fn print_jet_counter() {
71+ let counter = JET_COUNTER.lock().unwrap();
72+ let mut builder = tabled::builder::Builder::new();
73+ builder.push_record(vec!["jet", "number of calls"]);
74+ let mut counters = counter.iter().collect::<Vec<_>>();
75+ counters.sort_unstable_by_key(|x| x.1);
76+ for (key, count) in counters {
77+ let key = String::from_utf8(key.to_bytes_le()).unwrap();
78+ builder.push_record(vec![key, count.to_string()]);
79+ }
80+ println!(
81+ "\n{}",
82+ builder
83+ .build()
84+ .with(tabled::settings::style::Style::rounded())
85+ );
86+}
87+
6888fn main() -> Result<(), std::io::Error> {
89+ if cfg!(feature = "count-jets") {
90+ ctrlc::set_handler(|| {
91+ print_jet_counter();
92+ exit(1);
93+ })
94+ .unwrap();
95+ }
96+
6997 let urbit = LazyCell::new(GENERATE_URBIT);
7098 let mut ctx = InterpreterContext {
71− slog: |_| {},
99+ // slog: |_| {},
72100 ..generate_interpreter_context()
73101 };
74102
@@ -139,12 +167,16 @@ fn main() -> Result<(), std::io::Error> {
139167 let (_typ, nok) = read_nock_or_compile(&mut spinner, &mut ctx, &urbit, &nock)?;
140168
141169 let sig = ctx.nouns.sig.clone();
142− let result = tar(&mut ctx, sig, &nok).unwrap();
170+ let result = tar(&mut ctx, &sig, &nok).unwrap();
143171
144172 println!("{result}");
145173 }
146174 }
147175
176+ if cfg!(feature = "count-jets") {
177+ print_jet_counter()
178+ }
179+
148180 Ok(())
149181}
150182
nock/src/noun.rs+156
@@ -111,6 +111,52 @@ impl<'a> Noun {
111111 }
112112 }
113113
114+ pub fn tuple_2(self: &Self) -> Option<(&Rc<Noun>, &Rc<Noun>)> {
115+ self.as_cell()
116+ }
117+
118+ pub fn tuple_3(self: &Self) -> Option<(&Rc<Noun>, &Rc<Noun>, &Rc<Noun>)> {
119+ let (a, b) = self.as_cell()?;
120+ let (b, c) = b.as_cell()?;
121+
122+ Some((a, b, c))
123+ }
124+ pub fn tuple_4(self: &Self) -> Option<(&Rc<Noun>, &Rc<Noun>, &Rc<Noun>, &Rc<Noun>)> {
125+ let (a, b) = self.as_cell()?;
126+ let (b, c) = b.as_cell()?;
127+ let (c, d) = c.as_cell()?;
128+
129+ Some((a, b, c, d))
130+ }
131+
132+ pub fn tuple_5(self: &Self) -> Option<(&Rc<Noun>, &Rc<Noun>, &Rc<Noun>, &Rc<Noun>, &Rc<Noun>)> {
133+ let (a, b) = self.as_cell()?;
134+ let (b, c) = b.as_cell()?;
135+ let (c, d) = c.as_cell()?;
136+ let (d, e) = d.as_cell()?;
137+
138+ Some((a, b, c, d, e))
139+ }
140+
141+ pub fn tuple_6(
142+ self: &Self,
143+ ) -> Option<(
144+ &Rc<Noun>,
145+ &Rc<Noun>,
146+ &Rc<Noun>,
147+ &Rc<Noun>,
148+ &Rc<Noun>,
149+ &Rc<Noun>,
150+ )> {
151+ let (a, b) = self.as_cell()?;
152+ let (b, c) = b.as_cell()?;
153+ let (c, d) = c.as_cell()?;
154+ let (d, e) = d.as_cell()?;
155+ let (e, f) = e.as_cell()?;
156+
157+ Some((a, b, c, d, e, f))
158+ }
159+
114160 pub fn is_sig(self: &Self) -> bool {
115161 match self {
116162 Noun::Atom(n) if *n == Atom::ZERO => true,
@@ -255,6 +301,116 @@ impl<'a> Noun {
255301 }
256302}
257303
304+pub trait AsNoun {
305+ fn as_noun(self: &Self) -> Rc<Noun>;
306+}
307+
308+impl AsNoun for Atom {
309+ fn as_noun(self: &Self) -> Rc<Noun> {
310+ Rc::new(Noun::Atom(self.clone()))
311+ }
312+}
313+
314+impl AsNoun for Noun {
315+ fn as_noun(self: &Self) -> Rc<Noun> {
316+ Rc::new(self.clone())
317+ }
318+}
319+
320+impl AsNoun for Rc<Noun> {
321+ fn as_noun(self: &Self) -> Rc<Noun> {
322+ self.clone()
323+ }
324+}
325+
326+impl<A, B> AsNoun for (A, B)
327+where
328+ A: AsNoun,
329+ B: AsNoun,
330+{
331+ fn as_noun(self: &Self) -> Rc<Noun> {
332+ let (a, b) = self;
333+
334+ cell(&a.as_noun(), &b.as_noun())
335+ }
336+}
337+
338+impl<A, B, C> AsNoun for (A, B, C)
339+where
340+ A: AsNoun,
341+ B: AsNoun,
342+ C: AsNoun,
343+{
344+ fn as_noun(self: &Self) -> Rc<Noun> {
345+ let (a, b, c) = self;
346+
347+ cell(&a.as_noun(), &cell(&b.as_noun(), &c.as_noun()))
348+ }
349+}
350+
351+impl<A, B, C, D> AsNoun for (A, B, C, D)
352+where
353+ A: AsNoun,
354+ B: AsNoun,
355+ C: AsNoun,
356+ D: AsNoun,
357+{
358+ fn as_noun(self: &Self) -> Rc<Noun> {
359+ let (a, b, c, d) = self;
360+
361+ cell(
362+ &a.as_noun(),
363+ &cell(&b.as_noun(), &cell(&c.as_noun(), &d.as_noun())),
364+ )
365+ }
366+}
367+
368+impl<A, B, C, D, E> AsNoun for (A, B, C, D, E)
369+where
370+ A: AsNoun,
371+ B: AsNoun,
372+ C: AsNoun,
373+ D: AsNoun,
374+ E: AsNoun,
375+{
376+ fn as_noun(self: &Self) -> Rc<Noun> {
377+ let (a, b, c, d, e) = self;
378+
379+ cell(
380+ &a.as_noun(),
381+ &cell(
382+ &b.as_noun(),
383+ &cell(&c.as_noun(), &cell(&d.as_noun(), &e.as_noun())),
384+ ),
385+ )
386+ }
387+}
388+
389+impl<A, B, C, D, E, F> AsNoun for (A, B, C, D, E, F)
390+where
391+ A: AsNoun,
392+ B: AsNoun,
393+ C: AsNoun,
394+ D: AsNoun,
395+ E: AsNoun,
396+ F: AsNoun,
397+{
398+ fn as_noun(self: &Self) -> Rc<Noun> {
399+ let (a, b, c, d, e, f) = self;
400+
401+ cell(
402+ &a.as_noun(),
403+ &cell(
404+ &b.as_noun(),
405+ &cell(
406+ &c.as_noun(),
407+ &cell(&d.as_noun(), &cell(&e.as_noun(), &f.as_noun())),
408+ ),
409+ ),
410+ )
411+ }
412+}
413+
258414impl From<(Rc<Noun>, Rc<Noun>)> for Noun {
259415 fn from(value: (Rc<Noun>, Rc<Noun>)) -> Self {
260416 naked_cell(&value.0, &value.1)
nock/test.hoonnew file+3
@@ -0,0 +1,3 @@
1+|= *
2+=/ target (gor 'd' 'c')
3+(crip "{<target>}")
nock/wock.hoon+198−10
@@ -1,16 +1,204 @@
11=>
22|%
3+++ http ^?
4+ |%
5+ :: +header-list: an ordered list of http headers
6+ ::
7+ +$ header-list
8+ (list [key=@t value=@t])
9+ :: +method: exhaustive list of http verbs
10+ ::
11+ +$ method
12+ $? %'CONNECT'
13+ %'DELETE'
14+ %'GET'
15+ %'HEAD'
16+ %'OPTIONS'
17+ %'PATCH'
18+ %'POST'
19+ %'PUT'
20+ %'TRACE'
21+ ==
22+ :: +request: a single http request
23+ ::
24+ +$ request
25+ $: :: method: http method
26+ ::
27+ method=method
28+ :: url: the url requested
29+ ::
30+ :: The url is not escaped. There is no escape.
31+ ::
32+ url=@t
33+ :: header-list: headers to pass with this request
34+ ::
35+ =header-list
36+ :: body: optionally, data to send with this request
37+ ::
38+ body=(unit octs)
39+ ==
40+ :: +response-header: the status code and header list on an http request
41+ ::
42+ :: We separate these away from the body data because we may not wait for
43+ :: the entire body before we send a %progress to the caller.
44+ ::
45+ +$ response-header
46+ $: :: status: http status code
47+ ::
48+ status-code=@ud
49+ :: headers: http headers
50+ ::
51+ headers=header-list
52+ ==
53+ :: +http-event: packetized http
54+ ::
55+ :: Urbit treats Earth's HTTP servers as pipes, where Urbit sends or
56+ :: receives one or more %http-events. The first of these will always be a
57+ :: %start or an %error, and the last will always be %cancel or will have
58+ :: :complete set to %.y to finish the connection.
59+ ::
60+ :: Calculation of control headers such as 'Content-Length' or
61+ :: 'Transfer-Encoding' should be performed at a higher level; this structure
62+ :: is merely for what gets sent to or received from Earth.
63+ ::
64+ +$ http-event
65+ $% :: %start: the first packet in a response
66+ ::
67+ $: %start
68+ :: response-header: first event information
69+ ::
70+ =response-header
71+ :: data: data to pass to the pipe
72+ ::
73+ data=(unit octs)
74+ :: whether this completes the request
75+ ::
76+ complete=?
77+ ==
78+ :: %continue: every subsequent packet
79+ ::
80+ $: %continue
81+ :: data: data to pass to the pipe
82+ ::
83+ data=(unit octs)
84+ :: complete: whether this completes the request
85+ ::
86+ complete=?
87+ ==
88+ :: %cancel: represents unsuccessful termination
89+ ::
90+ [%cancel ~]
91+ ==
92+ :: +get-header: returns the value for :header, if it exists in :header-list
93+ ::
94+ ++ get-header
95+ |= [header=@t =header-list]
96+ ^- (unit @t)
97+ ::
98+ ?~ header-list
99+ ~
100+ ::
101+ ?: =(key.i.header-list header)
102+ `value.i.header-list
103+ ::
104+ $(header-list t.header-list)
105+ :: +set-header: sets the value of an item in the header list
106+ ::
107+ :: This adds to the end if it doesn't exist.
108+ ::
109+ ++ set-header
110+ |= [header=@t value=@t =header-list]
111+ ^- ^header-list
112+ ::
113+ ?~ header-list
114+ :: we didn't encounter the value, add it to the end
115+ ::
116+ [[header value] ~]
117+ ::
118+ ?: =(key.i.header-list header)
119+ [[header value] t.header-list]
120+ ::
121+ [i.header-list $(header-list t.header-list)]
122+ :: +delete-header: removes the first instance of a header from the list
123+ ::
124+ ++ delete-header
125+ |= [header=@t =header-list]
126+ ^- ^header-list
127+ ::
128+ ?~ header-list
129+ ~
130+ :: if we see it in the list, remove it
131+ ::
132+ ?: =(key.i.header-list header)
133+ t.header-list
134+ ::
135+ [i.header-list $(header-list t.header-list)]
136+ :: +unpack-header: parse header field values
137+ ::
138+ ++ unpack-header
139+ |^ |= value=@t
140+ ^- (unit (list (map @t @t)))
141+ (rust (cass (trip value)) values)
142+ ::
143+ ++ values
144+ %+ more
145+ (ifix [. .]:(star ;~(pose ace (just '\09'))) com)
146+ pairs
147+ ::
148+ ++ pairs
149+ %+ cook
150+ ~(gas by *(map @t @t))
151+ %+ most (ifix [. .]:(star ace) mic)
152+ ;~(plug token ;~(pose ;~(pfix tis value) (easy '')))
153+ ::
154+ ++ value
155+ ;~(pose token quoted-string)
156+ ::
157+ ++ token :: 7230 token
158+ %+ cook crip
159+ ::NOTE this is ptok:de-purl:html, but can't access that here
160+ %- plus
161+ ;~ pose
162+ aln zap hax buc cen pam soq tar lus
163+ hep dot ket cab tic bar sig
164+ ==
165+ ::
166+ ++ quoted-string :: 7230 quoted string
167+ %+ cook crip
168+ %+ ifix [. .]:;~(less (jest '\\"') doq)
169+ %- star
170+ ;~ pose
171+ ;~(pfix bas ;~(pose (just '\09') ace prn))
172+ ;~(pose (just '\09') ;~(less (mask "\22\5c\7f") (shim 0x20 0xff)))
173+ ==
174+ --
175+ :: +simple-payload: a simple, one event response used for generators
176+ ::
177+ +$ simple-payload
178+ $: :: response-header: status code, etc
179+ ::
180+ =response-header
181+ :: data: the data returned as the body
182+ ::
183+ data=(unit octs)
184+ ==
185+ --
186+++ hop
187+ |$ step
188+ $%
189+ [%fetch request:http]
190+ ==
3191++ wock-app
4− |$ [model event]
192+ |$ [model step]
5193 $_ ^?
6194 |%
7195 ++ init *model
8196 ++ view |~ model *manx
9− ++ move |~ [model event] *model
197+ ++ move |~ [model step] *model
10198 --
11199++ app
12− |* [model=mold event=mold]
13− |= app=(wock-app model event)
200+ |* [model=mold step=mold]
201+ |= app=(wock-app model step)
14202 |= [cmd=@tas args=*]
15203 ?+ cmd !!
16204 %init
@@ -18,19 +206,19 @@
18206 %view
19207 (view.app (model args))
20208 %move
21− =/ [=model event-tape=tape payload=*] (,[model tape *] args)
22− (move.app [model (event [(crip event-tape) payload])])
209+ =/ [=model step-tape=tape payload=*] (,[model tape *] args)
210+ (move.app [model (step [(crip step-tape) payload])])
23211 ==
24212--
25213=>
26214|%
27−+$ event
215++$ step
28216 $%
29217 [%inc ~]
30218 [%dec ~]
31219 ==
32220--
33−%- (app @ud event)
221+%- (app @ud step)
34222|%
35223++ view
36224 |= count=@ud
@@ -40,9 +228,9 @@
40228 ;button(on-click "inc"): +
41229 ;button(on-click "dec"): -
42230 ==
43−++ init 8
231+++ init 11
44232++ move
45− |= [model=@ud step=event]
233+ |= [model=@ud step=step]
46234 ^- @ud
47235 ?- -.step
48236 %inc +(model)
wock.nock

Binary file not shown.