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.
@@ -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
@@ -0,0 +1,5 @@
1
+ - tests
2
+ - docs
3
+ - ci
4
+ - better perf
5
+ - package for npm
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
@@ -0,0 +1,3 @@
1
+ export * from "./parsers";
2
+ export * from "./combinators";
3
+ export * from "./trace";