@hviana/sema 0.1.5 → 0.1.6

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 (160) hide show
  1. package/AGENTS.md +6 -5
  2. package/CITATION.cff +49 -0
  3. package/HOW_IT_WORKS.md +11 -12
  4. package/README.md +7 -5
  5. package/dist/example/demo.js +25 -25
  6. package/dist/example/train_base.d.ts +19 -21
  7. package/dist/example/train_base.js +1687 -1952
  8. package/dist/src/alphabet.d.ts +3 -3
  9. package/dist/src/alphabet.js +24 -27
  10. package/dist/src/alu/src/alu.d.ts +169 -194
  11. package/dist/src/alu/src/alu.js +374 -414
  12. package/dist/src/alu/src/expr.d.ts +36 -43
  13. package/dist/src/alu/src/expr.js +252 -278
  14. package/dist/src/alu/src/index.d.ts +9 -98
  15. package/dist/src/alu/src/index.js +10 -64
  16. package/dist/src/alu/src/kernel-arith.d.ts +1 -5
  17. package/dist/src/alu/src/kernel-arith.js +195 -285
  18. package/dist/src/alu/src/kernel-bits.d.ts +1 -5
  19. package/dist/src/alu/src/kernel-bits.js +81 -115
  20. package/dist/src/alu/src/kernel-logic.js +33 -63
  21. package/dist/src/alu/src/kernel-nd.js +155 -195
  22. package/dist/src/alu/src/kernel-numeric.d.ts +16 -80
  23. package/dist/src/alu/src/kernel-numeric.js +259 -331
  24. package/dist/src/alu/src/operation.d.ts +112 -132
  25. package/dist/src/alu/src/operation.js +149 -163
  26. package/dist/src/alu/src/parser.d.ts +168 -175
  27. package/dist/src/alu/src/parser.js +382 -421
  28. package/dist/src/alu/src/resonance.d.ts +16 -25
  29. package/dist/src/alu/src/resonance.js +49 -56
  30. package/dist/src/alu/src/text.d.ts +3 -7
  31. package/dist/src/alu/src/text.js +31 -37
  32. package/dist/src/alu/src/value.d.ts +14 -14
  33. package/dist/src/alu/src/value.js +150 -160
  34. package/dist/src/alu/test/alu.test.js +543 -656
  35. package/dist/src/bytes.d.ts +1 -5
  36. package/dist/src/bytes.js +33 -40
  37. package/dist/src/config.d.ts +99 -102
  38. package/dist/src/config.js +78 -83
  39. package/dist/src/derive/src/deduction.d.ts +60 -62
  40. package/dist/src/derive/src/deduction.js +103 -109
  41. package/dist/src/derive/src/index.d.ts +1 -7
  42. package/dist/src/derive/src/priority-queue.d.ts +8 -8
  43. package/dist/src/derive/src/priority-queue.js +57 -61
  44. package/dist/src/derive/src/rewrite.d.ts +15 -18
  45. package/dist/src/derive/src/rewrite.js +59 -67
  46. package/dist/src/derive/src/trie.d.ts +56 -56
  47. package/dist/src/derive/src/trie.js +175 -179
  48. package/dist/src/derive/test/derive.test.js +96 -100
  49. package/dist/src/extension.d.ts +13 -16
  50. package/dist/src/geometry.d.ts +17 -42
  51. package/dist/src/geometry.js +235 -266
  52. package/dist/src/index.d.ts +2 -17
  53. package/dist/src/index.js +2 -12
  54. package/dist/src/ingest-cache.d.ts +25 -30
  55. package/dist/src/ingest-cache.js +108 -127
  56. package/dist/src/mind/articulation.d.ts +1 -5
  57. package/dist/src/mind/articulation.js +77 -112
  58. package/dist/src/mind/attention.d.ts +40 -108
  59. package/dist/src/mind/attention.js +749 -871
  60. package/dist/src/mind/canonical.d.ts +4 -19
  61. package/dist/src/mind/canonical.js +27 -31
  62. package/dist/src/mind/graph-search.d.ts +201 -225
  63. package/dist/src/mind/graph-search.js +742 -821
  64. package/dist/src/mind/index.d.ts +2 -10
  65. package/dist/src/mind/junction.d.ts +31 -58
  66. package/dist/src/mind/junction.js +172 -237
  67. package/dist/src/mind/learning.d.ts +16 -52
  68. package/dist/src/mind/learning.js +143 -165
  69. package/dist/src/mind/match.d.ts +20 -69
  70. package/dist/src/mind/match.js +259 -318
  71. package/dist/src/mind/mechanisms/alu.js +16 -16
  72. package/dist/src/mind/mechanisms/cast.d.ts +9 -13
  73. package/dist/src/mind/mechanisms/cast.js +363 -456
  74. package/dist/src/mind/mechanisms/confluence.d.ts +8 -12
  75. package/dist/src/mind/mechanisms/confluence.js +152 -183
  76. package/dist/src/mind/mechanisms/cover.d.ts +2 -8
  77. package/dist/src/mind/mechanisms/cover.js +148 -210
  78. package/dist/src/mind/mechanisms/extraction.d.ts +8 -34
  79. package/dist/src/mind/mechanisms/extraction.js +234 -288
  80. package/dist/src/mind/mechanisms/recall.d.ts +6 -10
  81. package/dist/src/mind/mechanisms/recall.js +126 -185
  82. package/dist/src/mind/mind.d.ts +129 -154
  83. package/dist/src/mind/mind.js +258 -288
  84. package/dist/src/mind/pipeline-mechanism.d.ts +112 -124
  85. package/dist/src/mind/pipeline-mechanism.js +161 -172
  86. package/dist/src/mind/pipeline.d.ts +4 -14
  87. package/dist/src/mind/pipeline.js +125 -189
  88. package/dist/src/mind/primitives.d.ts +8 -32
  89. package/dist/src/mind/primitives.js +99 -117
  90. package/dist/src/mind/rationale.d.ts +86 -98
  91. package/dist/src/mind/rationale.js +113 -121
  92. package/dist/src/mind/reasoning.d.ts +2 -13
  93. package/dist/src/mind/reasoning.js +129 -166
  94. package/dist/src/mind/recognition.d.ts +1 -4
  95. package/dist/src/mind/recognition.js +183 -208
  96. package/dist/src/mind/resonance.d.ts +5 -22
  97. package/dist/src/mind/resonance.js +0 -0
  98. package/dist/src/mind/trace.d.ts +6 -25
  99. package/dist/src/mind/trace.js +50 -58
  100. package/dist/src/mind/traverse.d.ts +16 -58
  101. package/dist/src/mind/traverse.js +304 -357
  102. package/dist/src/mind/types.d.ts +120 -127
  103. package/dist/src/mind/types.js +60 -69
  104. package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
  105. package/dist/src/rabitq-ivf/src/database.js +201 -0
  106. package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
  107. package/dist/src/{rabitq-hnsw → rabitq-ivf}/src/index.js +1 -3
  108. package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
  109. package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
  110. package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
  111. package/dist/src/rabitq-ivf/src/prng.js +36 -0
  112. package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
  113. package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
  114. package/dist/src/sema.d.ts +9 -13
  115. package/dist/src/sema.js +26 -40
  116. package/dist/src/store-sqlite.d.ts +169 -166
  117. package/dist/src/store-sqlite.js +762 -662
  118. package/dist/src/store.d.ts +586 -630
  119. package/dist/src/store.js +1423 -1581
  120. package/dist/src/vec.d.ts +5 -9
  121. package/dist/src/vec.js +61 -73
  122. package/example/train_base.ts +13 -10
  123. package/package.json +1 -1
  124. package/src/alu/README.md +1 -1
  125. package/src/alu/src/index.ts +1 -1
  126. package/src/config.ts +19 -27
  127. package/src/index.ts +6 -11
  128. package/src/rabitq-ivf/README.md +56 -0
  129. package/src/rabitq-ivf/src/database.ts +276 -0
  130. package/src/{rabitq-hnsw → rabitq-ivf}/src/index.ts +2 -5
  131. package/src/rabitq-ivf/src/ivf.ts +1330 -0
  132. package/src/{rabitq-hnsw → rabitq-ivf}/src/prng.ts +1 -1
  133. package/src/store-sqlite.ts +196 -9
  134. package/src/store.ts +8 -32
  135. package/test/08-storage.test.mjs +3 -3
  136. package/test/14-scaling.test.mjs +2 -2
  137. package/test/35-ivf.test.mjs +263 -0
  138. package/test/36-bloom.test.mjs +123 -0
  139. package/dist/src/rabitq-hnsw/src/database.d.ts +0 -202
  140. package/dist/src/rabitq-hnsw/src/database.js +0 -405
  141. package/dist/src/rabitq-hnsw/src/heap.d.ts +0 -22
  142. package/dist/src/rabitq-hnsw/src/heap.js +0 -94
  143. package/dist/src/rabitq-hnsw/src/hnsw.d.ts +0 -125
  144. package/dist/src/rabitq-hnsw/src/hnsw.js +0 -500
  145. package/dist/src/rabitq-hnsw/src/index.d.ts +0 -15
  146. package/dist/src/rabitq-hnsw/src/prng.d.ts +0 -19
  147. package/dist/src/rabitq-hnsw/src/prng.js +0 -38
  148. package/dist/src/rabitq-hnsw/src/rabitq.d.ts +0 -95
  149. package/dist/src/rabitq-hnsw/src/rabitq.js +0 -299
  150. package/dist/src/rabitq-hnsw/src/store.d.ts +0 -162
  151. package/dist/src/rabitq-hnsw/src/store.js +0 -916
  152. package/dist/src/rabitq-hnsw/test/hnsw.test.d.ts +0 -1
  153. package/dist/src/rabitq-hnsw/test/hnsw.test.js +0 -1197
  154. package/src/rabitq-hnsw/README.md +0 -303
  155. package/src/rabitq-hnsw/src/database.ts +0 -492
  156. package/src/rabitq-hnsw/src/heap.ts +0 -90
  157. package/src/rabitq-hnsw/src/hnsw.ts +0 -514
  158. package/src/rabitq-hnsw/src/store.ts +0 -994
  159. package/src/rabitq-hnsw/test/hnsw.test.ts +0 -1213
  160. /package/src/{rabitq-hnsw → rabitq-ivf}/src/rabitq.ts +0 -0
@@ -36,73 +36,65 @@ import { lightestDerivation } from "./deduction.js";
36
36
  * start there. No quadratic span structure.
37
37
  */
38
38
  export function coverSequence(length, candidates) {
39
- if (length <= 0) {
40
- return { spans: [], covered: 0, uncovered: 0 };
41
- }
42
- // Candidates indexed by where they start, and by their exact (start,end) edge
43
- // for reconstruction. Both are O(|candidates|) to build and to read.
44
- const byStart = Array.from({ length }, () => []);
45
- const byEdge = new Map();
46
- let maxWeight = 1;
47
- for (const c of candidates) {
48
- if (c.end <= c.start || c.start < 0 || c.end > length) {
49
- continue;
39
+ if (length <= 0)
40
+ return { spans: [], covered: 0, uncovered: 0 };
41
+ // Candidates indexed by where they start, and by their exact (start,end) edge
42
+ // for reconstruction. Both are O(|candidates|) to build and to read.
43
+ const byStart = Array.from({ length }, () => []);
44
+ const byEdge = new Map();
45
+ let maxWeight = 1;
46
+ for (const c of candidates) {
47
+ if (c.end <= c.start || c.start < 0 || c.end > length)
48
+ continue;
49
+ const w = c.weight ?? 1;
50
+ if (w > maxWeight)
51
+ maxWeight = w;
52
+ byStart[c.start].push(c);
53
+ const edge = c.start * (length + 1) + c.end;
54
+ const prev = byEdge.get(edge);
55
+ if (!prev || w < (prev.weight ?? 1))
56
+ byEdge.set(edge, c);
50
57
  }
51
- const w = c.weight ?? 1;
52
- if (w > maxWeight) {
53
- maxWeight = w;
58
+ // One uncovered symbol must outweigh any sum of span weights, so coverage is
59
+ // strictly the primary objective and weight only breaks ties.
60
+ const skipCost = maxWeight * length + 1;
61
+ const system = {
62
+ key: (p) => "" + p,
63
+ axioms: () => [{ item: 0, cost: 0 }],
64
+ isGoal: (p) => p === length,
65
+ // No nonzero admissible bound is available here: a single candidate can
66
+ // cover the whole remainder for unit cost, so any per-symbol estimate would
67
+ // overestimate. With h = 0 this is exact Knuth/Dijkstra over the frontier —
68
+ // optimal, and linear in the positions. (The A* outside bound is for systems
69
+ // whose remaining cost can be genuinely lower-bounded; see the engine.)
70
+ *rules(p) {
71
+ if (p >= length)
72
+ return;
73
+ yield { premises: [p], conclusion: p + 1, cost: skipCost }; // leave uncovered
74
+ for (const c of byStart[p]) {
75
+ yield { premises: [p], conclusion: c.end, cost: c.weight ?? 1 };
76
+ }
77
+ },
78
+ };
79
+ const best = lightestDerivation(system);
80
+ if (!best)
81
+ return { spans: [], covered: 0, uncovered: length };
82
+ // Walk the frontier chain back to the axiom; an edge p→q is a chosen span iff
83
+ // a candidate spans exactly [p, q) (a covering edge is always cheaper than the
84
+ // skips it replaces, so it appears on the optimal path wherever it is used).
85
+ const spans = [];
86
+ let node = best;
87
+ while (node && node.rule) {
88
+ const to = node.item;
89
+ const from = node.premises[0].item;
90
+ const span = byEdge.get(from * (length + 1) + to);
91
+ if (span)
92
+ spans.push(span);
93
+ node = node.premises[0];
54
94
  }
55
- byStart[c.start].push(c);
56
- const edge = c.start * (length + 1) + c.end;
57
- const prev = byEdge.get(edge);
58
- if (!prev || w < (prev.weight ?? 1)) {
59
- byEdge.set(edge, c);
60
- }
61
- }
62
- // One uncovered symbol must outweigh any sum of span weights, so coverage is
63
- // strictly the primary objective and weight only breaks ties.
64
- const skipCost = maxWeight * length + 1;
65
- const system = {
66
- key: (p) => "" + p,
67
- axioms: () => [{ item: 0, cost: 0 }],
68
- isGoal: (p) => p === length,
69
- // No nonzero admissible bound is available here: a single candidate can
70
- // cover the whole remainder for unit cost, so any per-symbol estimate would
71
- // overestimate. With h = 0 this is exact Knuth/Dijkstra over the frontier —
72
- // optimal, and linear in the positions. (The A* outside bound is for systems
73
- // whose remaining cost can be genuinely lower-bounded; see the engine.)
74
- *rules(p) {
75
- if (p >= length) {
76
- return;
77
- }
78
- yield { premises: [p], conclusion: p + 1, cost: skipCost }; // leave uncovered
79
- for (const c of byStart[p]) {
80
- yield { premises: [p], conclusion: c.end, cost: c.weight ?? 1 };
81
- }
82
- },
83
- };
84
- const best = lightestDerivation(system);
85
- if (!best) {
86
- return { spans: [], covered: 0, uncovered: length };
87
- }
88
- // Walk the frontier chain back to the axiom; an edge p→q is a chosen span iff
89
- // a candidate spans exactly [p, q) (a covering edge is always cheaper than the
90
- // skips it replaces, so it appears on the optimal path wherever it is used).
91
- const spans = [];
92
- let node = best;
93
- while (node && node.rule) {
94
- const to = node.item;
95
- const from = node.premises[0].item;
96
- const span = byEdge.get(from * (length + 1) + to);
97
- if (span) {
98
- spans.push(span);
99
- }
100
- node = node.premises[0];
101
- }
102
- spans.reverse();
103
- let covered = 0;
104
- for (const s of spans) {
105
- covered += s.end - s.start;
106
- }
107
- return { spans, covered, uncovered: length - covered };
95
+ spans.reverse();
96
+ let covered = 0;
97
+ for (const s of spans)
98
+ covered += s.end - s.start;
99
+ return { spans, covered, uncovered: length - covered };
108
100
  }
@@ -29,62 +29,62 @@
29
29
  */
30
30
  /** One pattern occurrence found by the trie. */
31
31
  export interface Match<P> {
32
- /** Start offset in the searched sequence (inclusive). */
33
- start: number;
34
- /** End offset (exclusive); `end - start === length`. */
35
- end: number;
36
- /** Length of the matched pattern. */
37
- length: number;
38
- /** Pattern id, assigned in insertion order (0-based). */
39
- id: number;
40
- /** Payload registered with the pattern. */
41
- payload: P;
42
- }
43
- export declare class Trie<P = undefined> {
44
- private _nxt;
45
- private _sym;
46
- private _multi;
47
- private _end;
48
- private readonly _lens;
49
- private readonly _vals;
50
- private _len;
51
- constructor();
52
- /** The root state, for cursor walks. */
53
- get root(): number;
54
- /** Number of distinct patterns stored. */
55
- get size(): number;
56
- /** Allocate a fresh state, growing the typed arrays by a fixed increment
57
- * when full. No power-of-2 doubling — the transient double-memory spike
58
- * during growth is bounded to the increment. */
59
- private _state;
60
- /** Follow symbol `c` from state `s`. Returns the next state, or -1. */
61
- private _follow;
62
- /** Follow one symbol from `state`; returns the next state or -1 if none. */
63
- step(state: number, symbol: number): number;
64
- /** The pattern ending exactly at `state`, or null. */
65
- terminal(state: number): {
32
+ /** Start offset in the searched sequence (inclusive). */
33
+ start: number;
34
+ /** End offset (exclusive); `end - start === length`. */
35
+ end: number;
36
+ /** Length of the matched pattern. */
37
+ length: number;
38
+ /** Pattern id, assigned in insertion order (0-based). */
66
39
  id: number;
40
+ /** Payload registered with the pattern. */
67
41
  payload: P;
68
- } | null;
69
- /** Length of the pattern with this id. */
70
- lengthOf(id: number): number;
71
- /**
72
- * Insert a pattern, returning its id. Inserting the same symbol-sequence
73
- * twice returns the first id and keeps the first payload (patterns are keyed
74
- * by content). Empty patterns are ignored and return -1.
75
- */
76
- insert(pattern: ArrayLike<number>, payload: P): number;
77
- /**
78
- * Every stored pattern that begins exactly at `pos` in `seq`, shortest first.
79
- * Walks forward from the root in O(longest match); reports nothing about any
80
- * other position. This is the on-demand probe the search uses.
81
- */
82
- matchesAt(seq: ArrayLike<number>, pos: number): Array<Match<P>>;
83
- /**
84
- * Every occurrence of every pattern anywhere in `seq` (eager form, the union
85
- * of {@link matchesAt} over all start positions). O(seq · longest pattern),
86
- * independent of how many patterns are stored. Use {@link matchesAt} when the
87
- * search only needs the sites at a particular position.
88
- */
89
- scan(seq: ArrayLike<number>): Array<Match<P>>;
42
+ }
43
+ export declare class Trie<P = undefined> {
44
+ private _nxt;
45
+ private _sym;
46
+ private _multi;
47
+ private _end;
48
+ private readonly _lens;
49
+ private readonly _vals;
50
+ private _len;
51
+ constructor();
52
+ /** The root state, for cursor walks. */
53
+ get root(): number;
54
+ /** Number of distinct patterns stored. */
55
+ get size(): number;
56
+ /** Allocate a fresh state, growing the typed arrays by a fixed increment
57
+ * when full. No power-of-2 doubling — the transient double-memory spike
58
+ * during growth is bounded to the increment. */
59
+ private _state;
60
+ /** Follow symbol `c` from state `s`. Returns the next state, or -1. */
61
+ private _follow;
62
+ /** Follow one symbol from `state`; returns the next state or -1 if none. */
63
+ step(state: number, symbol: number): number;
64
+ /** The pattern ending exactly at `state`, or null. */
65
+ terminal(state: number): {
66
+ id: number;
67
+ payload: P;
68
+ } | null;
69
+ /** Length of the pattern with this id. */
70
+ lengthOf(id: number): number;
71
+ /**
72
+ * Insert a pattern, returning its id. Inserting the same symbol-sequence
73
+ * twice returns the first id and keeps the first payload (patterns are keyed
74
+ * by content). Empty patterns are ignored and return -1.
75
+ */
76
+ insert(pattern: ArrayLike<number>, payload: P): number;
77
+ /**
78
+ * Every stored pattern that begins exactly at `pos` in `seq`, shortest first.
79
+ * Walks forward from the root in O(longest match); reports nothing about any
80
+ * other position. This is the on-demand probe the search uses.
81
+ */
82
+ matchesAt(seq: ArrayLike<number>, pos: number): Array<Match<P>>;
83
+ /**
84
+ * Every occurrence of every pattern anywhere in `seq` (eager form, the union
85
+ * of {@link matchesAt} over all start positions). O(seq · longest pattern),
86
+ * independent of how many patterns are stored. Use {@link matchesAt} when the
87
+ * search only needs the sites at a particular position.
88
+ */
89
+ scan(seq: ArrayLike<number>): Array<Match<P>>;
90
90
  }
@@ -29,193 +29,189 @@
29
29
  */
30
30
  const ROOT = 0;
31
31
  export class Trie {
32
- // ── compact transition storage ──────────────────────────────────────────
33
- // _nxt[s] — -1 terminal (no outgoing transition)
34
- // -2 multi-transition → _multi.get(s)
35
- // >=0 singleton target; the symbol is _sym[s]
36
- _nxt;
37
- _sym;
38
- _multi = null;
39
- // _end[s] is the pattern id ending exactly at s, or -1.
40
- _end;
41
- // Per-pattern data, indexed by pattern id.
42
- _lens = [];
43
- _vals = [];
44
- _len = 1; // next free state (state 0 = root)
45
- constructor() {
46
- const c = 256;
47
- this._nxt = new Int32Array(c);
48
- this._sym = new Uint8Array(c);
49
- this._end = new Int32Array(c);
50
- this._nxt[ROOT] = -1;
51
- this._end[ROOT] = -1;
52
- }
53
- /** The root state, for cursor walks. */
54
- get root() {
55
- return ROOT;
56
- }
57
- /** Number of distinct patterns stored. */
58
- get size() {
59
- return this._lens.length;
60
- }
61
- // ── internal helpers ────────────────────────────────────────────────────
62
- /** Allocate a fresh state, growing the typed arrays by a fixed increment
63
- * when full. No power-of-2 doubling — the transient double-memory spike
64
- * during growth is bounded to the increment. */
65
- _state() {
66
- const s = this._len++;
67
- if (s >= this._nxt.length) {
68
- const c = s + 4096;
69
- const nn = new Int32Array(c);
70
- nn.set(this._nxt);
71
- this._nxt = nn;
72
- const ns = new Uint8Array(c);
73
- ns.set(this._sym);
74
- this._sym = ns;
75
- const ne = new Int32Array(c);
76
- ne.set(this._end);
77
- this._end = ne;
32
+ // ── compact transition storage ──────────────────────────────────────────
33
+ // _nxt[s] — -1 terminal (no outgoing transition)
34
+ // -2 multi-transition → _multi.get(s)
35
+ // >=0 singleton target; the symbol is _sym[s]
36
+ _nxt;
37
+ _sym;
38
+ _multi = null;
39
+ // _end[s] is the pattern id ending exactly at s, or -1.
40
+ _end;
41
+ // Per-pattern data, indexed by pattern id.
42
+ _lens = [];
43
+ _vals = [];
44
+ _len = 1; // next free state (state 0 = root)
45
+ constructor() {
46
+ const c = 256;
47
+ this._nxt = new Int32Array(c);
48
+ this._sym = new Uint8Array(c);
49
+ this._end = new Int32Array(c);
50
+ this._nxt[ROOT] = -1;
51
+ this._end[ROOT] = -1;
78
52
  }
79
- this._nxt[s] = -1;
80
- this._end[s] = -1;
81
- return s;
82
- }
83
- /** Follow symbol `c` from state `s`. Returns the next state, or -1. */
84
- _follow(s, c) {
85
- const n = this._nxt[s];
86
- if (n >= 0) {
87
- return this._sym[s] === c ? n : -1;
53
+ /** The root state, for cursor walks. */
54
+ get root() {
55
+ return ROOT;
88
56
  }
89
- if (n === -2) {
90
- return this._multi.get(s).get(c) ?? -1;
57
+ /** Number of distinct patterns stored. */
58
+ get size() {
59
+ return this._lens.length;
91
60
  }
92
- return -1; // n === -1
93
- }
94
- // ── cursor API (allocation-free) ─────────────────────────────────────────
95
- /** Follow one symbol from `state`; returns the next state or -1 if none. */
96
- step(state, symbol) {
97
- return this._follow(state, symbol);
98
- }
99
- /** The pattern ending exactly at `state`, or null. */
100
- terminal(state) {
101
- const id = this._end[state];
102
- return id === -1 ? null : { id, payload: this._vals[id] };
103
- }
104
- /** Length of the pattern with this id. */
105
- lengthOf(id) {
106
- return this._lens[id];
107
- }
108
- // ── build ───────────────────────────────────────────────────────────────
109
- /**
110
- * Insert a pattern, returning its id. Inserting the same symbol-sequence
111
- * twice returns the first id and keeps the first payload (patterns are keyed
112
- * by content). Empty patterns are ignored and return -1.
113
- */
114
- insert(pattern, payload) {
115
- const n = pattern.length;
116
- if (n === 0) {
117
- return -1;
118
- }
119
- let s = ROOT;
120
- for (let i = 0; i < n; i++) {
121
- const c = pattern[i];
122
- const t = this._nxt[s];
123
- if (t === -1) {
124
- // Terminal — place first transition as a singleton.
125
- const ns = this._state();
126
- this._nxt[s] = ns;
127
- this._sym[s] = c;
128
- s = ns;
129
- } else if (t >= 0) {
130
- // Singleton — either advance on match, or expand to multi.
131
- if (this._sym[s] === c) {
132
- s = t;
133
- } else {
134
- const map = new Map();
135
- map.set(this._sym[s], t);
136
- const ns = this._state();
137
- map.set(c, ns);
138
- if (!this._multi) {
139
- this._multi = new Map();
140
- }
141
- this._multi.set(s, map);
142
- this._nxt[s] = -2;
143
- s = ns;
144
- }
145
- } else {
146
- // Multi (t === -2) — extend the existing Map.
147
- const map = this._multi.get(s);
148
- let ns = map.get(c);
149
- if (ns === undefined) {
150
- ns = this._state();
151
- map.set(c, ns);
61
+ // ── internal helpers ────────────────────────────────────────────────────
62
+ /** Allocate a fresh state, growing the typed arrays by a fixed increment
63
+ * when full. No power-of-2 doubling — the transient double-memory spike
64
+ * during growth is bounded to the increment. */
65
+ _state() {
66
+ const s = this._len++;
67
+ if (s >= this._nxt.length) {
68
+ const c = s + 4096;
69
+ const nn = new Int32Array(c);
70
+ nn.set(this._nxt);
71
+ this._nxt = nn;
72
+ const ns = new Uint8Array(c);
73
+ ns.set(this._sym);
74
+ this._sym = ns;
75
+ const ne = new Int32Array(c);
76
+ ne.set(this._end);
77
+ this._end = ne;
152
78
  }
153
- s = ns;
154
- }
79
+ this._nxt[s] = -1;
80
+ this._end[s] = -1;
81
+ return s;
82
+ }
83
+ /** Follow symbol `c` from state `s`. Returns the next state, or -1. */
84
+ _follow(s, c) {
85
+ const n = this._nxt[s];
86
+ if (n >= 0)
87
+ return this._sym[s] === c ? n : -1;
88
+ if (n === -2)
89
+ return this._multi.get(s).get(c) ?? -1;
90
+ return -1; // n === -1
91
+ }
92
+ // ── cursor API (allocation-free) ─────────────────────────────────────────
93
+ /** Follow one symbol from `state`; returns the next state or -1 if none. */
94
+ step(state, symbol) {
95
+ return this._follow(state, symbol);
155
96
  }
156
- if (this._end[s] !== -1) {
157
- return this._end[s]; // already present
97
+ /** The pattern ending exactly at `state`, or null. */
98
+ terminal(state) {
99
+ const id = this._end[state];
100
+ return id === -1 ? null : { id, payload: this._vals[id] };
158
101
  }
159
- const id = this._lens.length;
160
- this._lens.push(n);
161
- this._vals.push(payload);
162
- this._end[s] = id;
163
- return id;
164
- }
165
- // ── matching ───────────────────────────────────────────────────────────
166
- /**
167
- * Every stored pattern that begins exactly at `pos` in `seq`, shortest first.
168
- * Walks forward from the root in O(longest match); reports nothing about any
169
- * other position. This is the on-demand probe the search uses.
170
- */
171
- matchesAt(seq, pos) {
172
- const out = [];
173
- let s = ROOT;
174
- for (let i = pos, n = seq.length; i < n; i++) {
175
- s = this._follow(s, seq[i]);
176
- if (s === -1) {
177
- break;
178
- }
179
- const id = this._end[s];
180
- if (id !== -1) {
181
- out.push({
182
- start: pos,
183
- end: i + 1,
184
- length: this._lens[id],
185
- id,
186
- payload: this._vals[id],
187
- });
188
- }
102
+ /** Length of the pattern with this id. */
103
+ lengthOf(id) {
104
+ return this._lens[id];
189
105
  }
190
- return out;
191
- }
192
- /**
193
- * Every occurrence of every pattern anywhere in `seq` (eager form, the union
194
- * of {@link matchesAt} over all start positions). O(seq · longest pattern),
195
- * independent of how many patterns are stored. Use {@link matchesAt} when the
196
- * search only needs the sites at a particular position.
197
- */
198
- scan(seq) {
199
- const out = [];
200
- for (let pos = 0, n = seq.length; pos < n; pos++) {
201
- let s = ROOT;
202
- for (let i = pos; i < n; i++) {
203
- s = this._follow(s, seq[i]);
204
- if (s === -1) {
205
- break;
106
+ // ── build ───────────────────────────────────────────────────────────────
107
+ /**
108
+ * Insert a pattern, returning its id. Inserting the same symbol-sequence
109
+ * twice returns the first id and keeps the first payload (patterns are keyed
110
+ * by content). Empty patterns are ignored and return -1.
111
+ */
112
+ insert(pattern, payload) {
113
+ const n = pattern.length;
114
+ if (n === 0)
115
+ return -1;
116
+ let s = ROOT;
117
+ for (let i = 0; i < n; i++) {
118
+ const c = pattern[i];
119
+ const t = this._nxt[s];
120
+ if (t === -1) {
121
+ // Terminal — place first transition as a singleton.
122
+ const ns = this._state();
123
+ this._nxt[s] = ns;
124
+ this._sym[s] = c;
125
+ s = ns;
126
+ }
127
+ else if (t >= 0) {
128
+ // Singleton — either advance on match, or expand to multi.
129
+ if (this._sym[s] === c) {
130
+ s = t;
131
+ }
132
+ else {
133
+ const map = new Map();
134
+ map.set(this._sym[s], t);
135
+ const ns = this._state();
136
+ map.set(c, ns);
137
+ if (!this._multi)
138
+ this._multi = new Map();
139
+ this._multi.set(s, map);
140
+ this._nxt[s] = -2;
141
+ s = ns;
142
+ }
143
+ }
144
+ else {
145
+ // Multi (t === -2) — extend the existing Map.
146
+ const map = this._multi.get(s);
147
+ let ns = map.get(c);
148
+ if (ns === undefined) {
149
+ ns = this._state();
150
+ map.set(c, ns);
151
+ }
152
+ s = ns;
153
+ }
206
154
  }
207
- const id = this._end[s];
208
- if (id !== -1) {
209
- out.push({
210
- start: pos,
211
- end: i + 1,
212
- length: this._lens[id],
213
- id,
214
- payload: this._vals[id],
215
- });
155
+ if (this._end[s] !== -1)
156
+ return this._end[s]; // already present
157
+ const id = this._lens.length;
158
+ this._lens.push(n);
159
+ this._vals.push(payload);
160
+ this._end[s] = id;
161
+ return id;
162
+ }
163
+ // ── matching ───────────────────────────────────────────────────────────
164
+ /**
165
+ * Every stored pattern that begins exactly at `pos` in `seq`, shortest first.
166
+ * Walks forward from the root in O(longest match); reports nothing about any
167
+ * other position. This is the on-demand probe the search uses.
168
+ */
169
+ matchesAt(seq, pos) {
170
+ const out = [];
171
+ let s = ROOT;
172
+ for (let i = pos, n = seq.length; i < n; i++) {
173
+ s = this._follow(s, seq[i]);
174
+ if (s === -1)
175
+ break;
176
+ const id = this._end[s];
177
+ if (id !== -1) {
178
+ out.push({
179
+ start: pos,
180
+ end: i + 1,
181
+ length: this._lens[id],
182
+ id,
183
+ payload: this._vals[id],
184
+ });
185
+ }
186
+ }
187
+ return out;
188
+ }
189
+ /**
190
+ * Every occurrence of every pattern anywhere in `seq` (eager form, the union
191
+ * of {@link matchesAt} over all start positions). O(seq · longest pattern),
192
+ * independent of how many patterns are stored. Use {@link matchesAt} when the
193
+ * search only needs the sites at a particular position.
194
+ */
195
+ scan(seq) {
196
+ const out = [];
197
+ for (let pos = 0, n = seq.length; pos < n; pos++) {
198
+ let s = ROOT;
199
+ for (let i = pos; i < n; i++) {
200
+ s = this._follow(s, seq[i]);
201
+ if (s === -1)
202
+ break;
203
+ const id = this._end[s];
204
+ if (id !== -1) {
205
+ out.push({
206
+ start: pos,
207
+ end: i + 1,
208
+ length: this._lens[id],
209
+ id,
210
+ payload: this._vals[id],
211
+ });
212
+ }
213
+ }
216
214
  }
217
- }
215
+ return out;
218
216
  }
219
- return out;
220
- }
221
217
  }