functionalscript 0.33.0 → 0.35.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.
Files changed (89) hide show
  1. package/README.md +27 -14
  2. package/fs/asserts/module.f.d.ts +1 -1
  3. package/fs/asserts/module.f.js +4 -1
  4. package/fs/base64/proof.f.d.ts +1 -0
  5. package/fs/base64/proof.f.js +25 -56
  6. package/fs/base_n/module.f.js +11 -5
  7. package/fs/base_n/proof.f.d.ts +1 -0
  8. package/fs/base_n/proof.f.js +12 -1
  9. package/fs/bnf/data/module.f.d.ts +0 -103
  10. package/fs/bnf/data/module.f.js +10 -246
  11. package/fs/bnf/data/proof.f.d.ts +0 -10
  12. package/fs/bnf/data/proof.f.js +2 -642
  13. package/fs/bnf/descent/module.f.d.ts +56 -0
  14. package/fs/bnf/descent/module.f.js +119 -0
  15. package/fs/bnf/descent/proof.f.d.ts +5 -0
  16. package/fs/bnf/descent/proof.f.js +296 -0
  17. package/fs/bnf/ll1/module.f.d.ts +72 -0
  18. package/fs/bnf/ll1/module.f.js +153 -0
  19. package/fs/bnf/ll1/proof.f.d.ts +9 -0
  20. package/fs/bnf/ll1/proof.f.js +358 -0
  21. package/fs/cas/cli/module.f.d.ts +6 -0
  22. package/fs/cas/cli/module.f.js +56 -0
  23. package/fs/cas/cli/proof.f.d.ts +14 -0
  24. package/fs/cas/cli/proof.f.js +149 -0
  25. package/fs/cas/mcp/module.f.d.ts +9 -17
  26. package/fs/cas/mcp/module.f.js +123 -96
  27. package/fs/cas/mcp/proof.f.d.ts +12 -1
  28. package/fs/cas/mcp/proof.f.js +199 -32
  29. package/fs/cas/module.f.d.ts +35 -15
  30. package/fs/cas/module.f.js +168 -131
  31. package/fs/cas/proof.f.d.ts +16 -13
  32. package/fs/cas/proof.f.js +255 -121
  33. package/fs/ci/config/module.f.d.ts +14 -14
  34. package/fs/ci/config/module.f.js +14 -14
  35. package/fs/djs/tokenizer-new/module.f.js +1 -1
  36. package/fs/djs/tokenizer-new/proof.f.d.ts +2 -0
  37. package/fs/djs/tokenizer-new/proof.f.js +357 -229
  38. package/fs/effects/list/module.f.d.ts +31 -0
  39. package/fs/effects/list/module.f.js +16 -0
  40. package/fs/effects/module.f.d.ts +11 -5
  41. package/fs/effects/module.f.js +16 -6
  42. package/fs/effects/node/module.f.d.ts +29 -3
  43. package/fs/effects/node/module.f.js +35 -1
  44. package/fs/effects/node/module.js +32 -18
  45. package/fs/effects/node/virtual/module.f.js +57 -0
  46. package/fs/effects/proof.f.d.ts +4 -0
  47. package/fs/effects/proof.f.js +18 -1
  48. package/fs/fjs/module.f.js +2 -8
  49. package/fs/html/module.f.js +6 -8
  50. package/fs/mcp/module.f.d.ts +1 -1
  51. package/fs/mcp/stdio/module.f.d.ts +8 -0
  52. package/fs/mcp/stdio/module.f.js +33 -6
  53. package/fs/mcp/stdio/proof.f.d.ts +3 -0
  54. package/fs/mcp/stdio/proof.f.js +52 -1
  55. package/fs/mime/module.f.d.ts +101 -5
  56. package/fs/mime/module.f.js +159 -6
  57. package/fs/mime/proof.f.d.ts +25 -0
  58. package/fs/mime/proof.f.js +193 -3
  59. package/fs/text/code_point/module.f.d.ts +39 -2
  60. package/fs/text/code_point/module.f.js +74 -2
  61. package/fs/text/code_point/proof.f.d.ts +8 -0
  62. package/fs/text/code_point/proof.f.js +72 -0
  63. package/fs/text/module.f.d.ts +11 -1
  64. package/fs/text/module.f.js +13 -3
  65. package/fs/text/proof.f.d.ts +6 -0
  66. package/fs/text/proof.f.js +25 -2
  67. package/fs/text/utf16/module.f.js +4 -26
  68. package/fs/text/utf8/module.f.d.ts +27 -0
  69. package/fs/text/utf8/module.f.js +8 -14
  70. package/fs/types/bit_vec/module.f.d.ts +20 -2
  71. package/fs/types/bit_vec/module.f.js +77 -27
  72. package/fs/types/bit_vec/proof.f.d.ts +8 -0
  73. package/fs/types/bit_vec/proof.f.js +24 -6
  74. package/fs/types/function/operator/module.f.d.ts +26 -0
  75. package/fs/types/list/module.f.d.ts +3 -0
  76. package/fs/types/nullable/module.f.d.ts +9 -5
  77. package/fs/types/nullable/module.f.js +19 -0
  78. package/fs/types/result/module.d.ts +2 -4
  79. package/fs/types/result/module.js +9 -3
  80. package/package.json +3 -3
  81. package/issues/demo/data/data.f.js +0 -12
  82. package/issues/demo/data/shared.f.js +0 -3
  83. package/issues/demo/fs/app.js +0 -4
  84. package/issues/demo/fs/math.f.js +0 -4
  85. package/issues/demo/sample/proof.f.js +0 -13
  86. /package/{issues → todo}/031-json.f.d.ts +0 -0
  87. /package/{issues → todo}/031-json.f.js +0 -0
  88. /package/{issues → todo}/proof.f.d.ts +0 -0
  89. /package/{issues → todo}/proof.f.js +0 -0
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Recursive descent matcher backend over the BNF data {@link RuleSet}.
3
+ *
4
+ * Built from the serializable IR in `fs/bnf/data`, this is a sibling of the
5
+ * LL(1) dispatch builder (`fs/bnf/ll1`). It walks the grammar by recursive
6
+ * descent and preserves per-code-point metadata, producing a metadata-aware
7
+ * AST ({@link AstRuleMeta}). It also exposes {@link createEmptyTagMap}, which
8
+ * records whether each rule can match the empty input.
9
+ *
10
+ * @module
11
+ */
12
+ import { type CodePoint } from '../../text/utf16/module.f.ts';
13
+ import { type StringMap } from '../../types/object/module.f.ts';
14
+ import { type RuleSet } from '../data/module.f.ts';
15
+ import { type Rule as FRule } from '../module.f.ts';
16
+ type EmptyTagEntry = string | boolean;
17
+ type EmptyTagMap = StringMap<string, EmptyTagEntry>;
18
+ export type AstTag = string | true | undefined;
19
+ /**
20
+ * Recursive descent matcher for a single named rule.
21
+ */
22
+ export type DescentMatchRule<T> = (name: string, tag: AstTag, s: readonly CodePointMeta<T>[], idx: number) => DescentMatchResult<T>;
23
+ /**
24
+ * Result tuple of a descent match operation: AST node, success flag, and next index.
25
+ */
26
+ export type DescentMatchResult<T> = readonly [AstRuleMeta<T>, boolean, number];
27
+ /**
28
+ * Entry-point recursive descent matcher.
29
+ */
30
+ export type DescentMatch<T> = (name: string, s: readonly CodePointMeta<T>[]) => DescentMatchResult<T>;
31
+ /**
32
+ * Code point value paired with metadata.
33
+ */
34
+ export type CodePointMeta<T> = readonly [CodePoint, T];
35
+ /**
36
+ * AST sequence for the metadata-aware parser.
37
+ */
38
+ export type AstSequenceMeta<T> = readonly (AstRuleMeta<T> | CodePointMeta<T>)[];
39
+ /**
40
+ * Metadata-aware AST node.
41
+ */
42
+ export type AstRuleMeta<T> = {
43
+ readonly tag: AstTag;
44
+ readonly sequence: AstSequenceMeta<T>;
45
+ };
46
+ /**
47
+ * Creates a map that describes whether each rule can consume empty input and,
48
+ * for tagged variants, which tag represents the empty match.
49
+ */
50
+ export declare const createEmptyTagMap: (data: readonly [RuleSet, string]) => EmptyTagMap;
51
+ /**
52
+ * Creates a recursive descent parser that preserves metadata for each consumed
53
+ * code point.
54
+ */
55
+ export declare const descentParser: <T>(fr: FRule) => DescentMatch<T>;
56
+ export {};
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Recursive descent matcher backend over the BNF data {@link RuleSet}.
3
+ *
4
+ * Built from the serializable IR in `fs/bnf/data`, this is a sibling of the
5
+ * LL(1) dispatch builder (`fs/bnf/ll1`). It walks the grammar by recursive
6
+ * descent and preserves per-code-point metadata, producing a metadata-aware
7
+ * AST ({@link AstRuleMeta}). It also exposes {@link createEmptyTagMap}, which
8
+ * records whether each rule can match the empty input.
9
+ *
10
+ * @module
11
+ */
12
+ import {} from "../../text/utf16/module.f.js";
13
+ import { rangeDecode } from "../module.f.js";
14
+ import { contains as rangeContains } from "../../types/range/module.f.js";
15
+ import { definedEntries } from "../../types/object/module.f.js";
16
+ import { toData } from "../data/module.f.js";
17
+ import {} from "../module.f.js";
18
+ const emptyTagMapAdd = (ruleSet) => (map) => (name) => {
19
+ if (name in map) {
20
+ return [ruleSet, map, map[name]];
21
+ }
22
+ const rule = ruleSet[name];
23
+ if (typeof rule === 'number') {
24
+ return [ruleSet, { ...map, [name]: false }, false];
25
+ }
26
+ else if (rule instanceof Array) {
27
+ map = { ...map, [name]: true };
28
+ let emptyTag = rule.length == 0;
29
+ for (const item of rule) {
30
+ const [, newMap, itemEmptyTag] = emptyTagMapAdd(ruleSet)(map)(item);
31
+ map = newMap;
32
+ if (emptyTag === false) {
33
+ emptyTag = itemEmptyTag !== false;
34
+ }
35
+ }
36
+ return [ruleSet, { ...map, [name]: emptyTag }, emptyTag];
37
+ }
38
+ else {
39
+ map = { ...map, [name]: true };
40
+ const entries = definedEntries(rule);
41
+ let emptyTag = false;
42
+ for (const [tag, item] of entries) {
43
+ const [, newMap, itemEmptyTag] = emptyTagMapAdd(ruleSet)(map)(item);
44
+ map = newMap;
45
+ if (itemEmptyTag !== false) {
46
+ emptyTag = tag;
47
+ }
48
+ }
49
+ return [ruleSet, { ...map, [name]: emptyTag }, emptyTag];
50
+ }
51
+ };
52
+ /**
53
+ * Creates a map that describes whether each rule can consume empty input and,
54
+ * for tagged variants, which tag represents the empty match.
55
+ */
56
+ export const createEmptyTagMap = (data) => {
57
+ return emptyTagMapAdd(data[0])({})(data[1])[1];
58
+ };
59
+ /**
60
+ * Creates a recursive descent parser that preserves metadata for each consumed
61
+ * code point.
62
+ */
63
+ export const descentParser = (fr) => {
64
+ const data = toData(fr);
65
+ const emptyTagMap = createEmptyTagMap(data);
66
+ const getEmptyTag = (name) => {
67
+ const res = emptyTagMap[name];
68
+ return res === false ? undefined : res;
69
+ };
70
+ const f = (name, tag, cp, idx) => {
71
+ const mrSuccess = (tag, sequence, idx) => [{ tag, sequence }, true, idx];
72
+ const mrFail = (tag, sequence, idx) => [{ tag, sequence }, false, idx];
73
+ const rule = data[0][name];
74
+ if (typeof rule === 'number') {
75
+ const emptyTag = getEmptyTag(name);
76
+ if (idx >= cp.length) {
77
+ return emptyTag === undefined ? mrFail(emptyTag, [], idx) : mrSuccess(emptyTag, [], idx);
78
+ }
79
+ const cpi = cp[idx];
80
+ const range = rangeDecode(rule);
81
+ if (rangeContains(range)(cpi[0])) {
82
+ return mrSuccess(tag, [cpi], idx + 1);
83
+ }
84
+ return mrFail(emptyTag, [], idx);
85
+ }
86
+ else if (rule instanceof Array) {
87
+ let seq = [];
88
+ let tidx = idx;
89
+ for (const item of rule) {
90
+ const m = f(item, undefined, cp, tidx);
91
+ const [astRule, success, nidx] = m;
92
+ tidx = nidx;
93
+ if (success === false) {
94
+ return mrFail(tag, [], idx);
95
+ }
96
+ seq = [...seq, astRule];
97
+ }
98
+ return mrSuccess(tag, seq, tidx);
99
+ }
100
+ else {
101
+ const entries = definedEntries(rule);
102
+ const emptyTag = getEmptyTag(name);
103
+ let emptyResult = mrFail(emptyTag, [], idx);
104
+ for (const [tag, item] of entries) {
105
+ const m = f(item, tag, cp, idx);
106
+ if (m[1]) {
107
+ if (idx !== m[2])
108
+ return m;
109
+ emptyResult = m;
110
+ }
111
+ }
112
+ return emptyResult;
113
+ }
114
+ };
115
+ const match = (name, cp) => {
116
+ return f(name, undefined, cp, 0);
117
+ };
118
+ return match;
119
+ };
@@ -0,0 +1,5 @@
1
+ export declare const proof: {
2
+ emptyTags: (() => void)[];
3
+ descentParser: (() => void)[];
4
+ descentParserWithMeta: (() => void)[];
5
+ };
@@ -0,0 +1,296 @@
1
+ import { stringToCodePointList } from "../../text/utf16/module.f.js";
2
+ import { map, toArray } from "../../types/list/module.f.js";
3
+ import { commaJoin0Plus, option, range, repeat0Plus, set } from "../module.f.js";
4
+ import { deterministic } from "../testlib.f.js";
5
+ import { toData } from "../data/module.f.js";
6
+ import { createEmptyTagMap, descentParser } from "./module.f.js";
7
+ const mapCodePoint = (cp) => [cp, undefined];
8
+ const descentParserCpOnly = (m, name, cp) => {
9
+ const cpm = toArray(map(mapCodePoint)(cp));
10
+ return m(name, cpm);
11
+ };
12
+ export const proof = {
13
+ emptyTags: [
14
+ () => {
15
+ const stringRule = 'true';
16
+ const data = toData(stringRule);
17
+ const emptyTags = createEmptyTagMap(data);
18
+ const result = JSON.stringify(emptyTags);
19
+ if (result !== '{"0":false,"1":false,"2":false,"3":false,"":false}') {
20
+ throw result;
21
+ }
22
+ },
23
+ () => {
24
+ const terminalRangeRule = range('AF');
25
+ const data = toData(terminalRangeRule);
26
+ const emptyTags = createEmptyTagMap(data);
27
+ const result = JSON.stringify(emptyTags);
28
+ if (result !== '{"":false}') {
29
+ throw result;
30
+ }
31
+ },
32
+ () => {
33
+ const varintRule = { true: 'true', false: 'false' };
34
+ const data = toData(varintRule);
35
+ const emptyTags = createEmptyTagMap(data);
36
+ const result = JSON.stringify(emptyTags);
37
+ if (result !== '{"0":false,"1":false,"2":false,"3":false,"4":false,"5":false,"6":false,"7":false,"8":false,"9":false,"":false}') {
38
+ throw result;
39
+ }
40
+ },
41
+ () => {
42
+ const emptyRule = '';
43
+ const data = toData(emptyRule);
44
+ const emptyTags = createEmptyTagMap(data);
45
+ const result = JSON.stringify(emptyTags);
46
+ if (result !== '{"":true}') {
47
+ throw result;
48
+ }
49
+ },
50
+ () => {
51
+ const emptyRule = '';
52
+ const varintRule = { true: 'true', e: emptyRule };
53
+ const data = toData(varintRule);
54
+ const emptyTags = createEmptyTagMap(data);
55
+ const result = JSON.stringify(emptyTags);
56
+ if (result !== '{"0":false,"1":false,"2":false,"3":false,"4":false,"5":true,"":"e"}') {
57
+ throw result;
58
+ }
59
+ },
60
+ () => {
61
+ const repeatRule = repeat0Plus(option('a'));
62
+ const data = toData(repeatRule);
63
+ const emptyTags = createEmptyTagMap(data);
64
+ const result = JSON.stringify(emptyTags);
65
+ if (result !== '{"0":"none","1":false,"2":false,"3":true,"r":"none","":true}') {
66
+ throw result;
67
+ }
68
+ },
69
+ () => {
70
+ const repeatRule = repeat0Plus(set(' \n\r\t'));
71
+ const data = toData(repeatRule);
72
+ const emptyTags = createEmptyTagMap(data);
73
+ const result = JSON.stringify(emptyTags);
74
+ if (result !== '{"0":false,"1":false,"2":false,"3":false,"4":false,"5":true,"r":"none","":true}') {
75
+ throw result;
76
+ }
77
+ }
78
+ ],
79
+ descentParser: [
80
+ () => {
81
+ const emptyRule = '';
82
+ const m = descentParser(emptyRule);
83
+ const mr = m("", []);
84
+ const result = JSON.stringify(mr);
85
+ if (result !== '[{"sequence":[]},true,0]') {
86
+ throw result;
87
+ }
88
+ },
89
+ () => {
90
+ const emptyRule = '';
91
+ const m = descentParser(emptyRule);
92
+ const mr = descentParserCpOnly(m, "", [65, 70]);
93
+ const result = JSON.stringify(mr);
94
+ if (result !== '[{"sequence":[]},true,0]') {
95
+ throw result;
96
+ }
97
+ },
98
+ () => {
99
+ const terminalRangeRule = range('AF');
100
+ const m = descentParser(terminalRangeRule);
101
+ const mr = descentParserCpOnly(m, "", [65]);
102
+ const result = JSON.stringify(mr);
103
+ if (result !== '[{"sequence":[[65,null]]},true,1]') {
104
+ throw result;
105
+ }
106
+ },
107
+ () => {
108
+ const terminalRangeRule = range('AF');
109
+ const m = descentParser(terminalRangeRule);
110
+ const mr = descentParserCpOnly(m, "", [64]);
111
+ const result = JSON.stringify(mr);
112
+ if (result !== '[{"sequence":[]},false,0]') {
113
+ throw result;
114
+ }
115
+ },
116
+ () => {
117
+ const variantRule = { 'a': range('AA'), 'b': range('BB') };
118
+ const m = descentParser(variantRule);
119
+ const mr = descentParserCpOnly(m, "", [65]);
120
+ const result = JSON.stringify(mr);
121
+ if (result !== '[{"tag":"a","sequence":[[65,null]]},true,1]') {
122
+ throw result;
123
+ }
124
+ },
125
+ () => {
126
+ const variantRule = { 'a': range('AA'), 'b': range('BB') };
127
+ const m = descentParser(variantRule);
128
+ const mr = descentParserCpOnly(m, "", [64]);
129
+ const result = JSON.stringify(mr);
130
+ if (result !== '[{"sequence":[]},false,0]') {
131
+ throw result;
132
+ }
133
+ },
134
+ () => {
135
+ const emptyRule = '';
136
+ const variantRule = { 'e': emptyRule, 'a': range('AA') };
137
+ const m = descentParser(variantRule);
138
+ const mr = m("", []);
139
+ const result = JSON.stringify(mr);
140
+ if (result !== '[{"tag":"e","sequence":[]},true,0]') {
141
+ throw result;
142
+ }
143
+ },
144
+ () => {
145
+ const emptyRule = '';
146
+ const variantRule = { 'e': emptyRule, 'a': range('AA') };
147
+ const m = descentParser(variantRule);
148
+ const mr = descentParserCpOnly(m, "", [64]);
149
+ const result = JSON.stringify(mr);
150
+ if (result !== '[{"tag":"e","sequence":[]},true,0]') {
151
+ throw result;
152
+ }
153
+ },
154
+ () => {
155
+ const stringRule = 'AB';
156
+ const m = descentParser(stringRule);
157
+ const mr = descentParserCpOnly(m, "", [65, 66]);
158
+ const result = JSON.stringify(mr);
159
+ if (result !== '[{"sequence":[{"sequence":[[65,null]]},{"sequence":[[66,null]]}]},true,2]') {
160
+ throw result;
161
+ }
162
+ },
163
+ () => {
164
+ const stringRule = 'AB';
165
+ const m = descentParser(stringRule);
166
+ const mr = descentParserCpOnly(m, "", [65, 67]);
167
+ const result = JSON.stringify(mr);
168
+ if (result !== '[{"sequence":[]},false,0]') {
169
+ throw result;
170
+ }
171
+ },
172
+ () => {
173
+ const emptyRule = '';
174
+ const minursRule = range('--');
175
+ const optionalMinusRule = { 'none': emptyRule, 'minus': minursRule };
176
+ const digitRule = range('09');
177
+ const numberRule = [optionalMinusRule, digitRule];
178
+ const m = descentParser(numberRule);
179
+ const mr = descentParserCpOnly(m, "", [50]);
180
+ const result = JSON.stringify(mr);
181
+ if (result !== '[{"sequence":[{"tag":"none","sequence":[]},{"sequence":[[50,null]]}]},true,1]') {
182
+ throw result;
183
+ }
184
+ },
185
+ () => {
186
+ const emptyRule = '';
187
+ const minusRule = range('--');
188
+ const optionalMinusRule = { 'none': emptyRule, 'minus': minusRule };
189
+ const digitRule = range('09');
190
+ const numberRule = [optionalMinusRule, digitRule];
191
+ const m = descentParser(numberRule);
192
+ const mr = descentParserCpOnly(m, "", [45, 50]);
193
+ const result = JSON.stringify(mr);
194
+ if (result !== '[{"sequence":[{"tag":"minus","sequence":[[45,null]]},{"sequence":[[50,null]]}]},true,2]') {
195
+ throw result;
196
+ }
197
+ },
198
+ () => {
199
+ const emptyRule = '';
200
+ const minursRule = range('--');
201
+ const optionalMinusRule = { 'none': emptyRule, 'minus': minursRule };
202
+ const digitRule = range('09');
203
+ const numberRule = [optionalMinusRule, digitRule];
204
+ const m = descentParser(numberRule);
205
+ const mr = m("", []);
206
+ const result = JSON.stringify(mr);
207
+ if (result !== '[{"sequence":[]},false,0]') {
208
+ throw result;
209
+ }
210
+ },
211
+ () => {
212
+ const m = descentParser(option('a'));
213
+ const expect = (s, expected) => {
214
+ const cp = toArray(stringToCodePointList(s));
215
+ const mr = descentParserCpOnly(m, '', cp);
216
+ const success = mr[1] && mr[2] === cp.length;
217
+ if (success !== expected) {
218
+ throw mr;
219
+ }
220
+ };
221
+ expect('a', true);
222
+ expect('', true);
223
+ expect('aa', false);
224
+ expect('b', false);
225
+ },
226
+ () => {
227
+ const ws = repeat0Plus(set(' \n\r\t'));
228
+ const cj = commaJoin0Plus(ws);
229
+ const value = () => ({
230
+ object: cj('{}', 'a'),
231
+ array: cj('[]', 'a')
232
+ });
233
+ value.name; //bun will fail if no usage of name found
234
+ const m = descentParser(value);
235
+ const expect = (s, expected) => {
236
+ const cp = toArray(stringToCodePointList(s));
237
+ const mr = descentParserCpOnly(m, 'value', cp);
238
+ const success = mr[1] && mr[2] === cp.length;
239
+ if (success !== expected) {
240
+ throw mr;
241
+ }
242
+ };
243
+ expect('', false);
244
+ expect('[]', true);
245
+ expect('[a]', true);
246
+ expect('[a, a]', true);
247
+ expect('{a}', true);
248
+ },
249
+ () => {
250
+ const m = descentParser(deterministic());
251
+ const expect = (s, expected) => {
252
+ const cp = toArray(stringToCodePointList(s));
253
+ const mr = descentParserCpOnly(m, '', cp);
254
+ const success = mr[1] && mr[2] === cp.length;
255
+ if (success !== expected) {
256
+ throw mr;
257
+ }
258
+ };
259
+ expect(' true ', true);
260
+ expect(' tr2ue ', false);
261
+ expect(' true" ', false);
262
+ expect(' "Hello" ', true);
263
+ expect(' "Hello ', false);
264
+ expect(' "Hello\\n\\r\\"" ', true);
265
+ expect(' -56.7e+5 ', true);
266
+ expect(' h-56.7e+5 ', false);
267
+ expect(' -56.7e+5 3', false);
268
+ expect(' [] ', true);
269
+ expect(' {} ', true);
270
+ expect(' [[[]]] ', true);
271
+ expect(' [1] ', true);
272
+ expect(' [ 12, false, "a"] ', true);
273
+ expect(' [ 12, false2, "a"] ', false);
274
+ expect(' { "q": [ 12, false, [{"b" : "c"}], "a"] } ', true);
275
+ expect(' { "q": [ 12, false, [{}], "a"] } ', true);
276
+ expect(' { "q": [ 12, false, [}], "a"] } ', false);
277
+ expect(' [{ "q": [ 12, false, [{}], "a"] }] ', true);
278
+ expect(' [{ "q": [ 12, false, [}], "a"] }] ', false);
279
+ }
280
+ ],
281
+ descentParserWithMeta: [
282
+ () => {
283
+ const emptyRule = '';
284
+ const minusRule = range('--');
285
+ const optionalMinusRule = { 'none': emptyRule, 'minus': minusRule };
286
+ const digitRule = range('09');
287
+ const numberRule = [optionalMinusRule, digitRule];
288
+ const m = descentParser(numberRule);
289
+ const mr = m("", [[45, 'minus'], [50, 'two']]);
290
+ const result = JSON.stringify(mr);
291
+ if (result !== '[{"sequence":[{"tag":"minus","sequence":[[45,"minus"]]},{"sequence":[[50,"two"]]}]},true,2]') {
292
+ throw result;
293
+ }
294
+ },
295
+ ],
296
+ };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * LL(1) dispatch/matcher backend over the BNF data {@link RuleSet}.
3
+ *
4
+ * Built from the serializable IR in `fs/bnf/data`, this is one member of the
5
+ * family of automaton builders that consume a {@link RuleSet}: it compiles the
6
+ * grammar into a predictive {@link dispatchMap} and matches input into an AST
7
+ * ({@link MatchResult}). It throws at build time (`can not merge …`) when the
8
+ * grammar is not LL(1) — a first/first conflict.
9
+ *
10
+ * @module
11
+ */
12
+ import { type CodePoint } from '../../text/utf16/module.f.ts';
13
+ import { type RangeMapArray } from '../../types/range_map/module.f.ts';
14
+ import { type StringMap } from '../../types/object/module.f.ts';
15
+ import { type RuleSet } from '../data/module.f.ts';
16
+ import { type Rule as FRule } from '../module.f.ts';
17
+ type EmptyTag = string | true | undefined;
18
+ type DispatchRule = {
19
+ readonly emptyTag: EmptyTag;
20
+ readonly rangeMap: Dispatch;
21
+ };
22
+ type Dispatch = RangeMapArray<DispatchResult>;
23
+ type DispatchResult = DispatchRuleCollection | null;
24
+ type DispatchRuleOrName = DispatchRule | string;
25
+ type DispatchRuleCollection = {
26
+ readonly tag: string | undefined;
27
+ readonly rules: DispatchRuleOrName[];
28
+ };
29
+ type DispatchMap = StringMap<string, DispatchRule>;
30
+ /**
31
+ * Represents a parsed AST sequence.
32
+ */
33
+ export type AstSequence = readonly (AstRule | CodePoint)[];
34
+ export type AstTag = string | true | undefined;
35
+ /**
36
+ * Represents a parsed AST rule, consisting of a rule name and its parsed sequence.
37
+ */
38
+ type AstRule = {
39
+ readonly tag: AstTag;
40
+ readonly sequence: AstSequence;
41
+ };
42
+ /**
43
+ * Represents the remaining input after a match attempt, or `null` if no match is possible.
44
+ */
45
+ export type Remainder = readonly CodePoint[] | null;
46
+ /**
47
+ * Parsing result of {@link parser} and {@link parserRuleSet}.
48
+ *
49
+ * Represents the result of a match operation, including the parsed AST rule and the remainder of the input.
50
+ */
51
+ export type MatchResult = readonly [AstRule, boolean, Remainder];
52
+ /**
53
+ * LL(1) parser function for matching by rule name.
54
+ */
55
+ export type Match = (name: string, s: readonly CodePoint[]) => MatchResult;
56
+ /**
57
+ * Internal match function signature used by compiled dispatch rules.
58
+ */
59
+ export type MatchRule = (dr: DispatchRule, s: readonly CodePoint[]) => MatchResult;
60
+ /**
61
+ * Builds a dispatch map for a {@link RuleSet} to enable predictive parsing.
62
+ */
63
+ export declare const dispatchMap: (ruleSet: RuleSet) => DispatchMap;
64
+ /**
65
+ * Creates an LL(1) parser from a functional grammar rule.
66
+ */
67
+ export declare const parser: (fr: FRule) => Match;
68
+ /**
69
+ * Creates an LL(1) parser from an already materialized {@link RuleSet}.
70
+ */
71
+ export declare const parserRuleSet: (ruleSet: RuleSet) => Match;
72
+ export {};