From 9725793a6c2e6f4efa9a8377c802eec7550c3621 Mon Sep 17 00:00:00 2001 From: fang Date: Thu, 6 Feb 2025 13:14:22 +0100 Subject: [PATCH 1/8] helpers: split into enjs and dejs files --- src/noun-dejs.ts | 55 ++++++++++++ src/noun-enjs.ts | 159 ++++++++++++++++++++++++++++++++ src/noun-helpers.ts | 214 +------------------------------------------- 3 files changed, 217 insertions(+), 211 deletions(-) create mode 100644 src/noun-dejs.ts create mode 100644 src/noun-enjs.ts diff --git a/src/noun-dejs.ts b/src/noun-dejs.ts new file mode 100644 index 0000000..acd5c35 --- /dev/null +++ b/src/noun-dejs.ts @@ -0,0 +1,55 @@ +import { Atom, Cell } from "./noun"; +import type { Noun } from "./noun"; + +function list(args: any[]): Noun { + if (args.length === 0) return Atom.zero; + return dwim([...args, Atom.zero]); +} + +type Atomizable = number | string | Atom; + +// "Do What I Mean" +function dwim(a: number): Atom; +function dwim(a: string): Atom; +function dwim(a: Atomizable, b: Atomizable): Cell; +function dwim( + a: Atomizable, + b: Atomizable, + c: Atomizable +): Cell>; +function dwim(a: Atomizable, ...b: any[]): Cell>; +function dwim(...a: any[]): Cell; +// implementation +function dwim(...args: any[]): Noun { + const n = args.length === 1 ? args[0] : args; + if (n instanceof Atom || n instanceof Cell) return n; + if (typeof n === "number") { + return Atom.fromInt(n); + } else if (typeof n === "string") { + return Atom.fromCord(n); + } else if (Array.isArray(n)) { + if (n.length < 2) { + return dwim(n[0]); + } + const head = dwim(n[n.length - 2]); + const tail = dwim(n[n.length - 1]); + let cel = new Cell(head, tail); + for (var j = n.length - 3; j >= 0; --j) { + cel = new Cell(dwim(n[j]), cel); + } + return cel; + } else if (n === null) { + return Atom.zero; + } + // objects, undefined, etc + console.error("what do you mean??", typeof n, JSON.stringify(n)); + throw new Error('dwim, but meaning unclear'); +} + +const dejs = { + nounify: dwim, + dwim, + list, +}; + +export { dejs }; diff --git a/src/noun-enjs.ts b/src/noun-enjs.ts new file mode 100644 index 0000000..6e7d51e --- /dev/null +++ b/src/noun-enjs.ts @@ -0,0 +1,159 @@ +import { Atom, Cell } from "./noun"; +import type { Noun } from "./noun"; +import { bitLength } from "./bigint"; + +type Json = null | boolean | number | string | Json[] | { [key: string]: Json }; +export type EnjsFunction = (n: Noun) => Json; +type frondOpt = { tag: string; get: EnjsFunction }; + +const frond = function (opts: frondOpt[]): EnjsFunction { + return function (noun) { + if (!(noun instanceof Cell && noun.head instanceof Atom)) { + throw new Error("frond: noun not cell with tag head"); + } + const tag = Atom.cordToString(noun.head); + for (let i = 0; i < opts.length; i++) { + if (tag === opts[i].tag) { + return { [tag]: opts[i].get(noun.tail) }; + } + } + throw new Error("frond: unknown tag" + tag); + }; +}; +type PairCell = { nom: string; get: EnjsFunction }; +const pairs = function (cels: PairCell[]): EnjsFunction { + return function (noun) { + let i = 0; + let o: Record = {}; + while (i < cels.length - 1) { + if (!(noun instanceof Cell)) { + throw new Error("pairs: noun too shallow"); + } + o[cels[i].nom] = cels[i].get(noun.head); + noun = noun.tail; + i++; + } + o[cels[i].nom] = cels[i].get(noun); + return o; + }; +}; + +const pair = function ( + na: string, + ga: EnjsFunction, + nb: string, + gb: EnjsFunction +): EnjsFunction { + return pairs([ + { nom: na, get: ga }, + { nom: nb, get: gb }, + ]); +}; + +const bucwut = function (opts: EnjsFunction[]): EnjsFunction { + return function (noun) { + for (let i = 0; i < opts.length; i++) { + try { + const res = opts[i](noun); + return res; + } catch (e) { + continue; + } + } + throw new Error("bucwut: no matches"); + }; +}; +const array = function (item: EnjsFunction): (n: Noun) => Json[] { + return function (noun) { + let a: Json[] = []; + while (noun instanceof Cell) { + a.push(item(noun.head)); + noun = noun.tail; + } + return a; + }; +}; + +const tree = function (item: EnjsFunction): (n: Noun) => Json[] { + return function (noun) { + let a: Json[] = []; + if (noun instanceof Cell) { + if (!(noun.tail instanceof Cell)) { + throw new Error("tree: malformed"); + } + a = [ + ...a, + item(noun.head), + ...tree(item)(noun.tail.head), + ...tree(item)(noun.tail.tail), + ]; + } + return a; + }; +}; + +const cord = function (noun: Noun): string { + if (!(noun instanceof Atom)) { + throw new Error(`cord: noun not atom ${noun.toString()}`); + } + return Atom.cordToString(noun); +}; + +const numb = function (noun: Noun): number | string { + if (!(noun instanceof Atom)) { + throw new Error("numb: noun not atom"); + } + if (bitLength(noun.number) <= 32) { + return Number(noun.number); + } else { + return noun.number.toString(); + } +}; + +const numb32 = function (noun: Noun): number { + if (!(noun instanceof Atom)) { + throw new Error("numb32: noun not atom"); + } + if (bitLength(noun.number) > 32) { + throw new Error("numb32: number too big"); + } + return Number(noun.number); +} + +const numbString = function (noun: Noun): string { + if (!(noun instanceof Atom)) { + throw new Error("numbString: noun not atom"); + } + return noun.number.toString(); +} + +const loob = function (noun: Noun): boolean { + return noun.loob(); +}; + +const nill = function (noun: Noun): null { + if (!(noun instanceof Atom && noun.number === 0n)) { + throw new Error("nill: not null"); + } + return null; +}; + +const path = array(cord); + +const enjs = { + frond, + pairs, + pair, + array, + loob, + tree, + cord, + numb, + numb32, + numbString, + path, + bucwut, + nill, +}; + +export { enjs }; diff --git a/src/noun-helpers.ts b/src/noun-helpers.ts index 759da77..2d0a32d 100644 --- a/src/noun-helpers.ts +++ b/src/noun-helpers.ts @@ -1,212 +1,4 @@ -import { Atom, Cell } from "./noun"; -import type { Noun } from "./noun"; -import { bitLength } from "./bigint"; - -type Json = null | boolean | number | string | Json[] | { [key: string]: Json }; -export type EnjsFunction = (n: Noun) => Json; -type frondOpt = { tag: string; get: EnjsFunction }; - -const frond = function (opts: frondOpt[]): EnjsFunction { - return function (noun) { - if (!(noun instanceof Cell && noun.head instanceof Atom)) { - throw new Error("frond: noun not cell with tag head"); - } - const tag = Atom.cordToString(noun.head); - for (let i = 0; i < opts.length; i++) { - if (tag === opts[i].tag) { - return { [tag]: opts[i].get(noun.tail) }; - } - } - throw new Error("frond: unknown tag" + tag); - }; -}; -type PairCell = { nom: string; get: EnjsFunction }; -const pairs = function (cels: PairCell[]): EnjsFunction { - return function (noun) { - let i = 0; - let o: Record = {}; - while (i < cels.length - 1) { - if (!(noun instanceof Cell)) { - throw new Error("pairs: noun too shallow"); - } - o[cels[i].nom] = cels[i].get(noun.head); - noun = noun.tail; - i++; - } - o[cels[i].nom] = cels[i].get(noun); - return o; - }; -}; - -const pair = function ( - na: string, - ga: EnjsFunction, - nb: string, - gb: EnjsFunction -): EnjsFunction { - return pairs([ - { nom: na, get: ga }, - { nom: nb, get: gb }, - ]); -}; - -const bucwut = function (opts: EnjsFunction[]): EnjsFunction { - return function (noun) { - for (let i = 0; i < opts.length; i++) { - try { - const res = opts[i](noun); - return res; - } catch (e) { - continue; - } - } - throw new Error("bucwut: no matches"); - }; -}; -const array = function (item: EnjsFunction): (n: Noun) => Json[] { - return function (noun) { - let a: Json[] = []; - while (noun instanceof Cell) { - a.push(item(noun.head)); - noun = noun.tail; - } - return a; - }; -}; - -const tree = function (item: EnjsFunction): (n: Noun) => Json[] { - return function (noun) { - let a: Json[] = []; - if (noun instanceof Cell) { - if (!(noun.tail instanceof Cell)) { - throw new Error("tree: malformed"); - } - a = [ - ...a, - item(noun.head), - ...tree(item)(noun.tail.head), - ...tree(item)(noun.tail.tail), - ]; - } - return a; - }; -}; - -const cord = function (noun: Noun): string { - if (!(noun instanceof Atom)) { - throw new Error(`cord: noun not atom ${noun.toString()}`); - } - return Atom.cordToString(noun); -}; - -const numb = function (noun: Noun): number | string { - if (!(noun instanceof Atom)) { - throw new Error("numb: noun not atom"); - } - if (bitLength(noun.number) <= 32) { - return Number(noun.number); - } else { - return noun.number.toString(); - } -}; - -const numb32 = function (noun: Noun): number { - if (!(noun instanceof Atom)) { - throw new Error("numb32: noun not atom"); - } - if (bitLength(noun.number) > 32) { - throw new Error("numb32: number too big"); - } - return Number(noun.number); -} - -const numbString = function (noun: Noun): string { - if (!(noun instanceof Atom)) { - throw new Error("numbString: noun not atom"); - } - return noun.number.toString(); -} - -const loob = function (noun: Noun): boolean { - return noun.loob(); -}; - -const nill = function (noun: Noun): null { - if (!(noun instanceof Atom && noun.number === 0n)) { - throw new Error("nill: not null"); - } - return null; -}; - -const path = array(cord); - -const enjs = { - frond, - pairs, - pair, - array, - loob, - tree, - cord, - numb, - numb32, - numbString, - path, - bucwut, - nill, -}; - -// - -function list(args: any[]): Noun { - if (args.length === 0) return Atom.zero; - return dwim([...args, Atom.zero]); -} - -type Atomizable = number | string | Atom; - -// "Do What I Mean" -function dwim(a: number): Atom; -function dwim(a: string): Atom; -function dwim(a: Atomizable, b: Atomizable): Cell; -function dwim( - a: Atomizable, - b: Atomizable, - c: Atomizable -): Cell>; -function dwim(a: Atomizable, ...b: any[]): Cell>; -function dwim(...a: any[]): Cell; -// implementation -function dwim(...args: any[]): Noun { - const n = args.length === 1 ? args[0] : args; - if (n instanceof Atom || n instanceof Cell) return n; - if (typeof n === "number") { - return Atom.fromInt(n); - } else if (typeof n === "string") { - return Atom.fromCord(n); - } else if (Array.isArray(n)) { - if (n.length < 2) { - return dwim(n[0]); - } - const head = dwim(n[n.length - 2]); - const tail = dwim(n[n.length - 1]); - let cel = new Cell(head, tail); - for (var j = n.length - 3; j >= 0; --j) { - cel = new Cell(dwim(n[j]), cel); - } - return cel; - } else if (n === null) { - return Atom.zero; - } - // objects, undefined, etc - console.error("what do you mean??", typeof n, JSON.stringify(n)); - throw new Error('dwim, but meaning unclear'); -} - -const dejs = { - nounify: dwim, - dwim, - list, -} - +import { enjs } from "./noun-enjs"; +import { dejs } from "./noun-dejs"; +const dwim = dejs.dwim; export { enjs, dejs, dwim }; From 06e25495b1c37ee6cf5b58d4b011e997c8241e85 Mon Sep 17 00:00:00 2001 From: fang Date: Thu, 6 Feb 2025 14:17:21 +0100 Subject: [PATCH 2/8] std helpers: basic set and map creation helpers --- src/index.test.ts | 27 ++++++++- src/noun-dejs.ts | 2 +- src/noun-std.ts | 147 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 src/noun-std.ts diff --git a/src/index.test.ts b/src/index.test.ts index 081ba07..114fbd6 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,6 +1,7 @@ import { jam, cue, cue_bytes, bigintToDataView, bi_cut } from "./serial"; import bits from "./bits"; import { dwim, enjs } from "./noun-helpers"; +import { putIn, putBy } from "./noun-std"; import { Atom, Cell, Noun } from "./noun"; import compiler from "./compiler"; import { bigIntFromStringWithRadix } from "./bigint"; @@ -261,4 +262,28 @@ test('dwim <-> enjs.cord', () => { const cord = enjs.cord(atom); expect(cord).toEqual(str); -}) \ No newline at end of file +}); + +test('putIn', () => { + const nums = [1, 2, 3, 3, 3, 4, 5, 6, 7, 7, 7]; + const ex = [6, [7, [5, 0, 0], 0], 4, [2, [1, 0, 0], 3, 0, 0], 0]; + + let set: Noun = Atom.zero; + for (let num of nums) { + set = putIn(set, Atom.fromInt(num)); + }; + + expect(set.equals(dwim(ex))); +}); + +test('putBy', () => { + const nums = [[1, 11], [2, 22], [3, 33], [3, 34], [3, 35], [4, 44], [5, 55], [6, 66], [7, 77], [7, 78], [7, 79]]; + const ex = [[6, 66], [[7, 79], [[5, 55], 0, 0], 0], [4, 44], [[2, 22], [[1, 11], 0, 0], [3, 35], 0, 0], 0]; + + let map: Noun = Atom.zero; + for (let num of nums) { + map = putBy(map, Atom.fromInt(num[0]), Atom.fromInt(num[1])); + }; + + expect(map.equals(dwim(ex))); +}); diff --git a/src/noun-dejs.ts b/src/noun-dejs.ts index acd5c35..ffcec06 100644 --- a/src/noun-dejs.ts +++ b/src/noun-dejs.ts @@ -52,4 +52,4 @@ const dejs = { list, }; -export { dejs }; +export { dejs, dwim }; diff --git a/src/noun-std.ts b/src/noun-std.ts new file mode 100644 index 0000000..7693e17 --- /dev/null +++ b/src/noun-std.ts @@ -0,0 +1,147 @@ +import { Atom, Cell } from "./noun"; +import type { Noun } from "./noun"; +import { dwim } from "./noun-dejs"; + +// +dor: depth order +export function dor(a: Noun, b: Noun): boolean { + // ?: =(a b) & + if (a.equals(b)) return true; + // ?. ?=(@ a) + if (a instanceof Cell) { + // ?: ?=(@ b) | + if (b instanceof Atom) return false; + // ?: =(-.a -.b) + if (a.head.equals(b.head)) + // $(a +.a, b +.b) + return dor(a.tail, b.tail); + // $(a -.a, b -.b) + return dor(a.head, b.head); + } + // ?. ?=(@ b) & + if (b instanceof Cell) return true; + // (lth a b) + return (a < b); +} + +// +gor: mug hash order, collisions fall back to +dor +export function gor(a: Noun, b: Noun): boolean { + // =+ [c=(mug a) d=(mug b)] + const c = a.mug(); + const d = b.mug(); + // ?: =(c d) + if (c === d) + // (dor a b) + return dor(a, b); + // (lth c d) + return c < d; +} + +// +mor: double mug hash order, collisions fall back to +dor +export function mor(a: Noun, b: Noun): boolean { + // =+ [c=(mug (mug a)) d=(mug (mug b))] + const c = Atom.fromInt(a.mug()).mug(); + const d = Atom.fromInt(b.mug()).mug(); + // ?: =(c d) + if (c === d) + // (dor a b) + return dor(a, b); + // (lth c d) + return c < d; +} + +// +put:in: set insertion +export function putIn(a: Noun, b: Noun): Noun { + // ?~ a + // [b ~ ~] + if (a.equals(Atom.zero)) { + return dwim(b, null, null); + } + if (a instanceof Atom || a.tail instanceof Atom) { + throw new Error('malformed set'); + } + // ?: =(b n.a) + // a + if (b.equals(a.head)) { + return a; + } + // ?: (gor b n.a) + if (gor(b, a.head)) { + // =+ c=$(a l.a) + const c = putIn(a.tail.head, b); + // ?> ?=(^ c) + if (c instanceof Atom || c.tail instanceof Atom) { + throw new Error('implementation error'); + } + // ?: (mor n.a n.c) + // a(l c) + if (mor(a.head, c.head)) { + return dwim(a.head, c, a.tail.tail); + } + // c(r a(l r.c)) + return dwim(c.head, c.tail.head, [a.head, c.tail.tail, a.tail.tail]); + } + // =+ c=$(a r.a) + const c = putIn(a.tail.tail, b); + // ?> ?=(^ c) + if (c instanceof Atom || c.tail instanceof Atom) { + throw new Error('implementation error'); + } + // ?: (mor n.a n.c) + // a(r c) + if (mor(a.head, c.head)) { + return dwim(a.head, a.tail.head, c); + } + // c(l a(r l.c)) + return dwim(c.head, [a.head, a.tail.head, c.tail.head], c.tail.tail); +} + +// +put:by: map insertion +export function putBy(a: Noun, b: Noun, c: Noun): Noun { + // ?~ a + // [[b c] ~ ~] + if (a.equals(Atom.zero)) { + return dwim([b, c], null, null); + } + if (a instanceof Atom || a.head instanceof Atom || a.tail instanceof Atom) { + throw new Error('malformed map'); + } + // ?: =(b p.n.a) + if (b.equals(a.head.head)) { + // ?: =(c q.n.a) + // a + if (c.equals(a.head.tail)) { + return a; + } + // a(n [b c]) + return dwim([b, c], a.tail); + } + // ?: (gor b p.n.a) + if (gor(b, a.head.head)) { + // =+ d=$(a l.a) + const d = putBy(a.tail.head, b, c); + // ?> ?=(^ d) + if (d instanceof Atom || d.head instanceof Atom || d.tail instanceof Atom) { + throw new Error('implementation error'); + } + // ?: (mor p.n.a p.n.d) + // a(l d) + if (mor(a.head.head, d.head.head)) { + return dwim(a.head, d, a.tail.tail); + } + // d(r a(l r.d)) + return dwim(d.head, d.tail.head, [a.head, d.tail.tail, a.tail.tail]); + } + // =+ d=$(a r.a) + const d = putBy(a.tail.tail, b, c); + // ?> ?=(^ d) + if (d instanceof Atom || d.head instanceof Atom || d.tail instanceof Atom) { + throw new Error('implementation error'); + } + // ?: (mor p.n.a p.n.d) + // a(r d) + if (mor(a.head.head, d.head.head)) { + return dwim(a.head, a.tail.head, d); + } + // d(l a(r l.d)) + return dwim(d.head, [a.head, a.tail.head, d.tail.head], d.tail.tail); +} From 13e8fb61c84ec2c5c958a8e141c87642db669c25 Mon Sep 17 00:00:00 2001 From: fang Date: Thu, 6 Feb 2025 14:48:27 +0100 Subject: [PATCH 3/8] noun: provide typechecking helpers Should use these more broadly, but that would explode the linecount of the PR I want to make with largely unrelated changes... --- src/noun-dejs.ts | 4 ++-- src/noun-std.ts | 28 +++++++++++++++++++--------- src/noun.ts | 14 ++++++++++++++ 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/noun-dejs.ts b/src/noun-dejs.ts index ffcec06..edbf7ba 100644 --- a/src/noun-dejs.ts +++ b/src/noun-dejs.ts @@ -1,4 +1,4 @@ -import { Atom, Cell } from "./noun"; +import { Atom, Cell, isNoun } from "./noun"; import type { Noun } from "./noun"; function list(args: any[]): Noun { @@ -22,7 +22,7 @@ function dwim(...a: any[]): Cell; // implementation function dwim(...args: any[]): Noun { const n = args.length === 1 ? args[0] : args; - if (n instanceof Atom || n instanceof Cell) return n; + if (isNoun(n)) return n; if (typeof n === "number") { return Atom.fromInt(n); } else if (typeof n === "string") { diff --git a/src/noun-std.ts b/src/noun-std.ts index 7693e17..50bf985 100644 --- a/src/noun-std.ts +++ b/src/noun-std.ts @@ -7,9 +7,9 @@ export function dor(a: Noun, b: Noun): boolean { // ?: =(a b) & if (a.equals(b)) return true; // ?. ?=(@ a) - if (a instanceof Cell) { + if (a.isCell()) { // ?: ?=(@ b) | - if (b instanceof Atom) return false; + if (b.isAtom()) return false; // ?: =(-.a -.b) if (a.head.equals(b.head)) // $(a +.a, b +.b) @@ -18,7 +18,7 @@ export function dor(a: Noun, b: Noun): boolean { return dor(a.head, b.head); } // ?. ?=(@ b) & - if (b instanceof Cell) return true; + if (b.isCell()) return true; // (lth a b) return (a < b); } @@ -49,6 +49,11 @@ export function mor(a: Noun, b: Noun): boolean { return c < d; } +// isSet: check for set with >0 entries, ?=([* * *]) +export function isSet(a: Noun): a is Cell> { + return a.isCell() && a.tail.isCell(); +} + // +put:in: set insertion export function putIn(a: Noun, b: Noun): Noun { // ?~ a @@ -56,7 +61,7 @@ export function putIn(a: Noun, b: Noun): Noun { if (a.equals(Atom.zero)) { return dwim(b, null, null); } - if (a instanceof Atom || a.tail instanceof Atom) { + if (!isSet(a)) { throw new Error('malformed set'); } // ?: =(b n.a) @@ -69,7 +74,7 @@ export function putIn(a: Noun, b: Noun): Noun { // =+ c=$(a l.a) const c = putIn(a.tail.head, b); // ?> ?=(^ c) - if (c instanceof Atom || c.tail instanceof Atom) { + if (!isSet(c)) { throw new Error('implementation error'); } // ?: (mor n.a n.c) @@ -83,7 +88,7 @@ export function putIn(a: Noun, b: Noun): Noun { // =+ c=$(a r.a) const c = putIn(a.tail.tail, b); // ?> ?=(^ c) - if (c instanceof Atom || c.tail instanceof Atom) { + if (!isSet(c)) { throw new Error('implementation error'); } // ?: (mor n.a n.c) @@ -95,6 +100,11 @@ export function putIn(a: Noun, b: Noun): Noun { return dwim(c.head, [a.head, a.tail.head, c.tail.head], c.tail.tail); } +// isMap: check for map with >0 entries, ?=([[* *] * *]) +export function isMap(a: Noun): a is Cell, Cell> { + return a.isCell() && a.head.isCell() && a.tail.isCell(); +} + // +put:by: map insertion export function putBy(a: Noun, b: Noun, c: Noun): Noun { // ?~ a @@ -102,7 +112,7 @@ export function putBy(a: Noun, b: Noun, c: Noun): Noun { if (a.equals(Atom.zero)) { return dwim([b, c], null, null); } - if (a instanceof Atom || a.head instanceof Atom || a.tail instanceof Atom) { + if (!isMap(a)) { throw new Error('malformed map'); } // ?: =(b p.n.a) @@ -120,7 +130,7 @@ export function putBy(a: Noun, b: Noun, c: Noun): Noun { // =+ d=$(a l.a) const d = putBy(a.tail.head, b, c); // ?> ?=(^ d) - if (d instanceof Atom || d.head instanceof Atom || d.tail instanceof Atom) { + if (!isMap(d)) { throw new Error('implementation error'); } // ?: (mor p.n.a p.n.d) @@ -134,7 +144,7 @@ export function putBy(a: Noun, b: Noun, c: Noun): Noun { // =+ d=$(a r.a) const d = putBy(a.tail.tail, b, c); // ?> ?=(^ d) - if (d instanceof Atom || d.head instanceof Atom || d.tail instanceof Atom) { + if (!isMap(d)) { throw new Error('implementation error'); } // ?: (mor p.n.a p.n.d) diff --git a/src/noun.ts b/src/noun.ts index d52309b..68f6441 100644 --- a/src/noun.ts +++ b/src/noun.ts @@ -51,6 +51,8 @@ class Atom { constructor(public readonly number: bigint) {} // common methods with Cell + isAtom(): this is Atom { return true; } + isCell(): this is Cell { return false; } pretty(out: string[], hasTail = false): void { if (this.number < 65536n) out.push(this.number.toString(10)); else { @@ -207,6 +209,8 @@ class Cell { private _mug = 0; constructor(public readonly head: TH, public readonly tail: TT, public deep = true) {} // common methods + isAtom(): this is Atom { return false; } + isCell(): this is Cell { return true; } pretty(out: string[], hasTail: boolean): void { if (!hasTail) out.push("["); this.head.pretty(out, false); @@ -266,4 +270,14 @@ class Cell { } type Noun = Atom | Cell; +export function isAtom(a: any): a is Atom { + return a instanceof Atom; +} +export function isCell(a: any): a is Cell { + return a instanceof Cell; +} +export function isNoun(a: any): a is Noun { + return isAtom(a) || isCell(a); +} + export { Atom, Cell, Noun }; From 2a9316f8ad55f8ff3bf0d71038a5d0e3001cec7e Mon Sep 17 00:00:00 2001 From: fang Date: Thu, 6 Feb 2025 14:57:14 +0100 Subject: [PATCH 4/8] dejs helpers: map and set creation --- src/noun-dejs.ts | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/noun-dejs.ts b/src/noun-dejs.ts index edbf7ba..4905502 100644 --- a/src/noun-dejs.ts +++ b/src/noun-dejs.ts @@ -1,10 +1,8 @@ import { Atom, Cell, isNoun } from "./noun"; import type { Noun } from "./noun"; +import { putIn, putBy } from "./noun-std"; -function list(args: any[]): Noun { - if (args.length === 0) return Atom.zero; - return dwim([...args, Atom.zero]); -} +// primitives type Atomizable = number | string | Atom; @@ -46,10 +44,37 @@ function dwim(...args: any[]): Noun { throw new Error('dwim, but meaning unclear'); } +// structures + +function list(args: any[]): Noun { + if (args.length === 0) return Atom.zero; + return dwim([...args, Atom.zero]); +} + +function set(args: any[]): Noun { + if (args.length === 0) return Atom.zero; + let set: Noun = Atom.zero; + for (let arg of args) { + set = putIn(set, dwim(arg)); + } + return set; +} + +function map(args: {key: any, val: any}[]): Noun { + if (args.length === 0) return Atom.zero; + let map: Noun = Atom.zero; + for (let arg of args) { + map = putBy(map, dwim(arg.key), dwim(arg.val)); + } + return map; +} + const dejs = { nounify: dwim, dwim, list, + set, + map }; export { dejs, dwim }; From c43bc86a4a5696d80ca7f4b563638eafa4e7d7ab Mon Sep 17 00:00:00 2001 From: fang Date: Thu, 6 Feb 2025 15:02:20 +0100 Subject: [PATCH 5/8] enjs helpers: frond w/o wrapper object --- src/noun-enjs.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/noun-enjs.ts b/src/noun-enjs.ts index 6e7d51e..4a9b840 100644 --- a/src/noun-enjs.ts +++ b/src/noun-enjs.ts @@ -63,6 +63,23 @@ const bucwut = function (opts: EnjsFunction[]): EnjsFunction { throw new Error("bucwut: no matches"); }; }; + +// buccen: like frond, but without the wrapper object +const buccen = function (opts: frondOpt[]): EnjsFunction { + return function (noun) { + if (!(noun instanceof Cell && noun.head instanceof Atom)) { + throw new Error("buccen: noun not cell with tag head"); + } + const tag = Atom.cordToString(noun.head); + for (let i = 0; i < opts.length; i++) { + if (tag === opts[i].tag) { + return opts[i].get(noun.tail); + } + } + throw new Error("buccen: unknown tag" + tag); + }; +}; + const array = function (item: EnjsFunction): (n: Noun) => Json[] { return function (noun) { let a: Json[] = []; @@ -152,6 +169,7 @@ const enjs = { numb32, numbString, path, + buccen, bucwut, nill, }; From 89a0fc27b14d5b16d61ad5c4e7d9cb7310c9d0dd Mon Sep 17 00:00:00 2001 From: fang Date: Thu, 6 Feb 2025 15:06:36 +0100 Subject: [PATCH 6/8] enjs helpers: tuple for "flat" cell reading --- src/noun-enjs.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/noun-enjs.ts b/src/noun-enjs.ts index 4a9b840..0b24b7d 100644 --- a/src/noun-enjs.ts +++ b/src/noun-enjs.ts @@ -20,6 +20,22 @@ const frond = function (opts: frondOpt[]): EnjsFunction { throw new Error("frond: unknown tag" + tag); }; }; + +const tuple = function(funs: EnjsFunction[]): EnjsFunction { + return function (noun) { + let o = []; + while (noun.isCell()) { + o.push(funs[0](noun.head)); + funs.splice(0, 1); + noun = noun.tail; + } + if (funs.length > 0) { + throw new Error("tuple: noun too shallow"); + } + return o; + } +} + type PairCell = { nom: string; get: EnjsFunction }; const pairs = function (cels: PairCell[]): EnjsFunction { return function (noun) { @@ -37,7 +53,6 @@ const pairs = function (cels: PairCell[]): EnjsFunction { return o; }; }; - const pair = function ( na: string, ga: EnjsFunction, @@ -159,6 +174,7 @@ const path = array(cord); const enjs = { frond, + tuple, pairs, pair, array, From 2cca5443cc85cb7d5f3f6a6386e8b719c8d43bbb Mon Sep 17 00:00:00 2001 From: fang Date: Tue, 11 Feb 2025 17:33:16 +0100 Subject: [PATCH 7/8] enjs helpers: less stupid tuple function This one can at least process the full noun always. --- src/noun-enjs.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/noun-enjs.ts b/src/noun-enjs.ts index 0b24b7d..b3b1ce4 100644 --- a/src/noun-enjs.ts +++ b/src/noun-enjs.ts @@ -24,14 +24,15 @@ const frond = function (opts: frondOpt[]): EnjsFunction { const tuple = function(funs: EnjsFunction[]): EnjsFunction { return function (noun) { let o = []; - while (noun.isCell()) { + while (funs.length > 1) { + if (noun.isAtom()) { + throw new Error("tuple: noun too shallow"); + } o.push(funs[0](noun.head)); funs.splice(0, 1); noun = noun.tail; } - if (funs.length > 0) { - throw new Error("tuple: noun too shallow"); - } + o.push(funs[0](noun)); return o; } } From a78778533c18194114b5d8ac73d160212bdad273 Mon Sep 17 00:00:00 2001 From: fang Date: Wed, 12 Feb 2025 19:47:35 +0100 Subject: [PATCH 8/8] enjs helpers: string from tape --- src/noun-enjs.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/noun-enjs.ts b/src/noun-enjs.ts index b3b1ce4..a518568 100644 --- a/src/noun-enjs.ts +++ b/src/noun-enjs.ts @@ -132,6 +132,15 @@ const cord = function (noun: Noun): string { return Atom.cordToString(noun); }; +const tape = function (noun: Noun): string { + return (array(((n: Noun) => { + if (n.isCell()) { + throw new Error("tape: malformed"); + } + return Atom.cordToString(n); + }))(noun)).join(); +} + const numb = function (noun: Noun): number | string { if (!(noun instanceof Atom)) { throw new Error("numb: noun not atom"); @@ -182,6 +191,7 @@ const enjs = { loob, tree, cord, + tape, numb, numb32, numbString,