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/parsers.js CHANGED
@@ -4,18 +4,18 @@
4
4
  if (v !== undefined) module.exports = v;
5
5
  }
6
6
  else if (typeof define === "function" && define.amd) {
7
- define(["require", "exports", "./combinators", "./trace", "./types", "./utils", "./parsers/betweenWithin"], factory);
7
+ define(["require", "exports", "./combinators", "./trace", "./types", "./utils", "./parsers/within"], factory);
8
8
  }
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.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");
16
16
  const utils_1 = require("./utils");
17
- var betweenWithin_1 = require("./parsers/betweenWithin");
18
- Object.defineProperty(exports, "betweenWithin", { enumerable: true, get: function () { return betweenWithin_1.betweenWithin; } });
17
+ var within_1 = require("./parsers/within");
18
+ Object.defineProperty(exports, "betweenWithin", { enumerable: true, get: function () { return within_1.within; } });
19
19
  /**
20
20
  * Takes a character. Returns a parser that parses that character.
21
21
  *
@@ -105,16 +105,27 @@
105
105
  }
106
106
  return (0, types_1.success)(input[0], input.slice(1));
107
107
  });
108
+ /** A parser that matches one of " \t\n\r". */
108
109
  exports.space = oneOf(" \t\n\r");
110
+ /** A parser that matches one or more spaces. */
109
111
  exports.spaces = (0, combinators_1.many1WithJoin)(exports.space);
112
+ /** A parser that matches one digit. */
110
113
  exports.digit = oneOf("0123456789");
114
+ /** A parser that matches one letter, currently lowercase only. */
111
115
  exports.letter = oneOf("abcdefghijklmnopqrstuvwxyz");
116
+ /** A parser that matches one digit or letter, currently lowercase only. */
112
117
  exports.alphanum = oneOf("abcdefghijklmnopqrstuvwxyz0123456789");
118
+ /** A parser that matches one lowercase word. */
113
119
  exports.word = (0, combinators_1.many1WithJoin)(exports.letter);
120
+ /** A parser that matches one or more digits. */
114
121
  exports.num = (0, combinators_1.many1WithJoin)(exports.digit);
122
+ /** A parser that matches one single or double quote. */
115
123
  exports.quote = oneOf(`'"`);
124
+ /** A parser that matches one tab character. */
116
125
  exports.tab = char("\t");
126
+ /** A parser that matches one newline ("\n" only) character. */
117
127
  exports.newline = char("\n");
128
+ /** A parser that succeeds on an empty string. Returns `null` as the result. */
118
129
  const eof = (input) => {
119
130
  if (input === "") {
120
131
  return (0, types_1.success)(null, input);
@@ -122,5 +133,19 @@
122
133
  return (0, types_1.failure)("expected end of input", input);
123
134
  };
124
135
  exports.eof = eof;
125
- exports.quotedString = (0, combinators_1.seq)([exports.quote, exports.word, exports.quote], (results) => results.join(""));
136
+ /** A parser that matches a quoted string, in single or double quotes.
137
+ * Returns the string as the result, including the quotes.
138
+ */
139
+ exports.quotedString = (0, combinators_1.seq)([exports.quote, (0, combinators_1.manyWithJoin)(noneOf(`"'`)), exports.quote], (results) => results.join(""));
140
+ function regexParser(str) {
141
+ const re = typeof str === "string" ? new RegExp(`^(${str})`) : str;
142
+ return (0, trace_1.trace)(`regex(${str})`, (input) => {
143
+ const match = input.match(re);
144
+ if (match) {
145
+ return (0, types_1.success)(match[0], input.slice(match[0].length));
146
+ }
147
+ return (0, types_1.failure)(`expected ${str}, got ${input.slice(0, 10)}`, input);
148
+ });
149
+ }
150
+ exports.regexParser = regexParser;
126
151
  });
package/dist/trace.d.ts CHANGED
@@ -1,3 +1,6 @@
1
1
  import { ParserResult } from "./types";
2
2
  export declare function resultToString<T>(name: string, result: ParserResult<T>): string;
3
3
  export declare function trace(name: string, parser: any): any;
4
+ export declare function parserTime(callback: Function): number | null;
5
+ export declare function printTime(name: string, callback: Function): void;
6
+ export declare function parserDebug(name: string, callback: Function): void;
package/dist/trace.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.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
15
  function resultToString(name, result) {
@@ -20,23 +20,85 @@
20
20
  }
21
21
  exports.resultToString = resultToString;
22
22
  let level = 0;
23
+ let counts = {};
24
+ let times = {};
25
+ let debugFlag = !!process.env.DEBUG;
26
+ let stepCount = 0;
27
+ let stepLimit = -1;
23
28
  function trace(name, parser) {
24
29
  return (input) => {
25
- if (process.env.DEBUG) {
30
+ if (debugFlag) {
26
31
  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) {
32
+ let result;
33
+ const time = parserTime(() => {
34
+ level += STEP;
35
+ result = parser(input);
36
+ level -= STEP;
37
+ });
38
+ counts[name] = counts[name] ? counts[name] + 1 : 1;
39
+ stepCount += 1;
40
+ if (time) {
41
+ times[name] = times[name] ? times[name] + time : time;
42
+ }
32
43
  console.log(" ".repeat(level) + resultToString(name, result));
33
44
  if (result.success && result.captures) {
34
45
  console.log(" ".repeat(level) +
35
46
  `⭐ ${name} -- captures: ${JSON.stringify(result.captures)}`);
36
47
  }
48
+ return result;
49
+ }
50
+ else {
51
+ return parser(input);
37
52
  }
38
- return result;
39
53
  };
40
54
  }
41
55
  exports.trace = trace;
56
+ function parserTime(callback) {
57
+ if (performance && performance.now) {
58
+ const start = performance.now();
59
+ callback();
60
+ const end = performance.now();
61
+ return end - start;
62
+ }
63
+ else {
64
+ console.error("performance.now not available");
65
+ callback();
66
+ return null;
67
+ }
68
+ }
69
+ exports.parserTime = parserTime;
70
+ function printTime(name, callback) {
71
+ const time = parserTime(callback);
72
+ if (time) {
73
+ console.log(`⏱ ${name} -- time: ${(0, utils_1.round)(time)}ms`);
74
+ }
75
+ }
76
+ exports.printTime = printTime;
77
+ function parserDebug(name, callback) {
78
+ debugFlag = true;
79
+ stepCount = 0;
80
+ counts = {};
81
+ times = {};
82
+ printTime(name, callback);
83
+ debugFlag = false;
84
+ console.log("\n");
85
+ console.log(`📊 ${name} -- counts:`);
86
+ const sorted = Object.entries(counts).sort((a, b) => b[1] - a[1]);
87
+ for (const [name, count] of sorted) {
88
+ console.log(` ${name}: ${count}`);
89
+ }
90
+ console.log("\n");
91
+ console.log(`📊 ${name} -- times:`);
92
+ const sortedTimes = Object.entries(times).sort((a, b) => b[1] - a[1]);
93
+ for (const [name, time] of sortedTimes) {
94
+ console.log(` ${name}: ${(0, utils_1.round)(time)}ms`);
95
+ }
96
+ console.log("\n");
97
+ console.log(`📊 ${name} -- step count: ${stepCount}`);
98
+ console.log("\n\n");
99
+ stepCount = 0;
100
+ counts = {};
101
+ times = {};
102
+ }
103
+ exports.parserDebug = parserDebug;
42
104
  });
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,67 @@ 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>>;
32
- export type MergedCaptures<T extends readonly GeneralParser<any, any>[]> = Prettify<UnionToIntersection<ExtractCaptures<ExtractCaptureParsers<T>>>>;
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. */
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
+ */
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. */
66
+ export type HasCaptureParsers<T extends readonly GeneralParser<any, any>[]> = ExtractCaptureParsers<T> extends never ? false : true;
67
+ /**
68
+ * For a given array of GeneralParsers, if any of them is a CaptureParser,
69
+ * PickParserType says the array is an array of CaptureParsers,
70
+ * otherwise it's an array of Parsers. It also correctly merges
71
+ * the result and capture types. This is useful for a combinator like `or`
72
+ * which is not able to infer its return type correctly.
73
+ */
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[]>;
33
85
  export type MergedResults<T extends readonly GeneralParser<any, any>[]> = ExtractResults<T[number]>;
86
+ /** Used to create a parser tree for backtracking. */
34
87
  export type Node = ParserNode | EmptyNode;
35
88
  export type ParserNode = {
36
89
  parent: Node;
@@ -40,8 +93,14 @@ export type ParserNode = {
40
93
  closed: boolean;
41
94
  };
42
95
  export type EmptyNode = null;
96
+ /** Convenience function to create a ParserNode. */
43
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
+ */
44
102
  export declare function createTree(parsers: readonly GeneralParser<any, any>[]): Node;
103
+ /** Used by `within`. */
45
104
  export type Matched = {
46
105
  type: "matched";
47
106
  value: string;
@@ -50,5 +109,5 @@ export type Unmatched = {
50
109
  type: "unmatched";
51
110
  value: string;
52
111
  };
53
- export type BetweenWithinResult = Matched | Unmatched;
112
+ export type WithinResult = Matched | Unmatched;
54
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.11",
3
+ "version": "0.0.13",
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",
@@ -1,2 +0,0 @@
1
- import { BetweenWithinResult, Parser } from "../types";
2
- export declare function betweenWithin(parser: Parser<string>): Parser<BetweenWithinResult[]>;
@@ -1,68 +0,0 @@
1
- (function (factory) {
2
- if (typeof module === "object" && typeof module.exports === "object") {
3
- var v = factory(require, exports);
4
- if (v !== undefined) module.exports = v;
5
- }
6
- else if (typeof define === "function" && define.amd) {
7
- define(["require", "exports", "../trace", "../types"], factory);
8
- }
9
- })(function (require, exports) {
10
- "use strict";
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.betweenWithin = void 0;
13
- const trace_1 = require("../trace");
14
- const types_1 = require("../types");
15
- function betweenWithin(parser) {
16
- return (0, trace_1.trace)("betweenWithin", (input) => {
17
- let start = 0;
18
- let current = 0;
19
- let currentlyMatching = false;
20
- const results = [];
21
- while (current < input.length) {
22
- const parsed = parser(input.slice(current));
23
- if (parsed.success) {
24
- if (currentlyMatching) {
25
- const value = input.slice(start, current + parsed.result.length);
26
- if (value.length > 0) {
27
- results.push({
28
- type: "matched",
29
- value,
30
- });
31
- }
32
- current += parsed.result.length;
33
- start = current;
34
- currentlyMatching = false;
35
- }
36
- else {
37
- const value = input.slice(start, current);
38
- if (value.length > 0) {
39
- results.push({
40
- type: "unmatched",
41
- value,
42
- });
43
- }
44
- currentlyMatching = true;
45
- start = current;
46
- current += parsed.result.length;
47
- }
48
- }
49
- else {
50
- current += 1;
51
- }
52
- }
53
- if (start < current) {
54
- if (currentlyMatching) {
55
- return (0, types_1.failure)("unexpected end of input", "");
56
- }
57
- else {
58
- results.push({
59
- type: "unmatched",
60
- value: input.slice(start, current),
61
- });
62
- }
63
- }
64
- return (0, types_1.success)(results, "");
65
- });
66
- }
67
- exports.betweenWithin = betweenWithin;
68
- });