trilean-regex 0.0.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,86 @@
1
+ import { parseRegex } from "./parser.js";
2
+ import { compileToNfa } from "./nfa.js";
3
+ //#region src/matcher.ts
4
+ /**
5
+ * Epsilon-closure of `seeds` at absolute input position `pos` (0-based, counting the boundary *before* the character at that index -- so `pos === length` means "after the last character"): follows every `split` unconditionally and every `anchorStart`/`anchorEnd` only when `pos` satisfies it, stopping at each `char` or `match` state reached, which are the only kinds `matchesNfa`'s step loop ever needs to see.
6
+ *
7
+ * `length` is threaded through only for the `anchorEnd` check (`pos === length`) rather than read off a captured string, so this function depends on nothing beyond the automaton and two numbers -- it has no notion of "the input," only of where the boundary it is being asked about sits relative to the input's own length.
8
+ */
9
+ function closure(nfa, seeds, pos, length) {
10
+ const result = /* @__PURE__ */ new Set();
11
+ const visited = /* @__PURE__ */ new Set();
12
+ const stack = [...seeds];
13
+ while (stack.length > 0) {
14
+ const idx = stack.pop();
15
+ if (idx === void 0) continue;
16
+ if (visited.has(idx)) continue;
17
+ visited.add(idx);
18
+ const state = nfa.states[idx];
19
+ if (state === void 0) continue;
20
+ switch (state.kind) {
21
+ case "char":
22
+ case "match":
23
+ result.add(idx);
24
+ break;
25
+ case "split":
26
+ stack.push(state.to1, state.to2);
27
+ break;
28
+ case "anchorStart":
29
+ if (pos === 0) stack.push(state.to);
30
+ break;
31
+ case "anchorEnd": if (pos === length) stack.push(state.to);
32
+ }
33
+ }
34
+ return result;
35
+ }
36
+ /** The `char` states in `current` whose test accepts `codeUnit`, mapped to their (not yet closed) successor. Never includes `match` states -- there is nothing past a match to step from. */
37
+ function step(nfa, current, codeUnit) {
38
+ const next = /* @__PURE__ */ new Set();
39
+ for (const idx of current) {
40
+ const state = nfa.states[idx];
41
+ if (state?.kind !== "char") continue;
42
+ if (state.test(codeUnit)) next.add(state.to);
43
+ }
44
+ return next;
45
+ }
46
+ /**
47
+ * Simulates `nfa` against `input` and reports whether any substring of `input` matches -- the same "search," not "full match," semantics a bare `RegExp.prototype.test()` call has without the `^.../$...` anchors pinning it down, since `^`/`$` are already ordinary states inside the automaton (see `nfa.ts`) rather than a separate mode this function chooses between.
48
+ *
49
+ * The technique is the standard "add a fresh thread at every position" Thompson NFA search (Cox, "Regular Expression Matching Can Be Simple And Fast"): a new candidate match start is injected into the active state set before each character is consumed, exactly as if `.*?` had been prepended to an anchored pattern, except no such prefix is ever built -- the injection is the runtime equivalent of it. Because injection happens unconditionally and `^`'s own position check is what actually restricts where a match may begin, a pattern beginning with `^` still only ever matches from position 0, and one that does not can begin anywhere.
50
+ *
51
+ * Runs in O(length x states) time and allocates no more than two small state sets at a time, regardless of the pattern's shape -- there is no exponential blow-up for any input this compiles, which is the whole reason this package builds an automaton instead of calling a backtracking engine.
52
+ */
53
+ function matchesNfa(nfa, input) {
54
+ const length = input.length;
55
+ let current = closure(nfa, [nfa.start], 0, length);
56
+ if (current.has(nfa.matchState)) return true;
57
+ for (let pos = 0; pos < length; pos += 1) {
58
+ const codeUnit = input.charCodeAt(pos);
59
+ const stepped = step(nfa, current, codeUnit);
60
+ stepped.add(nfa.start);
61
+ current = closure(nfa, [...stepped], pos + 1, length);
62
+ if (current.has(nfa.matchState)) return true;
63
+ }
64
+ return false;
65
+ }
66
+ /**
67
+ * Parses `pattern` (this package's grammar; see README.md) and compiles it to a reference matcher.
68
+ *
69
+ * Prefer this over `testPattern` when the same pattern will be tested against more than one input: it parses and compiles exactly once, and the returned `CompiledPattern.test` is then only the O(length x states) simulation.
70
+ *
71
+ * @throws {RegexParseError} if `pattern` is not a valid pattern in this grammar.
72
+ */
73
+ function compilePattern(pattern) {
74
+ const nfa = compileToNfa(parseRegex(pattern));
75
+ return { test: (input) => matchesNfa(nfa, input) };
76
+ }
77
+ /**
78
+ * Parses `pattern` and tests it against `input` in one call. Equivalent to `compilePattern(pattern).test(input)`, and exactly as cheap for a single test -- prefer `compilePattern` instead when the same pattern will be reused, so the parse and compile steps happen once rather than once per input.
79
+ *
80
+ * @throws {RegexParseError} if `pattern` is not a valid pattern in this grammar.
81
+ */
82
+ function testPattern(pattern, input) {
83
+ return compilePattern(pattern).test(input);
84
+ }
85
+ //#endregion
86
+ export { compilePattern, matchesNfa, testPattern };
package/dist/nfa.cjs ADDED
@@ -0,0 +1,246 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_ast = require("./ast.cjs");
3
+ //#region src/nfa.ts
4
+ /**
5
+ * Thompson construction: each AST node compiles to a fragment with one entry state and a list of dangling exits, and a parent node patches its children's exits to point at whatever comes next once it knows what that is. This is the standard technique (Thompson 1968; see also Cox, "Regular Expression Matching Can Be Simple And Fast") specifically because it never needs to look ahead -- a fragment is complete and correct in isolation, and composition is always "point these exits here," which is what keeps construction linear in pattern size even for deeply nested alternation and repetition.
6
+ */
7
+ var NfaBuilder = class {
8
+ states = [];
9
+ add(state) {
10
+ this.states.push(state);
11
+ return this.states.length - 1;
12
+ }
13
+ patch(targets, target) {
14
+ for (const patchTarget of targets) {
15
+ const state = this.states[patchTarget.state];
16
+ if (state === void 0) continue;
17
+ switch (patchTarget.field) {
18
+ case "to":
19
+ if (state.kind === "char" || state.kind === "anchorStart" || state.kind === "anchorEnd") state.to = target;
20
+ break;
21
+ case "to1":
22
+ if (state.kind === "split") state.to1 = target;
23
+ break;
24
+ case "to2": if (state.kind === "split") state.to2 = target;
25
+ }
26
+ }
27
+ }
28
+ /** A fragment that matches the empty string unconditionally: both of a split's own branches dangle straight out, so whichever target they are later patched to is reached having consumed nothing. Used for `{0}`/`{0,0}` repetition, the one shape bounded repetition can produce that no other node kind already covers. */
29
+ emptyFrag() {
30
+ const idx = this.add({
31
+ kind: "split",
32
+ to1: -1,
33
+ to2: -1
34
+ });
35
+ return {
36
+ start: idx,
37
+ out: [{
38
+ state: idx,
39
+ field: "to1"
40
+ }, {
41
+ state: idx,
42
+ field: "to2"
43
+ }]
44
+ };
45
+ }
46
+ compileRepeat(node) {
47
+ const { operand, min, max } = node;
48
+ if (max === void 0) {
49
+ if (min === 0) return this.compileNode({
50
+ kind: "star",
51
+ operand
52
+ });
53
+ const parts = [];
54
+ for (let i = 0; i < min - 1; i += 1) parts.push(operand);
55
+ parts.push({
56
+ kind: "plus",
57
+ operand
58
+ });
59
+ return this.compileNode({
60
+ kind: "concat",
61
+ operands: parts
62
+ });
63
+ }
64
+ const parts = [];
65
+ for (let i = 0; i < min; i += 1) parts.push(operand);
66
+ for (let i = 0; i < max - min; i += 1) parts.push({
67
+ kind: "optional",
68
+ operand
69
+ });
70
+ if (parts.length === 0) return this.emptyFrag();
71
+ return this.compileNode({
72
+ kind: "concat",
73
+ operands: parts
74
+ });
75
+ }
76
+ compileNode(node) {
77
+ switch (node.kind) {
78
+ case "literal": {
79
+ const codeUnit = node.codeUnit;
80
+ const idx = this.add({
81
+ kind: "char",
82
+ test: (unit) => unit === codeUnit,
83
+ to: -1
84
+ });
85
+ return {
86
+ start: idx,
87
+ out: [{
88
+ state: idx,
89
+ field: "to"
90
+ }]
91
+ };
92
+ }
93
+ case "anyChar": {
94
+ const idx = this.add({
95
+ kind: "char",
96
+ test: () => true,
97
+ to: -1
98
+ });
99
+ return {
100
+ start: idx,
101
+ out: [{
102
+ state: idx,
103
+ field: "to"
104
+ }]
105
+ };
106
+ }
107
+ case "charClass": {
108
+ const idx = this.add({
109
+ kind: "char",
110
+ test: (unit) => require_ast.charClassMatches(node, unit),
111
+ to: -1
112
+ });
113
+ return {
114
+ start: idx,
115
+ out: [{
116
+ state: idx,
117
+ field: "to"
118
+ }]
119
+ };
120
+ }
121
+ case "anchorStart": {
122
+ const idx = this.add({
123
+ kind: "anchorStart",
124
+ to: -1
125
+ });
126
+ return {
127
+ start: idx,
128
+ out: [{
129
+ state: idx,
130
+ field: "to"
131
+ }]
132
+ };
133
+ }
134
+ case "anchorEnd": {
135
+ const idx = this.add({
136
+ kind: "anchorEnd",
137
+ to: -1
138
+ });
139
+ return {
140
+ start: idx,
141
+ out: [{
142
+ state: idx,
143
+ field: "to"
144
+ }]
145
+ };
146
+ }
147
+ case "concat": {
148
+ if (node.operands.length === 0) return this.emptyFrag();
149
+ const [first, ...rest] = node.operands;
150
+ if (first === void 0) throw new Error("unreachable: a non-empty operands array always has a first element");
151
+ let frag = this.compileNode(first);
152
+ for (const operand of rest) {
153
+ const next = this.compileNode(operand);
154
+ this.patch(frag.out, next.start);
155
+ frag = {
156
+ start: frag.start,
157
+ out: next.out
158
+ };
159
+ }
160
+ return frag;
161
+ }
162
+ case "alternation": {
163
+ const [first, ...rest] = node.branches;
164
+ if (first === void 0) throw new Error("unreachable: AlternationNode always has at least two branches");
165
+ let frag = this.compileNode(first);
166
+ for (const branch of rest) {
167
+ const next = this.compileNode(branch);
168
+ frag = {
169
+ start: this.add({
170
+ kind: "split",
171
+ to1: frag.start,
172
+ to2: next.start
173
+ }),
174
+ out: [...frag.out, ...next.out]
175
+ };
176
+ }
177
+ return frag;
178
+ }
179
+ case "star": {
180
+ const inner = this.compileNode(node.operand);
181
+ const splitIdx = this.add({
182
+ kind: "split",
183
+ to1: inner.start,
184
+ to2: -1
185
+ });
186
+ this.patch(inner.out, splitIdx);
187
+ return {
188
+ start: splitIdx,
189
+ out: [{
190
+ state: splitIdx,
191
+ field: "to2"
192
+ }]
193
+ };
194
+ }
195
+ case "plus": {
196
+ const inner = this.compileNode(node.operand);
197
+ const splitIdx = this.add({
198
+ kind: "split",
199
+ to1: inner.start,
200
+ to2: -1
201
+ });
202
+ this.patch(inner.out, splitIdx);
203
+ return {
204
+ start: inner.start,
205
+ out: [{
206
+ state: splitIdx,
207
+ field: "to2"
208
+ }]
209
+ };
210
+ }
211
+ case "optional": {
212
+ const inner = this.compileNode(node.operand);
213
+ const splitIdx = this.add({
214
+ kind: "split",
215
+ to1: inner.start,
216
+ to2: -1
217
+ });
218
+ return {
219
+ start: splitIdx,
220
+ out: [...inner.out, {
221
+ state: splitIdx,
222
+ field: "to2"
223
+ }]
224
+ };
225
+ }
226
+ case "repeat": return this.compileRepeat(node);
227
+ default: throw new Error("unreachable regex node kind");
228
+ }
229
+ }
230
+ build(root) {
231
+ const frag = this.compileNode(root);
232
+ const matchState = this.add({ kind: "match" });
233
+ this.patch(frag.out, matchState);
234
+ return {
235
+ states: this.states,
236
+ start: frag.start,
237
+ matchState
238
+ };
239
+ }
240
+ };
241
+ /** Compiles a `RegexNode` AST into a Thompson-construction NFA, ready for `matchesNfa` (see `matcher.ts`) to simulate. Construction is linear in the size of the AST -- bounded repetition is the only node kind that can multiply state count beyond that, by exactly the multiple its own bound names, which is a property of what `{n,m}` means rather than of this algorithm. */
242
+ function compileToNfa(node) {
243
+ return new NfaBuilder().build(node);
244
+ }
245
+ //#endregion
246
+ exports.compileToNfa = compileToNfa;
package/dist/nfa.d.cts ADDED
@@ -0,0 +1,34 @@
1
+ import { d as RegexNode } from "./ast-D8HLFxxv.cjs";
2
+ //#region src/nfa.d.ts
3
+ type CharTest = (codeUnit: number) => boolean;
4
+ /**
5
+ * One state of the compiled automaton. `char` is the only kind that consumes input; every other kind is resolved away during epsilon-closure (see `closure` in `matcher.ts`) before a position is ever tested against an input character.
6
+ *
7
+ * `anchorStart`/`anchorEnd` are what let `^`/`$` live anywhere in the pattern -- inside a group, inside an alternation branch -- rather than only at the very start or end of the whole pattern text: they are ordinary epsilon transitions that the closure walk additionally gates on the absolute string position, so `^`'s condition ("this position is index 0 of the whole input") is checked at the moment the automaton would step through it, not baked into where `^` was allowed to appear syntactically.
8
+ */
9
+ type NfaState = {
10
+ kind: "char";
11
+ test: CharTest;
12
+ to: number;
13
+ } | {
14
+ kind: "split";
15
+ to1: number;
16
+ to2: number;
17
+ } | {
18
+ kind: "anchorStart";
19
+ to: number;
20
+ } | {
21
+ kind: "anchorEnd";
22
+ to: number;
23
+ } | {
24
+ kind: "match";
25
+ };
26
+ interface CompiledNfa {
27
+ readonly states: readonly NfaState[];
28
+ readonly start: number;
29
+ readonly matchState: number;
30
+ }
31
+ /** Compiles a `RegexNode` AST into a Thompson-construction NFA, ready for `matchesNfa` (see `matcher.ts`) to simulate. Construction is linear in the size of the AST -- bounded repetition is the only node kind that can multiply state count beyond that, by exactly the multiple its own bound names, which is a property of what `{n,m}` means rather than of this algorithm. */
32
+ declare function compileToNfa(node: RegexNode): CompiledNfa;
33
+ //#endregion
34
+ export { CompiledNfa, NfaState, compileToNfa };
package/dist/nfa.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { d as RegexNode } from "./ast-D8HLFxxv.js";
2
+ //#region src/nfa.d.ts
3
+ type CharTest = (codeUnit: number) => boolean;
4
+ /**
5
+ * One state of the compiled automaton. `char` is the only kind that consumes input; every other kind is resolved away during epsilon-closure (see `closure` in `matcher.ts`) before a position is ever tested against an input character.
6
+ *
7
+ * `anchorStart`/`anchorEnd` are what let `^`/`$` live anywhere in the pattern -- inside a group, inside an alternation branch -- rather than only at the very start or end of the whole pattern text: they are ordinary epsilon transitions that the closure walk additionally gates on the absolute string position, so `^`'s condition ("this position is index 0 of the whole input") is checked at the moment the automaton would step through it, not baked into where `^` was allowed to appear syntactically.
8
+ */
9
+ type NfaState = {
10
+ kind: "char";
11
+ test: CharTest;
12
+ to: number;
13
+ } | {
14
+ kind: "split";
15
+ to1: number;
16
+ to2: number;
17
+ } | {
18
+ kind: "anchorStart";
19
+ to: number;
20
+ } | {
21
+ kind: "anchorEnd";
22
+ to: number;
23
+ } | {
24
+ kind: "match";
25
+ };
26
+ interface CompiledNfa {
27
+ readonly states: readonly NfaState[];
28
+ readonly start: number;
29
+ readonly matchState: number;
30
+ }
31
+ /** Compiles a `RegexNode` AST into a Thompson-construction NFA, ready for `matchesNfa` (see `matcher.ts`) to simulate. Construction is linear in the size of the AST -- bounded repetition is the only node kind that can multiply state count beyond that, by exactly the multiple its own bound names, which is a property of what `{n,m}` means rather than of this algorithm. */
32
+ declare function compileToNfa(node: RegexNode): CompiledNfa;
33
+ //#endregion
34
+ export { CompiledNfa, NfaState, compileToNfa };
package/dist/nfa.js ADDED
@@ -0,0 +1,245 @@
1
+ import { charClassMatches } from "./ast.js";
2
+ //#region src/nfa.ts
3
+ /**
4
+ * Thompson construction: each AST node compiles to a fragment with one entry state and a list of dangling exits, and a parent node patches its children's exits to point at whatever comes next once it knows what that is. This is the standard technique (Thompson 1968; see also Cox, "Regular Expression Matching Can Be Simple And Fast") specifically because it never needs to look ahead -- a fragment is complete and correct in isolation, and composition is always "point these exits here," which is what keeps construction linear in pattern size even for deeply nested alternation and repetition.
5
+ */
6
+ var NfaBuilder = class {
7
+ states = [];
8
+ add(state) {
9
+ this.states.push(state);
10
+ return this.states.length - 1;
11
+ }
12
+ patch(targets, target) {
13
+ for (const patchTarget of targets) {
14
+ const state = this.states[patchTarget.state];
15
+ if (state === void 0) continue;
16
+ switch (patchTarget.field) {
17
+ case "to":
18
+ if (state.kind === "char" || state.kind === "anchorStart" || state.kind === "anchorEnd") state.to = target;
19
+ break;
20
+ case "to1":
21
+ if (state.kind === "split") state.to1 = target;
22
+ break;
23
+ case "to2": if (state.kind === "split") state.to2 = target;
24
+ }
25
+ }
26
+ }
27
+ /** A fragment that matches the empty string unconditionally: both of a split's own branches dangle straight out, so whichever target they are later patched to is reached having consumed nothing. Used for `{0}`/`{0,0}` repetition, the one shape bounded repetition can produce that no other node kind already covers. */
28
+ emptyFrag() {
29
+ const idx = this.add({
30
+ kind: "split",
31
+ to1: -1,
32
+ to2: -1
33
+ });
34
+ return {
35
+ start: idx,
36
+ out: [{
37
+ state: idx,
38
+ field: "to1"
39
+ }, {
40
+ state: idx,
41
+ field: "to2"
42
+ }]
43
+ };
44
+ }
45
+ compileRepeat(node) {
46
+ const { operand, min, max } = node;
47
+ if (max === void 0) {
48
+ if (min === 0) return this.compileNode({
49
+ kind: "star",
50
+ operand
51
+ });
52
+ const parts = [];
53
+ for (let i = 0; i < min - 1; i += 1) parts.push(operand);
54
+ parts.push({
55
+ kind: "plus",
56
+ operand
57
+ });
58
+ return this.compileNode({
59
+ kind: "concat",
60
+ operands: parts
61
+ });
62
+ }
63
+ const parts = [];
64
+ for (let i = 0; i < min; i += 1) parts.push(operand);
65
+ for (let i = 0; i < max - min; i += 1) parts.push({
66
+ kind: "optional",
67
+ operand
68
+ });
69
+ if (parts.length === 0) return this.emptyFrag();
70
+ return this.compileNode({
71
+ kind: "concat",
72
+ operands: parts
73
+ });
74
+ }
75
+ compileNode(node) {
76
+ switch (node.kind) {
77
+ case "literal": {
78
+ const codeUnit = node.codeUnit;
79
+ const idx = this.add({
80
+ kind: "char",
81
+ test: (unit) => unit === codeUnit,
82
+ to: -1
83
+ });
84
+ return {
85
+ start: idx,
86
+ out: [{
87
+ state: idx,
88
+ field: "to"
89
+ }]
90
+ };
91
+ }
92
+ case "anyChar": {
93
+ const idx = this.add({
94
+ kind: "char",
95
+ test: () => true,
96
+ to: -1
97
+ });
98
+ return {
99
+ start: idx,
100
+ out: [{
101
+ state: idx,
102
+ field: "to"
103
+ }]
104
+ };
105
+ }
106
+ case "charClass": {
107
+ const idx = this.add({
108
+ kind: "char",
109
+ test: (unit) => charClassMatches(node, unit),
110
+ to: -1
111
+ });
112
+ return {
113
+ start: idx,
114
+ out: [{
115
+ state: idx,
116
+ field: "to"
117
+ }]
118
+ };
119
+ }
120
+ case "anchorStart": {
121
+ const idx = this.add({
122
+ kind: "anchorStart",
123
+ to: -1
124
+ });
125
+ return {
126
+ start: idx,
127
+ out: [{
128
+ state: idx,
129
+ field: "to"
130
+ }]
131
+ };
132
+ }
133
+ case "anchorEnd": {
134
+ const idx = this.add({
135
+ kind: "anchorEnd",
136
+ to: -1
137
+ });
138
+ return {
139
+ start: idx,
140
+ out: [{
141
+ state: idx,
142
+ field: "to"
143
+ }]
144
+ };
145
+ }
146
+ case "concat": {
147
+ if (node.operands.length === 0) return this.emptyFrag();
148
+ const [first, ...rest] = node.operands;
149
+ if (first === void 0) throw new Error("unreachable: a non-empty operands array always has a first element");
150
+ let frag = this.compileNode(first);
151
+ for (const operand of rest) {
152
+ const next = this.compileNode(operand);
153
+ this.patch(frag.out, next.start);
154
+ frag = {
155
+ start: frag.start,
156
+ out: next.out
157
+ };
158
+ }
159
+ return frag;
160
+ }
161
+ case "alternation": {
162
+ const [first, ...rest] = node.branches;
163
+ if (first === void 0) throw new Error("unreachable: AlternationNode always has at least two branches");
164
+ let frag = this.compileNode(first);
165
+ for (const branch of rest) {
166
+ const next = this.compileNode(branch);
167
+ frag = {
168
+ start: this.add({
169
+ kind: "split",
170
+ to1: frag.start,
171
+ to2: next.start
172
+ }),
173
+ out: [...frag.out, ...next.out]
174
+ };
175
+ }
176
+ return frag;
177
+ }
178
+ case "star": {
179
+ const inner = this.compileNode(node.operand);
180
+ const splitIdx = this.add({
181
+ kind: "split",
182
+ to1: inner.start,
183
+ to2: -1
184
+ });
185
+ this.patch(inner.out, splitIdx);
186
+ return {
187
+ start: splitIdx,
188
+ out: [{
189
+ state: splitIdx,
190
+ field: "to2"
191
+ }]
192
+ };
193
+ }
194
+ case "plus": {
195
+ const inner = this.compileNode(node.operand);
196
+ const splitIdx = this.add({
197
+ kind: "split",
198
+ to1: inner.start,
199
+ to2: -1
200
+ });
201
+ this.patch(inner.out, splitIdx);
202
+ return {
203
+ start: inner.start,
204
+ out: [{
205
+ state: splitIdx,
206
+ field: "to2"
207
+ }]
208
+ };
209
+ }
210
+ case "optional": {
211
+ const inner = this.compileNode(node.operand);
212
+ const splitIdx = this.add({
213
+ kind: "split",
214
+ to1: inner.start,
215
+ to2: -1
216
+ });
217
+ return {
218
+ start: splitIdx,
219
+ out: [...inner.out, {
220
+ state: splitIdx,
221
+ field: "to2"
222
+ }]
223
+ };
224
+ }
225
+ case "repeat": return this.compileRepeat(node);
226
+ default: throw new Error("unreachable regex node kind");
227
+ }
228
+ }
229
+ build(root) {
230
+ const frag = this.compileNode(root);
231
+ const matchState = this.add({ kind: "match" });
232
+ this.patch(frag.out, matchState);
233
+ return {
234
+ states: this.states,
235
+ start: frag.start,
236
+ matchState
237
+ };
238
+ }
239
+ };
240
+ /** Compiles a `RegexNode` AST into a Thompson-construction NFA, ready for `matchesNfa` (see `matcher.ts`) to simulate. Construction is linear in the size of the AST -- bounded repetition is the only node kind that can multiply state count beyond that, by exactly the multiple its own bound names, which is a property of what `{n,m}` means rather than of this algorithm. */
241
+ function compileToNfa(node) {
242
+ return new NfaBuilder().build(node);
243
+ }
244
+ //#endregion
245
+ export { compileToNfa };