clarity-pattern-parser 10.0.3 → 10.0.4

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.
@@ -2,6 +2,11 @@ import { Node } from "../ast/Node";
2
2
  import { Match } from "./CursorHistory";
3
3
  import { ParseError } from "./ParseError";
4
4
  import { Pattern } from "./Pattern";
5
+ export declare class CyclicalParseError extends Error {
6
+ readonly patternId: string;
7
+ readonly patternName: string;
8
+ constructor(patternId: string, patternName: string);
9
+ }
5
10
  export declare class Cursor {
6
11
  private _text;
7
12
  private _index;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clarity-pattern-parser",
3
- "version": "10.0.3",
3
+ "version": "10.0.4",
4
4
  "description": "Parsing Library for Typescript and Javascript.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.esm.js",
@@ -322,7 +322,7 @@ export class Grammar {
322
322
  const trimDivider = repeatNode.find(n => n.name === "trim-flag") != null;
323
323
  const patterNode = repeatNode.children[1].type === "spaces" ? repeatNode.children[2] : repeatNode.children[1];
324
324
  const pattern = this._buildPattern(patterNode);
325
- const dividerSectionNode = repeatNode.find(n => n.name === "divider-section");
325
+ const dividerSectionNode = repeatNode.find(n => n.name === "repeat-divider-section");
326
326
 
327
327
  const options: RepeatOptions = {
328
328
  min: 1,
@@ -9,7 +9,7 @@ import { allSpaces, lineSpaces } from "./spaces";
9
9
  import { Optional } from "../../patterns/Optional";
10
10
 
11
11
  const optionalSpaces = new Optional("optional-spaces", allSpaces);
12
- const optionalLineSpaces = new Optional("options-line-spaces", lineSpaces);
12
+ const optionalLineSpaces = new Optional("optional-line-spaces", lineSpaces);
13
13
 
14
14
  const importNameDivider = new Regex("import-name-divider", "(\\s+)?,(\\s+)?");
15
15
  importNameDivider.setTokens([", "]);
@@ -7,7 +7,7 @@ import { Options } from "../../patterns/Options";
7
7
  const patternName = name.clone("pattern-name");
8
8
  patternName.setTokens(["[PATTERN_NAME]"]);
9
9
 
10
- const patterns = new Options("or-patterns", [patternName, anonymousPattern]);
10
+ const patterns = new Options("options-patterns", [patternName, anonymousPattern]);
11
11
  const defaultDivider = new Regex("default-divider", "\\s*[|]\\s*");
12
12
  defaultDivider.setTokens(["|"]);
13
13
 
@@ -8,8 +8,8 @@ import { lineSpaces, spaces } from "./spaces";
8
8
  import { Optional } from "../../patterns/Optional";
9
9
 
10
10
  const optionalSpaces = new Optional("optional-spaces", spaces);
11
- const openBracket = new Literal("open-bracket", "{");
12
- const closeBracket = new Literal("close-bracket", "}");
11
+ const openBracket = new Literal("repeat-open-bracket", "{");
12
+ const closeBracket = new Literal("repeat-close-bracket", "}");
13
13
  const comma = new Literal("comma", ",");
14
14
 
15
15
  const integer = new Regex("integer", "([1-9][0-9]*)|0");
@@ -55,16 +55,16 @@ dividerComma.setTokens([", "]);
55
55
 
56
56
 
57
57
  const patternName = name.clone("pattern-name");
58
- const patterns = new Options("or-patterns", [patternName, anonymousPattern]);
59
- const dividerPattern = patterns.clone("divider-pattern");
60
- const dividerSection = new Sequence("divider-section", [dividerComma, dividerPattern, trimFlag]);
61
- const optionalDividerSection = new Optional("optional-divider-section", dividerSection);
58
+ const repeatPattern = new Options("repeat-pattern", [patternName, anonymousPattern]);
59
+ const repeatDividerPattern = repeatPattern.clone("repeat-divider-pattern");
60
+ const repeatDividerSection = new Sequence("repeat-divider-section", [dividerComma, repeatDividerPattern, trimFlag]);
61
+ const repeatOptionalDividerSection = new Optional("repeat-optional-divider-section", repeatDividerSection);
62
62
 
63
63
  export const repeatLiteral = new Sequence("repeat-literal", [
64
64
  openParen,
65
65
  optionalSpaces,
66
- patterns,
67
- optionalDividerSection,
66
+ repeatPattern,
67
+ repeatOptionalDividerSection,
68
68
  optionalSpaces,
69
69
  closeParen,
70
70
  new Sequence("quantifier-section", [quantifier]),
@@ -48,11 +48,11 @@ export class AutoComplete {
48
48
  errorAtIndex: 0,
49
49
  cursor,
50
50
  ast: null
51
- }
51
+ };
52
52
  }
53
53
 
54
54
  let errorAtIndex = null;
55
- let error = null
55
+ let error = null;
56
56
 
57
57
  const ast = this._pattern.parse(this._cursor);
58
58
  const isComplete = ast?.value === this._text;
@@ -74,7 +74,7 @@ export class AutoComplete {
74
74
  errorAtIndex = startIndex;
75
75
  } else if (!isComplete && this._cursor.hasError && this._cursor.furthestError != null) {
76
76
  errorAtIndex = this._cursor.furthestError.endIndex;
77
- error = this._cursor.furthestError
77
+ error = this._cursor.furthestError;
78
78
 
79
79
  errorAtIndex = options.reduce((errorAtIndex, option) =>
80
80
  Math.max(errorAtIndex, option.startIndex),
@@ -88,7 +88,7 @@ export class AutoComplete {
88
88
  errorAtIndex,
89
89
  cursor: cursor,
90
90
  ast,
91
- }
91
+ };
92
92
 
93
93
  }
94
94
 
@@ -108,7 +108,7 @@ export class AutoComplete {
108
108
  }
109
109
  });
110
110
 
111
- return finalResults
111
+ return finalResults;
112
112
  }
113
113
 
114
114
  private _getOptionsFromErrors() {
@@ -159,7 +159,7 @@ export class AutoComplete {
159
159
  }
160
160
 
161
161
  private _getTokensForPattern(pattern: Pattern) {
162
- const augmentedTokens = this._getAugmentedTokens(pattern)
162
+ const augmentedTokens = this._getAugmentedTokens(pattern);
163
163
 
164
164
  if (this._options.greedyPatternNames != null && this._options.greedyPatternNames.includes(pattern.name)) {
165
165
  const nextPatterns = pattern.getNextPatterns();
@@ -225,7 +225,7 @@ export class AutoComplete {
225
225
  return {
226
226
  text: text,
227
227
  startIndex: furthestMatch,
228
- }
228
+ };
229
229
  }
230
230
 
231
231
  static suggestFor(text: string, pattern: Pattern, options?: AutoCompleteOptions) {
@@ -3,6 +3,17 @@ import { CursorHistory, Match, Trace } from "./CursorHistory";
3
3
  import { ParseError } from "./ParseError";
4
4
  import { Pattern } from "./Pattern";
5
5
 
6
+ export class CyclicalParseError extends Error {
7
+ readonly patternId: string;
8
+ readonly patternName: string;
9
+
10
+ constructor(patternId: string, patternName: string) {
11
+ super("Cyclical Parse Error");
12
+ this.patternId = patternId;
13
+ this.patternName = patternName;
14
+ }
15
+ }
16
+
6
17
  export class Cursor {
7
18
  private _text: string;
8
19
  private _index: number;
@@ -145,16 +156,14 @@ export class Cursor {
145
156
  }
146
157
 
147
158
  startParseWith(pattern: Pattern) {
148
- const patternName = pattern.name;
149
-
150
159
  const trace = {
151
160
  pattern,
152
161
  cursorIndex: this.index
153
162
  };
154
163
 
155
- const hasCycle = this._stackTrace.find(t => t.pattern.id === pattern.id && this.index === t.cursorIndex);
164
+ const hasCycle = this._stackTrace.filter(t => t.pattern.id === pattern.id && this.index === t.cursorIndex).length > 1;
156
165
  if (hasCycle) {
157
- throw new Error(`Cyclical Pattern: ${this._stackTrace.map(t => `${t.pattern.name}#${t.pattern.id}{${t.cursorIndex}}`).join(" -> ")} -> ${patternName}#${pattern.id}{${this.index}}.`);
166
+ throw new CyclicalParseError(pattern.id, pattern.name);
158
167
  }
159
168
 
160
169
  this._history.pushStackTrace(trace);
@@ -5,6 +5,8 @@ import { Options } from "./Options";
5
5
  import { Sequence } from "./Sequence";
6
6
  import { Pattern } from "./Pattern";
7
7
  import { Optional } from "./Optional";
8
+ import { Regex } from "./Regex";
9
+ import { Reference } from "./Reference";
8
10
 
9
11
  describe("Options", () => {
10
12
  test("Empty Options", () => {
@@ -271,4 +273,26 @@ describe("Options", () => {
271
273
  const result = firstName.exec("Jane");
272
274
  expect(result.ast?.value).toBe("Jane");
273
275
  });
276
+
277
+ test("Cyclical Error Recorvery", () => {
278
+ const john = new Literal("john", "John");
279
+ const jane = new Literal("jane", "Jane");
280
+ const names = new Options("names", [john, jane]);
281
+ const questionMark = new Literal("?", "?");
282
+ const colon = new Literal(":", ":");
283
+ const space = new Regex("space", "\\s+");
284
+ const expressionReference = new Reference("expression");
285
+ const ternary = new Sequence("ternary", [expressionReference, space, questionMark, space, expressionReference, space, colon, space, expressionReference]);
286
+ const expression = new Options("expression", [names, ternary], true);
287
+
288
+ let result = expression.exec("John");
289
+ expect(result.ast?.toString()).toBe("John");
290
+
291
+ result = expression.exec("John ? Jane : John");
292
+ expect(result.ast?.toString()).toBe("John ? Jane : John");
293
+
294
+
295
+ result = expression.exec("John ? Jane : John ? Jane : John");
296
+ expect(result.ast?.toString()).toBe("John ? Jane : John ? Jane : John");
297
+ });
274
298
  });
@@ -84,6 +84,7 @@ export class Options implements Pattern {
84
84
 
85
85
  parse(cursor: Cursor): Node | null {
86
86
  cursor.startParseWith(this);
87
+
87
88
  this._firstIndex = cursor.index;
88
89
 
89
90
  const node = this._tryToParse(cursor);
@@ -106,7 +107,18 @@ export class Options implements Pattern {
106
107
 
107
108
  for (const pattern of this._children) {
108
109
  cursor.moveTo(this._firstIndex);
109
- const result = pattern.parse(cursor);
110
+ let result = null;
111
+
112
+ try {
113
+ result = pattern.parse(cursor);
114
+ } catch (error: any) {
115
+ if (error.patternId === this._id) {
116
+ continue;
117
+ } else {
118
+ cursor.endParse();
119
+ throw error;
120
+ }
121
+ }
110
122
 
111
123
  if (this._isGreedy) {
112
124
  results.push(result);