@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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +389 -0
  3. package/esm/_dnt.shims.d.ts +2 -0
  4. package/esm/_dnt.shims.d.ts.map +1 -0
  5. package/esm/_dnt.shims.js +57 -0
  6. package/esm/mod.d.ts +6 -0
  7. package/esm/mod.d.ts.map +1 -0
  8. package/esm/mod.js +5 -0
  9. package/esm/package.json +3 -0
  10. package/esm/src/Parser.d.ts +85 -0
  11. package/esm/src/Parser.d.ts.map +1 -0
  12. package/esm/src/Parser.js +93 -0
  13. package/esm/src/Trie.d.ts +17 -0
  14. package/esm/src/Trie.d.ts.map +1 -0
  15. package/esm/src/Trie.js +75 -0
  16. package/esm/src/combinators.d.ts +199 -0
  17. package/esm/src/combinators.d.ts.map +1 -0
  18. package/esm/src/combinators.js +531 -0
  19. package/esm/src/internal_assert.d.ts +2 -0
  20. package/esm/src/internal_assert.d.ts.map +1 -0
  21. package/esm/src/internal_assert.js +5 -0
  22. package/esm/src/language.d.ts +12 -0
  23. package/esm/src/language.d.ts.map +1 -0
  24. package/esm/src/language.js +13 -0
  25. package/esm/src/parsers.d.ts +94 -0
  26. package/esm/src/parsers.d.ts.map +1 -0
  27. package/esm/src/parsers.js +256 -0
  28. package/esm/src/utility.d.ts +91 -0
  29. package/esm/src/utility.d.ts.map +1 -0
  30. package/esm/src/utility.js +178 -0
  31. package/package.json +21 -0
  32. package/script/_dnt.shims.d.ts +2 -0
  33. package/script/_dnt.shims.d.ts.map +1 -0
  34. package/script/_dnt.shims.js +60 -0
  35. package/script/mod.d.ts +6 -0
  36. package/script/mod.d.ts.map +1 -0
  37. package/script/mod.js +21 -0
  38. package/script/package.json +3 -0
  39. package/script/src/Parser.d.ts +85 -0
  40. package/script/src/Parser.d.ts.map +1 -0
  41. package/script/src/Parser.js +104 -0
  42. package/script/src/Trie.d.ts +17 -0
  43. package/script/src/Trie.d.ts.map +1 -0
  44. package/script/src/Trie.js +80 -0
  45. package/script/src/combinators.d.ts +199 -0
  46. package/script/src/combinators.d.ts.map +1 -0
  47. package/script/src/combinators.js +557 -0
  48. package/script/src/internal_assert.d.ts +2 -0
  49. package/script/src/internal_assert.d.ts.map +1 -0
  50. package/script/src/internal_assert.js +9 -0
  51. package/script/src/language.d.ts +12 -0
  52. package/script/src/language.d.ts.map +1 -0
  53. package/script/src/language.js +17 -0
  54. package/script/src/parsers.d.ts +94 -0
  55. package/script/src/parsers.d.ts.map +1 -0
  56. package/script/src/parsers.js +281 -0
  57. package/script/src/utility.d.ts +91 -0
  58. package/script/src/utility.d.ts.map +1 -0
  59. package/script/src/utility.js +214 -0
@@ -0,0 +1,256 @@
1
+ import { any, either, many1, peek, seq, skipMany1 } from "./combinators.js";
2
+ import { failure, success } from "./Parser.js";
3
+ import { Trie } from "./Trie.js";
4
+ import { map } from "./utility.js";
5
+ /**
6
+ * Matches a given string.
7
+ */
8
+ export const str = (match) => {
9
+ return (ctx) => {
10
+ const endIdx = ctx.index + match.length;
11
+ if (ctx.text.substring(ctx.index, endIdx) === match) {
12
+ return success({ ...ctx, index: endIdx }, match);
13
+ }
14
+ else {
15
+ return failure(ctx, match);
16
+ }
17
+ };
18
+ };
19
+ /**
20
+ * Matches any of the given strings by using a trie.
21
+ * Use instead of `any(str("..."), ...) when you want
22
+ * to match against many possible strings.
23
+ */
24
+ export const trie = (matches) => {
25
+ // Build trie once at parser creation time, not on every parse
26
+ const t = new Trie();
27
+ t.insertMany(matches);
28
+ const longest = matches.reduce((acc, s) => (s.length > acc ? s.length : acc), 0);
29
+ return (ctx) => {
30
+ const candidate = ctx.text.substring(ctx.index, ctx.index + longest);
31
+ const [exists, match] = t.existsSubstring(candidate);
32
+ if (exists && match) {
33
+ return success({
34
+ ...ctx,
35
+ index: ctx.index + match.length,
36
+ }, match);
37
+ }
38
+ return failure(ctx, `Expected one of ${matches.join(", ")}`);
39
+ };
40
+ };
41
+ /**
42
+ * Matches a given character by UTF-16 code.
43
+ */
44
+ export const char = (code) => {
45
+ return (ctx) => {
46
+ const match = String.fromCharCode(code);
47
+ return str(match)(ctx);
48
+ };
49
+ };
50
+ /**
51
+ * Matches any single character.
52
+ */
53
+ export const anyChar = () => {
54
+ return (ctx) => {
55
+ if (ctx.index === ctx.text.length) {
56
+ return failure(ctx, "reached end of input");
57
+ }
58
+ return success({ ...ctx, index: ctx.index + 1 }, ctx.text.substring(ctx.index, ctx.index + 1));
59
+ };
60
+ };
61
+ /**
62
+ * Matches any character not matching the given UTF-16 code.
63
+ */
64
+ export const notChar = (code) => {
65
+ return (ctx) => {
66
+ const res = char(code)(ctx);
67
+ const matchLength = String.fromCharCode(code).length;
68
+ if (!res.success) {
69
+ const endIdx = res.ctx.index + matchLength;
70
+ return success({ ...res.ctx, index: endIdx }, res.ctx.text.substring(res.ctx.index, endIdx));
71
+ }
72
+ return failure({ ...res.ctx, index: res.ctx.index - matchLength }, `found char "${res.value}"`);
73
+ };
74
+ };
75
+ /**
76
+ * Matches any character based on a predicate.
77
+ */
78
+ export const charWhere = (pred) => {
79
+ return (ctx) => {
80
+ const res = regex(/./, "expected any single char")(ctx);
81
+ if (!res.success) {
82
+ return res;
83
+ }
84
+ const satisfied = pred(res.value.charCodeAt(0));
85
+ if (satisfied) {
86
+ return res;
87
+ }
88
+ return failure(res.ctx, `char ${res.value} failed the predicate`);
89
+ };
90
+ };
91
+ /**
92
+ * Skips matching any character not matching the given UTF-16 code.
93
+ */
94
+ export const skipCharWhere = (pred) => {
95
+ return (ctx) => {
96
+ const res = charWhere(pred)(ctx);
97
+ if (!res.success) {
98
+ return res;
99
+ }
100
+ return success(res.ctx, null);
101
+ };
102
+ };
103
+ /**
104
+ * Matches any single decimal digit
105
+ */
106
+ export const digit = () => {
107
+ return (ctx) => {
108
+ const isDigit = regex(/[0-9]/, "expected digit");
109
+ return map(isDigit, (digit) => {
110
+ return parseInt(digit, 10);
111
+ })(ctx);
112
+ };
113
+ };
114
+ /**
115
+ * Matches any single letter (case insesitive A-Z)
116
+ */
117
+ export const letter = () => {
118
+ return (ctx) => {
119
+ return regex(/[a-zA-Z]/, "expected letter")(ctx);
120
+ };
121
+ };
122
+ /**
123
+ * Matches any whitespace
124
+ */
125
+ export const space = () => {
126
+ return (ctx) => {
127
+ return regex(/\s+/, "expected whitespace")(ctx);
128
+ };
129
+ };
130
+ /**
131
+ * Matches any `count` characters as long as there's enough input
132
+ * left to parse.
133
+ */
134
+ export const take = (count) => {
135
+ return (ctx) => {
136
+ const endIdx = ctx.index + count;
137
+ if (endIdx <= ctx.text.length) {
138
+ return success({ ...ctx, index: endIdx }, ctx.text.substring(ctx.index, endIdx));
139
+ }
140
+ else {
141
+ return failure(ctx, "unexpected end of input");
142
+ }
143
+ };
144
+ };
145
+ /**
146
+ * Matches the rest of the input.
147
+ */
148
+ export const takeText = () => {
149
+ return (ctx) => {
150
+ return success({ ...ctx, index: ctx.text.length }, ctx.text.substring(ctx.index, ctx.text.length));
151
+ };
152
+ };
153
+ /**
154
+ * Matches an end of line marker
155
+ */
156
+ export const eol = () => {
157
+ return (ctx) => {
158
+ return either(str("\n"), str("\r\n"))(ctx);
159
+ };
160
+ };
161
+ /**
162
+ * Matches if there's no input left to parse
163
+ */
164
+ export const eof = () => {
165
+ return (ctx) => {
166
+ if (ctx.index < ctx.text.length) {
167
+ return failure(ctx, "eof not reached");
168
+ }
169
+ return success(ctx, null);
170
+ };
171
+ };
172
+ /**
173
+ * Matches horizontal space (spaces/tabs), if at least one space
174
+ * follows.
175
+ */
176
+ export const horizontalSpace = () => {
177
+ return (ctx) => {
178
+ return skipMany1(charWhere((code) => String.fromCharCode(code).trim() === ""))(ctx);
179
+ };
180
+ };
181
+ /**
182
+ * Matches a positive integer
183
+ */
184
+ export const int = () => {
185
+ return (ctx) => {
186
+ return map(many1(digit()), (digits) => parseInt(digits.join("")))(ctx);
187
+ };
188
+ };
189
+ /**
190
+ * Matches a dot-separated double
191
+ */
192
+ export const double = () => {
193
+ return (ctx) => {
194
+ const comma = str(".");
195
+ const fractional = map(seq(comma, int()), (fraction) => fraction[1]);
196
+ return map(seq(int(), either(fractional, map(str("."), () => 0))), (double) => {
197
+ return parseFloat(double.join("."));
198
+ })(ctx);
199
+ };
200
+ };
201
+ /**
202
+ * Matches a hexadecimal digit
203
+ */
204
+ export const hexDigit = () => {
205
+ return (ctx) => map(any(digit(), charWhere((code) => code >= 65 && code <= 70), // A-F
206
+ charWhere((code) => code >= 97 && code <= 102)), (digit) => digit.toString())(ctx);
207
+ };
208
+ /**
209
+ * Matches a hexadecimal number (`0x` lead not allowed)
210
+ */
211
+ export const hex = () => {
212
+ return (ctx) => {
213
+ const lead = peek(str("0x"))(ctx);
214
+ if (lead.success) {
215
+ return failure(ctx, "unexpected 0x lead");
216
+ }
217
+ return map(many1(hexDigit()), (hex) => hex.join())(ctx);
218
+ };
219
+ };
220
+ /**
221
+ * Matches a positive decimal number
222
+ */
223
+ export const number = () => {
224
+ return (ctx) => either(double(), int())(ctx);
225
+ };
226
+ /**
227
+ * Matches a signed decimal number (with explicit +/- sign)
228
+ */
229
+ export const signed = (nParser = number()) => {
230
+ return (ctx) => {
231
+ return map(seq(either(str("+"), str("-")), nParser), (out) => {
232
+ const [sign, num] = out;
233
+ if (sign === "+") {
234
+ return num;
235
+ }
236
+ return -num;
237
+ })(ctx);
238
+ };
239
+ };
240
+ /**
241
+ * Matches input for given regex
242
+ */
243
+ export const regex = (re, expected) => {
244
+ return (ctx) => {
245
+ // Non-global regexps don't support `lastIndex`
246
+ const globalRe = new RegExp(re.source, re.global ? re.flags : `${re.flags}g`);
247
+ globalRe.lastIndex = ctx.index;
248
+ const res = globalRe.exec(ctx.text);
249
+ if (res && res.index === ctx.index) {
250
+ return success({ ...ctx, index: ctx.index + res[0].length }, res[0]);
251
+ }
252
+ else {
253
+ return failure(ctx, expected);
254
+ }
255
+ };
256
+ };
@@ -0,0 +1,91 @@
1
+ import { type Context, type Failure, type Parser } from "./Parser.js";
2
+ /**
3
+ * Map the result of a parser using a given function. Useful for building
4
+ * AST nodes or interpreting the parse results. The mapping function receives
5
+ * the result of the parser, as well as the state of the input before and after
6
+ * the parser being applied.
7
+ */
8
+ export declare const map: <A, B>(parser: Parser<A>, fn: (val: A, before: Context, after: Context, measurement?: string) => B, opts?: {
9
+ trace: boolean;
10
+ name: string;
11
+ }) => Parser<B>;
12
+ /**
13
+ * A simple parser used to collapse a string array parser back into a string.
14
+ * Useful for smaller parsing tasks.
15
+ */
16
+ export declare const mapJoin: (parser: Parser<string[]>) => Parser<string>;
17
+ export declare const lazy: <A>(fn: () => Parser<A>) => Parser<A>;
18
+ export declare const peekAnd: <A, B>(peek: Parser<A>, and: Parser<B>) => Parser<B | null>;
19
+ /**
20
+ * @param peek A parser to probe the waters with
21
+ * @param continueWith A parser to continue parsing with, if the previous
22
+ * one succeeded. Note that this will take off from where the peek parser
23
+ * left off.
24
+ * @returns
25
+ */
26
+ export declare const ifPeek: <A, B>(peek: Parser<A>, continueWith: Parser<B>) => Parser<B | null>;
27
+ /**
28
+ * Map the result of a parser using a given function. Useful for building
29
+ * AST nodes or interpreting the parse results. The mapping function receives
30
+ * the result of the parser, as well as the state of the input before and after
31
+ * the parser being applied.
32
+ */
33
+ export declare const onFailure: <T>(parser: Parser<T>, onFail: (f: Failure) => Failure) => Parser<T>;
34
+ export declare const trim: <T>(p: Parser<T>) => Parser<T>;
35
+ /**
36
+ * Add context to a parser's error messages by pushing a frame onto the error stack.
37
+ * This creates TypeScript-style error traces showing where in the grammar the error occurred:
38
+ *
39
+ * ```
40
+ * expected '}' at line 5, column 3
41
+ * in function body at line 2, column 1
42
+ * in function declaration at line 2, column 1
43
+ * in program at line 1, column 1
44
+ * ```
45
+ *
46
+ * @param contextLabel - A human-readable description of the parsing context (e.g., "in function body")
47
+ * @param parser - The parser to wrap with context
48
+ * @returns A parser that adds the label to the error stack on failure
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * const fnDecl = context("in function declaration",
53
+ * seq(str("fn"), identifier, str("("), params, str(")"), block)
54
+ * );
55
+ * ```
56
+ */
57
+ export declare const context: <T>(contextLabel: string, parser: Parser<T>) => Parser<T>;
58
+ /**
59
+ * Mark a point of no return in parsing. After `cut`, if the inner parser fails,
60
+ * the failure becomes fatal and will not be caught by alternative parsers like `any` or `either`.
61
+ *
62
+ * This is useful after parsing enough to "commit" to a grammar branch. For example,
63
+ * after seeing "if", we're committed to parsing an if-expression and shouldn't backtrack.
64
+ *
65
+ * @param parser - The parser that, if it fails, should produce a fatal error
66
+ * @param expected - Optional custom error message for the fatal failure
67
+ * @returns A parser that produces fatal failures
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * // After seeing "if", we're committed - don't backtrack if "then" is missing
72
+ * const ifExpr = seq(
73
+ * str("if"),
74
+ * cut(expr, "condition after 'if'"),
75
+ * cut(str("then"), "'then' keyword"),
76
+ * cut(expr, "expression after 'then'"),
77
+ * cut(str("else"), "'else' keyword"),
78
+ * cut(expr, "expression after 'else'")
79
+ * );
80
+ * ```
81
+ */
82
+ export declare const cut: <T>(parser: Parser<T>, expected?: string) => Parser<T>;
83
+ /**
84
+ * Attempt a parser, but if it fails with a fatal error, convert it back to a
85
+ * non-fatal failure. This allows catching committed parse errors in specific contexts.
86
+ *
87
+ * @param parser - The parser whose fatal errors should be caught
88
+ * @returns A parser that converts fatal failures to non-fatal ones
89
+ */
90
+ export declare const attempt: <T>(parser: Parser<T>) => Parser<T>;
91
+ //# sourceMappingURL=utility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utility.d.ts","sourceRoot":"","sources":["../../src/src/utility.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,OAAO,EAIZ,KAAK,MAAM,EAGZ,MAAM,aAAa,CAAC;AAGrB;;;;;GAKG;AACH,eAAO,MAAM,GAAG,GAAI,CAAC,EAAE,CAAC,UACd,MAAM,CAAC,CAAC,CAAC,MACb,CAAC,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,CAAC,SACjE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,KACtC,MAAM,CAAC,CAAC,CAsBV,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,OAAO,WAAY,MAAM,CAAC,MAAM,EAAE,CAAC,KAAG,MAAM,CAAC,MAAM,CAE/D,CAAC;AAEF,eAAO,MAAM,IAAI,GAAI,CAAC,MAAM,MAAM,MAAM,CAAC,CAAC,CAAC,KAAG,MAAM,CAAC,CAAC,CAErD,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,CAAC,EAAE,CAAC,QACpB,MAAM,CAAC,CAAC,CAAC,OACV,MAAM,CAAC,CAAC,CAAC,KACb,MAAM,CAAC,CAAC,GAAG,IAAI,CASjB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,MAAM,GAAI,CAAC,EAAE,CAAC,QACnB,MAAM,CAAC,CAAC,CAAC,gBACD,MAAM,CAAC,CAAC,CAAC,KACtB,MAAM,CAAC,CAAC,GAAG,IAAI,CASjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,UACjB,MAAM,CAAC,CAAC,CAAC,UACT,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,KAC9B,MAAM,CAAC,CAAC,CAiBV,CAAC;AAEF,eAAO,MAAM,IAAI,GAAI,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,KAAG,MAAM,CAAC,CAAC,CAE9C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,gBACT,MAAM,UACZ,MAAM,CAAC,CAAC,CAAC,KAChB,MAAM,CAAC,CAAC,CAUV,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,GAAG,GAAI,CAAC,UAAU,MAAM,CAAC,CAAC,CAAC,aAAa,MAAM,KAAG,MAAM,CAAC,CAAC,CAmBrE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,UAAU,MAAM,CAAC,CAAC,CAAC,KAAG,MAAM,CAAC,CAAC,CActD,CAAC"}
@@ -0,0 +1,178 @@
1
+ import * as dntShim from "../_dnt.shims.js";
2
+ import { optional, seq } from "./combinators.js";
3
+ import { failure, fatalFailure, isFatal, pushFrame, success, } from "./Parser.js";
4
+ import { space } from "./parsers.js";
5
+ /**
6
+ * Map the result of a parser using a given function. Useful for building
7
+ * AST nodes or interpreting the parse results. The mapping function receives
8
+ * the result of the parser, as well as the state of the input before and after
9
+ * the parser being applied.
10
+ */
11
+ export const map = (parser, fn, opts) => {
12
+ return (ctx) => {
13
+ const now = () => {
14
+ // Avoid relying on DOM lib typings; works in Deno and Node.
15
+ const perf = dntShim.dntGlobalThis
16
+ .performance;
17
+ return typeof perf?.now === "function" ? perf.now() : Date.now();
18
+ };
19
+ let a = 0, b = 0;
20
+ opts?.trace && (a = now());
21
+ const res = parser(ctx);
22
+ opts?.trace && (b = now());
23
+ return res.success
24
+ ? success(res.ctx, fn(res.value, ctx, res.ctx, opts && (b - a).toFixed(5)))
25
+ : res;
26
+ };
27
+ };
28
+ /**
29
+ * A simple parser used to collapse a string array parser back into a string.
30
+ * Useful for smaller parsing tasks.
31
+ */
32
+ export const mapJoin = (parser) => {
33
+ return (ctx) => map(parser, (parts) => parts.join(""))(ctx);
34
+ };
35
+ export const lazy = (fn) => {
36
+ return (ctx) => fn()(ctx);
37
+ };
38
+ export const peekAnd = (peek, and) => {
39
+ return (ctx) => {
40
+ const res = peek(ctx);
41
+ if (res.success) {
42
+ return and(ctx);
43
+ }
44
+ return failure(ctx, `Peek unsuccesful: expected ${res.expected}`);
45
+ };
46
+ };
47
+ /**
48
+ * @param peek A parser to probe the waters with
49
+ * @param continueWith A parser to continue parsing with, if the previous
50
+ * one succeeded. Note that this will take off from where the peek parser
51
+ * left off.
52
+ * @returns
53
+ */
54
+ export const ifPeek = (peek, continueWith) => {
55
+ return (ctx) => {
56
+ const res = peek(ctx);
57
+ if (res.success) {
58
+ return continueWith(res.ctx);
59
+ }
60
+ return success(ctx, null);
61
+ };
62
+ };
63
+ /**
64
+ * Map the result of a parser using a given function. Useful for building
65
+ * AST nodes or interpreting the parse results. The mapping function receives
66
+ * the result of the parser, as well as the state of the input before and after
67
+ * the parser being applied.
68
+ */
69
+ export const onFailure = (parser, onFail) => {
70
+ return (ctx) => {
71
+ const res = parser(ctx);
72
+ if (res.success) {
73
+ return res;
74
+ }
75
+ const rewritten = onFail(res);
76
+ return {
77
+ ...rewritten,
78
+ variants: [
79
+ ...rewritten.variants,
80
+ ...res.variants,
81
+ failure(res.ctx, res.expected),
82
+ ],
83
+ };
84
+ };
85
+ };
86
+ export const trim = (p) => {
87
+ return map(seq(optional(space()), p, optional(space())), ([_, p]) => p);
88
+ };
89
+ /**
90
+ * Add context to a parser's error messages by pushing a frame onto the error stack.
91
+ * This creates TypeScript-style error traces showing where in the grammar the error occurred:
92
+ *
93
+ * ```
94
+ * expected '}' at line 5, column 3
95
+ * in function body at line 2, column 1
96
+ * in function declaration at line 2, column 1
97
+ * in program at line 1, column 1
98
+ * ```
99
+ *
100
+ * @param contextLabel - A human-readable description of the parsing context (e.g., "in function body")
101
+ * @param parser - The parser to wrap with context
102
+ * @returns A parser that adds the label to the error stack on failure
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * const fnDecl = context("in function declaration",
107
+ * seq(str("fn"), identifier, str("("), params, str(")"), block)
108
+ * );
109
+ * ```
110
+ */
111
+ export const context = (contextLabel, parser) => {
112
+ return (ctx) => {
113
+ const res = parser(ctx);
114
+ if (res.success) {
115
+ return res;
116
+ }
117
+ // Add context frame to the error stack
118
+ return pushFrame(res, contextLabel, ctx);
119
+ };
120
+ };
121
+ /**
122
+ * Mark a point of no return in parsing. After `cut`, if the inner parser fails,
123
+ * the failure becomes fatal and will not be caught by alternative parsers like `any` or `either`.
124
+ *
125
+ * This is useful after parsing enough to "commit" to a grammar branch. For example,
126
+ * after seeing "if", we're committed to parsing an if-expression and shouldn't backtrack.
127
+ *
128
+ * @param parser - The parser that, if it fails, should produce a fatal error
129
+ * @param expected - Optional custom error message for the fatal failure
130
+ * @returns A parser that produces fatal failures
131
+ *
132
+ * @example
133
+ * ```ts
134
+ * // After seeing "if", we're committed - don't backtrack if "then" is missing
135
+ * const ifExpr = seq(
136
+ * str("if"),
137
+ * cut(expr, "condition after 'if'"),
138
+ * cut(str("then"), "'then' keyword"),
139
+ * cut(expr, "expression after 'then'"),
140
+ * cut(str("else"), "'else' keyword"),
141
+ * cut(expr, "expression after 'else'")
142
+ * );
143
+ * ```
144
+ */
145
+ export const cut = (parser, expected) => {
146
+ return (ctx) => {
147
+ const res = parser(ctx);
148
+ if (res.success) {
149
+ return res;
150
+ }
151
+ // If it's already fatal, preserve it
152
+ if (isFatal(res)) {
153
+ return res;
154
+ }
155
+ // Make this failure fatal
156
+ return fatalFailure(res.ctx, expected ?? res.expected, res.stack);
157
+ };
158
+ };
159
+ /**
160
+ * Attempt a parser, but if it fails with a fatal error, convert it back to a
161
+ * non-fatal failure. This allows catching committed parse errors in specific contexts.
162
+ *
163
+ * @param parser - The parser whose fatal errors should be caught
164
+ * @returns A parser that converts fatal failures to non-fatal ones
165
+ */
166
+ export const attempt = (parser) => {
167
+ return (ctx) => {
168
+ const res = parser(ctx);
169
+ if (res.success) {
170
+ return res;
171
+ }
172
+ // Convert fatal back to non-fatal
173
+ if (res.fatal) {
174
+ return { ...res, fatal: false };
175
+ }
176
+ return res;
177
+ };
178
+ };
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@claudiu-ceia/combine",
3
+ "version": "0.2.4",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/ClaudiuCeia/combine.git"
7
+ },
8
+ "license": "MIT",
9
+ "bugs": {
10
+ "url": "https://github.com/ClaudiuCeia/combine/issues"
11
+ },
12
+ "main": "./script/mod.js",
13
+ "module": "./esm/mod.js",
14
+ "exports": {
15
+ ".": {
16
+ "import": "./esm/mod.js",
17
+ "require": "./script/mod.js"
18
+ }
19
+ },
20
+ "_generatedBy": "dnt@dev"
21
+ }
@@ -0,0 +1,2 @@
1
+ export declare const dntGlobalThis: Omit<typeof globalThis, never>;
2
+ //# sourceMappingURL=_dnt.shims.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dnt.shims.d.ts","sourceRoot":"","sources":["../src/_dnt.shims.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,gCAA2C,CAAC"}
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dntGlobalThis = void 0;
4
+ const dntGlobals = {};
5
+ exports.dntGlobalThis = createMergeProxy(globalThis, dntGlobals);
6
+ function createMergeProxy(baseObj, extObj) {
7
+ return new Proxy(baseObj, {
8
+ get(_target, prop, _receiver) {
9
+ if (prop in extObj) {
10
+ return extObj[prop];
11
+ }
12
+ else {
13
+ return baseObj[prop];
14
+ }
15
+ },
16
+ set(_target, prop, value) {
17
+ if (prop in extObj) {
18
+ delete extObj[prop];
19
+ }
20
+ baseObj[prop] = value;
21
+ return true;
22
+ },
23
+ deleteProperty(_target, prop) {
24
+ let success = false;
25
+ if (prop in extObj) {
26
+ delete extObj[prop];
27
+ success = true;
28
+ }
29
+ if (prop in baseObj) {
30
+ delete baseObj[prop];
31
+ success = true;
32
+ }
33
+ return success;
34
+ },
35
+ ownKeys(_target) {
36
+ const baseKeys = Reflect.ownKeys(baseObj);
37
+ const extKeys = Reflect.ownKeys(extObj);
38
+ const extKeysSet = new Set(extKeys);
39
+ return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys];
40
+ },
41
+ defineProperty(_target, prop, desc) {
42
+ if (prop in extObj) {
43
+ delete extObj[prop];
44
+ }
45
+ Reflect.defineProperty(baseObj, prop, desc);
46
+ return true;
47
+ },
48
+ getOwnPropertyDescriptor(_target, prop) {
49
+ if (prop in extObj) {
50
+ return Reflect.getOwnPropertyDescriptor(extObj, prop);
51
+ }
52
+ else {
53
+ return Reflect.getOwnPropertyDescriptor(baseObj, prop);
54
+ }
55
+ },
56
+ has(_target, prop) {
57
+ return prop in extObj || prop in baseObj;
58
+ },
59
+ });
60
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./src/Parser.js";
2
+ export * from "./src/combinators.js";
3
+ export * from "./src/parsers.js";
4
+ export * from "./src/utility.js";
5
+ export * from "./src/language.js";
6
+ //# sourceMappingURL=mod.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC"}
package/script/mod.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./src/Parser.js"), exports);
18
+ __exportStar(require("./src/combinators.js"), exports);
19
+ __exportStar(require("./src/parsers.js"), exports);
20
+ __exportStar(require("./src/utility.js"), exports);
21
+ __exportStar(require("./src/language.js"), exports);
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }