tarsec 0.0.11 → 0.0.13
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/dist/combinators/seq.d.ts +1 -2
- package/dist/combinators/seq.js +1 -112
- package/dist/combinators.d.ts +135 -8
- package/dist/combinators.js +337 -86
- package/dist/parsers/within.d.ts +2 -0
- package/dist/parsers/within.js +51 -0
- package/dist/parsers.d.ts +16 -1
- package/dist/parsers.js +30 -5
- package/dist/trace.d.ts +3 -0
- package/dist/trace.js +70 -8
- package/dist/types.d.ts +63 -4
- package/dist/types.js +8 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +5 -1
- package/package.json +2 -1
- package/dist/parsers/betweenWithin.d.ts +0 -2
- package/dist/parsers/betweenWithin.js +0 -68
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
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>;
|
|
1
|
+
export {};
|
package/dist/combinators/seq.js
CHANGED
|
@@ -4,120 +4,9 @@
|
|
|
4
4
|
if (v !== undefined) module.exports = v;
|
|
5
5
|
}
|
|
6
6
|
else if (typeof define === "function" && define.amd) {
|
|
7
|
-
define(["require", "exports"
|
|
7
|
+
define(["require", "exports"], factory);
|
|
8
8
|
}
|
|
9
9
|
})(function (require, exports) {
|
|
10
10
|
"use strict";
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.seq = void 0;
|
|
13
|
-
const trace_1 = require("@/lib/trace");
|
|
14
|
-
const types_1 = require("@/lib/types");
|
|
15
|
-
const utils_1 = require("@/lib/utils");
|
|
16
|
-
/*
|
|
17
|
-
To add backtracking support requires a fairly big change. Here's an example that needs backtracking.
|
|
18
|
-
|
|
19
|
-
```ts
|
|
20
|
-
const parser = seq([
|
|
21
|
-
str("hello "),
|
|
22
|
-
or([str("world"), str("world!")]),
|
|
23
|
-
optional("?")
|
|
24
|
-
], getResults);
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
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.
|
|
28
|
-
|
|
29
|
-
1. instead of just processing these parsers sequentially in a for loop, we need to model them as a tree
|
|
30
|
-
2. the OR parser needs to let us know that there are other branches to try.
|
|
31
|
-
|
|
32
|
-
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")])`.
|
|
33
|
-
|
|
34
|
-
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.
|
|
35
|
-
|
|
36
|
-
```ts
|
|
37
|
-
parent: Node;
|
|
38
|
-
parser: GeneralParser<any, any> | null;
|
|
39
|
-
child: Node;
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
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.
|
|
43
|
-
|
|
44
|
-
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.
|
|
45
|
-
|
|
46
|
-
So, going back to the hello world example, let's say we're stuck at the `optional`:
|
|
47
|
-
|
|
48
|
-
```ts
|
|
49
|
-
const parser = seq([
|
|
50
|
-
str("hello "),
|
|
51
|
-
or([str("world"), str("world!")]),
|
|
52
|
-
optional("?")
|
|
53
|
-
], getResults);
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
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.
|
|
57
|
-
|
|
58
|
-
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.
|
|
59
|
-
|
|
60
|
-
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.
|
|
61
|
-
|
|
62
|
-
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?
|
|
63
|
-
|
|
64
|
-
This is where the final key on a tree node comes in. Nodes also have an optional `input` key.
|
|
65
|
-
|
|
66
|
-
IF a parser succeeds, and
|
|
67
|
-
IF there's a nextParser,
|
|
68
|
-
We know we may come back to this node. So we save the current input as `.input` on the node.
|
|
69
|
-
|
|
70
|
-
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.
|
|
71
|
-
|
|
72
|
-
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.
|
|
73
|
-
*/
|
|
74
|
-
function seq(parsers, transform, debugName = "") {
|
|
75
|
-
return (0, trace_1.trace)(`seq(${debugName})`, (input) => {
|
|
76
|
-
const results = [];
|
|
77
|
-
let rest = input;
|
|
78
|
-
const captures = {};
|
|
79
|
-
const rootNode = (0, types_1.createTree)(parsers);
|
|
80
|
-
let current = rootNode;
|
|
81
|
-
while (current) {
|
|
82
|
-
const parser = current.parser;
|
|
83
|
-
if (!parser) {
|
|
84
|
-
console.log({ current, parser, results, captures });
|
|
85
|
-
throw new Error("parser is null");
|
|
86
|
-
}
|
|
87
|
-
const parsed = parser(rest);
|
|
88
|
-
current.closed = true;
|
|
89
|
-
/* console.log({ parsed }); */
|
|
90
|
-
if (!parsed.success) {
|
|
91
|
-
const [ancestor, count] = (0, utils_1.findAncestorWithNextParser)(current);
|
|
92
|
-
if (ancestor) {
|
|
93
|
-
current = ancestor;
|
|
94
|
-
rest = ancestor.input;
|
|
95
|
-
(0, utils_1.popMany)(results, count);
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
// don't consume input if we're failing
|
|
100
|
-
return Object.assign(Object.assign({}, parsed), { rest: input });
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
results.push(parsed.result);
|
|
104
|
-
if (parsed.nextParser) {
|
|
105
|
-
/* console.log("setting next parser", parsed.nextParser); */
|
|
106
|
-
current.parser = parsed.nextParser;
|
|
107
|
-
current.input = rest;
|
|
108
|
-
current.closed = false;
|
|
109
|
-
}
|
|
110
|
-
rest = parsed.rest;
|
|
111
|
-
if ((0, types_1.isCaptureResult)(parsed)) {
|
|
112
|
-
for (const key in parsed.captures) {
|
|
113
|
-
captures[key] = parsed.captures[key];
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
current = current.child;
|
|
117
|
-
}
|
|
118
|
-
const result = transform(results, captures);
|
|
119
|
-
return (0, types_1.success)(result, rest);
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
exports.seq = seq;
|
|
123
12
|
});
|
package/dist/combinators.d.ts
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
+
* Fails on empty strings
|
|
11
|
+
* @param parser - parser to run
|
|
12
|
+
* @returns - parser that runs the given parser zero to many times,
|
|
13
|
+
* and returns the result as an array
|
|
14
|
+
*/
|
|
15
|
+
export declare function many<const T extends GeneralParser<any, any>>(parser: T): InferManyReturnType<T>;
|
|
16
|
+
/**
|
|
17
|
+
* Same as `many`, but fails if the parser doesn't match at least once.
|
|
18
|
+
*
|
|
19
|
+
* @param parser - parser to run
|
|
20
|
+
* @returns a parser that runs the given parser one to many times,
|
|
21
|
+
*/
|
|
22
|
+
export declare function many1<const T extends GeneralParser<any, any>>(parser: T): InferManyReturnType<T>;
|
|
5
23
|
/**
|
|
6
24
|
* Takes a parser, runs it n times, and returns the results as an array.
|
|
7
25
|
* If it cannot run the parser n times, it fails without consuming input.
|
|
@@ -10,15 +28,124 @@ export declare function many1<T>(parser: Parser<T>): Parser<T[]>;
|
|
|
10
28
|
* @returns - parser that runs the given parser `num` times and returns an array of the results
|
|
11
29
|
*/
|
|
12
30
|
export declare function count<T>(num: number, parser: Parser<T>): Parser<T[]>;
|
|
13
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Same as `many`, but joins the results into a single string.
|
|
33
|
+
*
|
|
34
|
+
* @param parser - parser to run. The parser must return a string as its result.
|
|
35
|
+
* @returns - parser that runs the given parser zero to many times,
|
|
36
|
+
* and returns the result as a single string
|
|
37
|
+
*/
|
|
38
|
+
export declare function manyWithJoin<const T extends GeneralParser<string, any>>(parser: T): GeneralParser<string, any>;
|
|
39
|
+
/**
|
|
40
|
+
* Same as `many1`, but joins the results into a single string.
|
|
41
|
+
*
|
|
42
|
+
* @param parser - parser to run. The parser must return a string as its result.
|
|
43
|
+
* @returns - parser that runs the given parser one to many times,
|
|
44
|
+
* and returns the result as a single string
|
|
45
|
+
*/
|
|
14
46
|
export declare function many1WithJoin(parser: Parser<string>): Parser<string>;
|
|
15
|
-
|
|
47
|
+
/**
|
|
48
|
+
* `or` takes an array of parsers and runs them sequentially.
|
|
49
|
+
* It returns the results of the first parser that succeeds.
|
|
50
|
+
* You can use `capture` in an `or`:
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* const parser = or(capture(digit, "num"), capture(word, "name"));
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* `or` supports backtracking by returning a `nextParser`:
|
|
57
|
+
*
|
|
58
|
+
* ```ts
|
|
59
|
+
* const parser = or(str("hello"), str("hello!"));
|
|
60
|
+
*
|
|
61
|
+
* // this will match the first parser
|
|
62
|
+
* const result = parser("hello");
|
|
63
|
+
*
|
|
64
|
+
* // but or returns the untried parsers as a new parser
|
|
65
|
+
* result.nextParser("hello!"); // works
|
|
66
|
+
*
|
|
67
|
+
* // result.nextParser is the same as or(str("hello!"))
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @param parsers - parsers to try
|
|
71
|
+
* @returns - a parser that tries each parser in order. Returns the result of the first parser that succeeds.
|
|
72
|
+
*/
|
|
73
|
+
export declare function or<const T extends readonly GeneralParser<any, any>[]>(...parsers: T): PickParserType<T>;
|
|
74
|
+
/**
|
|
75
|
+
* Takes a parser and runs it. If the parser fails,
|
|
76
|
+
* optional returns a success with a null result.
|
|
77
|
+
*
|
|
78
|
+
* @param parser - parser to run
|
|
79
|
+
* @returns - a parser that runs the given parser.
|
|
80
|
+
* If it fails, returns a success with a null result.
|
|
81
|
+
*/
|
|
16
82
|
export declare function optional<T>(parser: Parser<T>): Parser<T | null>;
|
|
83
|
+
/**
|
|
84
|
+
* Takes a parser and runs it. If the parser fails,
|
|
85
|
+
* `not` returns a success with a `null` result.
|
|
86
|
+
* If the parser succeeds, `not` returns a failure.
|
|
87
|
+
*
|
|
88
|
+
* @param parser - parser to run
|
|
89
|
+
* @returns - a parser that runs the given parser.
|
|
90
|
+
* If it fails, returns a success with a `null` result.
|
|
91
|
+
* If it succeeds, returns a failure.
|
|
92
|
+
*/
|
|
17
93
|
export declare function not(parser: Parser<any>): Parser<null>;
|
|
94
|
+
/**
|
|
95
|
+
* Takes three parsers, `open`, `close`, and `parser`.
|
|
96
|
+
* `between` matches something that matches `parser`,
|
|
97
|
+
* surrounded by `open` and `close`. It returns the result of `parser`.
|
|
98
|
+
* If any of the parsers fail, `between` fails.
|
|
99
|
+
*
|
|
100
|
+
* @param open - parser for the opening delimiter
|
|
101
|
+
* @param close - parser for the closing delimiter
|
|
102
|
+
* @param parser - parser for the content
|
|
103
|
+
* @returns a parser that returns the result of `parser`.
|
|
104
|
+
*/
|
|
18
105
|
export declare function between<O, C, P>(open: Parser<O>, close: Parser<C>, parser: Parser<P>): Parser<P>;
|
|
19
106
|
export declare function sepBy<S, P>(separator: Parser<S>, parser: Parser<P>): Parser<P[]>;
|
|
20
107
|
export declare function getResults<R, C>(results: R, captures: C): R;
|
|
21
108
|
export declare function getCaptures<R, C>(results: R, captures: C): C;
|
|
22
109
|
export declare function capture<T, const S extends string>(parser: Parser<T>, name: S): CaptureParser<T, Record<S, T>>;
|
|
23
|
-
|
|
24
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Returns a parser that consumes input till the given parser succeeds.
|
|
112
|
+
* @param parser - the stop parser
|
|
113
|
+
* @returns a parser that consumes the input string until the stop parser succeeds.
|
|
114
|
+
* Then it returns the consumed input as a string.
|
|
115
|
+
* The stop parser's match is not included in the result.
|
|
116
|
+
*/
|
|
117
|
+
export declare function manyTill<T>(parser: Parser<T>): Parser<string>;
|
|
118
|
+
export declare function many1Till<T>(parser: Parser<T>): Parser<string>;
|
|
119
|
+
export declare function manyTillStr(str: string): Parser<string>;
|
|
120
|
+
export declare function transform<R, C extends PlainObject, X>(parser: GeneralParser<R, C>, transformerFunc: (x: R) => X): GeneralParser<X, C>;
|
|
121
|
+
export declare function search(parser: Parser<string>): Parser<string[]>;
|
|
122
|
+
/**
|
|
123
|
+
* seq takes an array of parsers and runs them sequentially.
|
|
124
|
+
* If any of the parsers fail, seq fails without consuming any input.
|
|
125
|
+
*
|
|
126
|
+
* The second argument to seq is a function.
|
|
127
|
+
* The first argument of that function is an array of results:
|
|
128
|
+
* one result from each of the parsers you gave to seq.
|
|
129
|
+
* The second is an object containing any captures.
|
|
130
|
+
* You can use this second argument, the transformer function,
|
|
131
|
+
* to transform these however you want and return a result
|
|
132
|
+
*
|
|
133
|
+
* Tarsec includes the utility functions `getResults` and `getCaptures`
|
|
134
|
+
* to just return the results array or captures object respectively for you.
|
|
135
|
+
*
|
|
136
|
+
* Finally, you don't need to use seq at all. You can just hand write the logic.
|
|
137
|
+
* But you'll need to do the error handling
|
|
138
|
+
* and pass the remaining input to the next parser yourself.
|
|
139
|
+
* seq also does some backtracking for you that you will need to do yourself.
|
|
140
|
+
*
|
|
141
|
+
* Also see `seqR` and `seqC` for convenience functions that return the results or captures respectively.
|
|
142
|
+
*
|
|
143
|
+
* @param parsers - parsers to run sequentially
|
|
144
|
+
* @param transform - function to transform the results and captures. The params are the results and captures
|
|
145
|
+
* @param debugName - optional name for trace debugging
|
|
146
|
+
* @returns
|
|
147
|
+
*/
|
|
148
|
+
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>;
|
|
149
|
+
export declare function seqR<const T extends readonly GeneralParser<any, any>[]>(...parsers: T): Parser<MergedResults<T>[]>;
|
|
150
|
+
export declare function seqC<const T extends readonly GeneralParser<any, any>[]>(...parsers: T): Parser<MergedCaptures<T>>;
|
|
151
|
+
export declare function match(input: string, parser: GeneralParser<any, any>): boolean;
|