tarsec 0.0.1
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/.github/workflows/node.js.yml +31 -0
- package/README.md +64 -0
- package/TODOS.md +5 -0
- package/index.ts +105 -0
- package/lib/combinators.ts +224 -0
- package/lib/index.ts +3 -0
- package/lib/parsers.test.ts +300 -0
- package/lib/parsers.ts +108 -0
- package/lib/trace.ts +40 -0
- package/lib/types.ts +24 -0
- package/lib/utils.ts +33 -0
- package/makefile +5 -0
- package/package.json +17 -0
- package/tests/combinators/many.test.ts +18 -0
- package/tests/combinators/many1.test.ts +20 -0
- package/tests/combinators/or.test.ts +25 -0
- package/tests/combinators/seq.test.ts +38 -0
- package/tests/integration/hello_capture.test.ts +21 -0
- package/tests/integration/hello_world.test.ts +14 -0
- package/tests/parsers/char.test.ts +30 -0
- package/tests/parsers/noneOf.test.ts +19 -0
- package/tests/parsers/oneOf.test.ts +19 -0
- package/tests/parsers/str.test.ts +35 -0
- package/tsconfig.json +105 -0
- package/vitest.config.ts +23 -0
- package/vitest.globals.ts +20 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
|
|
3
|
+
|
|
4
|
+
name: Node.js CI
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [ "main" ]
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: [ "main" ]
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
|
|
17
|
+
strategy:
|
|
18
|
+
matrix:
|
|
19
|
+
node-version: [16.x, 18.x]
|
|
20
|
+
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v3
|
|
24
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
25
|
+
uses: actions/setup-node@v3
|
|
26
|
+
with:
|
|
27
|
+
node-version: ${{ matrix.node-version }}
|
|
28
|
+
cache: 'npm'
|
|
29
|
+
- run: npm install
|
|
30
|
+
- run: npm run build --if-present
|
|
31
|
+
- run: npm test
|
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
```
|
|
2
|
+
__
|
|
3
|
+
/\ \__
|
|
4
|
+
\ \ ,_\ __ _ __ ____ __ ___
|
|
5
|
+
\ \ \/ /'__`\ /\`'__\/',__\ /'__`\ /'___\
|
|
6
|
+
\ \ \_/\ \L\.\_\ \ \//\__, `\/\ __//\ \__/
|
|
7
|
+
\ \__\ \__/.\_\\ \_\\/\____/\ \____\ \____\
|
|
8
|
+
\/__/\/__/\/_/ \/_/ \/___/ \/____/\/____/
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
A parser combinator library for TypeScript, inspired by Parsec.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
npm install egonSchiele/tarsec
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Hello world
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
// define a parser
|
|
23
|
+
const parser = seq([
|
|
24
|
+
str("hello"),
|
|
25
|
+
space,
|
|
26
|
+
str("world")
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
// then use it
|
|
30
|
+
parser("hello world"); // success
|
|
31
|
+
parser("hello there"); // failure
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## A longer example
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
// define a parser to parse "hello, <name>!"
|
|
38
|
+
const parser = seq([
|
|
39
|
+
str("hello"),
|
|
40
|
+
space,
|
|
41
|
+
|
|
42
|
+
/*
|
|
43
|
+
|
|
44
|
+
Capture group to capture the name.
|
|
45
|
+
|
|
46
|
+
`many1(noneOf("!"))` parses one or more characters
|
|
47
|
+
that are not an exclamation mark.
|
|
48
|
+
`many1` returns an array of characters,
|
|
49
|
+
`many1WithJoin` joins them into a string.
|
|
50
|
+
|
|
51
|
+
This capture group is then given the name "person".
|
|
52
|
+
|
|
53
|
+
*/
|
|
54
|
+
capture(many1WithJoin(noneOf("!")), "person"),
|
|
55
|
+
char("!"),
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
// parse
|
|
59
|
+
const result = parser("hello adit!");
|
|
60
|
+
|
|
61
|
+
console.log(result.success); // true
|
|
62
|
+
console.log(result.captures); // { person: "adit" }
|
|
63
|
+
```
|
|
64
|
+
|
package/TODOS.md
ADDED
package/index.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {
|
|
2
|
+
seq,
|
|
3
|
+
capture,
|
|
4
|
+
many1WithJoin,
|
|
5
|
+
many,
|
|
6
|
+
optional,
|
|
7
|
+
or,
|
|
8
|
+
manyWithJoin,
|
|
9
|
+
transform,
|
|
10
|
+
many1,
|
|
11
|
+
captureCaptures,
|
|
12
|
+
shapeCaptures,
|
|
13
|
+
} from "./lib/combinators.js";
|
|
14
|
+
import {
|
|
15
|
+
noneOf,
|
|
16
|
+
space,
|
|
17
|
+
str,
|
|
18
|
+
char,
|
|
19
|
+
quote,
|
|
20
|
+
word,
|
|
21
|
+
quotedString,
|
|
22
|
+
} from "./lib/parsers.js";
|
|
23
|
+
/*
|
|
24
|
+
const helloParser = seq<any, "name">([
|
|
25
|
+
str("hello"),
|
|
26
|
+
space,
|
|
27
|
+
capture<string, "name">(many1WithJoin(noneOf("!")), "name"),
|
|
28
|
+
char("!"),
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
const questionParser = seq<any, "question">([
|
|
32
|
+
capture<string, "question">(many1WithJoin(noneOf("?")), "question"),
|
|
33
|
+
char("?"),
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
const parser = seq<any, "name" | "question">([
|
|
37
|
+
helloParser,
|
|
38
|
+
space,
|
|
39
|
+
questionParser,
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
const result = parser("hello adit! how are you?");
|
|
43
|
+
console.log(result); */
|
|
44
|
+
|
|
45
|
+
/*
|
|
46
|
+
if (result.success) {
|
|
47
|
+
console.log(result.captures?.name);
|
|
48
|
+
} */
|
|
49
|
+
|
|
50
|
+
const input = `terraform {
|
|
51
|
+
required_providers {
|
|
52
|
+
aws = {
|
|
53
|
+
source = "hashicorp"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}`;
|
|
57
|
+
|
|
58
|
+
const line = seq<any, string>(
|
|
59
|
+
[
|
|
60
|
+
many(space),
|
|
61
|
+
capture(word, "key"),
|
|
62
|
+
many(space),
|
|
63
|
+
char("="),
|
|
64
|
+
many(space),
|
|
65
|
+
capture(quotedString, "value"),
|
|
66
|
+
],
|
|
67
|
+
"line"
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
let block: any = char("{");
|
|
71
|
+
block = shapeCaptures(
|
|
72
|
+
seq<any, string>(
|
|
73
|
+
[
|
|
74
|
+
manyWithJoin(space),
|
|
75
|
+
capture(
|
|
76
|
+
or(
|
|
77
|
+
[str("terraform"), str("required_providers"), str("aws")],
|
|
78
|
+
"blockName"
|
|
79
|
+
),
|
|
80
|
+
"blockName"
|
|
81
|
+
),
|
|
82
|
+
space,
|
|
83
|
+
optional(str("= ")),
|
|
84
|
+
char("{"),
|
|
85
|
+
manyWithJoin(space),
|
|
86
|
+
// this works
|
|
87
|
+
or([line, (x) => block(x)], "line or block"),
|
|
88
|
+
// but this doesnt
|
|
89
|
+
// many(or([line, (x) => block(x)], "line or block")),
|
|
90
|
+
// nor this
|
|
91
|
+
// many1(or([line, (x) => block(x)], "line or block")),
|
|
92
|
+
manyWithJoin(space),
|
|
93
|
+
char("}"),
|
|
94
|
+
],
|
|
95
|
+
"block"
|
|
96
|
+
),
|
|
97
|
+
({ blockName }) => ({ blockName }),
|
|
98
|
+
"block"
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const result2 = block(input);
|
|
102
|
+
console.log(result2);
|
|
103
|
+
if (result2.success) {
|
|
104
|
+
console.log(JSON.stringify(result2.captures, null, 2));
|
|
105
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { trace } from "./trace";
|
|
2
|
+
import { Parser, ParserSuccess } from "./types";
|
|
3
|
+
import { escape, merge, mergeCaptures } from "./utils";
|
|
4
|
+
|
|
5
|
+
export function many<T>(parser: Parser<T>): Parser<T[]> {
|
|
6
|
+
return trace("many", (input: string) => {
|
|
7
|
+
let match: T[] = [];
|
|
8
|
+
let rest = input;
|
|
9
|
+
while (true) {
|
|
10
|
+
let result = parser(rest);
|
|
11
|
+
if (!result.success) {
|
|
12
|
+
return { success: true, match, rest };
|
|
13
|
+
}
|
|
14
|
+
match.push(result.match);
|
|
15
|
+
rest = result.rest;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function many1<T>(parser: Parser<T>): Parser<T[]> {
|
|
21
|
+
return trace(`many1`, (input: string) => {
|
|
22
|
+
let result = many(parser)(input);
|
|
23
|
+
// this logic doesn't work with optional and not
|
|
24
|
+
if (result.rest !== input) {
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
success: false,
|
|
29
|
+
rest: input,
|
|
30
|
+
message: "expected at least one match",
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function manyWithJoin(parser: Parser<string>): Parser<string> {
|
|
36
|
+
return transform<string[], string>(many(parser), (x) => x.join(""));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function many1WithJoin(parser: Parser<string>): Parser<string> {
|
|
40
|
+
return transform<string[], string>(many1(parser), (x) => x.join(""));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function or<T>(parsers: Parser<T>[], name: string = ""): Parser<T> {
|
|
44
|
+
return trace(`or(${name})`, (input: string) => {
|
|
45
|
+
for (let parser of parsers) {
|
|
46
|
+
let result = parser(input);
|
|
47
|
+
if (result.success) {
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
rest: input,
|
|
54
|
+
message: "all parsers failed",
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function optional<T>(parser: Parser<T>): Parser<T | null> {
|
|
60
|
+
return trace<T | null>("optional", (input: string) => {
|
|
61
|
+
let result = parser(input);
|
|
62
|
+
if (result.success) {
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
return { success: true, match: null, rest: input };
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function not(parser: Parser<any>): Parser<null> {
|
|
70
|
+
return trace("not", (input: string) => {
|
|
71
|
+
let result = parser(input);
|
|
72
|
+
if (result.success) {
|
|
73
|
+
return {
|
|
74
|
+
success: false,
|
|
75
|
+
rest: input,
|
|
76
|
+
message: "unexpected match",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return { success: true, match: null, rest: input };
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function between<O, C, P>(
|
|
84
|
+
open: Parser<O>,
|
|
85
|
+
close: Parser<C>,
|
|
86
|
+
parser: Parser<P>
|
|
87
|
+
): Parser<P> {
|
|
88
|
+
return (input: string) => {
|
|
89
|
+
const result1 = open(input);
|
|
90
|
+
if (!result1.success) {
|
|
91
|
+
return result1;
|
|
92
|
+
}
|
|
93
|
+
const parserResult = parser(result1.rest);
|
|
94
|
+
if (!parserResult.success) {
|
|
95
|
+
return parserResult;
|
|
96
|
+
}
|
|
97
|
+
const result2 = close(parserResult.rest);
|
|
98
|
+
if (!result2.success) {
|
|
99
|
+
return result2;
|
|
100
|
+
}
|
|
101
|
+
return { success: true, match: parserResult.match, rest: result2.rest };
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function sepBy<S, P>(
|
|
106
|
+
separator: Parser<S>,
|
|
107
|
+
parser: Parser<P>
|
|
108
|
+
): Parser<P[]> {
|
|
109
|
+
return (input: string) => {
|
|
110
|
+
let match: P[] = [];
|
|
111
|
+
let rest = input;
|
|
112
|
+
while (true) {
|
|
113
|
+
const result = parser(rest);
|
|
114
|
+
if (!result.success) {
|
|
115
|
+
return { success: true, match, rest };
|
|
116
|
+
}
|
|
117
|
+
match.push(result.match);
|
|
118
|
+
rest = result.rest;
|
|
119
|
+
|
|
120
|
+
const sepResult = separator(rest);
|
|
121
|
+
if (!sepResult.success) {
|
|
122
|
+
return { success: true, match, rest };
|
|
123
|
+
}
|
|
124
|
+
rest = sepResult.rest;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function seq<M, C extends string>(
|
|
130
|
+
parsers: Parser<M>[],
|
|
131
|
+
name: string = ""
|
|
132
|
+
): Parser<M[], C> {
|
|
133
|
+
return trace(`seq(${name})`, (input: string) => {
|
|
134
|
+
let match: M[] = [];
|
|
135
|
+
let rest = input;
|
|
136
|
+
// @ts-ignore
|
|
137
|
+
let captures: Record<U, any> = {};
|
|
138
|
+
for (let parser of parsers) {
|
|
139
|
+
let result = parser(rest);
|
|
140
|
+
if (!result.success) {
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
match.push(result.match);
|
|
144
|
+
rest = result.rest;
|
|
145
|
+
if (result.captures) {
|
|
146
|
+
captures = mergeCaptures(captures, result.captures);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return { success: true, match, rest, captures };
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function capture<M, C extends string>(
|
|
154
|
+
parser: Parser<M>,
|
|
155
|
+
name: string
|
|
156
|
+
): Parser<M, C> {
|
|
157
|
+
return trace(`captures(${escape(name)})`, (input: string) => {
|
|
158
|
+
let result = parser(input);
|
|
159
|
+
if (result.success) {
|
|
160
|
+
const captures: Record<string, any> = {
|
|
161
|
+
[name]: result.match,
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
...result,
|
|
165
|
+
captures: mergeCaptures(result.captures || {}, captures),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function captureCaptures<M, C extends string>(
|
|
173
|
+
parser: Parser<M>,
|
|
174
|
+
name: string
|
|
175
|
+
): Parser<M, C> {
|
|
176
|
+
return trace(`captures(${escape(name)})`, (input: string) => {
|
|
177
|
+
let result = parser(input);
|
|
178
|
+
if (result.success) {
|
|
179
|
+
const captures: Record<string, any> = {
|
|
180
|
+
[name]: result.captures,
|
|
181
|
+
};
|
|
182
|
+
return {
|
|
183
|
+
...result,
|
|
184
|
+
captures: mergeCaptures(result.captures || {}, captures),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
return result;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function shapeCaptures<M, C extends string>(
|
|
192
|
+
parser: Parser<M>,
|
|
193
|
+
func: (captures: Record<string, any>) => Record<string, any>,
|
|
194
|
+
name: string
|
|
195
|
+
): Parser<M, C> {
|
|
196
|
+
return trace(`captures(${escape(name)})`, (input: string) => {
|
|
197
|
+
let result = parser(input);
|
|
198
|
+
if (result.success) {
|
|
199
|
+
const captures: Record<string, any> = result.captures || {};
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
...result,
|
|
203
|
+
captures: func(captures),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
return result;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function transform<T, X>(
|
|
211
|
+
parser: Parser<T>,
|
|
212
|
+
transformerFunc: (x: T) => X
|
|
213
|
+
): Parser<X> {
|
|
214
|
+
return trace(`transform(${transformerFunc})`, (input: string) => {
|
|
215
|
+
let result = parser(input);
|
|
216
|
+
if (result.success) {
|
|
217
|
+
return {
|
|
218
|
+
...result,
|
|
219
|
+
match: transformerFunc(result.match),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return result;
|
|
223
|
+
});
|
|
224
|
+
}
|
package/lib/index.ts
ADDED