tarsec 0.0.12 → 0.0.14

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
@@ -41,7 +41,11 @@ parser("hello there"); // failure
41
41
  ## Features
42
42
  - tarsec is entirely TypeScript. There's nothing to compile.
43
43
  - [Debug mode](/tutorials/debugging.md) that prints what's happening step-by-step
44
+ - Tools to debug your parser's [performance](/tutorials/performance.md)
44
45
  - Derived types: tarsec will generate TypeScript types for your parser
45
46
  - Partial [backtracking](/tutorials/backtracking.md) support
46
47
 
47
- Read more about [use cases for tarsec](/tutorials/use-case.md).
48
+ Read more about [use cases for tarsec](/tutorials/use-case.md).
49
+
50
+ ## Contributing
51
+ PRs for documentation, tests, and bug fixes are welcome.
@@ -1,6 +1,30 @@
1
- import { CaptureParser, GeneralParser, MergedCaptures, MergedResults, Parser, PickParserType, Prettify } from "./types";
2
- export declare function many<T>(parser: Parser<T>): Parser<T[]>;
3
- export declare function many1<T>(parser: Parser<T>): Parser<T[]>;
1
+ import { CaptureParser, GeneralParser, InferManyReturnType, MergedCaptures, MergedResults, Parser, PickParserType, PlainObject } from "./types";
2
+ /**
3
+ * Takes a parser and runs it zero or more times, returning the results as an array.
4
+ * If the parser is a capture parser, it returns the captures as an array in this form:
5
+ *
6
+ * ```ts
7
+ * { captures: <array of captures> }
8
+ * ```
9
+ *
10
+ * @param parser - parser to run
11
+ * @returns - parser that runs the given parser zero to many times,
12
+ * and returns the result as an array
13
+ */
14
+ export declare function many<const T extends GeneralParser<any, any>>(parser: T): InferManyReturnType<T>;
15
+ /**
16
+ * Same as `many`, but fails if the parser doesn't match at least once.
17
+ *
18
+ * @param parser - parser to run
19
+ * @returns a parser that runs the given parser one to many times,
20
+ */
21
+ export declare function many1<const T extends GeneralParser<any, any>>(parser: T): InferManyReturnType<T>;
22
+ /**
23
+ * Takes a parser, runs it, and returns the number of times it succeeded.
24
+ * @param parser - parser to run
25
+ * @returns - the number of times the parser succeeded.
26
+ */
27
+ export declare function count<T>(parser: Parser<T>): Parser<T[]>;
4
28
  /**
5
29
  * Takes a parser, runs it n times, and returns the results as an array.
6
30
  * If it cannot run the parser n times, it fails without consuming input.
@@ -8,8 +32,22 @@ export declare function many1<T>(parser: Parser<T>): Parser<T[]>;
8
32
  * @param parser - parser to run
9
33
  * @returns - parser that runs the given parser `num` times and returns an array of the results
10
34
  */
11
- export declare function count<T>(num: number, parser: Parser<T>): Parser<T[]>;
12
- export declare function manyWithJoin(parser: Parser<string>): Parser<string>;
35
+ export declare function exactly<T>(num: number, parser: Parser<T>): Parser<T[]>;
36
+ /**
37
+ * Same as `many`, but joins the results into a single string.
38
+ *
39
+ * @param parser - parser to run. The parser must return a string as its result.
40
+ * @returns - parser that runs the given parser zero to many times,
41
+ * and returns the result as a single string
42
+ */
43
+ export declare function manyWithJoin<const T extends GeneralParser<string, any>>(parser: T): GeneralParser<string, any>;
44
+ /**
45
+ * Same as `many1`, but joins the results into a single string.
46
+ *
47
+ * @param parser - parser to run. The parser must return a string as its result.
48
+ * @returns - parser that runs the given parser one to many times,
49
+ * and returns the result as a single string
50
+ */
13
51
  export declare function many1WithJoin(parser: Parser<string>): Parser<string>;
14
52
  /**
15
53
  * `or` takes an array of parsers and runs them sequentially.
@@ -38,16 +76,127 @@ export declare function many1WithJoin(parser: Parser<string>): Parser<string>;
38
76
  * @returns - a parser that tries each parser in order. Returns the result of the first parser that succeeds.
39
77
  */
40
78
  export declare function or<const T extends readonly GeneralParser<any, any>[]>(...parsers: T): PickParserType<T>;
79
+ /**
80
+ * Takes a parser and runs it. If the parser fails,
81
+ * optional returns a success with a null result.
82
+ *
83
+ * @param parser - parser to run
84
+ * @returns - a parser that runs the given parser.
85
+ * If it fails, returns a success with a null result.
86
+ */
41
87
  export declare function optional<T>(parser: Parser<T>): Parser<T | null>;
88
+ /**
89
+ * Takes a parser and runs it. If the parser fails,
90
+ * `not` returns a success with a `null` result.
91
+ * If the parser succeeds, `not` returns a failure.
92
+ *
93
+ * @param parser - parser to run
94
+ * @returns - a parser that runs the given parser.
95
+ * If it fails, returns a success with a `null` result.
96
+ * If it succeeds, returns a failure.
97
+ */
42
98
  export declare function not(parser: Parser<any>): Parser<null>;
99
+ /**
100
+ * Takes three parsers, `open`, `close`, and `parser`.
101
+ * `between` matches something that matches `parser`,
102
+ * surrounded by `open` and `close`. It returns the result of `parser`.
103
+ * If any of the parsers fail, `between` fails.
104
+ *
105
+ * @param open - parser for the opening delimiter
106
+ * @param close - parser for the closing delimiter
107
+ * @param parser - parser for the content
108
+ * @returns a parser that returns the result of `parser`.
109
+ */
43
110
  export declare function between<O, C, P>(open: Parser<O>, close: Parser<C>, parser: Parser<P>): Parser<P>;
111
+ /**
112
+ * Parses many instances of the parser separated by separator.
113
+ * @param separator
114
+ * @param parser
115
+ * @returns a parser that runs the given parser zero to many times, separated by the separator parser.
116
+ */
44
117
  export declare function sepBy<S, P>(separator: Parser<S>, parser: Parser<P>): Parser<P[]>;
118
+ /**
119
+ * Convenience function to use as the second argument to `seq` to get all the results from `seq`
120
+ * @param results
121
+ * @param captures
122
+ * @returns `results`
123
+ */
45
124
  export declare function getResults<R, C>(results: R, captures: C): R;
125
+ /**
126
+ * Convenience function to use as the second argument to seq to get all the captures.
127
+ * @param results
128
+ * @param captures
129
+ * @returns `captures`
130
+ */
46
131
  export declare function getCaptures<R, C>(results: R, captures: C): C;
132
+ /**
133
+ * `capture` is the only way to create a capture. Given a parser and a name,
134
+ * `capture` runs the parser and saves its result in a captures object
135
+ * with the given name as the key. It returns the result from the parser,
136
+ * and attaches the captures object along with it.
137
+ *
138
+ * @param parser - parser to run
139
+ * @param name - name of the capture
140
+ * @returns - the results of the parser, with the captures object attached.
141
+ */
47
142
  export declare function capture<T, const S extends string>(parser: Parser<T>, name: S): CaptureParser<T, Record<S, T>>;
48
- export declare function wrap<T, const S extends string>(parser: Parser<T>, name: S): Parser<Prettify<Record<S, T>>>;
143
+ /**
144
+ * Returns a parser that consumes input till the given parser succeeds.
145
+ * @param parser - the stop parser
146
+ * @returns a parser that consumes the input string until the stop parser succeeds.
147
+ * Then it returns the consumed input as a string.
148
+ * The stop parser's match is not included in the result.
149
+ */
49
150
  export declare function manyTill<T>(parser: Parser<T>): Parser<string>;
50
- export declare function transform<T, X>(parser: Parser<T>, transformerFunc: (x: T) => X): Parser<X>;
151
+ /**
152
+ * Just like `manyTill`, but fails unless at least one character of input is consumed.
153
+ * @param parser - the stop parser
154
+ * @returns a parser that consumes the input string until the stop parser succeeds.
155
+ */
156
+ export declare function many1Till<T>(parser: Parser<T>): Parser<string>;
157
+ /**
158
+ * `manyTillStr` is an optimized version of `manyTill`.
159
+ * The `manyTill` combinator is slow because it runs the given parser
160
+ * on every character of the string until it succeeds. However, if you
161
+ * just want to consume input until you get to a substring,
162
+ * use `manyTillStr`. It uses `indexOf`, which is significantly faster
163
+ * than running a parser over every character.
164
+ *
165
+ * @param str - the string to stop at
166
+ * @param options - object of optional parameters. { caseInsensitive: boolean }
167
+ * @returns a parser that consumes the input string until the given string is found.
168
+ */
169
+ export declare function manyTillStr(str: string, { caseInsensitive }?: {
170
+ caseInsensitive?: boolean;
171
+ }): Parser<string>;
172
+ /**
173
+ * Like `manyTillStr`, but case insensitive.
174
+ * @param str - the string to stop at
175
+ * @returns a parser that consumes the input string until the given string is found.
176
+ */
177
+ export declare function iManyTillStr(str: string): Parser<string>;
178
+ /**
179
+ * `map` is a parser combinator that takes a parser and a mapper function.
180
+ * If the parser succeeds, it maps its result using the mapper function.
181
+ * You can think of map as a general `map`, like for functors, applied to a parser.
182
+ * Since `map` itself is a parser, you can use it in `seq` or other combinators.
183
+ *
184
+ * @param parser - parser to run
185
+ * @param mapperFunc - function to map the result of the parser
186
+ * @returns
187
+ */
188
+ export declare function map<R, C extends PlainObject, X>(parser: GeneralParser<R, C>, mapperFunc: (x: R) => X): GeneralParser<X, C>;
189
+ /**
190
+ * Given a parser that returns a string, `search` looks for all substrings in a string that match that parser.
191
+ * For example, given a parser that matches quoted strings, `search` will return an array of all the quoted strings
192
+ * it finds in the input, as an array.
193
+ *
194
+ * The rest of the input that isn't part of the result is simply joined together and returned as a string.
195
+ * If you need a more structured result + rest, you can use `within` instead.
196
+ *
197
+ * @param parser - a parser that returns a string
198
+ * @returns - a parser that returns an array of strings
199
+ */
51
200
  export declare function search(parser: Parser<string>): Parser<string[]>;
52
201
  /**
53
202
  * seq takes an array of parsers and runs them sequentially.
@@ -68,11 +217,28 @@ export declare function search(parser: Parser<string>): Parser<string[]>;
68
217
  * and pass the remaining input to the next parser yourself.
69
218
  * seq also does some backtracking for you that you will need to do yourself.
70
219
  *
220
+ * Also see `seqR` and `seqC` for convenience functions that return the results or captures respectively.
221
+ *
71
222
  * @param parsers - parsers to run sequentially
72
223
  * @param transform - function to transform the results and captures. The params are the results and captures
73
224
  * @param debugName - optional name for trace debugging
74
225
  * @returns
75
226
  */
76
227
  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>;
228
+ /** Just like seq except it returns the results.
229
+ * It's like using `seq([parsers], getResults)`.
230
+ */
77
231
  export declare function seqR<const T extends readonly GeneralParser<any, any>[]>(...parsers: T): Parser<MergedResults<T>[]>;
232
+ /** Just like seq except it returns the captures.
233
+ * It's like using `seq([parsers], getCaptures)`.
234
+ */
78
235
  export declare function seqC<const T extends readonly GeneralParser<any, any>[]>(...parsers: T): Parser<MergedCaptures<T>>;
236
+ /**
237
+ * Match takes an input string and a parser. If the parser matches the input string
238
+ * and consumes the entire input string, `match` returns `true`. Otherwise it returns `false`.
239
+ *
240
+ * @param input - input string
241
+ * @param parser - parser to match input against
242
+ * @returns - true if the parser matches the input and consumes all input, false otherwise
243
+ */
244
+ export declare function match(input: string, parser: GeneralParser<any, any>): boolean;
@@ -9,26 +9,62 @@
9
9
  })(function (require, exports) {
10
10
  "use strict";
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
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;
12
+ exports.match = exports.seqC = exports.seqR = exports.seq = exports.search = exports.map = exports.iManyTillStr = exports.manyTillStr = exports.many1Till = exports.manyTill = exports.capture = exports.getCaptures = exports.getResults = exports.sepBy = exports.between = exports.not = exports.optional = exports.or = exports.many1WithJoin = exports.manyWithJoin = exports.exactly = exports.count = exports.many1 = exports.many = void 0;
13
13
  const within_1 = require("./parsers/within");
14
14
  const trace_1 = require("./trace");
15
15
  const types_1 = require("./types");
16
16
  const utils_1 = require("./utils");
17
+ /**
18
+ * Takes a parser and runs it zero or more times, returning the results as an array.
19
+ * If the parser is a capture parser, it returns the captures as an array in this form:
20
+ *
21
+ * ```ts
22
+ * { captures: <array of captures> }
23
+ * ```
24
+ *
25
+ * @param parser - parser to run
26
+ * @returns - parser that runs the given parser zero to many times,
27
+ * and returns the result as an array
28
+ */
17
29
  function many(parser) {
18
30
  return (0, trace_1.trace)("many", (input) => {
19
31
  let results = [];
32
+ let captures = [];
20
33
  let rest = input;
21
34
  while (true) {
22
35
  let parsed = parser(rest);
23
36
  if (!parsed.success) {
24
- return (0, types_1.success)(results, rest);
37
+ if (Object.keys(captures).length) {
38
+ return (0, types_1.captureSuccess)(results, rest, { captures });
39
+ }
40
+ else {
41
+ return (0, types_1.success)(results, rest);
42
+ }
25
43
  }
26
44
  results.push(parsed.result);
45
+ if ((0, types_1.isCaptureResult)(parsed)) {
46
+ captures.push(parsed.captures);
47
+ }
27
48
  rest = parsed.rest;
49
+ // don't loop infinitely on empty strings
50
+ if (rest === "") {
51
+ if (Object.keys(captures).length) {
52
+ return (0, types_1.captureSuccess)(results, rest, { captures });
53
+ }
54
+ else {
55
+ return (0, types_1.success)(results, rest);
56
+ }
57
+ }
28
58
  }
29
59
  });
30
60
  }
31
61
  exports.many = many;
62
+ /**
63
+ * Same as `many`, but fails if the parser doesn't match at least once.
64
+ *
65
+ * @param parser - parser to run
66
+ * @returns a parser that runs the given parser one to many times,
67
+ */
32
68
  function many1(parser) {
33
69
  return (0, trace_1.trace)(`many1`, (input) => {
34
70
  let result = many(parser)(input);
@@ -44,6 +80,21 @@
44
80
  });
45
81
  }
46
82
  exports.many1 = many1;
83
+ /**
84
+ * Takes a parser, runs it, and returns the number of times it succeeded.
85
+ * @param parser - parser to run
86
+ * @returns - the number of times the parser succeeded.
87
+ */
88
+ function count(parser) {
89
+ return (0, trace_1.trace)("count", (input) => {
90
+ const result = many(parser)(input);
91
+ if (result.success) {
92
+ return (0, types_1.success)(result.result.length, result.rest);
93
+ }
94
+ return result;
95
+ });
96
+ }
97
+ exports.count = count;
47
98
  /**
48
99
  * Takes a parser, runs it n times, and returns the results as an array.
49
100
  * If it cannot run the parser n times, it fails without consuming input.
@@ -51,8 +102,8 @@
51
102
  * @param parser - parser to run
52
103
  * @returns - parser that runs the given parser `num` times and returns an array of the results
53
104
  */
54
- function count(num, parser) {
55
- return (0, trace_1.trace)("count", (input) => {
105
+ function exactly(num, parser) {
106
+ return (0, trace_1.trace)("exactly", (input) => {
56
107
  let results = [];
57
108
  let rest = input;
58
109
  for (let i = 0; i < num; i++) {
@@ -66,13 +117,39 @@
66
117
  return (0, types_1.success)(results, rest);
67
118
  });
68
119
  }
69
- exports.count = count;
120
+ exports.exactly = exactly;
121
+ /**
122
+ * Same as `many`, but joins the results into a single string.
123
+ *
124
+ * @param parser - parser to run. The parser must return a string as its result.
125
+ * @returns - parser that runs the given parser zero to many times,
126
+ * and returns the result as a single string
127
+ */
70
128
  function manyWithJoin(parser) {
71
- return transform(many(parser), (x) => x.join(""));
129
+ return (0, trace_1.trace)("manyWithJoin", (input) => {
130
+ const result = many(parser)(input);
131
+ if (result.success) {
132
+ return Object.assign(Object.assign({}, result), { result: result.result.join("") });
133
+ }
134
+ return result;
135
+ });
72
136
  }
73
137
  exports.manyWithJoin = manyWithJoin;
138
+ /**
139
+ * Same as `many1`, but joins the results into a single string.
140
+ *
141
+ * @param parser - parser to run. The parser must return a string as its result.
142
+ * @returns - parser that runs the given parser one to many times,
143
+ * and returns the result as a single string
144
+ */
74
145
  function many1WithJoin(parser) {
75
- return transform(many1(parser), (x) => x.join(""));
146
+ return (0, trace_1.trace)("many1WithJoin", (input) => {
147
+ const result = many1(parser)(input);
148
+ if (result.success) {
149
+ return Object.assign(Object.assign({}, result), { result: result.result.join("") });
150
+ }
151
+ return result;
152
+ });
76
153
  }
77
154
  exports.many1WithJoin = many1WithJoin;
78
155
  /**
@@ -117,6 +194,14 @@
117
194
  });
118
195
  }
119
196
  exports.or = or;
197
+ /**
198
+ * Takes a parser and runs it. If the parser fails,
199
+ * optional returns a success with a null result.
200
+ *
201
+ * @param parser - parser to run
202
+ * @returns - a parser that runs the given parser.
203
+ * If it fails, returns a success with a null result.
204
+ */
120
205
  function optional(parser) {
121
206
  return (0, trace_1.trace)("optional", (input) => {
122
207
  let result = parser(input);
@@ -127,6 +212,16 @@
127
212
  });
128
213
  }
129
214
  exports.optional = optional;
215
+ /**
216
+ * Takes a parser and runs it. If the parser fails,
217
+ * `not` returns a success with a `null` result.
218
+ * If the parser succeeds, `not` returns a failure.
219
+ *
220
+ * @param parser - parser to run
221
+ * @returns - a parser that runs the given parser.
222
+ * If it fails, returns a success with a `null` result.
223
+ * If it succeeds, returns a failure.
224
+ */
130
225
  function not(parser) {
131
226
  return (0, trace_1.trace)("not", (input) => {
132
227
  let result = parser(input);
@@ -134,13 +229,24 @@
134
229
  return {
135
230
  success: false,
136
231
  rest: input,
137
- message: "unexpected match",
232
+ message: "expected parser not to succeed",
138
233
  };
139
234
  }
140
235
  return (0, types_1.success)(null, input);
141
236
  });
142
237
  }
143
238
  exports.not = not;
239
+ /**
240
+ * Takes three parsers, `open`, `close`, and `parser`.
241
+ * `between` matches something that matches `parser`,
242
+ * surrounded by `open` and `close`. It returns the result of `parser`.
243
+ * If any of the parsers fail, `between` fails.
244
+ *
245
+ * @param open - parser for the opening delimiter
246
+ * @param close - parser for the closing delimiter
247
+ * @param parser - parser for the content
248
+ * @returns a parser that returns the result of `parser`.
249
+ */
144
250
  function between(open, close, parser) {
145
251
  return (input) => {
146
252
  const result1 = open(input);
@@ -159,6 +265,12 @@
159
265
  };
160
266
  }
161
267
  exports.between = between;
268
+ /**
269
+ * Parses many instances of the parser separated by separator.
270
+ * @param separator
271
+ * @param parser
272
+ * @returns a parser that runs the given parser zero to many times, separated by the separator parser.
273
+ */
162
274
  function sepBy(separator, parser) {
163
275
  return (input) => {
164
276
  let results = [];
@@ -179,14 +291,36 @@
179
291
  };
180
292
  }
181
293
  exports.sepBy = sepBy;
294
+ /**
295
+ * Convenience function to use as the second argument to `seq` to get all the results from `seq`
296
+ * @param results
297
+ * @param captures
298
+ * @returns `results`
299
+ */
182
300
  function getResults(results, captures) {
183
301
  return results;
184
302
  }
185
303
  exports.getResults = getResults;
304
+ /**
305
+ * Convenience function to use as the second argument to seq to get all the captures.
306
+ * @param results
307
+ * @param captures
308
+ * @returns `captures`
309
+ */
186
310
  function getCaptures(results, captures) {
187
311
  return captures;
188
312
  }
189
313
  exports.getCaptures = getCaptures;
314
+ /**
315
+ * `capture` is the only way to create a capture. Given a parser and a name,
316
+ * `capture` runs the parser and saves its result in a captures object
317
+ * with the given name as the key. It returns the result from the parser,
318
+ * and attaches the captures object along with it.
319
+ *
320
+ * @param parser - parser to run
321
+ * @param name - name of the capture
322
+ * @returns - the results of the parser, with the captures object attached.
323
+ */
190
324
  function capture(parser, name) {
191
325
  return (0, trace_1.trace)(`capture(${(0, utils_1.escape)(name)})`, (input) => {
192
326
  let result = parser(input);
@@ -200,18 +334,13 @@
200
334
  });
201
335
  }
202
336
  exports.capture = capture;
203
- function wrap(parser, name) {
204
- return (0, trace_1.trace)(`capture(${(0, utils_1.escape)(name)})`, (input) => {
205
- let result = parser(input);
206
- if (result.success) {
207
- return Object.assign(Object.assign({}, result), { result: {
208
- [name]: result.result,
209
- } });
210
- }
211
- return result;
212
- });
213
- }
214
- exports.wrap = wrap;
337
+ /**
338
+ * Returns a parser that consumes input till the given parser succeeds.
339
+ * @param parser - the stop parser
340
+ * @returns a parser that consumes the input string until the stop parser succeeds.
341
+ * Then it returns the consumed input as a string.
342
+ * The stop parser's match is not included in the result.
343
+ */
215
344
  function manyTill(parser) {
216
345
  return (input) => {
217
346
  let current = 0;
@@ -226,16 +355,95 @@
226
355
  };
227
356
  }
228
357
  exports.manyTill = manyTill;
229
- function transform(parser, transformerFunc) {
230
- return (0, trace_1.trace)(`transform(${transformerFunc})`, (input) => {
358
+ /**
359
+ * Just like `manyTill`, but fails unless at least one character of input is consumed.
360
+ * @param parser - the stop parser
361
+ * @returns a parser that consumes the input string until the stop parser succeeds.
362
+ */
363
+ function many1Till(parser) {
364
+ return (input) => {
365
+ let current = 0;
366
+ while (current < input.length) {
367
+ const parsed = parser(input.slice(current));
368
+ if (parsed.success) {
369
+ if (current === 0) {
370
+ return (0, types_1.failure)("expected to consume at least one character of input", input);
371
+ }
372
+ return (0, types_1.success)(input.slice(0, current), input.slice(current));
373
+ }
374
+ current++;
375
+ }
376
+ if (current === 0) {
377
+ return (0, types_1.failure)("expected to consume at least one character of input", input);
378
+ }
379
+ return (0, types_1.success)(input, "");
380
+ };
381
+ }
382
+ exports.many1Till = many1Till;
383
+ /**
384
+ * `manyTillStr` is an optimized version of `manyTill`.
385
+ * The `manyTill` combinator is slow because it runs the given parser
386
+ * on every character of the string until it succeeds. However, if you
387
+ * just want to consume input until you get to a substring,
388
+ * use `manyTillStr`. It uses `indexOf`, which is significantly faster
389
+ * than running a parser over every character.
390
+ *
391
+ * @param str - the string to stop at
392
+ * @param options - object of optional parameters. { caseInsensitive: boolean }
393
+ * @returns a parser that consumes the input string until the given string is found.
394
+ */
395
+ function manyTillStr(str, { caseInsensitive = false } = {}) {
396
+ return (0, trace_1.trace)(`manyTillStr(${str})`, (input) => {
397
+ const index = caseInsensitive
398
+ ? input.toLocaleLowerCase().indexOf(str.toLocaleLowerCase())
399
+ : input.indexOf(str);
400
+ if (index === -1) {
401
+ return (0, types_1.success)(input, "");
402
+ }
403
+ return (0, types_1.success)(input.slice(0, index), input.slice(index));
404
+ });
405
+ }
406
+ exports.manyTillStr = manyTillStr;
407
+ /**
408
+ * Like `manyTillStr`, but case insensitive.
409
+ * @param str - the string to stop at
410
+ * @returns a parser that consumes the input string until the given string is found.
411
+ */
412
+ function iManyTillStr(str) {
413
+ return manyTillStr(str, { caseInsensitive: true });
414
+ }
415
+ exports.iManyTillStr = iManyTillStr;
416
+ /**
417
+ * `map` is a parser combinator that takes a parser and a mapper function.
418
+ * If the parser succeeds, it maps its result using the mapper function.
419
+ * You can think of map as a general `map`, like for functors, applied to a parser.
420
+ * Since `map` itself is a parser, you can use it in `seq` or other combinators.
421
+ *
422
+ * @param parser - parser to run
423
+ * @param mapperFunc - function to map the result of the parser
424
+ * @returns
425
+ */
426
+ function map(parser, mapperFunc) {
427
+ return (0, trace_1.trace)(`map(${mapperFunc})`, (input) => {
231
428
  let parsed = parser(input);
232
429
  if (parsed.success) {
233
- return Object.assign(Object.assign({}, parsed), { result: transformerFunc(parsed.result) });
430
+ return Object.assign(Object.assign({}, parsed), { result: mapperFunc(parsed.result) });
234
431
  }
235
432
  return parsed;
236
433
  });
237
434
  }
238
- exports.transform = transform;
435
+ exports.map = map;
436
+ /**
437
+ * Given a parser that returns a string, `search` looks for all substrings in a string that match that parser.
438
+ * For example, given a parser that matches quoted strings, `search` will return an array of all the quoted strings
439
+ * it finds in the input, as an array.
440
+ *
441
+ * The rest of the input that isn't part of the result is simply joined together and returned as a string.
442
+ * If you need a more structured result + rest, you can use `within` instead.
443
+ *
444
+ * @param parser - a parser that returns a string
445
+ * @returns - a parser that returns an array of strings
446
+ */
239
447
  function search(parser) {
240
448
  return (0, trace_1.trace)("search", (input) => {
241
449
  let parsed = (0, within_1.within)(parser)(input);
@@ -330,6 +538,8 @@
330
538
  * and pass the remaining input to the next parser yourself.
331
539
  * seq also does some backtracking for you that you will need to do yourself.
332
540
  *
541
+ * Also see `seqR` and `seqC` for convenience functions that return the results or captures respectively.
542
+ *
333
543
  * @param parsers - parsers to run sequentially
334
544
  * @param transform - function to transform the results and captures. The params are the results and captures
335
545
  * @param debugName - optional name for trace debugging
@@ -384,19 +594,31 @@
384
594
  });
385
595
  }
386
596
  exports.seq = seq;
597
+ /** Just like seq except it returns the results.
598
+ * It's like using `seq([parsers], getResults)`.
599
+ */
387
600
  function seqR(...parsers) {
388
601
  return seq(parsers, getResults);
389
602
  }
390
603
  exports.seqR = seqR;
604
+ /** Just like seq except it returns the captures.
605
+ * It's like using `seq([parsers], getCaptures)`.
606
+ */
391
607
  function seqC(...parsers) {
392
608
  return seq(parsers, getCaptures);
393
609
  }
394
610
  exports.seqC = seqC;
611
+ /**
612
+ * Match takes an input string and a parser. If the parser matches the input string
613
+ * and consumes the entire input string, `match` returns `true`. Otherwise it returns `false`.
614
+ *
615
+ * @param input - input string
616
+ * @param parser - parser to match input against
617
+ * @returns - true if the parser matches the input and consumes all input, false otherwise
618
+ */
619
+ function match(input, parser) {
620
+ const result = parser(input);
621
+ return result.success && result.rest === "";
622
+ }
623
+ exports.match = match;
395
624
  });
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
- */
@@ -1,2 +1,2 @@
1
- import { BetweenWithinResult, Parser } from "../types";
2
- export declare function within(parser: Parser<string>): Parser<BetweenWithinResult[]>;
1
+ import { WithinResult, Parser } from "../types";
2
+ export declare function within(parser: Parser<string>): Parser<WithinResult[]>;
package/dist/parsers.d.ts CHANGED
@@ -10,10 +10,16 @@ export declare function char<const S extends string>(c: S): Parser<S>;
10
10
  /**
11
11
  * Takes a string. Returns a parser that parses that string.
12
12
  *
13
- * @param s - string to parse
13
+ * @param s - string to match on
14
14
  * @returns - parser that parses the given string
15
15
  */
16
16
  export declare function str<const S extends string>(s: S): Parser<S>;
17
+ /**
18
+ * Like `str`, but case insensitive.
19
+ * @param s - string to match on, case insensitive
20
+ * @returns - parser that matches the given string, case insensitive
21
+ */
22
+ export declare function istr<const S extends string>(s: S): Parser<S>;
17
23
  /**
18
24
  * Takes a string. Returns a parser that parses
19
25
  * one of the characters in that string.
@@ -38,15 +44,39 @@ export declare function noneOf(chars: string): Parser<string>;
38
44
  * @returns - ParserResult
39
45
  */
40
46
  export declare const anyChar: any;
47
+ /** A parser that matches one of " \t\n\r". */
41
48
  export declare const space: Parser<string>;
49
+ /** A parser that matches one or more spaces. */
42
50
  export declare const spaces: Parser<string>;
51
+ /** A parser that matches one digit. */
43
52
  export declare const digit: Parser<string>;
53
+ /** A parser that matches one letter, case insensitive. */
44
54
  export declare const letter: Parser<string>;
55
+ /** A parser that matches one digit or letter, case insensitive. */
45
56
  export declare const alphanum: Parser<string>;
57
+ /** A parser that matches one word, case insensitive. */
46
58
  export declare const word: Parser<string>;
59
+ /** A parser that matches one or more digits. */
47
60
  export declare const num: Parser<string>;
61
+ /** A parser that matches one single or double quote. */
48
62
  export declare const quote: Parser<string>;
63
+ /** A parser that matches one tab character. */
49
64
  export declare const tab: Parser<string>;
65
+ /** A parser that matches one newline ("\n" only) character. */
50
66
  export declare const newline: Parser<string>;
67
+ /** A parser that succeeds on an empty string. Returns `null` as the result. */
51
68
  export declare const eof: Parser<null>;
69
+ /** A parser that matches a quoted string, in single or double quotes.
70
+ * Returns the string as the result, including the quotes.
71
+ */
52
72
  export declare const quotedString: Parser<string>;
73
+ /**
74
+ * Returns a parser that matches a regex. If you pass in a string,
75
+ * it will get converted to a regex. The regex should always match from the start of the input.
76
+ * If you pass in a string, a `^` will get prepended to it.
77
+ *
78
+ * @param str - regex string or RegExp instance to match
79
+ * @param options - regex options (i = ignore case, g = global, m = multiline, u = unicode)
80
+ * @returns - parser that matches the given regex
81
+ */
82
+ export declare function regexParser(str: string | RegExp, options?: string): Parser<string>;
package/dist/parsers.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.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;
12
+ exports.regexParser = 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.istr = 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");
@@ -41,7 +41,7 @@
41
41
  /**
42
42
  * Takes a string. Returns a parser that parses that string.
43
43
  *
44
- * @param s - string to parse
44
+ * @param s - string to match on
45
45
  * @returns - parser that parses the given string
46
46
  */
47
47
  function str(s) {
@@ -53,6 +53,20 @@
53
53
  });
54
54
  }
55
55
  exports.str = str;
56
+ /**
57
+ * Like `str`, but case insensitive.
58
+ * @param s - string to match on, case insensitive
59
+ * @returns - parser that matches the given string, case insensitive
60
+ */
61
+ function istr(s) {
62
+ return (0, trace_1.trace)(`istr(${(0, utils_1.escape)(s)})`, (input) => {
63
+ if (input.substring(0, s.length).toLocaleLowerCase() === s.toLocaleLowerCase()) {
64
+ return (0, types_1.success)(input.substring(0, s.length), input.slice(s.length));
65
+ }
66
+ return (0, types_1.failure)(`expected ${s}, got ${input.substring(0, s.length)}`, input);
67
+ });
68
+ }
69
+ exports.istr = istr;
56
70
  /**
57
71
  * Takes a string. Returns a parser that parses
58
72
  * one of the characters in that string.
@@ -105,16 +119,27 @@
105
119
  }
106
120
  return (0, types_1.success)(input[0], input.slice(1));
107
121
  });
122
+ /** A parser that matches one of " \t\n\r". */
108
123
  exports.space = oneOf(" \t\n\r");
124
+ /** A parser that matches one or more spaces. */
109
125
  exports.spaces = (0, combinators_1.many1WithJoin)(exports.space);
126
+ /** A parser that matches one digit. */
110
127
  exports.digit = oneOf("0123456789");
111
- exports.letter = oneOf("abcdefghijklmnopqrstuvwxyz");
112
- exports.alphanum = oneOf("abcdefghijklmnopqrstuvwxyz0123456789");
113
- exports.word = (0, combinators_1.many1WithJoin)(exports.letter);
114
- exports.num = (0, combinators_1.many1WithJoin)(exports.digit);
128
+ /** A parser that matches one letter, case insensitive. */
129
+ exports.letter = oneOf("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
130
+ /** A parser that matches one digit or letter, case insensitive. */
131
+ exports.alphanum = oneOf("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
132
+ /** A parser that matches one word, case insensitive. */
133
+ exports.word = regexParser("^[a-z]+", "ui");
134
+ /** A parser that matches one or more digits. */
135
+ exports.num = regexParser("^[0-9]+");
136
+ /** A parser that matches one single or double quote. */
115
137
  exports.quote = oneOf(`'"`);
138
+ /** A parser that matches one tab character. */
116
139
  exports.tab = char("\t");
140
+ /** A parser that matches one newline ("\n" only) character. */
117
141
  exports.newline = char("\n");
142
+ /** A parser that succeeds on an empty string. Returns `null` as the result. */
118
143
  const eof = (input) => {
119
144
  if (input === "") {
120
145
  return (0, types_1.success)(null, input);
@@ -122,5 +147,34 @@
122
147
  return (0, types_1.failure)("expected end of input", input);
123
148
  };
124
149
  exports.eof = eof;
150
+ /** A parser that matches a quoted string, in single or double quotes.
151
+ * Returns the string as the result, including the quotes.
152
+ */
125
153
  exports.quotedString = (0, combinators_1.seq)([exports.quote, (0, combinators_1.manyWithJoin)(noneOf(`"'`)), exports.quote], (results) => results.join(""));
154
+ /**
155
+ * Returns a parser that matches a regex. If you pass in a string,
156
+ * it will get converted to a regex. The regex should always match from the start of the input.
157
+ * If you pass in a string, a `^` will get prepended to it.
158
+ *
159
+ * @param str - regex string or RegExp instance to match
160
+ * @param options - regex options (i = ignore case, g = global, m = multiline, u = unicode)
161
+ * @returns - parser that matches the given regex
162
+ */
163
+ function regexParser(str, options = "") {
164
+ let re;
165
+ if (typeof str === "string") {
166
+ re = new RegExp(str.startsWith("^") ? str : `^${str}`, options);
167
+ }
168
+ else {
169
+ re = str;
170
+ }
171
+ return (0, trace_1.trace)(`regex(${str})`, (input) => {
172
+ const match = input.match(re);
173
+ if (match) {
174
+ return (0, types_1.success)(match[0], input.slice(match[0].length));
175
+ }
176
+ return (0, types_1.failure)(`expected ${str}, got ${input.slice(0, 10)}`, input);
177
+ });
178
+ }
179
+ exports.regexParser = regexParser;
126
180
  });
package/dist/trace.d.ts CHANGED
@@ -1,3 +1,92 @@
1
1
  import { ParserResult } from "./types";
2
+ /**
3
+ * This function is used internally by the `trace` function to create the string for each step.
4
+ * @param name - debug name for parser
5
+ * @param result - parser result
6
+ * @returns - A formatted string that describes the parser's result
7
+ */
2
8
  export declare function resultToString<T>(name: string, result: ParserResult<T>): string;
9
+ /**
10
+ * This function is used internally with debug mode. Given a parser and a debug name for it,
11
+ * when the parser is called, `trace` will:
12
+ * 1. Print a line when the parser starts
13
+ * 2. print a line when the parser ends, indicating success or failure.
14
+ * 3. If the parser returns any captures, print the captures.
15
+ * 4. Count the number of times this parser has been run.
16
+ * 5. Track the total time this parser has taken.
17
+ * 6. Track the total number of steps your parser has taken (a step equals one parser invocation).
18
+ * So, for example, you may find out that your parser to parse Markdown has taken 50 steps to parse that file.
19
+ *
20
+ * All this happens only if debug mode is on, which you can turn on by using `parserDebug`, or setting the env var `DEBUG` to `1`.
21
+ *
22
+ * Caveat: If you have debug mode on through an environment variable, `trace` will capture counts and times
23
+ * for all parsers across your entire application. If you want to profile just a particular section of code, use `parserDebug` instead.
24
+ * If you *do* want to track constant times for all parsers, don't use `parserDebug` as it will reset those.
25
+ *
26
+ *
27
+ * `trace` works with tarsec's built-in parsers out of the box. You can easily set it up to work with your custom parser too.
28
+ *
29
+ * For example, if your parser looks like this:
30
+ *
31
+ * ```ts
32
+ * const myParser = (input:string) => {
33
+ * if (input === "hello") {
34
+ * return { success: true, result: "hello", rest: "" };
35
+ * }
36
+ * return { success: false, message: "expected hello", rest: input };
37
+ * }
38
+ * ```
39
+ *
40
+ * You can wrap it in `trace` like this:
41
+ *
42
+ * ```ts
43
+ * const myParser = trace("myParser", (input:string) => {
44
+ * if (input === "hello") {
45
+ * return { success: true, result: "hello", rest: "" };
46
+ * }
47
+ * return { success: false, message: "expected hello", rest: input };
48
+ * });
49
+ * ```
50
+ *
51
+ * Now, when you run `myParser("hello")` with debug mode on,
52
+ * you will see the debug output.
53
+ *
54
+ * Some parsers, like `seq`, are very general. You might have a few parser that use `seq`.
55
+ * So when you see `seq` debug output, you might not know which `seq` parser that means.
56
+ * ou can pass `seq` a debug name as an optional third argument to be used in the debug output.
57
+ * This name is used to track count and time, so using this name will also mean this `seq` parser's
58
+ * count and time are tracked separately from the other `seq` parsers, which might be useful.
59
+ *
60
+ * @param name - debug name for parser
61
+ * @param parser - parser to run
62
+ * @returns
63
+ */
3
64
  export declare function trace(name: string, parser: any): any;
65
+ /**
66
+ * Utility timing function. Given a callback, it times the callback
67
+ * and returns its runtime in milliseconds. It uses `performance.now()` to do this.
68
+ * If `performance.now()` is not available, it returns null.
69
+ *
70
+ * @param callback - callback to time
71
+ * @returns - time in milliseconds
72
+ */
73
+ export declare function parserTime(callback: Function): number | null;
74
+ /**
75
+ * Wrapper for parser time. Instead of returning the time in milliseconds,
76
+ * it console.logs it, in a nicely formatted string.
77
+ * @param name - debug name for timing
78
+ * @param callback - callback to time
79
+ */
80
+ export declare function printTime(name: string, callback: Function): void;
81
+ /**
82
+ * This is the recommended way to run a parser in debug mode.
83
+ * Takes a callback and turns debug mode on just for the callback.
84
+ * This enables `trace` to capture all sorts of information
85
+ * about any executed parsers and print them to console.log.
86
+ * `trace` tracks counts and times but they don't actually get reset to zero
87
+ * unless you use this function to wrap your code.
88
+ *
89
+ * @param name - debug name
90
+ * @param callback - callback to run in debug mode
91
+ */
92
+ export declare function parserDebug(name: string, callback: Function): void;
package/dist/trace.js CHANGED
@@ -9,9 +9,15 @@
9
9
  })(function (require, exports) {
10
10
  "use strict";
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.trace = exports.resultToString = void 0;
12
+ exports.parserDebug = exports.printTime = exports.parserTime = exports.trace = exports.resultToString = void 0;
13
13
  const utils_1 = require("./utils");
14
14
  const STEP = 2;
15
+ /**
16
+ * This function is used internally by the `trace` function to create the string for each step.
17
+ * @param name - debug name for parser
18
+ * @param result - parser result
19
+ * @returns - A formatted string that describes the parser's result
20
+ */
15
21
  function resultToString(name, result) {
16
22
  if (result.success) {
17
23
  return `✅ ${name} -- match: ${(0, utils_1.escape)(result.result)}, rest: ${(0, utils_1.escape)(result.rest)}`;
@@ -20,23 +26,165 @@
20
26
  }
21
27
  exports.resultToString = resultToString;
22
28
  let level = 0;
29
+ let counts = {};
30
+ let times = {};
31
+ let debugFlag = !!process.env.DEBUG;
32
+ let stepCount = 0;
33
+ let stepLimit = -1;
34
+ /**
35
+ * This function is used internally with debug mode. Given a parser and a debug name for it,
36
+ * when the parser is called, `trace` will:
37
+ * 1. Print a line when the parser starts
38
+ * 2. print a line when the parser ends, indicating success or failure.
39
+ * 3. If the parser returns any captures, print the captures.
40
+ * 4. Count the number of times this parser has been run.
41
+ * 5. Track the total time this parser has taken.
42
+ * 6. Track the total number of steps your parser has taken (a step equals one parser invocation).
43
+ * So, for example, you may find out that your parser to parse Markdown has taken 50 steps to parse that file.
44
+ *
45
+ * All this happens only if debug mode is on, which you can turn on by using `parserDebug`, or setting the env var `DEBUG` to `1`.
46
+ *
47
+ * Caveat: If you have debug mode on through an environment variable, `trace` will capture counts and times
48
+ * for all parsers across your entire application. If you want to profile just a particular section of code, use `parserDebug` instead.
49
+ * If you *do* want to track constant times for all parsers, don't use `parserDebug` as it will reset those.
50
+ *
51
+ *
52
+ * `trace` works with tarsec's built-in parsers out of the box. You can easily set it up to work with your custom parser too.
53
+ *
54
+ * For example, if your parser looks like this:
55
+ *
56
+ * ```ts
57
+ * const myParser = (input:string) => {
58
+ * if (input === "hello") {
59
+ * return { success: true, result: "hello", rest: "" };
60
+ * }
61
+ * return { success: false, message: "expected hello", rest: input };
62
+ * }
63
+ * ```
64
+ *
65
+ * You can wrap it in `trace` like this:
66
+ *
67
+ * ```ts
68
+ * const myParser = trace("myParser", (input:string) => {
69
+ * if (input === "hello") {
70
+ * return { success: true, result: "hello", rest: "" };
71
+ * }
72
+ * return { success: false, message: "expected hello", rest: input };
73
+ * });
74
+ * ```
75
+ *
76
+ * Now, when you run `myParser("hello")` with debug mode on,
77
+ * you will see the debug output.
78
+ *
79
+ * Some parsers, like `seq`, are very general. You might have a few parser that use `seq`.
80
+ * So when you see `seq` debug output, you might not know which `seq` parser that means.
81
+ * ou can pass `seq` a debug name as an optional third argument to be used in the debug output.
82
+ * This name is used to track count and time, so using this name will also mean this `seq` parser's
83
+ * count and time are tracked separately from the other `seq` parsers, which might be useful.
84
+ *
85
+ * @param name - debug name for parser
86
+ * @param parser - parser to run
87
+ * @returns
88
+ */
23
89
  function trace(name, parser) {
24
90
  return (input) => {
25
- if (process.env.DEBUG) {
91
+ if (debugFlag) {
26
92
  console.log(" ".repeat(level) + `🔍 ${name} -- input: ${(0, utils_1.escape)(input)}`);
27
- }
28
- level += STEP;
29
- const result = parser(input);
30
- level -= STEP;
31
- if (process.env.DEBUG) {
93
+ let result;
94
+ const time = parserTime(() => {
95
+ level += STEP;
96
+ result = parser(input);
97
+ level -= STEP;
98
+ });
99
+ counts[name] = counts[name] ? counts[name] + 1 : 1;
100
+ stepCount += 1;
101
+ if (time) {
102
+ times[name] = times[name] ? times[name] + time : time;
103
+ }
32
104
  console.log(" ".repeat(level) + resultToString(name, result));
33
105
  if (result.success && result.captures) {
34
106
  console.log(" ".repeat(level) +
35
107
  `⭐ ${name} -- captures: ${JSON.stringify(result.captures)}`);
36
108
  }
109
+ return result;
110
+ }
111
+ else {
112
+ return parser(input);
37
113
  }
38
- return result;
39
114
  };
40
115
  }
41
116
  exports.trace = trace;
117
+ /**
118
+ * Utility timing function. Given a callback, it times the callback
119
+ * and returns its runtime in milliseconds. It uses `performance.now()` to do this.
120
+ * If `performance.now()` is not available, it returns null.
121
+ *
122
+ * @param callback - callback to time
123
+ * @returns - time in milliseconds
124
+ */
125
+ function parserTime(callback) {
126
+ if (performance && performance.now) {
127
+ const start = performance.now();
128
+ callback();
129
+ const end = performance.now();
130
+ return end - start;
131
+ }
132
+ else {
133
+ console.error("performance.now not available");
134
+ callback();
135
+ return null;
136
+ }
137
+ }
138
+ exports.parserTime = parserTime;
139
+ /**
140
+ * Wrapper for parser time. Instead of returning the time in milliseconds,
141
+ * it console.logs it, in a nicely formatted string.
142
+ * @param name - debug name for timing
143
+ * @param callback - callback to time
144
+ */
145
+ function printTime(name, callback) {
146
+ const time = parserTime(callback);
147
+ if (time) {
148
+ console.log(`⏱ ${name} -- time: ${(0, utils_1.round)(time)}ms`);
149
+ }
150
+ }
151
+ exports.printTime = printTime;
152
+ /**
153
+ * This is the recommended way to run a parser in debug mode.
154
+ * Takes a callback and turns debug mode on just for the callback.
155
+ * This enables `trace` to capture all sorts of information
156
+ * about any executed parsers and print them to console.log.
157
+ * `trace` tracks counts and times but they don't actually get reset to zero
158
+ * unless you use this function to wrap your code.
159
+ *
160
+ * @param name - debug name
161
+ * @param callback - callback to run in debug mode
162
+ */
163
+ function parserDebug(name, callback) {
164
+ debugFlag = true;
165
+ stepCount = 0;
166
+ counts = {};
167
+ times = {};
168
+ printTime(name, callback);
169
+ debugFlag = false;
170
+ console.log("\n");
171
+ console.log(`📊 ${name} -- counts:`);
172
+ const sorted = Object.entries(counts).sort((a, b) => b[1] - a[1]);
173
+ for (const [name, count] of sorted) {
174
+ console.log(` ${name}: ${count}`);
175
+ }
176
+ console.log("\n");
177
+ console.log(`📊 ${name} -- times:`);
178
+ const sortedTimes = Object.entries(times).sort((a, b) => b[1] - a[1]);
179
+ for (const [name, time] of sortedTimes) {
180
+ console.log(` ${name}: ${(0, utils_1.round)(time)}ms`);
181
+ }
182
+ console.log("\n");
183
+ console.log(`📊 ${name} -- step count: ${stepCount}`);
184
+ console.log("\n\n");
185
+ stepCount = 0;
186
+ counts = {};
187
+ times = {};
188
+ }
189
+ exports.parserDebug = parserDebug;
42
190
  });
package/dist/types.d.ts CHANGED
@@ -1,13 +1,21 @@
1
+ /** A generic object type. */
1
2
  export type PlainObject = Record<string, unknown>;
3
+ /** Represents a parse success with no captures. */
2
4
  export type ParserSuccess<T> = {
3
5
  success: true;
4
6
  result: T;
5
7
  rest: string;
6
- nextParser?: GeneralParser<any, any>;
8
+ nextParser?: Parser<any>;
7
9
  };
8
- export type CaptureParserSuccess<T, C extends PlainObject> = ParserSuccess<T> & {
10
+ /** Represents a parse success with captures. Notice nextParser is also a CaptureParser. */
11
+ export type CaptureParserSuccess<T, C extends PlainObject> = {
12
+ success: true;
13
+ result: T;
14
+ rest: string;
9
15
  captures: C;
16
+ nextParser?: CaptureParser<any, any>;
10
17
  };
18
+ /** Represents a parse failure. */
11
19
  export type ParserFailure = {
12
20
  success: false;
13
21
  rest: string;
@@ -15,22 +23,46 @@ export type ParserFailure = {
15
23
  };
16
24
  export type ParserResult<T> = ParserSuccess<T> | ParserFailure;
17
25
  export type CaptureParserResult<T, C extends PlainObject> = CaptureParserSuccess<T, C> | ParserFailure;
26
+ /** A parser is any function that takes a string and returns a ParserResult. */
18
27
  export type Parser<T> = (input: string) => ParserResult<T>;
28
+ /** A capture parser is any function that takes a string and returns a CaptureParserResult.
29
+ * A CaptureParserResult is the same as a ParserResult, except it also includes captures,
30
+ * i.e. matches selected using `capture`. */
19
31
  export type CaptureParser<T, C extends PlainObject> = (input: string) => CaptureParserResult<T, C>;
20
32
  export type GeneralParser<T, C extends PlainObject> = Parser<T> | CaptureParser<T, C>;
21
33
  export declare function isCaptureResult<T, C extends PlainObject>(result: ParserResult<T>): result is CaptureParserSuccess<T, C>;
34
+ /** Convenience function to return a ParserSuccess */
22
35
  export declare function success<T>(result: T, rest: string): ParserSuccess<T>;
36
+ /** Convenience function to return a CaptureParserSuccess */
23
37
  export declare function captureSuccess<T, C extends PlainObject>(result: T, rest: string, captures: C): CaptureParserSuccess<T, C>;
38
+ /** Convenience function to return a ParserFailure */
24
39
  export declare function failure(message: string, rest: string): ParserFailure;
40
+ /** Prettify an intersected type, to make it easier to read. */
25
41
  export type Prettify<T> = {
26
42
  [K in keyof T]: T[K];
27
43
  } & {};
44
+ /** see <https://stackoverflow.com/a/50375286/3625> */
28
45
  export type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
46
+ /** Convenience type to get the result type out of a parser. */
29
47
  type ExtractResults<T> = T extends Parser<infer U> ? U : never;
48
+ /** Convenience type to get the capture type out of a capture parser. */
30
49
  type ExtractCaptures<T> = T extends CaptureParser<any, infer U> ? U : never;
50
+ /** Convenience type where given an array of parsers and capture parsers,
51
+ * it returns the types of the capture parsers, like
52
+ * CaptureParser<string, { name: string }> | CaptureParser<number, { age: number }>
53
+ */
31
54
  type ExtractCaptureParsers<T extends readonly GeneralParser<any, any>[]> = Extract<T[number], CaptureParser<any, any>>;
55
+ /** Convenience type where given an array of parsers and capture parsers,
56
+ * it returns a single capture type that merges all the capture types. */
32
57
  export type MergedCaptures<T extends readonly GeneralParser<any, any>[]> = Prettify<UnionToIntersection<UnionOfCaptures<T>>>;
58
+ /** Convenience type where given an array of parsers and capture parsers,
59
+ * it first gets the capture parsers, then extracts the capture types,
60
+ * and returns a union of all the capture types. Example:
61
+ * { name: string } | { age: number }
62
+ */
33
63
  export type UnionOfCaptures<T extends readonly GeneralParser<any, any>[]> = Prettify<ExtractCaptures<ExtractCaptureParsers<T>>>;
64
+ /** Convenience type where given an array of parsers and capture parsers,
65
+ * it returns true if there are any capture parsers, otherwise false. */
34
66
  export type HasCaptureParsers<T extends readonly GeneralParser<any, any>[]> = ExtractCaptureParsers<T> extends never ? false : true;
35
67
  /**
36
68
  * For a given array of GeneralParsers, if any of them is a CaptureParser,
@@ -40,7 +72,18 @@ export type HasCaptureParsers<T extends readonly GeneralParser<any, any>[]> = Ex
40
72
  * which is not able to infer its return type correctly.
41
73
  */
42
74
  export type PickParserType<T extends readonly GeneralParser<any, any>[]> = HasCaptureParsers<T> extends true ? CaptureParser<MergedResults<T>, UnionOfCaptures<T>> : Parser<MergedResults<T>>;
75
+ /** This is used to generate a return type for the `many` and `many1` combinators.
76
+ * Given a parser we want to apply `many` to. Suppose its type is `Parser<string>`.
77
+ * This will return `Parser<string[]>` as the return type.
78
+ *
79
+ * Suppose the parser is a `CaptureParser<string, { name: string }>`.
80
+ * This will return `CaptureParser<string[], { captures: { name: string }[] }>` as the return type.
81
+ */
82
+ export type InferManyReturnType<T extends GeneralParser<any, any>> = T extends CaptureParser<infer R, infer C> ? CaptureParser<R[], {
83
+ captures: C[];
84
+ }> : Parser<T[]>;
43
85
  export type MergedResults<T extends readonly GeneralParser<any, any>[]> = ExtractResults<T[number]>;
86
+ /** Used to create a parser tree for backtracking. */
44
87
  export type Node = ParserNode | EmptyNode;
45
88
  export type ParserNode = {
46
89
  parent: Node;
@@ -50,8 +93,14 @@ export type ParserNode = {
50
93
  closed: boolean;
51
94
  };
52
95
  export type EmptyNode = null;
96
+ /** Convenience function to create a ParserNode. */
53
97
  export declare function createNode(parent: Node | null, parser: GeneralParser<any, any>): ParserNode;
98
+ /** Convenience function where, given an array of parsers, it creates a tree we can use for backtracking.
99
+ * This tree is what `seq` use. It's used to keep track of the parsers we've tried so far,
100
+ * so we can backtrack if we need to.
101
+ */
54
102
  export declare function createTree(parsers: readonly GeneralParser<any, any>[]): Node;
103
+ /** Used by `within`. */
55
104
  export type Matched = {
56
105
  type: "matched";
57
106
  value: string;
@@ -60,5 +109,5 @@ export type Unmatched = {
60
109
  type: "unmatched";
61
110
  value: string;
62
111
  };
63
- export type BetweenWithinResult = Matched | Unmatched;
112
+ export type WithinResult = Matched | Unmatched;
64
113
  export {};
package/dist/types.js CHANGED
@@ -14,18 +14,22 @@
14
14
  return "captures" in result;
15
15
  }
16
16
  exports.isCaptureResult = isCaptureResult;
17
+ /** Convenience function to return a ParserSuccess */
17
18
  function success(result, rest) {
18
19
  return { success: true, result, rest };
19
20
  }
20
21
  exports.success = success;
22
+ /** Convenience function to return a CaptureParserSuccess */
21
23
  function captureSuccess(result, rest, captures) {
22
24
  return { success: true, result, rest, captures };
23
25
  }
24
26
  exports.captureSuccess = captureSuccess;
27
+ /** Convenience function to return a ParserFailure */
25
28
  function failure(message, rest) {
26
29
  return { success: false, message, rest };
27
30
  }
28
31
  exports.failure = failure;
32
+ /** Convenience function to create a ParserNode. */
29
33
  function createNode(parent, parser) {
30
34
  return {
31
35
  parent,
@@ -35,6 +39,10 @@
35
39
  };
36
40
  }
37
41
  exports.createNode = createNode;
42
+ /** Convenience function where, given an array of parsers, it creates a tree we can use for backtracking.
43
+ * This tree is what `seq` use. It's used to keep track of the parsers we've tried so far,
44
+ * so we can backtrack if we need to.
45
+ */
38
46
  function createTree(parsers) {
39
47
  if (parsers.length === 0) {
40
48
  return null;
package/dist/utils.d.ts CHANGED
@@ -4,3 +4,4 @@ export declare function merge(a: any | any[], b: any | any[]): any[];
4
4
  export declare function mergeCaptures(a: Record<string, any>, b: Record<string, any>): Record<string, any>;
5
5
  export declare function findAncestorWithNextParser(node: Node, count?: number): [Node, number];
6
6
  export declare function popMany(arr: any[], count: number): void;
7
+ export declare function round(num: number, places?: number): number;
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.popMany = exports.findAncestorWithNextParser = exports.mergeCaptures = exports.merge = exports.escape = void 0;
12
+ exports.round = 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
  }
@@ -63,4 +63,8 @@
63
63
  }
64
64
  }
65
65
  exports.popMany = popMany;
66
+ function round(num, places = 2) {
67
+ return Math.round(num * 10 ** places) / 10 ** places;
68
+ }
69
+ exports.round = round;
66
70
  });
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "tarsec",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
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
+ "test:tsc": "tsc -p tests/tsconfig.json",
8
9
  "coverage": "vitest --coverage",
9
10
  "build": "rm -rf dist && tsc",
10
11
  "start": "cd dist && node index.js",
@@ -20,7 +21,8 @@
20
21
  "require": "./dist/index.js"
21
22
  }
22
23
  },
23
- "keywords": [],
24
+ "types": "./dist/index.d.ts",
25
+ "keywords": ["parser", "parser combinator", "parsec"],
24
26
  "author": "",
25
27
  "license": "ISC",
26
28
  "devDependencies": {