@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,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 @@
|
|
|
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
|
+
}
|
package/script/mod.d.ts
ADDED
|
@@ -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);
|