@claudiu-ceia/combine 0.2.4
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.
- package/LICENSE +21 -0
- package/README.md +389 -0
- package/esm/_dnt.shims.d.ts +2 -0
- package/esm/_dnt.shims.d.ts.map +1 -0
- package/esm/_dnt.shims.js +57 -0
- package/esm/mod.d.ts +6 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +5 -0
- package/esm/package.json +3 -0
- package/esm/src/Parser.d.ts +85 -0
- package/esm/src/Parser.d.ts.map +1 -0
- package/esm/src/Parser.js +93 -0
- package/esm/src/Trie.d.ts +17 -0
- package/esm/src/Trie.d.ts.map +1 -0
- package/esm/src/Trie.js +75 -0
- package/esm/src/combinators.d.ts +199 -0
- package/esm/src/combinators.d.ts.map +1 -0
- package/esm/src/combinators.js +531 -0
- package/esm/src/internal_assert.d.ts +2 -0
- package/esm/src/internal_assert.d.ts.map +1 -0
- package/esm/src/internal_assert.js +5 -0
- package/esm/src/language.d.ts +12 -0
- package/esm/src/language.d.ts.map +1 -0
- package/esm/src/language.js +13 -0
- package/esm/src/parsers.d.ts +94 -0
- package/esm/src/parsers.d.ts.map +1 -0
- package/esm/src/parsers.js +256 -0
- package/esm/src/utility.d.ts +91 -0
- package/esm/src/utility.d.ts.map +1 -0
- package/esm/src/utility.js +178 -0
- package/package.json +21 -0
- package/script/_dnt.shims.d.ts +2 -0
- package/script/_dnt.shims.d.ts.map +1 -0
- package/script/_dnt.shims.js +60 -0
- package/script/mod.d.ts +6 -0
- package/script/mod.d.ts.map +1 -0
- package/script/mod.js +21 -0
- package/script/package.json +3 -0
- package/script/src/Parser.d.ts +85 -0
- package/script/src/Parser.d.ts.map +1 -0
- package/script/src/Parser.js +104 -0
- package/script/src/Trie.d.ts +17 -0
- package/script/src/Trie.d.ts.map +1 -0
- package/script/src/Trie.js +80 -0
- package/script/src/combinators.d.ts +199 -0
- package/script/src/combinators.d.ts.map +1 -0
- package/script/src/combinators.js +557 -0
- package/script/src/internal_assert.d.ts +2 -0
- package/script/src/internal_assert.d.ts.map +1 -0
- package/script/src/internal_assert.js +9 -0
- package/script/src/language.d.ts +12 -0
- package/script/src/language.d.ts.map +1 -0
- package/script/src/language.js +17 -0
- package/script/src/parsers.d.ts +94 -0
- package/script/src/parsers.d.ts.map +1 -0
- package/script/src/parsers.js +281 -0
- package/script/src/utility.d.ts +91 -0
- package/script/src/utility.d.ts.map +1 -0
- package/script/src/utility.js +214 -0
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
import { assert } from "./internal_assert.js";
|
|
2
|
+
import { failure, isFatal, success, } from "./Parser.js";
|
|
3
|
+
import { map } from "./utility.js";
|
|
4
|
+
/**
|
|
5
|
+
* Apply a variadic list of parsers sequentially. The return type maintains
|
|
6
|
+
* the order of the parsers such that you get a typed result back.
|
|
7
|
+
*
|
|
8
|
+
* // p is a Parser<[number, string, number, string]>
|
|
9
|
+
* const p = seq(number(), str("-"), number(), str("-"), number());
|
|
10
|
+
*/
|
|
11
|
+
export const seq = (...parsers) => {
|
|
12
|
+
return (ctx) => {
|
|
13
|
+
if (parsers.length === 0) {
|
|
14
|
+
return failure(ctx, `A sequential parser needs to receive at least one parser.`);
|
|
15
|
+
}
|
|
16
|
+
const values = [];
|
|
17
|
+
let nextCtx = ctx;
|
|
18
|
+
for (const parser of parsers) {
|
|
19
|
+
const res = parser(nextCtx);
|
|
20
|
+
if (!res.success) {
|
|
21
|
+
return res;
|
|
22
|
+
}
|
|
23
|
+
values.push(res.value);
|
|
24
|
+
nextCtx = res.ctx;
|
|
25
|
+
}
|
|
26
|
+
return success(nextCtx, values);
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Try both parsers in sequence, return the first one that's succesful.
|
|
31
|
+
* Fatal errors from either parser will be propagated immediately.
|
|
32
|
+
*/
|
|
33
|
+
export const either = (a, b) => {
|
|
34
|
+
return (ctx) => {
|
|
35
|
+
return any(a, b)(ctx);
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Try all parsers in order, return the first one that is succesful.
|
|
40
|
+
* If none match, return the failure result of the parser that
|
|
41
|
+
* consumed most of the input.
|
|
42
|
+
*
|
|
43
|
+
* Fatal errors are propagated immediately without trying remaining parsers.
|
|
44
|
+
*/
|
|
45
|
+
export const any = (...parsers) => {
|
|
46
|
+
return (ctx) => {
|
|
47
|
+
let furthestRes;
|
|
48
|
+
for (const parser of parsers) {
|
|
49
|
+
const res = parser(ctx);
|
|
50
|
+
if (res.success) {
|
|
51
|
+
return res;
|
|
52
|
+
}
|
|
53
|
+
// Fatal errors propagate immediately - no backtracking
|
|
54
|
+
if (isFatal(res)) {
|
|
55
|
+
return res;
|
|
56
|
+
}
|
|
57
|
+
if (!furthestRes || furthestRes.ctx.index < res.ctx.index) {
|
|
58
|
+
furthestRes = res;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
assert(furthestRes);
|
|
62
|
+
return furthestRes;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Try all parsers in order. If more than one match is found,
|
|
67
|
+
* it's a failure. If only one matches, return it's result.
|
|
68
|
+
* If none matches, return the failure result of the parser
|
|
69
|
+
* that consumed the most input.
|
|
70
|
+
*
|
|
71
|
+
* Fatal errors are propagated immediately.
|
|
72
|
+
*/
|
|
73
|
+
export const oneOf = (...parsers) => {
|
|
74
|
+
return (ctx) => {
|
|
75
|
+
let match;
|
|
76
|
+
let furthestRes;
|
|
77
|
+
for (const parser of parsers) {
|
|
78
|
+
const res = parser(ctx);
|
|
79
|
+
// Fatal errors propagate immediately
|
|
80
|
+
if (!res.success && isFatal(res)) {
|
|
81
|
+
return res;
|
|
82
|
+
}
|
|
83
|
+
if (res.success) {
|
|
84
|
+
if (match) {
|
|
85
|
+
if (match.success) {
|
|
86
|
+
return failure(ctx, `expected single parser to match, already matched "${JSON.stringify(match.value)}", now matched ${JSON.stringify(res.value)}`);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
return failure(ctx, "expected single parser to match", [match]);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
match = res;
|
|
93
|
+
}
|
|
94
|
+
if (!furthestRes || furthestRes.ctx.index < res.ctx.index) {
|
|
95
|
+
furthestRes = res;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (match) {
|
|
99
|
+
return match;
|
|
100
|
+
}
|
|
101
|
+
assert(furthestRes);
|
|
102
|
+
return furthestRes;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Try all parsers in sequence and keep track of which one consumed
|
|
107
|
+
* the most input, then return it.
|
|
108
|
+
*
|
|
109
|
+
* Fatal errors are propagated immediately.
|
|
110
|
+
*/
|
|
111
|
+
export const furthest = (...parsers) => {
|
|
112
|
+
return (ctx) => {
|
|
113
|
+
let furthestRes;
|
|
114
|
+
for (const parser of parsers) {
|
|
115
|
+
const res = parser(ctx);
|
|
116
|
+
// Fatal errors propagate immediately
|
|
117
|
+
if (!res.success && isFatal(res)) {
|
|
118
|
+
return res;
|
|
119
|
+
}
|
|
120
|
+
if (!furthestRes || furthestRes.ctx.index < res.ctx.index) {
|
|
121
|
+
furthestRes = res;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
assert(furthestRes);
|
|
125
|
+
return furthestRes;
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Try a parser. If it matches, return it, otherwise return a `null`
|
|
130
|
+
* result without consuming any input.
|
|
131
|
+
*
|
|
132
|
+
* Fatal errors are propagated - optional only catches non-fatal failures.
|
|
133
|
+
*/
|
|
134
|
+
export const optional = (parser) => {
|
|
135
|
+
return (ctx) => {
|
|
136
|
+
const res = parser(ctx);
|
|
137
|
+
if (res.success) {
|
|
138
|
+
return res;
|
|
139
|
+
}
|
|
140
|
+
// Fatal errors propagate - don't swallow them
|
|
141
|
+
if (isFatal(res)) {
|
|
142
|
+
return res;
|
|
143
|
+
}
|
|
144
|
+
return success(ctx, null);
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Match the same parser until it fails. This parser never fails, so even
|
|
149
|
+
* if it doesn't match it's a succes.
|
|
150
|
+
*
|
|
151
|
+
* Fatal errors are propagated immediately.
|
|
152
|
+
*/
|
|
153
|
+
export const many = (parser) => {
|
|
154
|
+
return (ctx) => {
|
|
155
|
+
const values = [];
|
|
156
|
+
let nextCtx = ctx;
|
|
157
|
+
while (true) {
|
|
158
|
+
const res = parser(nextCtx);
|
|
159
|
+
if (!res.success) {
|
|
160
|
+
// Fatal errors propagate
|
|
161
|
+
if (isFatal(res)) {
|
|
162
|
+
return res;
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
values.push(res.value);
|
|
167
|
+
nextCtx = res.ctx;
|
|
168
|
+
}
|
|
169
|
+
return success(nextCtx, values);
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* Match the same parser until it fails. Needs to match at least
|
|
174
|
+
* once to be a success.
|
|
175
|
+
*
|
|
176
|
+
* Fatal errors are propagated immediately.
|
|
177
|
+
*/
|
|
178
|
+
export const many1 = (parser) => {
|
|
179
|
+
return (ctx) => {
|
|
180
|
+
const res = many(parser)(ctx);
|
|
181
|
+
// Propagate fatal errors
|
|
182
|
+
if (!res.success) {
|
|
183
|
+
return res;
|
|
184
|
+
}
|
|
185
|
+
if (ctx.index === res.ctx.index) {
|
|
186
|
+
return failure(res.ctx, "Expected at least one match");
|
|
187
|
+
}
|
|
188
|
+
return res;
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* Match parser until the second parser matches. The result is a tuple
|
|
193
|
+
* of results for the first parser, with the result of the second parser
|
|
194
|
+
* appended.
|
|
195
|
+
*
|
|
196
|
+
* Fatal errors from either parser are propagated immediately.
|
|
197
|
+
*/
|
|
198
|
+
export const manyTill = (parser, end) => {
|
|
199
|
+
return (ctx) => {
|
|
200
|
+
const values = [];
|
|
201
|
+
let nextCtx = ctx;
|
|
202
|
+
// eslint-disable-next-line no-constant-condition
|
|
203
|
+
while (true) {
|
|
204
|
+
const maybeEnd = end(nextCtx);
|
|
205
|
+
if (maybeEnd.success) {
|
|
206
|
+
return success(maybeEnd.ctx, [...values, maybeEnd.value]);
|
|
207
|
+
}
|
|
208
|
+
// Fatal errors from end parser propagate
|
|
209
|
+
if (isFatal(maybeEnd)) {
|
|
210
|
+
return maybeEnd;
|
|
211
|
+
}
|
|
212
|
+
const res = parser(nextCtx);
|
|
213
|
+
if (!res.success) {
|
|
214
|
+
// Fatal errors from content parser propagate
|
|
215
|
+
if (isFatal(res)) {
|
|
216
|
+
return res;
|
|
217
|
+
}
|
|
218
|
+
const maybeEnd = end(nextCtx);
|
|
219
|
+
if (maybeEnd.success) {
|
|
220
|
+
return success(maybeEnd.ctx, [...values, maybeEnd.value]);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
return maybeEnd;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
values.push(res.value);
|
|
227
|
+
nextCtx = res.ctx;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
/**
|
|
232
|
+
* Repeatedly match a parser
|
|
233
|
+
*
|
|
234
|
+
* Fatal errors are propagated immediately.
|
|
235
|
+
*/
|
|
236
|
+
export const repeat = (n, parser) => {
|
|
237
|
+
return (ctx) => {
|
|
238
|
+
const values = [];
|
|
239
|
+
let nextCtx = ctx;
|
|
240
|
+
let idx = 0;
|
|
241
|
+
while (idx < n) {
|
|
242
|
+
const res = parser(nextCtx);
|
|
243
|
+
if (!res.success) {
|
|
244
|
+
// Propagate fatal errors with their stack
|
|
245
|
+
if (isFatal(res)) {
|
|
246
|
+
return res;
|
|
247
|
+
}
|
|
248
|
+
return failure(ctx, res.expected, [], res.stack);
|
|
249
|
+
}
|
|
250
|
+
values.push(res.value);
|
|
251
|
+
nextCtx = res.ctx;
|
|
252
|
+
idx++;
|
|
253
|
+
}
|
|
254
|
+
return success(nextCtx, values);
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
/**
|
|
258
|
+
* Useful for parsing separated lists. Repeatedly match a sequence of both
|
|
259
|
+
* parsers while they both match. Doesn't support trailing separators.
|
|
260
|
+
*
|
|
261
|
+
* const p = sepBy(number(), str(","));
|
|
262
|
+
*
|
|
263
|
+
* const ok = p("1,2,3"); // success
|
|
264
|
+
* const fail = p("1,2,3,"); // failure, expecting one more number
|
|
265
|
+
*
|
|
266
|
+
* If no matches are found, it's a success.
|
|
267
|
+
*
|
|
268
|
+
* Fatal errors are propagated immediately.
|
|
269
|
+
*/
|
|
270
|
+
export const sepBy = (parser, sep) => {
|
|
271
|
+
return (ctx) => {
|
|
272
|
+
const values = [];
|
|
273
|
+
let nextCtx = ctx;
|
|
274
|
+
// eslint-disable-next-line no-constant-condition
|
|
275
|
+
while (true) {
|
|
276
|
+
const res = parser(nextCtx);
|
|
277
|
+
// Fatal errors propagate
|
|
278
|
+
if (!res.success && isFatal(res)) {
|
|
279
|
+
return res;
|
|
280
|
+
}
|
|
281
|
+
if (res.success) {
|
|
282
|
+
const sepCtx = res.ctx;
|
|
283
|
+
values.push(res.value);
|
|
284
|
+
const sepRes = sep(sepCtx);
|
|
285
|
+
// Fatal errors from separator propagate
|
|
286
|
+
if (!sepRes.success && isFatal(sepRes)) {
|
|
287
|
+
return sepRes;
|
|
288
|
+
}
|
|
289
|
+
if (!sepRes.success) {
|
|
290
|
+
return success(sepCtx, values);
|
|
291
|
+
}
|
|
292
|
+
nextCtx = sepRes.ctx;
|
|
293
|
+
values.push(sepRes.value);
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
return success(nextCtx, values);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
};
|
|
300
|
+
/**
|
|
301
|
+
* Same as `sepBy`, but at least one match is required.
|
|
302
|
+
*
|
|
303
|
+
* Fatal errors are propagated immediately.
|
|
304
|
+
*/
|
|
305
|
+
export const sepBy1 = (parser, sep) => {
|
|
306
|
+
return (ctx) => {
|
|
307
|
+
const res = sepBy(parser, sep)(ctx);
|
|
308
|
+
// Propagate fatal errors
|
|
309
|
+
if (!res.success) {
|
|
310
|
+
return res;
|
|
311
|
+
}
|
|
312
|
+
if (res.ctx.index === ctx.index) {
|
|
313
|
+
const parserTest = parser(ctx);
|
|
314
|
+
if (parserTest.success) {
|
|
315
|
+
// This should never happen since `sepBy` didn't match - need to rewrite for statical guarantee
|
|
316
|
+
return failure(parserTest.ctx, "Unjustified error");
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
return failure(res.ctx, "Expected at least one match", [parserTest]);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return res;
|
|
323
|
+
};
|
|
324
|
+
};
|
|
325
|
+
/**
|
|
326
|
+
* Skips matching parsers while consuming input.
|
|
327
|
+
*
|
|
328
|
+
* Fatal errors are propagated immediately.
|
|
329
|
+
*/
|
|
330
|
+
export const skipMany = (parser) => {
|
|
331
|
+
return (ctx) => {
|
|
332
|
+
const res = many(parser)(ctx);
|
|
333
|
+
// Propagate fatal errors
|
|
334
|
+
if (!res.success) {
|
|
335
|
+
return res;
|
|
336
|
+
}
|
|
337
|
+
return success(res.ctx, null);
|
|
338
|
+
};
|
|
339
|
+
};
|
|
340
|
+
/**
|
|
341
|
+
* Skips matching parsers while consuming input. At least one match is required.
|
|
342
|
+
*
|
|
343
|
+
* Fatal errors are propagated immediately.
|
|
344
|
+
*/
|
|
345
|
+
export const skipMany1 = (parser) => {
|
|
346
|
+
return (ctx) => {
|
|
347
|
+
const res = many1(parser)(ctx);
|
|
348
|
+
if (res.success) {
|
|
349
|
+
return success(res.ctx, null);
|
|
350
|
+
}
|
|
351
|
+
return res;
|
|
352
|
+
};
|
|
353
|
+
};
|
|
354
|
+
/**
|
|
355
|
+
* Match a parser, but don't consume any input.
|
|
356
|
+
*/
|
|
357
|
+
export const peek = (parser) => {
|
|
358
|
+
return (ctx) => {
|
|
359
|
+
const res = parser(ctx);
|
|
360
|
+
if (res.success) {
|
|
361
|
+
return success(ctx, null);
|
|
362
|
+
}
|
|
363
|
+
return failure(ctx, `lookahead failed, ${res.expected}`, [], res.stack);
|
|
364
|
+
};
|
|
365
|
+
};
|
|
366
|
+
/**
|
|
367
|
+
* Skips matching a single parser while consuming input.
|
|
368
|
+
*/
|
|
369
|
+
export const skip1 = (parser) => {
|
|
370
|
+
return (ctx) => {
|
|
371
|
+
const res = parser(ctx);
|
|
372
|
+
if (res.success) {
|
|
373
|
+
return success(res.ctx, null);
|
|
374
|
+
}
|
|
375
|
+
return res;
|
|
376
|
+
};
|
|
377
|
+
};
|
|
378
|
+
export const surrounded = (open, middle, close) => {
|
|
379
|
+
/* return (ctx) => {
|
|
380
|
+
const openRes = open(ctx);
|
|
381
|
+
if (openRes.success) {
|
|
382
|
+
let cursor = openRes.ctx;
|
|
383
|
+
let closeRes = close(cursor);
|
|
384
|
+
while (!closeRes.success) {
|
|
385
|
+
const res = middle(cursor);
|
|
386
|
+
if (res.success) {
|
|
387
|
+
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
} */
|
|
392
|
+
return map(seq(open, middle, close), ([_open, middle, _close]) => middle);
|
|
393
|
+
};
|
|
394
|
+
export const minus = (a, b) => {
|
|
395
|
+
return (ctx) => {
|
|
396
|
+
const excludedRes = b(ctx);
|
|
397
|
+
if (excludedRes.success) {
|
|
398
|
+
return failure(ctx, `Matched excluded "${JSON.stringify(excludedRes.value)}"`);
|
|
399
|
+
}
|
|
400
|
+
return a(ctx);
|
|
401
|
+
};
|
|
402
|
+
};
|
|
403
|
+
export const not = (a) => {
|
|
404
|
+
return (ctx) => {
|
|
405
|
+
const res = a(ctx);
|
|
406
|
+
if (res.success) {
|
|
407
|
+
return failure(ctx, `Matched "${JSON.stringify(res.value)}"`);
|
|
408
|
+
}
|
|
409
|
+
return success(ctx, null);
|
|
410
|
+
};
|
|
411
|
+
};
|
|
412
|
+
export const keepNonNull = (parser) => map(parser, (matches) => matches.filter((v) => v !== null));
|
|
413
|
+
export const seqNonNull = (...parsers) => keepNonNull(seq(...parsers));
|
|
414
|
+
/**
|
|
415
|
+
* Parse left-associative infix expressions.
|
|
416
|
+
* Parses: term (op term)* and folds left using the combine function.
|
|
417
|
+
*
|
|
418
|
+
* This is useful for parsing binary operators with the same precedence level.
|
|
419
|
+
*
|
|
420
|
+
* @param term - Parser for the operands
|
|
421
|
+
* @param op - Parser for the operator(s)
|
|
422
|
+
* @param combine - Function to combine left operand, operator, and right operand
|
|
423
|
+
* @returns A parser that produces the left-folded result
|
|
424
|
+
*
|
|
425
|
+
* @example
|
|
426
|
+
* ```ts
|
|
427
|
+
* // Parse addition/subtraction: 1 + 2 - 3 => ((1 + 2) - 3)
|
|
428
|
+
* const addSub = chainl1(
|
|
429
|
+
* numberParser,
|
|
430
|
+
* any(str("+"), str("-")),
|
|
431
|
+
* (left, op, right) => ({ type: "binary", op, left, right })
|
|
432
|
+
* );
|
|
433
|
+
* ```
|
|
434
|
+
*
|
|
435
|
+
* Fatal errors are propagated immediately.
|
|
436
|
+
*/
|
|
437
|
+
export const chainl1 = (term, op, combine) => {
|
|
438
|
+
return (ctx) => {
|
|
439
|
+
const firstRes = term(ctx);
|
|
440
|
+
if (!firstRes.success) {
|
|
441
|
+
return firstRes;
|
|
442
|
+
}
|
|
443
|
+
let acc = firstRes.value;
|
|
444
|
+
let nextCtx = firstRes.ctx;
|
|
445
|
+
while (true) {
|
|
446
|
+
const opRes = op(nextCtx);
|
|
447
|
+
if (!opRes.success) {
|
|
448
|
+
// Fatal errors propagate
|
|
449
|
+
if (isFatal(opRes)) {
|
|
450
|
+
return opRes;
|
|
451
|
+
}
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
const rightRes = term(opRes.ctx);
|
|
455
|
+
if (!rightRes.success) {
|
|
456
|
+
// Fatal errors propagate
|
|
457
|
+
if (isFatal(rightRes)) {
|
|
458
|
+
return rightRes;
|
|
459
|
+
}
|
|
460
|
+
// Operator matched but operand didn't - this is a failure
|
|
461
|
+
return rightRes;
|
|
462
|
+
}
|
|
463
|
+
acc = combine(acc, opRes.value, rightRes.value);
|
|
464
|
+
nextCtx = rightRes.ctx;
|
|
465
|
+
}
|
|
466
|
+
return success(nextCtx, acc);
|
|
467
|
+
};
|
|
468
|
+
};
|
|
469
|
+
/**
|
|
470
|
+
* Parse right-associative infix expressions.
|
|
471
|
+
* Parses: term (op term)* and folds right using the combine function.
|
|
472
|
+
*
|
|
473
|
+
* This is useful for operators like exponentiation: 2 ** 3 ** 4 => 2 ** (3 ** 4)
|
|
474
|
+
*
|
|
475
|
+
* @param term - Parser for the operands
|
|
476
|
+
* @param op - Parser for the operator(s)
|
|
477
|
+
* @param combine - Function to combine left operand, operator, and right operand
|
|
478
|
+
* @returns A parser that produces the right-folded result
|
|
479
|
+
*
|
|
480
|
+
* @example
|
|
481
|
+
* ```ts
|
|
482
|
+
* // Parse exponentiation: 2 ** 3 ** 4 => 2 ** (3 ** 4)
|
|
483
|
+
* const pow = chainr1(
|
|
484
|
+
* numberParser,
|
|
485
|
+
* str("**"),
|
|
486
|
+
* (left, op, right) => ({ type: "binary", op, left, right })
|
|
487
|
+
* );
|
|
488
|
+
* ```
|
|
489
|
+
*
|
|
490
|
+
* Fatal errors are propagated immediately.
|
|
491
|
+
*/
|
|
492
|
+
export const chainr1 = (term, op, combine) => {
|
|
493
|
+
return (ctx) => {
|
|
494
|
+
const firstRes = term(ctx);
|
|
495
|
+
if (!firstRes.success) {
|
|
496
|
+
return firstRes;
|
|
497
|
+
}
|
|
498
|
+
// Collect all terms and operators
|
|
499
|
+
const terms = [firstRes.value];
|
|
500
|
+
const ops = [];
|
|
501
|
+
let nextCtx = firstRes.ctx;
|
|
502
|
+
while (true) {
|
|
503
|
+
const opRes = op(nextCtx);
|
|
504
|
+
if (!opRes.success) {
|
|
505
|
+
// Fatal errors propagate
|
|
506
|
+
if (isFatal(opRes)) {
|
|
507
|
+
return opRes;
|
|
508
|
+
}
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
const rightRes = term(opRes.ctx);
|
|
512
|
+
if (!rightRes.success) {
|
|
513
|
+
// Fatal errors propagate
|
|
514
|
+
if (isFatal(rightRes)) {
|
|
515
|
+
return rightRes;
|
|
516
|
+
}
|
|
517
|
+
// Operator matched but operand didn't - this is a failure
|
|
518
|
+
return rightRes;
|
|
519
|
+
}
|
|
520
|
+
ops.push(opRes.value);
|
|
521
|
+
terms.push(rightRes.value);
|
|
522
|
+
nextCtx = rightRes.ctx;
|
|
523
|
+
}
|
|
524
|
+
// Fold right: a op b op c => a op (b op c)
|
|
525
|
+
let acc = terms[terms.length - 1];
|
|
526
|
+
for (let i = terms.length - 2; i >= 0; i--) {
|
|
527
|
+
acc = combine(terms[i], ops[i], acc);
|
|
528
|
+
}
|
|
529
|
+
return success(nextCtx, acc);
|
|
530
|
+
};
|
|
531
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal_assert.d.ts","sourceRoot":"","sources":["../../src/src/internal_assert.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM,EAAE,CACnB,SAAS,EAAE,OAAO,EAClB,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC,SAOZ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Parser } from "./Parser.js";
|
|
2
|
+
export type BoundDefinition<T extends UnboundDefinition<any>> = {
|
|
3
|
+
[Key in keyof T]: ReturnType<T[Key]>;
|
|
4
|
+
};
|
|
5
|
+
export type UnboundDefinition<T extends BoundDefinition<any>> = {
|
|
6
|
+
[Key in keyof T]: (self: T) => T[Key];
|
|
7
|
+
};
|
|
8
|
+
export type UntypedLanguage = {
|
|
9
|
+
[key: string]: Parser<unknown>;
|
|
10
|
+
};
|
|
11
|
+
export declare const createLanguage: <T extends BoundDefinition<any> = UntypedLanguage>(map: UnboundDefinition<T>) => BoundDefinition<UnboundDefinition<T>>;
|
|
12
|
+
//# sourceMappingURL=language.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"language.d.ts","sourceRoot":"","sources":["../../src/src/language.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAG1C,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,iBAAiB,CAAC,GAAG,CAAC,IAAI;KAC7D,GAAG,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,eAAe,CAAC,GAAG,CAAC,IAAI;KAC7D,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;CAChC,CAAC;AAEF,eAAO,MAAM,cAAc,GACzB,CAAC,SAAS,eAAe,CAAC,GAAG,CAAC,yBAEzB,iBAAiB,CAAC,CAAC,CAAC,KACxB,eAAe,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAetC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { lazy } from "./utility.js";
|
|
2
|
+
export const createLanguage = (map) => {
|
|
3
|
+
const LanguageDefinition = class LanguageDefinitionClass {
|
|
4
|
+
constructor() {
|
|
5
|
+
const bound = {};
|
|
6
|
+
for (const key of Object.keys(map)) {
|
|
7
|
+
bound[key] = lazy(() => map[key](this));
|
|
8
|
+
}
|
|
9
|
+
Object.assign(this, bound);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
return new LanguageDefinition();
|
|
13
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { type Parser } from "./Parser.js";
|
|
2
|
+
/**
|
|
3
|
+
* Matches a given string.
|
|
4
|
+
*/
|
|
5
|
+
export declare const str: (match: string) => Parser<string>;
|
|
6
|
+
/**
|
|
7
|
+
* Matches any of the given strings by using a trie.
|
|
8
|
+
* Use instead of `any(str("..."), ...) when you want
|
|
9
|
+
* to match against many possible strings.
|
|
10
|
+
*/
|
|
11
|
+
export declare const trie: (matches: string[]) => Parser<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Matches a given character by UTF-16 code.
|
|
14
|
+
*/
|
|
15
|
+
export declare const char: (code: number) => Parser<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Matches any single character.
|
|
18
|
+
*/
|
|
19
|
+
export declare const anyChar: () => Parser<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Matches any character not matching the given UTF-16 code.
|
|
22
|
+
*/
|
|
23
|
+
export declare const notChar: (code: number) => Parser<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Matches any character based on a predicate.
|
|
26
|
+
*/
|
|
27
|
+
export declare const charWhere: (pred: (code: number) => boolean) => Parser<string>;
|
|
28
|
+
/**
|
|
29
|
+
* Skips matching any character not matching the given UTF-16 code.
|
|
30
|
+
*/
|
|
31
|
+
export declare const skipCharWhere: (pred: (code: number) => boolean) => Parser<string | null>;
|
|
32
|
+
/**
|
|
33
|
+
* Matches any single decimal digit
|
|
34
|
+
*/
|
|
35
|
+
export declare const digit: () => Parser<number>;
|
|
36
|
+
/**
|
|
37
|
+
* Matches any single letter (case insesitive A-Z)
|
|
38
|
+
*/
|
|
39
|
+
export declare const letter: () => Parser<string>;
|
|
40
|
+
/**
|
|
41
|
+
* Matches any whitespace
|
|
42
|
+
*/
|
|
43
|
+
export declare const space: () => Parser<string>;
|
|
44
|
+
/**
|
|
45
|
+
* Matches any `count` characters as long as there's enough input
|
|
46
|
+
* left to parse.
|
|
47
|
+
*/
|
|
48
|
+
export declare const take: (count: number) => Parser<string>;
|
|
49
|
+
/**
|
|
50
|
+
* Matches the rest of the input.
|
|
51
|
+
*/
|
|
52
|
+
export declare const takeText: () => Parser<string>;
|
|
53
|
+
/**
|
|
54
|
+
* Matches an end of line marker
|
|
55
|
+
*/
|
|
56
|
+
export declare const eol: () => Parser<string>;
|
|
57
|
+
/**
|
|
58
|
+
* Matches if there's no input left to parse
|
|
59
|
+
*/
|
|
60
|
+
export declare const eof: () => Parser<null>;
|
|
61
|
+
/**
|
|
62
|
+
* Matches horizontal space (spaces/tabs), if at least one space
|
|
63
|
+
* follows.
|
|
64
|
+
*/
|
|
65
|
+
export declare const horizontalSpace: () => Parser<null>;
|
|
66
|
+
/**
|
|
67
|
+
* Matches a positive integer
|
|
68
|
+
*/
|
|
69
|
+
export declare const int: () => Parser<number>;
|
|
70
|
+
/**
|
|
71
|
+
* Matches a dot-separated double
|
|
72
|
+
*/
|
|
73
|
+
export declare const double: () => Parser<number>;
|
|
74
|
+
/**
|
|
75
|
+
* Matches a hexadecimal digit
|
|
76
|
+
*/
|
|
77
|
+
export declare const hexDigit: () => Parser<string>;
|
|
78
|
+
/**
|
|
79
|
+
* Matches a hexadecimal number (`0x` lead not allowed)
|
|
80
|
+
*/
|
|
81
|
+
export declare const hex: () => Parser<string>;
|
|
82
|
+
/**
|
|
83
|
+
* Matches a positive decimal number
|
|
84
|
+
*/
|
|
85
|
+
export declare const number: () => Parser<number>;
|
|
86
|
+
/**
|
|
87
|
+
* Matches a signed decimal number (with explicit +/- sign)
|
|
88
|
+
*/
|
|
89
|
+
export declare const signed: (nParser?: Parser<number>) => Parser<number>;
|
|
90
|
+
/**
|
|
91
|
+
* Matches input for given regex
|
|
92
|
+
*/
|
|
93
|
+
export declare const regex: (re: RegExp, expected: string) => Parser<string>;
|
|
94
|
+
//# sourceMappingURL=parsers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parsers.d.ts","sourceRoot":"","sources":["../../src/src/parsers.ts"],"names":[],"mappings":"AACA,OAAO,EAAW,KAAK,MAAM,EAAW,MAAM,aAAa,CAAC;AAI5D;;GAEG;AACH,eAAO,MAAM,GAAG,UAAW,MAAM,KAAG,MAAM,CAAC,MAAM,CAShD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,IAAI,YAAa,MAAM,EAAE,KAAG,MAAM,CAAC,MAAM,CAwBrD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,IAAI,SAAU,MAAM,KAAG,MAAM,CAAC,MAAM,CAKhD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,OAAO,QAAO,MAAM,CAAC,MAAM,CAWvC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,OAAO,SAAU,MAAM,KAAG,MAAM,CAAC,MAAM,CAiBnD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS,SAAU,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,KAAG,MAAM,CAAC,MAAM,CAcxE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,SAClB,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,KAC9B,MAAM,CAAC,MAAM,GAAG,IAAI,CAStB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,KAAK,QAAO,MAAM,CAAC,MAAM,CAQrC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,MAAM,QAAO,MAAM,CAAC,MAAM,CAItC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,KAAK,QAAO,MAAM,CAAC,MAAM,CAIrC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,IAAI,UAAW,MAAM,KAAG,MAAM,CAAC,MAAM,CAYjD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,QAAQ,QAAO,MAAM,CAAC,MAAM,CAOxC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,GAAG,QAAO,MAAM,CAAC,MAAM,CAInC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,GAAG,QAAO,MAAM,CAAC,IAAI,CAQjC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,QAAO,MAAM,CAAC,IAAI,CAM7C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,GAAG,QAAO,MAAM,CAAC,MAAM,CAInC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,MAAM,QAAO,MAAM,CAAC,MAAM,CAkBtC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,QAAQ,QAAO,MAAM,CAAC,MAAM,CAUxC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,GAAG,QAAO,MAAM,CAAC,MAAM,CASnC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,MAAM,QAAO,MAAM,CAAC,MAAM,CAEtC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,MAAM,aAAa,MAAM,CAAC,MAAM,CAAC,KAAc,MAAM,CAAC,MAAM,CAUxE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,KAAK,OAAQ,MAAM,YAAY,MAAM,KAAG,MAAM,CAAC,MAAM,CAgBjE,CAAC"}
|