tarsec 0.0.22 → 0.1.0

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.
@@ -98,18 +98,51 @@ export declare function optional<T>(parser: Parser<T>): Parser<T | null>;
98
98
  export declare function not(parser: Parser<any>): Parser<null>;
99
99
  /**
100
100
  * Takes three parsers, `open`, `close`, and `parser`.
101
- * `between` matches something that matches `parser`,
101
+ * `between` matches multiple instances of `parser`,
102
102
  * surrounded by `open` and `close`. It returns the result of `parser`.
103
- * If any of the parsers fail, `between` fails.
104
103
  *
104
+ * This is a look-ahead combinator. It keeps trying the `close` parser until it succeeds.
105
+ * That means you can use it like this, and `parser` won't consume the ending quote:
106
+ *
107
+ * ```ts
108
+ * const quotedString = between(quote, quote, anyChar);
109
+ * ```
110
+ *
111
+ * This combinator succeeds even if it parses zero instances of `parser`.
112
+ * So for the above parser, this input would succeed: `""`.
113
+ *
114
+ * If you want it to consume at least one instance of `parser`, use `between1`.
115
+ *
116
+ * Even though this is a look-ahead combinator, it won't work if you supply a greedy parser.
117
+ * You can make the above parser fail simply by adding `many1`:
118
+ *
119
+ * ```ts
120
+ * const quotedString = between(quote, quote, many1(anyChar));
121
+ * ```
122
+ *
123
+ * `many1(anyChar)` will consume all input until the end of the string,
124
+ * not giving the `close` parser a chance to succeed.
125
+ *
126
+ * This combinator is obviously expensive, as it applies the `close` parser at every step.
127
+ * This combinator can also get into an infinite loop if `parser` succeeds without consuming any input.
128
+ *
129
+ * @param open - parser for the opening delimiter
130
+ * @param close - parser for the closing delimiter
131
+ * @param parser - parser for the content
132
+ * @returns the result of `parser`.
133
+ */
134
+ export declare function between<O, C, P>(open: Parser<O>, close: Parser<C>, parser: Parser<P>): Parser<P[]>;
135
+ /**
136
+ * Like `between`, but fails unless it consumes at least one instance of the `parser`.
105
137
  * @param open - parser for the opening delimiter
106
138
  * @param close - parser for the closing delimiter
107
139
  * @param parser - parser for the content
108
- * @returns a parser that returns the result of `parser`.
140
+ * @returns the result of `parser`.
109
141
  */
110
- export declare function between<O, C, P>(open: Parser<O>, close: Parser<C>, parser: Parser<P>): Parser<P>;
142
+ export declare function between1<O, C, P>(open: Parser<O>, close: Parser<C>, parser: Parser<P>): Parser<P[]>;
111
143
  /**
112
144
  * Parses many instances of the parser separated by separator.
145
+ * The parser needs to succeed at least once, otherwise sepBy fails.
113
146
  * @param separator
114
147
  * @param parser
115
148
  * @returns a parser that runs the given parser zero to many times, separated by the separator parser.
@@ -293,10 +326,10 @@ export declare function map<R, C extends PlainObject, X>(parser: GeneralParser<R
293
326
  * The rest of the input that isn't part of the result is simply joined together and returned as a string.
294
327
  * If you need a more structured result + rest, you can use `within` instead.
295
328
  *
296
- * @param parser - a parser that returns a string
329
+ * @param parser - the parser to search for
297
330
  * @returns - a parser that returns an array of strings
298
331
  */
299
- export declare function search(parser: Parser<string>): Parser<string[]>;
332
+ export declare function search<T>(parser: Parser<T>): Parser<T[]>;
300
333
  /**
301
334
  * seq takes an array of parsers and runs them sequentially.
302
335
  * If any of the parsers fail, seq fails without consuming any input.
@@ -221,14 +221,38 @@ export function not(parser) {
221
221
  }
222
222
  /**
223
223
  * Takes three parsers, `open`, `close`, and `parser`.
224
- * `between` matches something that matches `parser`,
224
+ * `between` matches multiple instances of `parser`,
225
225
  * surrounded by `open` and `close`. It returns the result of `parser`.
226
- * If any of the parsers fail, `between` fails.
226
+ *
227
+ * This is a look-ahead combinator. It keeps trying the `close` parser until it succeeds.
228
+ * That means you can use it like this, and `parser` won't consume the ending quote:
229
+ *
230
+ * ```ts
231
+ * const quotedString = between(quote, quote, anyChar);
232
+ * ```
233
+ *
234
+ * This combinator succeeds even if it parses zero instances of `parser`.
235
+ * So for the above parser, this input would succeed: `""`.
236
+ *
237
+ * If you want it to consume at least one instance of `parser`, use `between1`.
238
+ *
239
+ * Even though this is a look-ahead combinator, it won't work if you supply a greedy parser.
240
+ * You can make the above parser fail simply by adding `many1`:
241
+ *
242
+ * ```ts
243
+ * const quotedString = between(quote, quote, many1(anyChar));
244
+ * ```
245
+ *
246
+ * `many1(anyChar)` will consume all input until the end of the string,
247
+ * not giving the `close` parser a chance to succeed.
248
+ *
249
+ * This combinator is obviously expensive, as it applies the `close` parser at every step.
250
+ * This combinator can also get into an infinite loop if `parser` succeeds without consuming any input.
227
251
  *
228
252
  * @param open - parser for the opening delimiter
229
253
  * @param close - parser for the closing delimiter
230
254
  * @param parser - parser for the content
231
- * @returns a parser that returns the result of `parser`.
255
+ * @returns the result of `parser`.
232
256
  */
233
257
  export function between(open, close, parser) {
234
258
  return (input) => {
@@ -236,19 +260,47 @@ export function between(open, close, parser) {
236
260
  if (!result1.success) {
237
261
  return result1;
238
262
  }
239
- const parserResult = parser(result1.rest);
240
- if (!parserResult.success) {
241
- return parserResult;
263
+ let rest = result1.rest;
264
+ // keep looking for close
265
+ let result2 = close(rest);
266
+ let successResult = [];
267
+ // while we don't succeed, keep trying to parse with parser
268
+ while (!result2.success) {
269
+ const parserResult = parser(rest);
270
+ // the parser should keep succeeding until we find the closer
271
+ // if it doesn't, we fail
272
+ if (!parserResult.success) {
273
+ return failure(parserResult.message, input);
274
+ }
275
+ successResult.push(parserResult.result);
276
+ rest = parserResult.rest;
277
+ result2 = close(rest);
242
278
  }
243
- const result2 = close(parserResult.rest);
244
- if (!result2.success) {
245
- return result2;
279
+ // we found the closer. Note we consume the closer and return the rest
280
+ return success(successResult, result2.rest);
281
+ };
282
+ }
283
+ /**
284
+ * Like `between`, but fails unless it consumes at least one instance of the `parser`.
285
+ * @param open - parser for the opening delimiter
286
+ * @param close - parser for the closing delimiter
287
+ * @param parser - parser for the content
288
+ * @returns the result of `parser`.
289
+ */
290
+ export function between1(open, close, parser) {
291
+ return (input) => {
292
+ const result = between(open, close, parser)(input);
293
+ if (result.success) {
294
+ if (result.result.length === 0) {
295
+ return failure("expected at least one match", input);
296
+ }
246
297
  }
247
- return success(parserResult.result, result2.rest);
298
+ return result;
248
299
  };
249
300
  }
250
301
  /**
251
302
  * Parses many instances of the parser separated by separator.
303
+ * The parser needs to succeed at least once, otherwise sepBy fails.
252
304
  * @param separator
253
305
  * @param parser
254
306
  * @returns a parser that runs the given parser zero to many times, separated by the separator parser.
@@ -260,13 +312,23 @@ export function sepBy(separator, parser) {
260
312
  while (true) {
261
313
  const result = parser(rest);
262
314
  if (!result.success) {
263
- return success(results, rest);
315
+ if (results.length === 0) {
316
+ return failure(result.message, input);
317
+ }
318
+ else {
319
+ return success(results, rest);
320
+ }
264
321
  }
265
322
  results.push(result.result);
266
323
  rest = result.rest;
267
324
  const sepResult = separator(rest);
268
325
  if (!sepResult.success) {
269
- return success(results, rest);
326
+ if (results.length === 0) {
327
+ return failure(sepResult.message, input);
328
+ }
329
+ else {
330
+ return success(results, rest);
331
+ }
270
332
  }
271
333
  rest = sepResult.rest;
272
334
  }
@@ -531,7 +593,7 @@ export function map(parser, mapperFunc) {
531
593
  * The rest of the input that isn't part of the result is simply joined together and returned as a string.
532
594
  * If you need a more structured result + rest, you can use `within` instead.
533
595
  *
534
- * @param parser - a parser that returns a string
596
+ * @param parser - the parser to search for
535
597
  * @returns - a parser that returns an array of strings
536
598
  */
537
599
  export function search(parser) {
@@ -545,6 +607,7 @@ export function search(parser) {
545
607
  .filter((x) => x.type === "unmatched")
546
608
  .map((x) => x.value)
547
609
  .join(" ");
610
+ // @ts-ignore
548
611
  return success(result, rest);
549
612
  }
550
613
  return success([], input);
@@ -1,2 +1,59 @@
1
1
  import { WithinResult, Parser } from "../types.js";
2
- export declare function within(parser: Parser<string>): Parser<WithinResult[]>;
2
+ /**
3
+ * `within` is a funny combinator. It finds zero or more instances of `parser` within the input.
4
+ * It always succeeds and returns an array of results, each one being a matched or unmatched string.
5
+ * You could think of "within" as a glorified search. For example, you can use it to
6
+ * look for quoted text or multiline comments within an input.
7
+ *
8
+ * Example:
9
+ *
10
+ * ```ts
11
+ * const multilineComments = seq(
12
+ * [str("/*"), manyWithJoin(noneOf(`*\/`)), str("*\/")],
13
+ * (results: string[]) => results.join("")
14
+ * );
15
+ * const parser = within(multilineComments);
16
+ * const input = `code before /* this is a comment *\/ code after /* another comment *\/ end`;
17
+ *
18
+ * const result = parser(input);
19
+ * console.log(JSON.stringify(result, null, 2));
20
+ * ```
21
+ *
22
+ * Result:
23
+ *
24
+ * ```json
25
+ * {
26
+ * "success": true,
27
+ * "result": [
28
+ * {
29
+ * "type": "unmatched",
30
+ * "value": "code before "
31
+ * },
32
+ * {
33
+ * "type": "matched",
34
+ * "value": "/* this is a comment *\/"
35
+ * },
36
+ * {
37
+ * "type": "unmatched",
38
+ * "value": " code after "
39
+ * },
40
+ * {
41
+ * "type": "matched",
42
+ * "value": "/* another comment *\/"
43
+ * },
44
+ * {
45
+ * "type": "unmatched",
46
+ * "value": " end"
47
+ * }
48
+ * ],
49
+ * "rest": ""
50
+ * }
51
+ * ```
52
+ *
53
+ * This parser is somewhat expensive, as it will go down the input string one character
54
+ * at a time, applying the given parser each time.
55
+ *
56
+ * @param parser - parser to find within the input
57
+ * @returns - an array of matched and unmatched strings
58
+ */
59
+ export declare function within<T>(parser: Parser<T>): Parser<WithinResult<T>[]>;
@@ -1,5 +1,62 @@
1
1
  import { trace } from "../trace.js";
2
2
  import { success } from "../types.js";
3
+ /**
4
+ * `within` is a funny combinator. It finds zero or more instances of `parser` within the input.
5
+ * It always succeeds and returns an array of results, each one being a matched or unmatched string.
6
+ * You could think of "within" as a glorified search. For example, you can use it to
7
+ * look for quoted text or multiline comments within an input.
8
+ *
9
+ * Example:
10
+ *
11
+ * ```ts
12
+ * const multilineComments = seq(
13
+ * [str("/*"), manyWithJoin(noneOf(`*\/`)), str("*\/")],
14
+ * (results: string[]) => results.join("")
15
+ * );
16
+ * const parser = within(multilineComments);
17
+ * const input = `code before /* this is a comment *\/ code after /* another comment *\/ end`;
18
+ *
19
+ * const result = parser(input);
20
+ * console.log(JSON.stringify(result, null, 2));
21
+ * ```
22
+ *
23
+ * Result:
24
+ *
25
+ * ```json
26
+ * {
27
+ * "success": true,
28
+ * "result": [
29
+ * {
30
+ * "type": "unmatched",
31
+ * "value": "code before "
32
+ * },
33
+ * {
34
+ * "type": "matched",
35
+ * "value": "/* this is a comment *\/"
36
+ * },
37
+ * {
38
+ * "type": "unmatched",
39
+ * "value": " code after "
40
+ * },
41
+ * {
42
+ * "type": "matched",
43
+ * "value": "/* another comment *\/"
44
+ * },
45
+ * {
46
+ * "type": "unmatched",
47
+ * "value": " end"
48
+ * }
49
+ * ],
50
+ * "rest": ""
51
+ * }
52
+ * ```
53
+ *
54
+ * This parser is somewhat expensive, as it will go down the input string one character
55
+ * at a time, applying the given parser each time.
56
+ *
57
+ * @param parser - parser to find within the input
58
+ * @returns - an array of matched and unmatched strings
59
+ */
3
60
  export function within(parser) {
4
61
  return trace("within", (input) => {
5
62
  let start = 0;
@@ -19,7 +76,7 @@ export function within(parser) {
19
76
  type: "matched",
20
77
  value: parsed.result,
21
78
  });
22
- current += parsed.result.length;
79
+ current = input.length - parsed.rest.length;
23
80
  start = current;
24
81
  }
25
82
  else {
package/dist/parsers.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CaptureParser, Parser, Prettify } from "./types.js";
2
- export { within as betweenWithin } from "./parsers/within.js";
2
+ export { within } from "./parsers/within.js";
3
3
  /**
4
4
  * Takes a character. Returns a parser that parses that character.
5
5
  *
package/dist/parsers.js CHANGED
@@ -2,7 +2,7 @@ import { many1WithJoin, manyWithJoin, seq } from "./combinators.js";
2
2
  import { trace } from "./trace.js";
3
3
  import { captureSuccess, failure, success, } from "./types.js";
4
4
  import { escape } from "./utils.js";
5
- export { within as betweenWithin } from "./parsers/within.js";
5
+ export { within } from "./parsers/within.js";
6
6
  /**
7
7
  * Takes a character. Returns a parser that parses that character.
8
8
  *
package/dist/types.d.ts CHANGED
@@ -116,13 +116,13 @@ export declare function createNode(parent: Node | null, parser: GeneralParser<an
116
116
  */
117
117
  export declare function createTree(parsers: readonly GeneralParser<any, any>[]): Node;
118
118
  /** Used by `within`. */
119
- export type Matched = {
119
+ export type Matched<T> = {
120
120
  type: "matched";
121
- value: string;
121
+ value: T;
122
122
  };
123
123
  export type Unmatched = {
124
124
  type: "unmatched";
125
125
  value: string;
126
126
  };
127
- export type WithinResult = Matched | Unmatched;
127
+ export type WithinResult<T> = Matched<T> | Unmatched;
128
128
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tarsec",
3
- "version": "0.0.22",
3
+ "version": "0.1.0",
4
4
  "description": "A parser combinator library for TypeScript, inspired by Parsec.",
5
5
  "homepage": "https://github.com/egonSchiele/tarsec",
6
6
  "scripts": {
@@ -38,4 +38,4 @@
38
38
  "typescript": "^5.4.2",
39
39
  "vitest": "^1.4.0"
40
40
  }
41
- }
41
+ }