tarsec 0.1.9 → 0.2.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.
package/dist/index.d.ts CHANGED
@@ -4,3 +4,4 @@ export * from "./trace.js";
4
4
  export * from "./types.js";
5
5
  export * from "./tarsecError.js";
6
6
  export * from "./position.js";
7
+ export * from "./rightmostFailure.js";
package/dist/index.js CHANGED
@@ -4,3 +4,4 @@ export * from "./trace.js";
4
4
  export * from "./types.js";
5
5
  export * from "./tarsecError.js";
6
6
  export * from "./position.js";
7
+ export * from "./rightmostFailure.js";
package/dist/parsers.d.ts CHANGED
@@ -44,6 +44,16 @@ export declare function noneOf(chars: string): Parser<string>;
44
44
  * @returns - ParserResult
45
45
  */
46
46
  export declare const anyChar: Parser<string>;
47
+ /**
48
+ * Wraps a parser with a human-readable label for error reporting.
49
+ * On failure, suppresses any inner failure recordings and records only the label.
50
+ * This produces clean error messages like `expected a digit` instead of `expected one of "0123456789"`.
51
+ *
52
+ * @param name - human-readable description of what the parser expects
53
+ * @param parser - the parser to wrap
54
+ * @returns - a parser that records the label on failure
55
+ */
56
+ export declare function label<T>(name: string, parser: Parser<T>): Parser<T>;
47
57
  /** A parser that matches one of " \t\n\r". */
48
58
  export declare const space: Parser<string>;
49
59
  /** A parser that matches one or more spaces. */
package/dist/parsers.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { many1WithJoin, manyWithJoin, seq } from "./combinators.js";
2
2
  import { trace } from "./trace.js";
3
+ import { recordFailure, saveRightmostFailure, restoreRightmostFailure } from "./rightmostFailure.js";
3
4
  import { captureSuccess, failure, success, } from "./types.js";
4
5
  import { escape } from "./utils.js";
5
6
  export { within } from "./parsers/within.js";
@@ -12,15 +13,13 @@ export { within } from "./parsers/within.js";
12
13
  export function char(c) {
13
14
  return trace(`char(${escape(c)})`, (input) => {
14
15
  if (input.length === 0) {
15
- return {
16
- success: false,
17
- rest: input,
18
- message: "unexpected end of input",
19
- };
16
+ recordFailure(input, `"${c}"`);
17
+ return failure("unexpected end of input", input);
20
18
  }
21
19
  if (input[0] === c) {
22
20
  return success(c, input.slice(1));
23
21
  }
22
+ recordFailure(input, `"${c}"`);
24
23
  return failure(`expected ${escape(c)}, got ${escape(input[0])}`, input);
25
24
  });
26
25
  }
@@ -35,6 +34,7 @@ export function str(s) {
35
34
  if (input.substring(0, s.length) === s) {
36
35
  return success(s, input.slice(s.length));
37
36
  }
37
+ recordFailure(input, `"${s}"`);
38
38
  return failure(`expected ${s}, got ${input.substring(0, s.length)}`, input);
39
39
  });
40
40
  }
@@ -48,6 +48,7 @@ export function istr(s) {
48
48
  if (input.substring(0, s.length).toLocaleLowerCase() === s.toLocaleLowerCase()) {
49
49
  return success(input.substring(0, s.length), input.slice(s.length));
50
50
  }
51
+ recordFailure(input, `"${s}"`);
51
52
  return failure(`expected ${s}, got ${input.substring(0, s.length)}`, input);
52
53
  });
53
54
  }
@@ -61,12 +62,14 @@ export function istr(s) {
61
62
  export function oneOf(chars) {
62
63
  return trace(`oneOf(${escape(chars)})`, (input) => {
63
64
  if (input.length === 0) {
65
+ recordFailure(input, `one of "${chars}"`);
64
66
  return failure("unexpected end of input", input);
65
67
  }
66
68
  const c = input[0];
67
69
  if (chars.includes(c)) {
68
70
  return char(c)(input);
69
71
  }
72
+ recordFailure(input, `one of "${chars}"`);
70
73
  return failure(`expected one of ${escape(chars)}, got ${c}`, input);
71
74
  });
72
75
  }
@@ -80,9 +83,11 @@ export function oneOf(chars) {
80
83
  export function noneOf(chars) {
81
84
  return trace(`noneOf(${escape(chars)})`, (input) => {
82
85
  if (input.length === 0) {
86
+ recordFailure(input, `none of "${chars}"`);
83
87
  return failure("unexpected end of input", input);
84
88
  }
85
89
  if (chars.includes(input[0])) {
90
+ recordFailure(input, `none of "${chars}"`);
86
91
  return failure(`expected none of ${escape(chars)}, got ${input[0]}`, input);
87
92
  }
88
93
  return char(input[0])(input);
@@ -97,26 +102,46 @@ export function noneOf(chars) {
97
102
  */
98
103
  export const anyChar = trace("anyChar", (input) => {
99
104
  if (input.length === 0) {
105
+ recordFailure(input, "any character");
100
106
  return failure("unexpected end of input", input);
101
107
  }
102
108
  return success(input[0], input.slice(1));
103
109
  });
110
+ /**
111
+ * Wraps a parser with a human-readable label for error reporting.
112
+ * On failure, suppresses any inner failure recordings and records only the label.
113
+ * This produces clean error messages like `expected a digit` instead of `expected one of "0123456789"`.
114
+ *
115
+ * @param name - human-readable description of what the parser expects
116
+ * @param parser - the parser to wrap
117
+ * @returns - a parser that records the label on failure
118
+ */
119
+ export function label(name, parser) {
120
+ return (input) => {
121
+ const saved = saveRightmostFailure();
122
+ const result = parser(input);
123
+ restoreRightmostFailure(saved);
124
+ if (!result.success)
125
+ recordFailure(input, name);
126
+ return result;
127
+ };
128
+ }
104
129
  /** A parser that matches one of " \t\n\r". */
105
- export const space = oneOf(" \t\n\r");
130
+ export const space = label("whitespace", oneOf(" \t\n\r"));
106
131
  /** A parser that matches one or more spaces. */
107
132
  export const spaces = many1WithJoin(space);
108
133
  /** A parser that matches one digit. */
109
- export const digit = oneOf("0123456789");
134
+ export const digit = label("a digit", oneOf("0123456789"));
110
135
  /** A parser that matches one letter, case insensitive. */
111
- export const letter = oneOf("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
136
+ export const letter = label("a letter", oneOf("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"));
112
137
  /** A parser that matches one digit or letter, case insensitive. */
113
- export const alphanum = oneOf("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
138
+ export const alphanum = label("a letter or digit", oneOf("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"));
114
139
  /** A parser that matches one word, case insensitive. */
115
- export const word = regexParser("^[a-z]+", "ui");
140
+ export const word = label("a word", regexParser("^[a-z]+", "ui"));
116
141
  /** A parser that matches one or more digits. */
117
- export const num = regexParser("^[0-9]+");
142
+ export const num = label("a number", regexParser("^[0-9]+"));
118
143
  /** A parser that matches one single or double quote. */
119
- export const quote = oneOf(`'"`);
144
+ export const quote = label("a quote", oneOf(`'"`));
120
145
  /** A parser that matches one tab character. */
121
146
  export const tab = char("\t");
122
147
  /** A parser that matches one newline ("\n" only) character. */
@@ -126,6 +151,7 @@ export const eof = (input) => {
126
151
  if (input === "") {
127
152
  return success(null, input);
128
153
  }
154
+ recordFailure(input, "end of input");
129
155
  return failure("expected end of input", input);
130
156
  };
131
157
  /** A parser that matches a quoted string, in single or double quotes.
@@ -154,6 +180,7 @@ export function regexParser(str, options = "") {
154
180
  if (match) {
155
181
  return success(match[0], input.slice(match[0].length));
156
182
  }
183
+ recordFailure(input, `${str}`);
157
184
  return failure(`expected ${str}, got ${input.slice(0, 10)}`, input);
158
185
  });
159
186
  }
@@ -187,6 +214,7 @@ export function captureRegex(str, options = "", ...captureNames) {
187
214
  const captures = Object.assign({}, Object.fromEntries(match.slice(1).map((value, index) => [captureNames[index], value])));
188
215
  return success(captures, input.slice(match[0].length));
189
216
  }
217
+ recordFailure(input, `${str}`);
190
218
  return failure(`expected ${str}, got ${input.slice(0, 10)}`, input);
191
219
  };
192
220
  return trace(`captureRegex(${str})`, _parser);
@@ -0,0 +1,32 @@
1
+ export declare function resetRightmostFailure(): void;
2
+ /**
3
+ * Record an expected alternative at the current failure position.
4
+ * If this position is further right than any previous failure, it replaces the previous.
5
+ * If it's at the same position, it adds to the list of expectations (with dedup).
6
+ * No-op if `setInputStr` has not been called.
7
+ *
8
+ * @param input - the remaining input at the point of failure
9
+ * @param expected - a human-readable description of what was expected
10
+ */
11
+ export declare function recordFailure(input: string, expected: string): void;
12
+ /**
13
+ * Returns the current rightmost failure position and expected alternatives,
14
+ * or `null` if no failures have been recorded.
15
+ */
16
+ export declare function getRightmostFailure(): {
17
+ pos: number;
18
+ expected: string[];
19
+ } | null;
20
+ type SavedRightmostFailure = {
21
+ pos: number;
22
+ expected: string[];
23
+ };
24
+ export declare function saveRightmostFailure(): SavedRightmostFailure;
25
+ export declare function restoreRightmostFailure(saved: SavedRightmostFailure): void;
26
+ /**
27
+ * Formats the rightmost failure into a human-readable error message with line and column info.
28
+ * Returns `null` if no failures have been recorded.
29
+ * Requires `setInputStr` to have been called.
30
+ */
31
+ export declare function getErrorMessage(): string | null;
32
+ export {};
@@ -0,0 +1,70 @@
1
+ import { getInputStr } from "./trace.js";
2
+ import { buildLineTable, offsetToPosition } from "./position.js";
3
+ let rightmostFailurePos = -1;
4
+ let rightmostFailureExpected = [];
5
+ export function resetRightmostFailure() {
6
+ rightmostFailurePos = -1;
7
+ rightmostFailureExpected = [];
8
+ }
9
+ /**
10
+ * Record an expected alternative at the current failure position.
11
+ * If this position is further right than any previous failure, it replaces the previous.
12
+ * If it's at the same position, it adds to the list of expectations (with dedup).
13
+ * No-op if `setInputStr` has not been called.
14
+ *
15
+ * @param input - the remaining input at the point of failure
16
+ * @param expected - a human-readable description of what was expected
17
+ */
18
+ export function recordFailure(input, expected) {
19
+ const source = getInputStr();
20
+ if (source.length === 0)
21
+ return;
22
+ const pos = source.length - input.length;
23
+ if (pos > rightmostFailurePos) {
24
+ rightmostFailurePos = pos;
25
+ rightmostFailureExpected = [expected];
26
+ }
27
+ else if (pos === rightmostFailurePos) {
28
+ if (!rightmostFailureExpected.includes(expected)) {
29
+ rightmostFailureExpected.push(expected);
30
+ }
31
+ }
32
+ }
33
+ /**
34
+ * Returns the current rightmost failure position and expected alternatives,
35
+ * or `null` if no failures have been recorded.
36
+ */
37
+ export function getRightmostFailure() {
38
+ if (rightmostFailurePos < 0)
39
+ return null;
40
+ return { pos: rightmostFailurePos, expected: [...rightmostFailureExpected] };
41
+ }
42
+ export function saveRightmostFailure() {
43
+ return { pos: rightmostFailurePos, expected: [...rightmostFailureExpected] };
44
+ }
45
+ export function restoreRightmostFailure(saved) {
46
+ rightmostFailurePos = saved.pos;
47
+ rightmostFailureExpected = [...saved.expected];
48
+ }
49
+ function formatExpected(expected) {
50
+ if (expected.length === 1)
51
+ return expected[0];
52
+ if (expected.length === 2)
53
+ return `${expected[0]} or ${expected[1]}`;
54
+ return expected.slice(0, -1).join(", ") + ", or " + expected[expected.length - 1];
55
+ }
56
+ /**
57
+ * Formats the rightmost failure into a human-readable error message with line and column info.
58
+ * Returns `null` if no failures have been recorded.
59
+ * Requires `setInputStr` to have been called.
60
+ */
61
+ export function getErrorMessage() {
62
+ if (rightmostFailurePos < 0)
63
+ return null;
64
+ const source = getInputStr();
65
+ const lineTable = buildLineTable(source);
66
+ const pos = offsetToPosition(lineTable, rightmostFailurePos);
67
+ const line = pos.line + 1;
68
+ const column = pos.column + 1;
69
+ return `Line ${line}, col ${column}: expected ${formatExpected(rightmostFailureExpected)}`;
70
+ }
package/dist/trace.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { escape, round, shorten } from "./utils.js";
2
2
  import process from "process";
3
3
  import { execSync } from "child_process";
4
+ import { resetRightmostFailure } from "./rightmostFailure.js";
4
5
  const isNode = typeof process !== "undefined" &&
5
6
  process.versions != null &&
6
7
  process.versions.node != null;
@@ -194,6 +195,7 @@ let inputStr = "";
194
195
  */
195
196
  export function setInputStr(s) {
196
197
  inputStr = s;
198
+ resetRightmostFailure();
197
199
  }
198
200
  export function getInputStr() {
199
201
  return inputStr;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tarsec",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "description": "A parser combinator library for TypeScript, inspired by Parsec.",
5
5
  "homepage": "https://github.com/egonSchiele/tarsec",
6
6
  "scripts": {