tarsec 0.0.10 → 0.0.12

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/README.md CHANGED
@@ -33,34 +33,15 @@ parser("hello world"); // success
33
33
  parser("hello there"); // failure
34
34
  ```
35
35
 
36
- ## A longer example
37
-
38
- ```ts
39
- // define a parser to parse "hello, <name>!"
40
- const parser = seq([
41
- str("hello"),
42
- space,
43
-
44
- /*
45
-
46
- Capture group to capture the name.
47
-
48
- `many1(noneOf("!"))` parses one or more characters
49
- that are not an exclamation mark.
50
- `many1` returns an array of characters,
51
- `many1WithJoin` joins them into a string.
52
-
53
- This capture group is then given the name "person".
54
-
55
- */
56
- capture(many1WithJoin(noneOf("!")), "person"),
57
- char("!"),
58
- ], getCaptures);
59
-
60
- // parse
61
- const parsed = parser("hello adit!");
62
-
63
- console.log(parsed.success); // true
64
- console.log(parsed.result); // { person: "adit" }
65
- ```
66
-
36
+ ## Learning tarsec
37
+ - [A five minute introduction](/tutorials/5-minute-intro.md)
38
+ - [The three building blocks in tarsec](/tutorials/three-building-blocks.md)
39
+ - [API reference](https://egonschiele.github.io/tarsec/)
40
+
41
+ ## Features
42
+ - tarsec is entirely TypeScript. There's nothing to compile.
43
+ - [Debug mode](/tutorials/debugging.md) that prints what's happening step-by-step
44
+ - Derived types: tarsec will generate TypeScript types for your parser
45
+ - Partial [backtracking](/tutorials/backtracking.md) support
46
+
47
+ Read more about [use cases for tarsec](/tutorials/use-case.md).
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ (function (factory) {
2
+ if (typeof module === "object" && typeof module.exports === "object") {
3
+ var v = factory(require, exports);
4
+ if (v !== undefined) module.exports = v;
5
+ }
6
+ else if (typeof define === "function" && define.amd) {
7
+ define(["require", "exports"], factory);
8
+ }
9
+ })(function (require, exports) {
10
+ "use strict";
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ });
@@ -1,17 +1,78 @@
1
- import { CaptureParser, GeneralParser, MergedCaptures, MergedResults, Parser, Prettify } from "./types";
1
+ import { CaptureParser, GeneralParser, MergedCaptures, MergedResults, Parser, PickParserType, Prettify } from "./types";
2
2
  export declare function many<T>(parser: Parser<T>): Parser<T[]>;
3
3
  export declare function many1<T>(parser: Parser<T>): Parser<T[]>;
4
- export declare function count<T>(parser: Parser<T>): Parser<number>;
4
+ /**
5
+ * Takes a parser, runs it n times, and returns the results as an array.
6
+ * If it cannot run the parser n times, it fails without consuming input.
7
+ * @param num - number of times to run the parser
8
+ * @param parser - parser to run
9
+ * @returns - parser that runs the given parser `num` times and returns an array of the results
10
+ */
11
+ export declare function count<T>(num: number, parser: Parser<T>): Parser<T[]>;
5
12
  export declare function manyWithJoin(parser: Parser<string>): Parser<string>;
6
13
  export declare function many1WithJoin(parser: Parser<string>): Parser<string>;
7
- export declare function or<const T extends readonly Parser<any>[]>(parsers: T, name?: string): Parser<MergedResults<T>>;
14
+ /**
15
+ * `or` takes an array of parsers and runs them sequentially.
16
+ * It returns the results of the first parser that succeeds.
17
+ * You can use `capture` in an `or`:
18
+ *
19
+ * ```ts
20
+ * const parser = or(capture(digit, "num"), capture(word, "name"));
21
+ * ```
22
+ *
23
+ * `or` supports backtracking by returning a `nextParser`:
24
+ *
25
+ * ```ts
26
+ * const parser = or(str("hello"), str("hello!"));
27
+ *
28
+ * // this will match the first parser
29
+ * const result = parser("hello");
30
+ *
31
+ * // but or returns the untried parsers as a new parser
32
+ * result.nextParser("hello!"); // works
33
+ *
34
+ * // result.nextParser is the same as or(str("hello!"))
35
+ * ```
36
+ *
37
+ * @param parsers - parsers to try
38
+ * @returns - a parser that tries each parser in order. Returns the result of the first parser that succeeds.
39
+ */
40
+ export declare function or<const T extends readonly GeneralParser<any, any>[]>(...parsers: T): PickParserType<T>;
8
41
  export declare function optional<T>(parser: Parser<T>): Parser<T | null>;
9
42
  export declare function not(parser: Parser<any>): Parser<null>;
10
43
  export declare function between<O, C, P>(open: Parser<O>, close: Parser<C>, parser: Parser<P>): Parser<P>;
11
44
  export declare function sepBy<S, P>(separator: Parser<S>, parser: Parser<P>): Parser<P[]>;
12
45
  export declare function getResults<R, C>(results: R, captures: C): R;
13
46
  export declare function getCaptures<R, C>(results: R, captures: C): C;
14
- export declare function seq<const T extends readonly GeneralParser<any, any>[], U>(parsers: T, transform: (results: MergedResults<T>[], captures: MergedCaptures<T>) => U, debugName?: string): Parser<U>;
15
47
  export declare function capture<T, const S extends string>(parser: Parser<T>, name: S): CaptureParser<T, Record<S, T>>;
16
48
  export declare function wrap<T, const S extends string>(parser: Parser<T>, name: S): Parser<Prettify<Record<S, T>>>;
49
+ export declare function manyTill<T>(parser: Parser<T>): Parser<string>;
17
50
  export declare function transform<T, X>(parser: Parser<T>, transformerFunc: (x: T) => X): Parser<X>;
51
+ export declare function search(parser: Parser<string>): Parser<string[]>;
52
+ /**
53
+ * seq takes an array of parsers and runs them sequentially.
54
+ * If any of the parsers fail, seq fails without consuming any input.
55
+ *
56
+ * The second argument to seq is a function.
57
+ * The first argument of that function is an array of results:
58
+ * one result from each of the parsers you gave to seq.
59
+ * The second is an object containing any captures.
60
+ * You can use this second argument, the transformer function,
61
+ * to transform these however you want and return a result
62
+ *
63
+ * Tarsec includes the utility functions `getResults` and `getCaptures`
64
+ * to just return the results array or captures object respectively for you.
65
+ *
66
+ * Finally, you don't need to use seq at all. You can just hand write the logic.
67
+ * But you'll need to do the error handling
68
+ * and pass the remaining input to the next parser yourself.
69
+ * seq also does some backtracking for you that you will need to do yourself.
70
+ *
71
+ * @param parsers - parsers to run sequentially
72
+ * @param transform - function to transform the results and captures. The params are the results and captures
73
+ * @param debugName - optional name for trace debugging
74
+ * @returns
75
+ */
76
+ export declare function seq<const T extends readonly GeneralParser<any, any>[], U>(parsers: T, transform: (results: MergedResults<T>[], captures: MergedCaptures<T>) => U, debugName?: string): Parser<U>;
77
+ export declare function seqR<const T extends readonly GeneralParser<any, any>[]>(...parsers: T): Parser<MergedResults<T>[]>;
78
+ export declare function seqC<const T extends readonly GeneralParser<any, any>[]>(...parsers: T): Parser<MergedCaptures<T>>;
@@ -4,12 +4,13 @@
4
4
  if (v !== undefined) module.exports = v;
5
5
  }
6
6
  else if (typeof define === "function" && define.amd) {
7
- define(["require", "exports", "./trace", "./types", "./utils"], factory);
7
+ define(["require", "exports", "./parsers/within", "./trace", "./types", "./utils"], factory);
8
8
  }
9
9
  })(function (require, exports) {
10
10
  "use strict";
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.transform = exports.wrap = exports.capture = exports.seq = exports.getCaptures = exports.getResults = exports.sepBy = exports.between = exports.not = exports.optional = exports.or = exports.many1WithJoin = exports.manyWithJoin = exports.count = exports.many1 = exports.many = void 0;
12
+ exports.seqC = exports.seqR = exports.seq = exports.search = exports.transform = exports.manyTill = exports.wrap = exports.capture = exports.getCaptures = exports.getResults = exports.sepBy = exports.between = exports.not = exports.optional = exports.or = exports.many1WithJoin = exports.manyWithJoin = exports.count = exports.many1 = exports.many = void 0;
13
+ const within_1 = require("./parsers/within");
13
14
  const trace_1 = require("./trace");
14
15
  const types_1 = require("./types");
15
16
  const utils_1 = require("./utils");
@@ -43,13 +44,26 @@
43
44
  });
44
45
  }
45
46
  exports.many1 = many1;
46
- function count(parser) {
47
+ /**
48
+ * Takes a parser, runs it n times, and returns the results as an array.
49
+ * If it cannot run the parser n times, it fails without consuming input.
50
+ * @param num - number of times to run the parser
51
+ * @param parser - parser to run
52
+ * @returns - parser that runs the given parser `num` times and returns an array of the results
53
+ */
54
+ function count(num, parser) {
47
55
  return (0, trace_1.trace)("count", (input) => {
48
- const result = many(parser)(input);
49
- if (result.success) {
50
- return (0, types_1.success)(result.result.length, result.rest);
56
+ let results = [];
57
+ let rest = input;
58
+ for (let i = 0; i < num; i++) {
59
+ let parsed = parser(rest);
60
+ if (!parsed.success) {
61
+ return (0, types_1.failure)(`expected ${num} matches, got ${i}`, input);
62
+ }
63
+ results.push(parsed.result);
64
+ rest = parsed.rest;
51
65
  }
52
- return result;
66
+ return (0, types_1.success)(results, rest);
53
67
  });
54
68
  }
55
69
  exports.count = count;
@@ -61,16 +75,42 @@
61
75
  return transform(many1(parser), (x) => x.join(""));
62
76
  }
63
77
  exports.many1WithJoin = many1WithJoin;
64
- /* seq<, U>(
65
- parsers: T,
66
- transform: (results: MergedResults<T>[], captures: MergedCaptures<T>) => U,
78
+ /**
79
+ * `or` takes an array of parsers and runs them sequentially.
80
+ * It returns the results of the first parser that succeeds.
81
+ * You can use `capture` in an `or`:
82
+ *
83
+ * ```ts
84
+ * const parser = or(capture(digit, "num"), capture(word, "name"));
85
+ * ```
86
+ *
87
+ * `or` supports backtracking by returning a `nextParser`:
88
+ *
89
+ * ```ts
90
+ * const parser = or(str("hello"), str("hello!"));
91
+ *
92
+ * // this will match the first parser
93
+ * const result = parser("hello");
94
+ *
95
+ * // but or returns the untried parsers as a new parser
96
+ * result.nextParser("hello!"); // works
97
+ *
98
+ * // result.nextParser is the same as or(str("hello!"))
99
+ * ```
100
+ *
101
+ * @param parsers - parsers to try
102
+ * @returns - a parser that tries each parser in order. Returns the result of the first parser that succeeds.
67
103
  */
68
- function or(parsers, name = "") {
69
- return (0, trace_1.trace)(`or(${name})`, (input) => {
70
- for (let parser of parsers) {
71
- let result = parser(input);
104
+ function or(...parsers) {
105
+ return (0, trace_1.trace)(`or()`, (input) => {
106
+ for (let i = 0; i < parsers.length; i++) {
107
+ let result = parsers[i](input);
72
108
  if (result.success) {
73
- return result;
109
+ if (i === parsers.length - 1)
110
+ return result;
111
+ const nextParser = or(...parsers.slice(i + 1));
112
+ /* console.log({ nextParser }, parsers.slice(i + 1)); */
113
+ return Object.assign(Object.assign({}, result), { nextParser });
74
114
  }
75
115
  }
76
116
  return (0, types_1.failure)(`all parsers failed`, input);
@@ -147,29 +187,6 @@
147
187
  return captures;
148
188
  }
149
189
  exports.getCaptures = getCaptures;
150
- function seq(parsers, transform, debugName = "") {
151
- return (0, trace_1.trace)(`seq(${debugName})`, (input) => {
152
- const results = [];
153
- let rest = input;
154
- const captures = {};
155
- for (let parser of parsers) {
156
- let parsed = parser(rest);
157
- if (!parsed.success) {
158
- return parsed;
159
- }
160
- results.push(parsed.result);
161
- rest = parsed.rest;
162
- if ((0, types_1.isCaptureResult)(parsed)) {
163
- for (const key in parsed.captures) {
164
- captures[key] = parsed.captures[key];
165
- }
166
- }
167
- }
168
- const result = transform(results, captures);
169
- return (0, types_1.success)(result, rest);
170
- });
171
- }
172
- exports.seq = seq;
173
190
  function capture(parser, name) {
174
191
  return (0, trace_1.trace)(`capture(${(0, utils_1.escape)(name)})`, (input) => {
175
192
  let result = parser(input);
@@ -195,70 +212,20 @@
195
212
  });
196
213
  }
197
214
  exports.wrap = wrap;
198
- /*
199
- export function setCapturesAsMatch<M, C extends PlainObject>(
200
- parser: Parser<M, C>
201
- ): Parser<C> {
202
- return trace(`setCapturesAsMatch`, (input: string) => {
203
- let result = parser(input);
204
- if (result.success) {
205
- return {
206
- ...result,
207
- match: result.captures as any,
208
- captures: {},
209
- };
210
- }
211
- return result;
212
- });
213
- }
214
-
215
- export function captureCaptures<
216
- M,
217
- C extends PlainObject,
218
- const S extends string
219
- >(parser: Parser<M, C>, name: S): Parser<C, Record<S, C>> {
220
- return trace(`captureCaptures(${escape(name)})`, (input: string) => {
221
- return capture(setCapturesAsMatch(parser), name)(input);
222
- });
223
- } */
224
- /* export function captureCaptures<M, C extends string>(
225
- parser: Parser<M>,
226
- name: string
227
- ): Parser<M, C> {
228
- return trace(`captures(${escape(name)})`, (input: string) => {
229
- let result = parser(input);
230
- if (result.success) {
231
- const captures: Record<string, any> = {
232
- [name]: result.captures,
233
- };
234
- return {
235
- ...result,
236
- captures: mergeCaptures(result.captures || {}, captures),
237
- };
238
- }
239
- return result;
240
- });
215
+ function manyTill(parser) {
216
+ return (input) => {
217
+ let current = 0;
218
+ while (current < input.length) {
219
+ const parsed = parser(input.slice(current));
220
+ if (parsed.success) {
221
+ return (0, types_1.success)(input.slice(0, current), input.slice(current));
222
+ }
223
+ current++;
224
+ }
225
+ return (0, types_1.success)(input, "");
226
+ };
241
227
  }
242
-
243
- */
244
- /* export function shapeCaptures<M, C extends string>(
245
- parser: Parser<M>,
246
- func: (captures: Record<string, any>) => Record<string, any>,
247
- name: string
248
- ): Parser<M, C> {
249
- return trace(`captures(${escape(name)})`, (input: string) => {
250
- let result = parser(input);
251
- if (result.success) {
252
- const captures: Record<string, any> = result.captures || {};
253
-
254
- return {
255
- ...result,
256
- captures: func(captures),
257
- };
258
- }
259
- return result;
260
- });
261
- } */
228
+ exports.manyTill = manyTill;
262
229
  function transform(parser, transformerFunc) {
263
230
  return (0, trace_1.trace)(`transform(${transformerFunc})`, (input) => {
264
231
  let parsed = parser(input);
@@ -269,4 +236,167 @@
269
236
  });
270
237
  }
271
238
  exports.transform = transform;
239
+ function search(parser) {
240
+ return (0, trace_1.trace)("search", (input) => {
241
+ let parsed = (0, within_1.within)(parser)(input);
242
+ if (parsed.success) {
243
+ const result = parsed.result
244
+ .filter((x) => x.type === "matched")
245
+ .map((x) => x.value);
246
+ const rest = parsed.result
247
+ .filter((x) => x.type === "unmatched")
248
+ .map((x) => x.value)
249
+ .join(" ");
250
+ return (0, types_1.success)(result, rest);
251
+ }
252
+ return (0, types_1.success)("", input);
253
+ });
254
+ }
255
+ exports.search = search;
256
+ /*
257
+ To add backtracking support requires a fairly big change. Here's an example that needs backtracking.
258
+
259
+ ```ts
260
+ const parser = seq([
261
+ str("hello "),
262
+ or(str("world"), str("world!")),
263
+ optional("?")
264
+ ], getResults);
265
+ ```
266
+
267
+ If we try to parse `"hello world!"`, the first parser in the OR will succeed, but then we'll get stuck at the `optional`. Instead, we need to go back up the tree and try the second parser in the OR. A few things need to happen.
268
+
269
+ 1. instead of just processing these parsers sequentially in a for loop, we need to model them as a tree
270
+ 2. the OR parser needs to let us know that there are other branches to try.
271
+
272
+ For #2, there's an optional `nextParser` key on a parser success. The or parser can use this to say "a parser succeeded and here's the result, but there are other parsers that could be tried". `nextParser` is a parser that runs the remaining branches. So in this example, the OR would return a success with `nextParser = or(str("world"))`.
273
+
274
+ Next, we need to model this as a tree. Each node in the tree has a parent and child and the parser for that node.
275
+
276
+ ```ts
277
+ parent: Node;
278
+ parser: GeneralParser<any, any> | null;
279
+ child: Node;
280
+ ```
281
+
282
+ Hopefully that is self-explanatory. We start at the root of the tree, try the parser there, then use `.child` to go to the next node and so on. We don't model multiple paths as multiple children. To keep the code simple, we do something else.
283
+
284
+ Each node also has a `closed` key. Once we've run the parser for a node, we mark it `closed`. Closed means there are no more branches here. UNLESS, the parser returns a `nextParser`. In that case, we *don't* mark it closed because there are still other options to try. In that case, we also *replace* the parser on that node with nextParser.
285
+
286
+ So, going back to the hello world example, let's say we're stuck at the `optional`:
287
+
288
+ ```ts
289
+ const parser = seq([
290
+ str("hello "),
291
+ or(str("world"), str("world!")),
292
+ optional("?")
293
+ ], getResults);
294
+ ```
295
+
296
+ We use `.parent` to go back up the tree. We're looking for a node that isn't closed. If we find one, we start again from there. In this case, we'd find an open node at the or with parser `or(str("world"))`. We can restart from there, but there's a bunch of state to reset.
297
+
298
+ 1. From the new `or` parser, we need to go to the optional parser. We're doing it all again in the same order. This is one reason why it's easier to model this without multiple children. Otherwise, all the children would have to point to the next level, the next level would have to point to all the children in the previous level, and you'd have multiple parents, which is awful to deal with.
299
+
300
+ 2. We have consumed input and added to the results. We need to undo that. At this point, the input is `!`, because we've consumed `hello world`. And the results array is `["hello ", "world"]`. We need to rewind both of those.
301
+
302
+ To do that, I count how many levels up we've gone to find another branch, and just pop that many elements off the results array. So results is now `["hello "]`. The input is trickier. How would I keep track of what the input was when we were at the OR the last time?
303
+
304
+ This is where the final key on a tree node comes in. Nodes also have an optional `input` key.
305
+
306
+ IF a parser succeeds, and
307
+ IF there's a nextParser,
308
+ We know we may come back to this node. So we save the current input as `.input` on the node.
309
+
310
+ This approach has some issues. Notably, it doesn't work if you need to backtrack at multiple points in the tree. The test `backtracking-deep.test.ts` shows this.
311
+
312
+ The code is also complex and it would be easy to have bugs in this logic. I wish there was a cleaner solution for rewinding state.
313
+ */
314
+ /**
315
+ * seq takes an array of parsers and runs them sequentially.
316
+ * If any of the parsers fail, seq fails without consuming any input.
317
+ *
318
+ * The second argument to seq is a function.
319
+ * The first argument of that function is an array of results:
320
+ * one result from each of the parsers you gave to seq.
321
+ * The second is an object containing any captures.
322
+ * You can use this second argument, the transformer function,
323
+ * to transform these however you want and return a result
324
+ *
325
+ * Tarsec includes the utility functions `getResults` and `getCaptures`
326
+ * to just return the results array or captures object respectively for you.
327
+ *
328
+ * Finally, you don't need to use seq at all. You can just hand write the logic.
329
+ * But you'll need to do the error handling
330
+ * and pass the remaining input to the next parser yourself.
331
+ * seq also does some backtracking for you that you will need to do yourself.
332
+ *
333
+ * @param parsers - parsers to run sequentially
334
+ * @param transform - function to transform the results and captures. The params are the results and captures
335
+ * @param debugName - optional name for trace debugging
336
+ * @returns
337
+ */
338
+ function seq(parsers, transform, debugName = "") {
339
+ return (0, trace_1.trace)(`seq(${debugName})`, (input) => {
340
+ const results = [];
341
+ let rest = input;
342
+ const captures = {};
343
+ const rootNode = (0, types_1.createTree)(parsers);
344
+ let current = rootNode;
345
+ while (current) {
346
+ const parser = current.parser;
347
+ if (!parser) {
348
+ console.log({ current, parser, results, captures });
349
+ throw new Error("parser is null");
350
+ }
351
+ const parsed = parser(rest);
352
+ current.closed = true;
353
+ /* console.log({ parsed }); */
354
+ if (!parsed.success) {
355
+ const [ancestor, count] = (0, utils_1.findAncestorWithNextParser)(current);
356
+ if (ancestor) {
357
+ current = ancestor;
358
+ rest = ancestor.input;
359
+ (0, utils_1.popMany)(results, count);
360
+ continue;
361
+ }
362
+ else {
363
+ // don't consume input if we're failing
364
+ return Object.assign(Object.assign({}, parsed), { rest: input });
365
+ }
366
+ }
367
+ results.push(parsed.result);
368
+ if (parsed.nextParser) {
369
+ /* console.log("setting next parser", parsed.nextParser); */
370
+ current.parser = parsed.nextParser;
371
+ current.input = rest;
372
+ current.closed = false;
373
+ }
374
+ rest = parsed.rest;
375
+ if ((0, types_1.isCaptureResult)(parsed)) {
376
+ for (const key in parsed.captures) {
377
+ captures[key] = parsed.captures[key];
378
+ }
379
+ }
380
+ current = current.child;
381
+ }
382
+ const result = transform(results, captures);
383
+ return (0, types_1.success)(result, rest);
384
+ });
385
+ }
386
+ exports.seq = seq;
387
+ function seqR(...parsers) {
388
+ return seq(parsers, getResults);
389
+ }
390
+ exports.seqR = seqR;
391
+ function seqC(...parsers) {
392
+ return seq(parsers, getCaptures);
393
+ }
394
+ exports.seqC = seqC;
272
395
  });
396
+ /*
397
+ export function seqX<const T extends readonly GeneralParser<any, any>[], U>(
398
+ parsers: T,
399
+ transform: (results: MergedResults<T>[], captures: MergedCaptures<T>) => U,
400
+ debugName: string = ""
401
+ ): Parser<U> {
402
+ */
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./parsers";
2
2
  export * from "./combinators";
3
3
  export * from "./trace";
4
+ export * from "./types";
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
18
18
  if (v !== undefined) module.exports = v;
19
19
  }
20
20
  else if (typeof define === "function" && define.amd) {
21
- define(["require", "exports", "./parsers", "./combinators", "./trace"], factory);
21
+ define(["require", "exports", "./parsers", "./combinators", "./trace", "./types"], factory);
22
22
  }
23
23
  })(function (require, exports) {
24
24
  "use strict";
@@ -26,4 +26,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
26
26
  __exportStar(require("./parsers"), exports);
27
27
  __exportStar(require("./combinators"), exports);
28
28
  __exportStar(require("./trace"), exports);
29
+ __exportStar(require("./types"), exports);
29
30
  });
@@ -0,0 +1,2 @@
1
+ import { BetweenWithinResult, Parser } from "../types";
2
+ export declare function within(parser: Parser<string>): Parser<BetweenWithinResult[]>;
@@ -0,0 +1,51 @@
1
+ (function (factory) {
2
+ if (typeof module === "object" && typeof module.exports === "object") {
3
+ var v = factory(require, exports);
4
+ if (v !== undefined) module.exports = v;
5
+ }
6
+ else if (typeof define === "function" && define.amd) {
7
+ define(["require", "exports", "../trace", "../types"], factory);
8
+ }
9
+ })(function (require, exports) {
10
+ "use strict";
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.within = void 0;
13
+ const trace_1 = require("../trace");
14
+ const types_1 = require("../types");
15
+ function within(parser) {
16
+ return (0, trace_1.trace)("within", (input) => {
17
+ let start = 0;
18
+ let current = 0;
19
+ const results = [];
20
+ while (current < input.length) {
21
+ const parsed = parser(input.slice(current));
22
+ if (parsed.success) {
23
+ const unmatchedValue = input.slice(start, current);
24
+ if (unmatchedValue.length > 0) {
25
+ results.push({
26
+ type: "unmatched",
27
+ value: unmatchedValue,
28
+ });
29
+ }
30
+ results.push({
31
+ type: "matched",
32
+ value: parsed.result,
33
+ });
34
+ current += parsed.result.length;
35
+ start = current;
36
+ }
37
+ else {
38
+ current += 1;
39
+ }
40
+ }
41
+ if (start < current) {
42
+ results.push({
43
+ type: "unmatched",
44
+ value: input.slice(start, current),
45
+ });
46
+ }
47
+ return (0, types_1.success)(results, "");
48
+ });
49
+ }
50
+ exports.within = within;
51
+ });
package/dist/parsers.d.ts CHANGED
@@ -1,9 +1,43 @@
1
1
  import { Parser } from "./types";
2
- export declare function char(c: string): Parser<string>;
3
- export declare function str(s: string): Parser<string>;
2
+ export { within as betweenWithin } from "./parsers/within";
3
+ /**
4
+ * Takes a character. Returns a parser that parses that character.
5
+ *
6
+ * @param c - character to parse
7
+ * @returns - parser that parses the given character
8
+ */
9
+ export declare function char<const S extends string>(c: S): Parser<S>;
10
+ /**
11
+ * Takes a string. Returns a parser that parses that string.
12
+ *
13
+ * @param s - string to parse
14
+ * @returns - parser that parses the given string
15
+ */
16
+ export declare function str<const S extends string>(s: S): Parser<S>;
17
+ /**
18
+ * Takes a string. Returns a parser that parses
19
+ * one of the characters in that string.
20
+ *
21
+ * @param chars - string of possible characters
22
+ * @returns - parser that parses one of the given characters
23
+ */
4
24
  export declare function oneOf(chars: string): Parser<string>;
25
+ /**
26
+ * Takes a string. Returns a parser that parses one character
27
+ * that's not any of the characters in the given string
28
+ *
29
+ * @param chars - string of characters to avoid
30
+ * @returns - parser that parses a character that is not in the given string
31
+ */
5
32
  export declare function noneOf(chars: string): Parser<string>;
6
- export declare function anyChar(input: string): Parser<string>;
33
+ /**
34
+ * A parser that parses any one character.
35
+ * Fails on empty strings, succeeds otherwise.
36
+ *
37
+ * @param input - input string
38
+ * @returns - ParserResult
39
+ */
40
+ export declare const anyChar: any;
7
41
  export declare const space: Parser<string>;
8
42
  export declare const spaces: Parser<string>;
9
43
  export declare const digit: Parser<string>;
@@ -14,4 +48,5 @@ export declare const num: Parser<string>;
14
48
  export declare const quote: Parser<string>;
15
49
  export declare const tab: Parser<string>;
16
50
  export declare const newline: Parser<string>;
51
+ export declare const eof: Parser<null>;
17
52
  export declare const quotedString: Parser<string>;
package/dist/parsers.js CHANGED
@@ -4,16 +4,24 @@
4
4
  if (v !== undefined) module.exports = v;
5
5
  }
6
6
  else if (typeof define === "function" && define.amd) {
7
- define(["require", "exports", "./combinators", "./trace", "./types", "./utils"], factory);
7
+ define(["require", "exports", "./combinators", "./trace", "./types", "./utils", "./parsers/within"], factory);
8
8
  }
9
9
  })(function (require, exports) {
10
10
  "use strict";
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.quotedString = exports.newline = exports.tab = exports.quote = exports.num = exports.word = exports.alphanum = exports.letter = exports.digit = exports.spaces = exports.space = exports.anyChar = exports.noneOf = exports.oneOf = exports.str = exports.char = void 0;
12
+ exports.quotedString = exports.eof = exports.newline = exports.tab = exports.quote = exports.num = exports.word = exports.alphanum = exports.letter = exports.digit = exports.spaces = exports.space = exports.anyChar = exports.noneOf = exports.oneOf = exports.str = exports.char = exports.betweenWithin = void 0;
13
13
  const combinators_1 = require("./combinators");
14
14
  const trace_1 = require("./trace");
15
15
  const types_1 = require("./types");
16
16
  const utils_1 = require("./utils");
17
+ var within_1 = require("./parsers/within");
18
+ Object.defineProperty(exports, "betweenWithin", { enumerable: true, get: function () { return within_1.within; } });
19
+ /**
20
+ * Takes a character. Returns a parser that parses that character.
21
+ *
22
+ * @param c - character to parse
23
+ * @returns - parser that parses the given character
24
+ */
17
25
  function char(c) {
18
26
  return (0, trace_1.trace)(`char(${(0, utils_1.escape)(c)})`, (input) => {
19
27
  if (input.length === 0) {
@@ -26,10 +34,16 @@
26
34
  if (input[0] === c) {
27
35
  return (0, types_1.success)(c, input.slice(1));
28
36
  }
29
- return (0, types_1.failure)(`expected ${c}, got ${input[0]}`, input);
37
+ return (0, types_1.failure)(`expected ${(0, utils_1.escape)(c)}, got ${(0, utils_1.escape)(input[0])}`, input);
30
38
  });
31
39
  }
32
40
  exports.char = char;
41
+ /**
42
+ * Takes a string. Returns a parser that parses that string.
43
+ *
44
+ * @param s - string to parse
45
+ * @returns - parser that parses the given string
46
+ */
33
47
  function str(s) {
34
48
  return (0, trace_1.trace)(`str(${(0, utils_1.escape)(s)})`, (input) => {
35
49
  if (input.substring(0, s.length) === s) {
@@ -39,6 +53,13 @@
39
53
  });
40
54
  }
41
55
  exports.str = str;
56
+ /**
57
+ * Takes a string. Returns a parser that parses
58
+ * one of the characters in that string.
59
+ *
60
+ * @param chars - string of possible characters
61
+ * @returns - parser that parses one of the given characters
62
+ */
42
63
  function oneOf(chars) {
43
64
  return (0, trace_1.trace)(`oneOf(${(0, utils_1.escape)(chars)})`, (input) => {
44
65
  if (input.length === 0) {
@@ -52,6 +73,13 @@
52
73
  });
53
74
  }
54
75
  exports.oneOf = oneOf;
76
+ /**
77
+ * Takes a string. Returns a parser that parses one character
78
+ * that's not any of the characters in the given string
79
+ *
80
+ * @param chars - string of characters to avoid
81
+ * @returns - parser that parses a character that is not in the given string
82
+ */
55
83
  function noneOf(chars) {
56
84
  return (0, trace_1.trace)(`noneOf(${(0, utils_1.escape)(chars)})`, (input) => {
57
85
  if (input.length === 0) {
@@ -64,15 +92,19 @@
64
92
  });
65
93
  }
66
94
  exports.noneOf = noneOf;
67
- function anyChar(input) {
68
- return (0, trace_1.trace)("anyChar", (input) => {
69
- if (input.length === 0) {
70
- return (0, types_1.failure)("unexpected end of input", input);
71
- }
72
- return { success: true, match: input[0], rest: input.slice(1) };
73
- });
74
- }
75
- exports.anyChar = anyChar;
95
+ /**
96
+ * A parser that parses any one character.
97
+ * Fails on empty strings, succeeds otherwise.
98
+ *
99
+ * @param input - input string
100
+ * @returns - ParserResult
101
+ */
102
+ exports.anyChar = (0, trace_1.trace)("anyChar", (input) => {
103
+ if (input.length === 0) {
104
+ return (0, types_1.failure)("unexpected end of input", input);
105
+ }
106
+ return (0, types_1.success)(input[0], input.slice(1));
107
+ });
76
108
  exports.space = oneOf(" \t\n\r");
77
109
  exports.spaces = (0, combinators_1.many1WithJoin)(exports.space);
78
110
  exports.digit = oneOf("0123456789");
@@ -83,5 +115,12 @@
83
115
  exports.quote = oneOf(`'"`);
84
116
  exports.tab = char("\t");
85
117
  exports.newline = char("\n");
86
- exports.quotedString = (0, combinators_1.transform)((0, combinators_1.seq)([exports.quote, exports.word, exports.quote], combinators_1.getResults), (x) => x.join(""));
118
+ const eof = (input) => {
119
+ if (input === "") {
120
+ return (0, types_1.success)(null, input);
121
+ }
122
+ return (0, types_1.failure)("expected end of input", input);
123
+ };
124
+ exports.eof = eof;
125
+ exports.quotedString = (0, combinators_1.seq)([exports.quote, (0, combinators_1.manyWithJoin)(noneOf(`"'`)), exports.quote], (results) => results.join(""));
87
126
  });
package/dist/types.d.ts CHANGED
@@ -3,6 +3,7 @@ export type ParserSuccess<T> = {
3
3
  success: true;
4
4
  result: T;
5
5
  rest: string;
6
+ nextParser?: GeneralParser<any, any>;
6
7
  };
7
8
  export type CaptureParserSuccess<T, C extends PlainObject> = ParserSuccess<T> & {
8
9
  captures: C;
@@ -28,6 +29,36 @@ export type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) ex
28
29
  type ExtractResults<T> = T extends Parser<infer U> ? U : never;
29
30
  type ExtractCaptures<T> = T extends CaptureParser<any, infer U> ? U : never;
30
31
  type ExtractCaptureParsers<T extends readonly GeneralParser<any, any>[]> = Extract<T[number], CaptureParser<any, any>>;
31
- export type MergedCaptures<T extends readonly GeneralParser<any, any>[]> = Prettify<UnionToIntersection<ExtractCaptures<ExtractCaptureParsers<T>>>>;
32
+ export type MergedCaptures<T extends readonly GeneralParser<any, any>[]> = Prettify<UnionToIntersection<UnionOfCaptures<T>>>;
33
+ export type UnionOfCaptures<T extends readonly GeneralParser<any, any>[]> = Prettify<ExtractCaptures<ExtractCaptureParsers<T>>>;
34
+ export type HasCaptureParsers<T extends readonly GeneralParser<any, any>[]> = ExtractCaptureParsers<T> extends never ? false : true;
35
+ /**
36
+ * For a given array of GeneralParsers, if any of them is a CaptureParser,
37
+ * PickParserType says the array is an array of CaptureParsers,
38
+ * otherwise it's an array of Parsers. It also correctly merges
39
+ * the result and capture types. This is useful for a combinator like `or`
40
+ * which is not able to infer its return type correctly.
41
+ */
42
+ export type PickParserType<T extends readonly GeneralParser<any, any>[]> = HasCaptureParsers<T> extends true ? CaptureParser<MergedResults<T>, UnionOfCaptures<T>> : Parser<MergedResults<T>>;
32
43
  export type MergedResults<T extends readonly GeneralParser<any, any>[]> = ExtractResults<T[number]>;
44
+ export type Node = ParserNode | EmptyNode;
45
+ export type ParserNode = {
46
+ parent: Node;
47
+ parser: GeneralParser<any, any> | null;
48
+ input?: string;
49
+ child: Node;
50
+ closed: boolean;
51
+ };
52
+ export type EmptyNode = null;
53
+ export declare function createNode(parent: Node | null, parser: GeneralParser<any, any>): ParserNode;
54
+ export declare function createTree(parsers: readonly GeneralParser<any, any>[]): Node;
55
+ export type Matched = {
56
+ type: "matched";
57
+ value: string;
58
+ };
59
+ export type Unmatched = {
60
+ type: "unmatched";
61
+ value: string;
62
+ };
63
+ export type BetweenWithinResult = Matched | Unmatched;
33
64
  export {};
package/dist/types.js CHANGED
@@ -9,7 +9,7 @@
9
9
  })(function (require, exports) {
10
10
  "use strict";
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.failure = exports.captureSuccess = exports.success = exports.isCaptureResult = void 0;
12
+ exports.createTree = exports.createNode = exports.failure = exports.captureSuccess = exports.success = exports.isCaptureResult = void 0;
13
13
  function isCaptureResult(result) {
14
14
  return "captures" in result;
15
15
  }
@@ -26,24 +26,26 @@
26
26
  return { success: false, message, rest };
27
27
  }
28
28
  exports.failure = failure;
29
+ function createNode(parent, parser) {
30
+ return {
31
+ parent,
32
+ parser,
33
+ child: null,
34
+ closed: false,
35
+ };
36
+ }
37
+ exports.createNode = createNode;
38
+ function createTree(parsers) {
39
+ if (parsers.length === 0) {
40
+ return null;
41
+ }
42
+ const rootNode = createNode(null, parsers[0]);
43
+ let currentNode = rootNode;
44
+ for (let i = 1; i < parsers.length; i++) {
45
+ currentNode.child = createNode(currentNode, parsers[i]);
46
+ currentNode = currentNode.child;
47
+ }
48
+ return rootNode;
49
+ }
50
+ exports.createTree = createTree;
29
51
  });
30
- /* export type Merge2<O extends Array<T>, T = any> = Prettify<
31
- UnionToIntersection<O[number]>
32
- >;
33
-
34
- export type NonNullObject<T> = {
35
- [K in keyof T]: T[K] extends null | undefined ? never : T[K];
36
- }; */
37
- /* export type NonNullableUnionOfObjects<T> = T extends object
38
- ? RemoveNeverKeys<DeepNonNullable<T>>
39
- : T;
40
-
41
- export type DeepNonNullable<T> = {
42
- [P in keyof T]-?: NonNullable<T[P]>;
43
- }; */
44
- /* export type FilterNeverKeys<T> = {
45
- [K in keyof T]: T[K] extends never ? never : K;
46
- };
47
- */
48
- /* type ValueOf<T> = T[keyof T]; */
49
- /* type RemoveNeverKeys<T> = Pick<T, ValueOf<FilterNeverKeys<T>>>; */
package/dist/utils.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { Node } from "./types";
1
2
  export declare function escape(str: any): string;
2
3
  export declare function merge(a: any | any[], b: any | any[]): any[];
3
4
  export declare function mergeCaptures(a: Record<string, any>, b: Record<string, any>): Record<string, any>;
5
+ export declare function findAncestorWithNextParser(node: Node, count?: number): [Node, number];
6
+ export declare function popMany(arr: any[], count: number): void;
package/dist/utils.js CHANGED
@@ -9,7 +9,7 @@
9
9
  })(function (require, exports) {
10
10
  "use strict";
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.mergeCaptures = exports.merge = exports.escape = void 0;
12
+ exports.popMany = exports.findAncestorWithNextParser = exports.mergeCaptures = exports.merge = exports.escape = void 0;
13
13
  function escape(str) {
14
14
  return JSON.stringify(str);
15
15
  }
@@ -45,4 +45,22 @@
45
45
  return result;
46
46
  }
47
47
  exports.mergeCaptures = mergeCaptures;
48
+ function findAncestorWithNextParser(node, count = 0) {
49
+ if (node === null)
50
+ return [null, count];
51
+ if (!node.closed) {
52
+ return [node, count];
53
+ }
54
+ if (node.parent) {
55
+ return findAncestorWithNextParser(node.parent, count + 1);
56
+ }
57
+ return [null, count];
58
+ }
59
+ exports.findAncestorWithNextParser = findAncestorWithNextParser;
60
+ function popMany(arr, count) {
61
+ for (let i = 0; i < count; i++) {
62
+ arr.pop();
63
+ }
64
+ }
65
+ exports.popMany = popMany;
48
66
  });
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "tarsec",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "A parser combinator library for TypeScript, inspired by Parsec.",
5
5
  "homepage": "https://github.com/egonSchiele/tarsec",
6
6
  "scripts": {
7
7
  "test": "vitest",
8
+ "coverage": "vitest --coverage",
8
9
  "build": "rm -rf dist && tsc",
9
- "start": "cd dist && node index.js"
10
+ "start": "cd dist && node index.js",
11
+ "doc": "typedoc --disableSources --out docs lib && prettier docs/ --write",
12
+ "doc:prettier": "prettier docs/ --write"
10
13
  },
11
14
  "files": [
12
15
  "./dist"
@@ -22,6 +25,9 @@
22
25
  "license": "ISC",
23
26
  "devDependencies": {
24
27
  "@types/node": "^20.11.28",
28
+ "@vitest/coverage-v8": "^1.4.0",
29
+ "prettier": "3.2.5",
30
+ "typedoc": "^0.25.12",
25
31
  "typescript": "^5.4.2",
26
32
  "vitest": "^1.4.0"
27
33
  }