@hviana/sema 0.1.3 → 0.1.5

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.
Files changed (123) hide show
  1. package/dist/example/demo.js +25 -25
  2. package/dist/example/train_base.d.ts +21 -19
  3. package/dist/example/train_base.js +1952 -1684
  4. package/dist/src/alphabet.d.ts +3 -3
  5. package/dist/src/alphabet.js +27 -24
  6. package/dist/src/alu/src/alu.d.ts +194 -169
  7. package/dist/src/alu/src/alu.js +414 -374
  8. package/dist/src/alu/src/expr.d.ts +43 -36
  9. package/dist/src/alu/src/expr.js +278 -252
  10. package/dist/src/alu/src/index.d.ts +98 -9
  11. package/dist/src/alu/src/index.js +63 -9
  12. package/dist/src/alu/src/kernel-arith.d.ts +5 -1
  13. package/dist/src/alu/src/kernel-arith.js +285 -195
  14. package/dist/src/alu/src/kernel-bits.d.ts +5 -1
  15. package/dist/src/alu/src/kernel-bits.js +115 -81
  16. package/dist/src/alu/src/kernel-logic.js +63 -33
  17. package/dist/src/alu/src/kernel-nd.js +195 -155
  18. package/dist/src/alu/src/kernel-numeric.d.ts +80 -16
  19. package/dist/src/alu/src/kernel-numeric.js +331 -259
  20. package/dist/src/alu/src/operation.d.ts +132 -112
  21. package/dist/src/alu/src/operation.js +163 -149
  22. package/dist/src/alu/src/parser.d.ts +175 -168
  23. package/dist/src/alu/src/parser.js +421 -382
  24. package/dist/src/alu/src/resonance.d.ts +25 -16
  25. package/dist/src/alu/src/resonance.js +56 -49
  26. package/dist/src/alu/src/text.d.ts +7 -3
  27. package/dist/src/alu/src/text.js +37 -31
  28. package/dist/src/alu/src/value.d.ts +14 -14
  29. package/dist/src/alu/src/value.js +160 -150
  30. package/dist/src/alu/test/alu.test.js +656 -543
  31. package/dist/src/bytes.d.ts +5 -1
  32. package/dist/src/bytes.js +40 -33
  33. package/dist/src/config.d.ts +102 -102
  34. package/dist/src/config.js +83 -83
  35. package/dist/src/derive/src/deduction.d.ts +62 -60
  36. package/dist/src/derive/src/deduction.js +109 -103
  37. package/dist/src/derive/src/index.d.ts +7 -1
  38. package/dist/src/derive/src/priority-queue.d.ts +8 -8
  39. package/dist/src/derive/src/priority-queue.js +61 -57
  40. package/dist/src/derive/src/rewrite.d.ts +18 -15
  41. package/dist/src/derive/src/rewrite.js +67 -59
  42. package/dist/src/derive/src/trie.d.ts +56 -56
  43. package/dist/src/derive/src/trie.js +179 -175
  44. package/dist/src/derive/test/derive.test.js +100 -96
  45. package/dist/src/extension.d.ts +16 -13
  46. package/dist/src/geometry.d.ts +42 -17
  47. package/dist/src/geometry.js +266 -235
  48. package/dist/src/index.d.ts +17 -2
  49. package/dist/src/index.js +7 -1
  50. package/dist/src/ingest-cache.d.ts +30 -25
  51. package/dist/src/ingest-cache.js +127 -108
  52. package/dist/src/mind/articulation.d.ts +5 -1
  53. package/dist/src/mind/articulation.js +112 -77
  54. package/dist/src/mind/attention.d.ts +108 -40
  55. package/dist/src/mind/attention.js +871 -749
  56. package/dist/src/mind/canonical.d.ts +19 -4
  57. package/dist/src/mind/canonical.js +31 -27
  58. package/dist/src/mind/graph-search.d.ts +225 -201
  59. package/dist/src/mind/graph-search.js +821 -742
  60. package/dist/src/mind/index.d.ts +10 -2
  61. package/dist/src/mind/junction.d.ts +58 -31
  62. package/dist/src/mind/junction.js +237 -172
  63. package/dist/src/mind/learning.d.ts +52 -16
  64. package/dist/src/mind/learning.js +165 -143
  65. package/dist/src/mind/match.d.ts +69 -20
  66. package/dist/src/mind/match.js +318 -259
  67. package/dist/src/mind/mechanisms/alu.js +16 -16
  68. package/dist/src/mind/mechanisms/cast.d.ts +13 -9
  69. package/dist/src/mind/mechanisms/cast.js +456 -363
  70. package/dist/src/mind/mechanisms/confluence.d.ts +12 -8
  71. package/dist/src/mind/mechanisms/confluence.js +183 -152
  72. package/dist/src/mind/mechanisms/cover.d.ts +8 -2
  73. package/dist/src/mind/mechanisms/cover.js +210 -148
  74. package/dist/src/mind/mechanisms/extraction.d.ts +34 -8
  75. package/dist/src/mind/mechanisms/extraction.js +288 -234
  76. package/dist/src/mind/mechanisms/recall.d.ts +10 -6
  77. package/dist/src/mind/mechanisms/recall.js +185 -126
  78. package/dist/src/mind/mind.d.ts +154 -129
  79. package/dist/src/mind/mind.js +288 -258
  80. package/dist/src/mind/pipeline-mechanism.d.ts +124 -112
  81. package/dist/src/mind/pipeline-mechanism.js +172 -161
  82. package/dist/src/mind/pipeline.d.ts +14 -4
  83. package/dist/src/mind/pipeline.js +189 -125
  84. package/dist/src/mind/primitives.d.ts +32 -8
  85. package/dist/src/mind/primitives.js +117 -99
  86. package/dist/src/mind/rationale.d.ts +98 -86
  87. package/dist/src/mind/rationale.js +121 -113
  88. package/dist/src/mind/reasoning.d.ts +13 -2
  89. package/dist/src/mind/reasoning.js +166 -129
  90. package/dist/src/mind/recognition.d.ts +4 -1
  91. package/dist/src/mind/recognition.js +208 -183
  92. package/dist/src/mind/resonance.d.ts +22 -5
  93. package/dist/src/mind/resonance.js +0 -0
  94. package/dist/src/mind/trace.d.ts +25 -6
  95. package/dist/src/mind/trace.js +58 -50
  96. package/dist/src/mind/traverse.d.ts +58 -16
  97. package/dist/src/mind/traverse.js +357 -304
  98. package/dist/src/mind/types.d.ts +127 -120
  99. package/dist/src/mind/types.js +69 -60
  100. package/dist/src/rabitq-hnsw/src/database.d.ts +179 -177
  101. package/dist/src/rabitq-hnsw/src/database.js +359 -342
  102. package/dist/src/rabitq-hnsw/src/heap.d.ts +12 -12
  103. package/dist/src/rabitq-hnsw/src/heap.js +80 -75
  104. package/dist/src/rabitq-hnsw/src/hnsw.d.ts +103 -103
  105. package/dist/src/rabitq-hnsw/src/hnsw.js +454 -428
  106. package/dist/src/rabitq-hnsw/src/index.d.ts +6 -1
  107. package/dist/src/rabitq-hnsw/src/prng.d.ts +10 -10
  108. package/dist/src/rabitq-hnsw/src/prng.js +27 -25
  109. package/dist/src/rabitq-hnsw/src/rabitq.d.ts +69 -69
  110. package/dist/src/rabitq-hnsw/src/rabitq.js +262 -246
  111. package/dist/src/rabitq-hnsw/src/store.d.ts +132 -132
  112. package/dist/src/rabitq-hnsw/src/store.js +843 -752
  113. package/dist/src/rabitq-hnsw/test/hnsw.test.js +1054 -805
  114. package/dist/src/sema.d.ts +35 -0
  115. package/dist/src/sema.js +77 -0
  116. package/dist/src/store-sqlite.d.ts +166 -144
  117. package/dist/src/store-sqlite.js +660 -578
  118. package/dist/src/store.d.ts +630 -593
  119. package/dist/src/store.js +1581 -1440
  120. package/dist/src/vec.d.ts +9 -5
  121. package/dist/src/vec.js +73 -61
  122. package/package.json +2 -2
  123. package/src/sema.ts +102 -0
@@ -23,130 +23,164 @@ import { asInt, bit, int } from "./value.js";
23
23
  * Width is chosen by the caller wide enough that the signed result cannot
24
24
  * overflow, so interpreting the top bit as the sign is always correct. */
25
25
  function toBits(n, width) {
26
- const mod = 1n << BigInt(width);
27
- let u = ((n % mod) + mod) % mod; // two's-complement representation
28
- const bits = new Array(width);
29
- for (let i = 0; i < width; i++) {
30
- bits[i] = Number(u & 1n);
31
- u >>= 1n;
32
- }
33
- return bits;
26
+ const mod = 1n << BigInt(width);
27
+ let u = ((n % mod) + mod) % mod; // two's-complement representation
28
+ const bits = new Array(width);
29
+ for (let i = 0; i < width; i++) {
30
+ bits[i] = Number(u & 1n);
31
+ u >>= 1n;
32
+ }
33
+ return bits;
34
34
  }
35
35
  /** Interpret a two's-complement little-endian bit array as a signed bigint. */
36
36
  function fromBits(bits) {
37
- const width = bits.length;
38
- let u = 0n;
39
- for (let i = 0; i < width; i++)
40
- if (bits[i])
41
- u |= 1n << BigInt(i);
42
- const signBitSet = bits[width - 1] === 1;
43
- return signBitSet ? u - (1n << BigInt(width)) : u;
37
+ const width = bits.length;
38
+ let u = 0n;
39
+ for (let i = 0; i < width; i++) {
40
+ if (bits[i]) {
41
+ u |= 1n << BigInt(i);
42
+ }
43
+ }
44
+ const signBitSet = bits[width - 1] === 1;
45
+ return signBitSet ? u - (1n << BigInt(width)) : u;
44
46
  }
45
47
  /** Bits needed to hold |n| (at least 1). */
46
48
  function magnitudeBits(n) {
47
- const a = n < 0n ? -n : n;
48
- return a === 0n ? 1 : a.toString(2).length;
49
+ const a = n < 0n ? -n : n;
50
+ return a === 0n ? 1 : a.toString(2).length;
49
51
  }
50
52
  /** A single full adder built from the logic gates: sum = a⊕b⊕cin, carry =
51
53
  * (a∧b) ∨ (cin ∧ (a⊕b)). Every gate goes through ctx.apply → nand. */
52
54
  function fullAdder(ctx, a, b, cin) {
53
- const A = bit(a), B = bit(b), C = bit(cin);
54
- const axb = ctx.apply("xor", [A, B]);
55
- const sum = ctx.apply("xor", [axb, C]);
56
- const cout = ctx.apply("or", [
57
- ctx.apply("and", [A, B]),
58
- ctx.apply("and", [C, axb]),
59
- ]);
60
- return {
61
- sum: sum.domain === "bit" ? sum.b : 0,
62
- cout: cout.domain === "bit" ? cout.b : 0,
63
- };
55
+ const A = bit(a), B = bit(b), C = bit(cin);
56
+ const axb = ctx.apply("xor", [A, B]);
57
+ const sum = ctx.apply("xor", [axb, C]);
58
+ const cout = ctx.apply("or", [
59
+ ctx.apply("and", [A, B]),
60
+ ctx.apply("and", [C, axb]),
61
+ ]);
62
+ return {
63
+ sum: sum.domain === "bit" ? sum.b : 0,
64
+ cout: cout.domain === "bit" ? cout.b : 0,
65
+ };
64
66
  }
65
67
  /** Ripple-carry add of two two's-complement bit arrays (equal width). */
66
68
  function rippleAdd(ctx, x, y) {
67
- const width = x.length;
68
- const out = new Array(width);
69
- let carry = 0;
70
- for (let i = 0; i < width; i++) {
71
- const { sum, cout } = fullAdder(ctx, x[i], y[i], carry);
72
- out[i] = sum;
73
- carry = cout;
74
- }
75
- return out;
69
+ const width = x.length;
70
+ const out = new Array(width);
71
+ let carry = 0;
72
+ for (let i = 0; i < width; i++) {
73
+ const { sum, cout } = fullAdder(ctx, x[i], y[i], carry);
74
+ out[i] = sum;
75
+ carry = cout;
76
+ }
77
+ return out;
76
78
  }
77
79
  /** Bitwise NOT of a bit array, via the not gate. */
78
80
  function notBits(ctx, x) {
79
- return x.map((b) => {
80
- const r = ctx.apply("not", [bit(b)]);
81
- return (r.domain === "bit" ? r.b : 0);
82
- });
81
+ return x.map((b) => {
82
+ const r = ctx.apply("not", [bit(b)]);
83
+ return (r.domain === "bit" ? r.b : 0);
84
+ });
83
85
  }
84
86
  /** Two's-complement negate: invert every bit, then add one. */
85
87
  function negateBitsArr(ctx, x) {
86
- const width = x.length;
87
- const one = toBits(1n, width);
88
- return rippleAdd(ctx, notBits(ctx, x), one);
88
+ const width = x.length;
89
+ const one = toBits(1n, width);
90
+ return rippleAdd(ctx, notBits(ctx, x), one);
89
91
  }
90
92
  // ── public pure functions (used by the tests and the explicit ops) ──────────
91
93
  /** Exact addition through the ripple of full_adders. */
92
94
  export function addBits(ctx, a, b) {
93
- const width = Math.max(magnitudeBits(a), magnitudeBits(b)) + 2;
94
- return fromBits(rippleAdd(ctx, toBits(a, width), toBits(b, width)));
95
+ const width = Math.max(magnitudeBits(a), magnitudeBits(b)) + 2;
96
+ return fromBits(rippleAdd(ctx, toBits(a, width), toBits(b, width)));
95
97
  }
96
98
  /** Exact two's-complement negation. */
97
99
  export function negateBits(ctx, a) {
98
- const width = magnitudeBits(a) + 2;
99
- return fromBits(negateBitsArr(ctx, toBits(a, width)));
100
+ const width = magnitudeBits(a) + 2;
101
+ return fromBits(negateBitsArr(ctx, toBits(a, width)));
100
102
  }
101
103
  /** Exact shift-add multiplication: for each set bit i of b, add (a << i) to the
102
104
  * accumulator. The per-bit gate is an AND mask of the shifted a with bᵢ; the
103
105
  * accumulation is rippleAdd — so multiply traces entirely to nand. */
104
106
  export function mulBits(ctx, a, b) {
105
- const width = magnitudeBits(a) + magnitudeBits(b) + 2;
106
- const aBits = toBits(a, width);
107
- const bBits = toBits(b, width);
108
- let acc = toBits(0n, width);
109
- for (let i = 0; i < width; i++) {
110
- if (bBits[i] === 0)
111
- continue;
112
- // a shifted left by i (within width), masked by bᵢ (= 1 here).
113
- const shifted = new Array(width);
114
- for (let k = 0; k < width; k++) {
115
- const src = k - i >= 0 ? aBits[k - i] : 0;
116
- const m = ctx.apply("and", [bit(src), bit(bBits[i])]);
117
- shifted[k] = (m.domain === "bit" ? m.b : 0);
118
- }
119
- acc = rippleAdd(ctx, acc, shifted);
107
+ const width = magnitudeBits(a) + magnitudeBits(b) + 2;
108
+ const aBits = toBits(a, width);
109
+ const bBits = toBits(b, width);
110
+ let acc = toBits(0n, width);
111
+ for (let i = 0; i < width; i++) {
112
+ if (bBits[i] === 0) {
113
+ continue;
114
+ }
115
+ // a shifted left by i (within width), masked by bᵢ (= 1 here).
116
+ const shifted = new Array(width);
117
+ for (let k = 0; k < width; k++) {
118
+ const src = k - i >= 0 ? aBits[k - i] : 0;
119
+ const m = ctx.apply("and", [bit(src), bit(bBits[i])]);
120
+ shifted[k] = m.domain === "bit" ? m.b : 0;
120
121
  }
121
- return fromBits(acc);
122
+ acc = rippleAdd(ctx, acc, shifted);
123
+ }
124
+ return fromBits(acc);
122
125
  }
123
126
  /** Sign of an integer as {-1, 0, 1}: zero when every bit is zero, −1 when the
124
127
  * two's-complement sign bit is set, +1 otherwise — read off the bit array. */
125
128
  export function signBits(ctx, a) {
126
- if (a === 0n)
127
- return 0n;
128
- const width = magnitudeBits(a) + 2;
129
- const bits = toBits(a, width);
130
- return bits[width - 1] === 1 ? -1n : 1n;
129
+ if (a === 0n) {
130
+ return 0n;
131
+ }
132
+ const width = magnitudeBits(a) + 2;
133
+ const bits = toBits(a, width);
134
+ return bits[width - 1] === 1 ? -1n : 1n;
131
135
  }
132
136
  /** Compare via sign(subtract): −1 if a<b, 0 if a=b, +1 if a>b, where subtract is
133
137
  * add ∘ negate — so comparison is structured arithmetic, not a new primitive. */
134
138
  export function compareBits(ctx, a, b) {
135
- return signBits(ctx, addBits(ctx, a, negateBits(ctx, b)));
139
+ return signBits(ctx, addBits(ctx, a, negateBits(ctx, b)));
136
140
  }
137
141
  /** Register the bit-bootstrap ops (int domain) into `r`. They have no surface
138
142
  * forms — they are an internal exhibit, exercised by the tests and by anyone
139
143
  * who wants to walk the nand→arithmetic chain, not matched from a query. */
140
144
  export function registerBits(r) {
141
- // full_adder exposed as a 3-bit op returning the 2-bit {sum, carry} as an int
142
- // 0..3 (carry in bit 1, sum in bit 0) so it is callable/testable on its own.
143
- r.derive("bits.fullAdder", 3, [], (args, ctx) => {
144
- const s = fullAdder(ctx, asInt(args[0]) === 0n ? 0 : 1, asInt(args[1]) === 0n ? 0 : 1, asInt(args[2]) === 0n ? 0 : 1);
145
- return int(BigInt(s.sum) | (BigInt(s.cout) << 1n));
146
- });
147
- r.derive("bits.add", 2, [], (args, ctx) => int(addBits(ctx, asInt(args[0]), asInt(args[1]))));
148
- r.derive("bits.negate", 1, [], (args, ctx) => int(negateBits(ctx, asInt(args[0]))));
149
- r.derive("bits.multiply", 2, [], (args, ctx) => int(mulBits(ctx, asInt(args[0]), asInt(args[1]))));
150
- r.derive("bits.sign", 1, [], (args, ctx) => int(signBits(ctx, asInt(args[0]))));
151
- r.derive("bits.compare", 2, [], (args, ctx) => int(compareBits(ctx, asInt(args[0]), asInt(args[1]))));
145
+ // full_adder exposed as a 3-bit op returning the 2-bit {sum, carry} as an int
146
+ // 0..3 (carry in bit 1, sum in bit 0) so it is callable/testable on its own.
147
+ r.derive("bits.fullAdder", 3, [], (args, ctx) => {
148
+ const s = fullAdder(
149
+ ctx,
150
+ asInt(args[0]) === 0n ? 0 : 1,
151
+ asInt(args[1]) === 0n ? 0 : 1,
152
+ asInt(args[2]) === 0n ? 0 : 1,
153
+ );
154
+ return int(BigInt(s.sum) | (BigInt(s.cout) << 1n));
155
+ });
156
+ r.derive(
157
+ "bits.add",
158
+ 2,
159
+ [],
160
+ (args, ctx) => int(addBits(ctx, asInt(args[0]), asInt(args[1]))),
161
+ );
162
+ r.derive(
163
+ "bits.negate",
164
+ 1,
165
+ [],
166
+ (args, ctx) => int(negateBits(ctx, asInt(args[0]))),
167
+ );
168
+ r.derive(
169
+ "bits.multiply",
170
+ 2,
171
+ [],
172
+ (args, ctx) => int(mulBits(ctx, asInt(args[0]), asInt(args[1]))),
173
+ );
174
+ r.derive(
175
+ "bits.sign",
176
+ 1,
177
+ [],
178
+ (args, ctx) => int(signBits(ctx, asInt(args[0]))),
179
+ );
180
+ r.derive(
181
+ "bits.compare",
182
+ 2,
183
+ [],
184
+ (args, ctx) => int(compareBits(ctx, asInt(args[0]), asInt(args[1]))),
185
+ );
152
186
  }
@@ -24,37 +24,67 @@ import { asBit, bit } from "./value.js";
24
24
  /** Register the logic layer into `r`. Idempotent in effect (re-registering
25
25
  * overwrites with the same definitions). */
26
26
  export function registerLogic(r) {
27
- // The one irreducible gate. ¬(a ∧ b).
28
- r.prim("nand", 2, [], (args) => {
29
- const a = asBit(args[0]);
30
- const b = asBit(args[1]);
31
- return bit(a & b ? 0 : 1);
32
- });
33
- // Exposed-but-derived basic gates.
34
- r.derive("not", 1, ["not", "!", "~", "¬"], (args, ctx) => ctx.apply("nand", [args[0], args[0]]));
35
- r.derive("and", 2, ["and", "&", "&&", "∧"], (args, ctx) => ctx.apply("not", [ctx.apply("nand", [args[0], args[1]])]));
36
- r.derive("or", 2, ["or", "|", "||", "∨"], (args, ctx) => {
37
- const na = ctx.apply("not", [args[0]]);
38
- const nb = ctx.apply("not", [args[1]]);
39
- return ctx.apply("nand", [na, nb]);
40
- });
41
- // Fully derived gates.
42
- r.derive("nor", 2, ["nor", "⊽"], (args, ctx) => ctx.apply("not", [ctx.apply("or", [args[0], args[1]])]));
43
- r.derive("xor", 2, ["xor", "^", ""], (args, ctx) => {
44
- const a = args[0], b = args[1];
45
- const left = ctx.apply("and", [a, ctx.apply("not", [b])]);
46
- const right = ctx.apply("and", [ctx.apply("not", [a]), b]);
47
- return ctx.apply("or", [left, right]);
48
- });
49
- r.derive("xnor", 2, ["xnor", "⊙"], (args, ctx) => ctx.apply("not", [ctx.apply("xor", [args[0], args[1]])]));
50
- r.derive("implies", 2, ["implies", "=>", "→", "⇒"], (args, ctx) => ctx.apply("or", [ctx.apply("not", [args[0]]), args[1]]));
51
- r.derive("iff", 2, ["iff", "<=>", "↔", "⇔"], (args, ctx) => ctx.apply("xnor", [args[0], args[1]]));
52
- // mux(s, a, b): select a when s = 0, b when s = 1. The bridge to control
53
- // flow — conditional selection, and (with recursion in the caller) looping.
54
- r.derive("mux", 3, ["mux", "select", "?:"], (args, ctx) => {
55
- const s = args[0], a = args[1], b = args[2];
56
- const lo = ctx.apply("and", [ctx.apply("not", [s]), a]);
57
- const hi = ctx.apply("and", [s, b]);
58
- return ctx.apply("or", [lo, hi]);
59
- });
27
+ // The one irreducible gate. ¬(a ∧ b).
28
+ r.prim("nand", 2, [], (args) => {
29
+ const a = asBit(args[0]);
30
+ const b = asBit(args[1]);
31
+ return bit(a & b ? 0 : 1);
32
+ });
33
+ // Exposed-but-derived basic gates.
34
+ r.derive(
35
+ "not",
36
+ 1,
37
+ ["not", "!", "~", "¬"],
38
+ (args, ctx) => ctx.apply("nand", [args[0], args[0]]),
39
+ );
40
+ r.derive(
41
+ "and",
42
+ 2,
43
+ ["and", "&", "&&", ""],
44
+ (args, ctx) => ctx.apply("not", [ctx.apply("nand", [args[0], args[1]])]),
45
+ );
46
+ r.derive("or", 2, ["or", "|", "||", "∨"], (args, ctx) => {
47
+ const na = ctx.apply("not", [args[0]]);
48
+ const nb = ctx.apply("not", [args[1]]);
49
+ return ctx.apply("nand", [na, nb]);
50
+ });
51
+ // Fully derived gates.
52
+ r.derive(
53
+ "nor",
54
+ 2,
55
+ ["nor", "⊽"],
56
+ (args, ctx) => ctx.apply("not", [ctx.apply("or", [args[0], args[1]])]),
57
+ );
58
+ r.derive("xor", 2, ["xor", "^", "⊕"], (args, ctx) => {
59
+ const a = args[0], b = args[1];
60
+ const left = ctx.apply("and", [a, ctx.apply("not", [b])]);
61
+ const right = ctx.apply("and", [ctx.apply("not", [a]), b]);
62
+ return ctx.apply("or", [left, right]);
63
+ });
64
+ r.derive(
65
+ "xnor",
66
+ 2,
67
+ ["xnor", "⊙"],
68
+ (args, ctx) => ctx.apply("not", [ctx.apply("xor", [args[0], args[1]])]),
69
+ );
70
+ r.derive(
71
+ "implies",
72
+ 2,
73
+ ["implies", "=>", "→", "⇒"],
74
+ (args, ctx) => ctx.apply("or", [ctx.apply("not", [args[0]]), args[1]]),
75
+ );
76
+ r.derive(
77
+ "iff",
78
+ 2,
79
+ ["iff", "<=>", "↔", "⇔"],
80
+ (args, ctx) => ctx.apply("xnor", [args[0], args[1]]),
81
+ );
82
+ // mux(s, a, b): select a when s = 0, b when s = 1. The bridge to control
83
+ // flow — conditional selection, and (with recursion in the caller) looping.
84
+ r.derive("mux", 3, ["mux", "select", "?:"], (args, ctx) => {
85
+ const s = args[0], a = args[1], b = args[2];
86
+ const lo = ctx.apply("and", [ctx.apply("not", [s]), a]);
87
+ const hi = ctx.apply("and", [s, b]);
88
+ return ctx.apply("or", [lo, hi]);
89
+ });
60
90
  }