clarity-pattern-parser 8.4.15 → 9.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.
Files changed (110) hide show
  1. package/TODO.md +4 -1
  2. package/dist/grammar/Grammar.d.ts +20 -12
  3. package/dist/grammar/patterns/andLiteral.d.ts +2 -0
  4. package/dist/grammar/patterns/anonymousPattern.d.ts +2 -0
  5. package/dist/grammar/patterns/grammar.d.ts +2 -2
  6. package/dist/grammar/patterns/import.d.ts +2 -2
  7. package/dist/grammar/patterns/inlinePattern.d.ts +1 -0
  8. package/dist/grammar/patterns/literals.d.ts +3 -0
  9. package/dist/grammar/patterns/optionsLiteral.d.ts +2 -0
  10. package/dist/grammar/patterns/pattern.d.ts +2 -2
  11. package/dist/grammar/patterns/repeatLiteral.d.ts +2 -2
  12. package/dist/grammar/patterns/sequenceLiteral.d.ts +4 -0
  13. package/dist/grammar/patterns/statement.d.ts +2 -2
  14. package/dist/grammar/patterns.d.ts +2 -0
  15. package/dist/index.browser.js +752 -382
  16. package/dist/index.browser.js.map +1 -1
  17. package/dist/index.d.ts +6 -3
  18. package/dist/index.esm.js +748 -381
  19. package/dist/index.esm.js.map +1 -1
  20. package/dist/index.js +752 -382
  21. package/dist/index.js.map +1 -1
  22. package/dist/patterns/And.d.ts +4 -1
  23. package/dist/patterns/Cursor.d.ts +5 -0
  24. package/dist/patterns/CursorHistory.d.ts +7 -0
  25. package/dist/patterns/FiniteRepeat.d.ts +7 -4
  26. package/dist/patterns/InfiniteRepeat.d.ts +6 -6
  27. package/dist/patterns/Literal.d.ts +9 -9
  28. package/dist/patterns/Not.d.ts +5 -4
  29. package/dist/patterns/Optional.d.ts +30 -0
  30. package/dist/patterns/Options.d.ts +34 -0
  31. package/dist/patterns/Or.d.ts +5 -4
  32. package/dist/patterns/Pattern.d.ts +5 -4
  33. package/dist/patterns/Reference.d.ts +7 -8
  34. package/dist/patterns/Regex.d.ts +8 -8
  35. package/dist/patterns/Repeat.d.ts +4 -2
  36. package/dist/patterns/Sequence.d.ts +39 -0
  37. package/dist/patterns/arePatternsEqual.d.ts +2 -0
  38. package/dist/patterns/clonePatterns.d.ts +1 -1
  39. package/package.json +1 -1
  40. package/src/grammar/Grammar.test.ts +117 -74
  41. package/src/grammar/Grammar.ts +241 -158
  42. package/src/grammar/patterns/anonymousPattern.ts +23 -0
  43. package/src/grammar/patterns/body.ts +9 -6
  44. package/src/grammar/patterns/comment.ts +3 -2
  45. package/src/grammar/patterns/grammar.ts +15 -12
  46. package/src/grammar/patterns/import.ts +18 -12
  47. package/src/grammar/patterns/literal.ts +2 -1
  48. package/src/grammar/patterns/literals.ts +20 -0
  49. package/src/grammar/patterns/optionsLiteral.ts +19 -0
  50. package/src/grammar/patterns/pattern.ts +23 -9
  51. package/src/grammar/patterns/regexLiteral.ts +1 -0
  52. package/src/grammar/patterns/repeatLiteral.ts +30 -25
  53. package/src/grammar/patterns/sequenceLiteral.ts +24 -0
  54. package/src/grammar/patterns/spaces.ts +8 -6
  55. package/src/grammar/patterns/statement.ts +8 -20
  56. package/src/grammar/patterns.test.ts +38 -0
  57. package/src/grammar/patterns.ts +24 -0
  58. package/src/grammar/spec.md +4 -12
  59. package/src/index.ts +11 -5
  60. package/src/intellisense/AutoComplete.test.ts +41 -40
  61. package/src/intellisense/css/method.ts +2 -2
  62. package/src/intellisense/css/unit.ts +2 -2
  63. package/src/intellisense/css/value.ts +1 -1
  64. package/src/intellisense/javascript/Javascript.test.ts +31 -32
  65. package/src/intellisense/javascript/arrayLiteral.ts +7 -6
  66. package/src/intellisense/javascript/assignment.ts +6 -6
  67. package/src/intellisense/javascript/deleteStatement.ts +2 -2
  68. package/src/intellisense/javascript/escapedCharacter.ts +6 -6
  69. package/src/intellisense/javascript/exponent.ts +6 -6
  70. package/src/intellisense/javascript/expression.ts +18 -17
  71. package/src/intellisense/javascript/fraction.ts +3 -3
  72. package/src/intellisense/javascript/infixOperator.ts +10 -10
  73. package/src/intellisense/javascript/integer.ts +1 -1
  74. package/src/intellisense/javascript/invocation.ts +8 -7
  75. package/src/intellisense/javascript/literal.ts +3 -3
  76. package/src/intellisense/javascript/numberLiteral.ts +5 -4
  77. package/src/intellisense/javascript/objectAccess.ts +2 -3
  78. package/src/intellisense/javascript/objectLiteral.ts +8 -7
  79. package/src/intellisense/javascript/optionalSpaces.ts +2 -1
  80. package/src/intellisense/javascript/parameters.ts +5 -5
  81. package/src/intellisense/javascript/prefixOperator.ts +3 -4
  82. package/src/intellisense/javascript/propertyAccess.ts +9 -8
  83. package/src/intellisense/javascript/stringLiteral.ts +14 -15
  84. package/src/patterns/Cursor.ts +42 -4
  85. package/src/patterns/CursorHistory.ts +20 -4
  86. package/src/patterns/FiniteRepeat.test.ts +52 -51
  87. package/src/patterns/FiniteRepeat.ts +60 -38
  88. package/src/patterns/InfiniteRepeat.test.ts +36 -49
  89. package/src/patterns/InfiniteRepeat.ts +70 -37
  90. package/src/patterns/Literal.test.ts +16 -27
  91. package/src/patterns/Literal.ts +38 -27
  92. package/src/patterns/Not.test.ts +7 -7
  93. package/src/patterns/Not.ts +24 -6
  94. package/src/patterns/Optional.test.ts +164 -0
  95. package/src/patterns/Optional.ts +143 -0
  96. package/src/patterns/{Or.test.ts → Options.test.ts} +51 -49
  97. package/src/patterns/{Or.ts → Options.ts} +32 -23
  98. package/src/patterns/Pattern.ts +6 -5
  99. package/src/patterns/Reference.test.ts +21 -22
  100. package/src/patterns/Reference.ts +26 -15
  101. package/src/patterns/Regex.test.ts +15 -15
  102. package/src/patterns/Regex.ts +33 -19
  103. package/src/patterns/Repeat.test.ts +12 -22
  104. package/src/patterns/Repeat.ts +22 -21
  105. package/src/patterns/{And.test.ts → Sequence.test.ts} +78 -78
  106. package/src/patterns/{And.ts → Sequence.ts} +40 -29
  107. package/src/patterns/arePatternsEqual.ts +12 -0
  108. package/src/patterns/clonePatterns.ts +2 -2
  109. package/src/grammar/patterns/andLiteral.ts +0 -8
  110. package/src/grammar/patterns/orLiteral.ts +0 -8
@@ -1,18 +1,25 @@
1
1
  import { Node } from "../ast/Node";
2
2
  import { Cursor } from "./Cursor";
3
+ import { ParseResult } from "./ParseResult";
3
4
  import { Pattern } from "./Pattern";
4
5
 
6
+ let idIndex = 0;
7
+
5
8
  export class Literal implements Pattern {
9
+ private _id: string;
6
10
  private _type: string;
7
11
  private _name: string;
8
12
  private _parent: Pattern | null;
9
- private _isOptional: boolean;
10
- private _literal: string;
13
+ private _text: string;
11
14
  private _runes: string[];
12
15
  private _firstIndex: number;
13
16
  private _lastIndex: number;
14
17
  private _endIndex: number;
15
18
 
19
+ get id(): string {
20
+ return this._id;
21
+ }
22
+
16
23
  get type(): string {
17
24
  return this._type;
18
25
  }
@@ -21,6 +28,10 @@ export class Literal implements Pattern {
21
28
  return this._name;
22
29
  }
23
30
 
31
+ get value(): string {
32
+ return this._text;
33
+ }
34
+
24
35
  get parent(): Pattern | null {
25
36
  return this._parent;
26
37
  }
@@ -33,20 +44,16 @@ export class Literal implements Pattern {
33
44
  return [];
34
45
  }
35
46
 
36
- get isOptional(): boolean {
37
- return this._isOptional;
38
- }
39
-
40
- constructor(name: string, value: string, isOptional = false) {
47
+ constructor(name: string, value: string) {
41
48
  if (value.length === 0) {
42
49
  throw new Error("Value Cannot be empty.");
43
50
  }
44
51
 
52
+ this._id = `literal-${idIndex++}`;
45
53
  this._type = "literal";
46
54
  this._name = name;
47
- this._literal = value;
55
+ this._text = value;
48
56
  this._runes = Array.from(value);
49
- this._isOptional = isOptional;
50
57
  this._parent = null;
51
58
  this._firstIndex = 0;
52
59
  this._lastIndex = 0;
@@ -60,8 +67,10 @@ export class Literal implements Pattern {
60
67
  return ast?.value === text;
61
68
  }
62
69
 
63
- exec(text: string) {
70
+ exec(text: string, record = false): ParseResult {
64
71
  const cursor = new Cursor(text);
72
+ record && cursor.startRecording();
73
+
65
74
  const ast = this.parse(cursor);
66
75
 
67
76
  return {
@@ -71,6 +80,8 @@ export class Literal implements Pattern {
71
80
  }
72
81
 
73
82
  parse(cursor: Cursor): Node | null {
83
+ cursor.startParseWith(this);
84
+
74
85
  this._firstIndex = cursor.index;
75
86
 
76
87
  const passed = this._tryToParse(cursor);
@@ -80,16 +91,12 @@ export class Literal implements Pattern {
80
91
  const node = this._createNode();
81
92
  cursor.recordMatch(this, node);
82
93
 
94
+ cursor.endParse();
83
95
  return node;
84
96
  }
85
97
 
86
- if (!this._isOptional) {
87
- cursor.recordErrorAt(this._firstIndex, this._endIndex, this)
88
- return null;
89
- }
90
-
91
- cursor.resolveError();
92
- cursor.moveTo(this._firstIndex);
98
+ cursor.recordErrorAt(this._firstIndex, this._endIndex, this);
99
+ cursor.endParse();
93
100
  return null;
94
101
  }
95
102
 
@@ -103,11 +110,11 @@ export class Literal implements Pattern {
103
110
 
104
111
  if (literalRune !== cursorRune) {
105
112
  this._endIndex = cursor.index;
106
- break
113
+ break;
107
114
  }
108
115
 
109
116
  if (i + 1 === literalRuneLength) {
110
- this._lastIndex = this._firstIndex + this._literal.length - 1;
117
+ this._lastIndex = this._firstIndex + this._text.length - 1;
111
118
  passed = true;
112
119
  break;
113
120
  }
@@ -120,7 +127,7 @@ export class Literal implements Pattern {
120
127
  cursor.next();
121
128
  }
122
129
 
123
- return passed
130
+ return passed;
124
131
  }
125
132
 
126
133
  private _createNode(): Node {
@@ -130,17 +137,18 @@ export class Literal implements Pattern {
130
137
  this._firstIndex,
131
138
  this._lastIndex,
132
139
  undefined,
133
- this._literal
140
+ this._text
134
141
  );
135
142
  }
136
143
 
137
- clone(name = this._name, isOptional = this._isOptional): Pattern {
138
- const clone = new Literal(name, this._literal, isOptional);
144
+ clone(name = this._name): Pattern {
145
+ const clone = new Literal(name, this._text);
146
+ clone._id = this._id;
139
147
  return clone;
140
148
  }
141
149
 
142
150
  getTokens(): string[] {
143
- return [this._literal];
151
+ return [this._text];
144
152
  }
145
153
 
146
154
  getTokensAfter(_lastMatched: Pattern): string[] {
@@ -149,7 +157,7 @@ export class Literal implements Pattern {
149
157
 
150
158
  getNextTokens(): string[] {
151
159
  if (this.parent == null) {
152
- return []
160
+ return [];
153
161
  }
154
162
 
155
163
  return this.parent.getTokensAfter(this);
@@ -160,7 +168,7 @@ export class Literal implements Pattern {
160
168
  }
161
169
 
162
170
  getPatternsAfter(): Pattern[] {
163
- return []
171
+ return [];
164
172
  }
165
173
 
166
174
  getNextPatterns(): Pattern[] {
@@ -168,11 +176,14 @@ export class Literal implements Pattern {
168
176
  return [];
169
177
  }
170
178
 
171
- return this.parent.getPatternsAfter(this)
179
+ return this.parent.getPatternsAfter(this);
172
180
  }
173
181
 
174
182
  find(_predicate: (p: Pattern) => boolean): Pattern | null {
175
183
  return null;
176
184
  }
177
185
 
186
+ isEqual(pattern: Literal) {
187
+ return pattern.type === this.type && pattern._text === this._text;
188
+ }
178
189
  }
@@ -1,4 +1,4 @@
1
- import { And } from "./And";
1
+ import { Sequence } from "./Sequence";
2
2
  import { Cursor } from "./Cursor";
3
3
  import { Literal } from "./Literal";
4
4
  import { Not } from "./Not";
@@ -86,7 +86,7 @@ describe("Not", () => {
86
86
 
87
87
  test("Get Next Tokens", () => {
88
88
  const notAboutUs = new Not("not-about-us", new Literal("about-us", "About Us"));
89
- const sequence = new And("sequence", [notAboutUs, new Literal("about-them", "About Them")]);
89
+ const sequence = new Sequence("sequence", [notAboutUs, new Literal("about-them", "About Them")]);
90
90
 
91
91
  const cloneNotAboutUs = sequence.find(p => p.name === "not-about-us") as Pattern;
92
92
  const nextTokens = cloneNotAboutUs.getNextTokens() || [];
@@ -103,7 +103,7 @@ describe("Not", () => {
103
103
 
104
104
  test("Get Tokens", () => {
105
105
  const notAboutUs = new Not("not-about-us", new Literal("about-us", "About Us"));
106
- const sequence = new And("sequence", [notAboutUs, new Literal("about-them", "About Them")]);
106
+ const sequence = new Sequence("sequence", [notAboutUs, new Literal("about-them", "About Them")]);
107
107
 
108
108
  const cloneNotAboutUs = sequence.find(p => p.name === "not-about-us") as Pattern;
109
109
  const nextTokens = cloneNotAboutUs.getTokens() || [];
@@ -113,7 +113,7 @@ describe("Not", () => {
113
113
 
114
114
  test("Get Tokens After", () => {
115
115
  const notAboutUs = new Not("not-about-us", new Literal("about-us", "About Us"));
116
- const sequence = new And("sequence", [notAboutUs, new Literal("about-them", "About Them")]);
116
+ const sequence = new Sequence("sequence", [notAboutUs, new Literal("about-them", "About Them")]);
117
117
  const notAboutUsClone = sequence.find(p => p.name === "not-about-us") as Pattern;
118
118
  const aboutUsClone = sequence.find(p => p.name === "about-us") as Pattern;
119
119
  const nextTokens = notAboutUsClone.getTokensAfter(aboutUsClone) || [];
@@ -130,7 +130,7 @@ describe("Not", () => {
130
130
 
131
131
  test("Get Patterns", () => {
132
132
  const notAboutUs = new Not("not-about-us", new Literal("about-us", "About Us"));
133
- const sequence = new And("sequence", [notAboutUs, new Literal("about-them", "About Them")]);
133
+ const sequence = new Sequence("sequence", [notAboutUs, new Literal("about-them", "About Them")]);
134
134
 
135
135
  const cloneNotAboutUs = sequence.find(p => p.name === "not-about-us") as Pattern;
136
136
  const nextPatterns = cloneNotAboutUs.getPatterns();
@@ -141,7 +141,7 @@ describe("Not", () => {
141
141
 
142
142
  test("Get Next Patterns", () => {
143
143
  const notAboutUs = new Not("not-about-us", new Literal("about-us", "About Us"));
144
- const sequence = new And("sequence", [notAboutUs, new Literal("about-them", "About Them")]);
144
+ const sequence = new Sequence("sequence", [notAboutUs, new Literal("about-them", "About Them")]);
145
145
 
146
146
  const cloneNotAboutUs = sequence.find(p => p.name === "not-about-us") as Pattern;
147
147
  const patterns = cloneNotAboutUs.getNextPatterns() || [];
@@ -159,7 +159,7 @@ describe("Not", () => {
159
159
 
160
160
  test("Get Patterns After", () => {
161
161
  const notAboutUs = new Not("not-about-us", new Literal("about-us", "About Us"));
162
- const sequence = new And("sequence", [notAboutUs, new Literal("about-them", "About Them")]);
162
+ const sequence = new Sequence("sequence", [notAboutUs, new Literal("about-them", "About Them")]);
163
163
  const notAboutUsClone = sequence.find(p => p.name === "not-about-us") as Pattern;
164
164
  const aboutUsClone = sequence.find(p => p.name === "about-us") as Pattern;
165
165
  const patterns = notAboutUsClone.getPatternsAfter(aboutUsClone) || [];
@@ -1,13 +1,21 @@
1
1
  import { Node } from "../ast/Node";
2
2
  import { Cursor } from "./Cursor";
3
+ import { ParseResult } from "./ParseResult";
3
4
  import { Pattern } from "./Pattern";
4
5
 
6
+ let idIndex = 0;
7
+
5
8
  export class Not implements Pattern {
9
+ private _id: string;
6
10
  private _type: string;
7
11
  private _name: string;
8
12
  private _parent: Pattern | null;
9
13
  private _children: Pattern[];
10
14
 
15
+ get id(): string {
16
+ return this._id;
17
+ }
18
+
11
19
  get type(): string {
12
20
  return this._type;
13
21
  }
@@ -33,10 +41,11 @@ export class Not implements Pattern {
33
41
  }
34
42
 
35
43
  constructor(name: string, pattern: Pattern) {
44
+ this._id = `not-${idIndex++}`;
36
45
  this._type = "not";
37
46
  this._name = name;
38
47
  this._parent = null;
39
- this._children = [pattern.clone(pattern.name, false)];
48
+ this._children = [pattern.clone()];
40
49
  this._children[0].parent = this;
41
50
  }
42
51
 
@@ -47,17 +56,21 @@ export class Not implements Pattern {
47
56
  return !cursor.hasError;
48
57
  }
49
58
 
50
- exec(text: string) {
59
+ exec(text: string, record = false): ParseResult {
51
60
  const cursor = new Cursor(text);
61
+ record && cursor.startRecording();
62
+
52
63
  const ast = this.parse(cursor);
53
64
 
54
65
  return {
55
- ast,
66
+ ast: ast?.value === text ? ast : null,
56
67
  cursor
57
68
  };
58
69
  }
59
70
 
60
71
  parse(cursor: Cursor): Node | null {
72
+ cursor.startParseWith(this);
73
+
61
74
  const firstIndex = cursor.index;
62
75
  this._children[0].parse(cursor);
63
76
 
@@ -70,11 +83,13 @@ export class Not implements Pattern {
70
83
  cursor.recordErrorAt(firstIndex, firstIndex, this);
71
84
  }
72
85
 
86
+ cursor.endParse();
73
87
  return null;
74
88
  }
75
89
 
76
90
  clone(name = this._name): Pattern {
77
91
  const not = new Not(name, this._children[0]);
92
+ not._id = this._id;
78
93
  return not;
79
94
  }
80
95
 
@@ -100,7 +115,7 @@ export class Not implements Pattern {
100
115
 
101
116
  getNextTokens(): string[] {
102
117
  if (this.parent == null) {
103
- return []
118
+ return [];
104
119
  }
105
120
 
106
121
  return this.parent.getTokensAfter(this);
@@ -117,7 +132,7 @@ export class Not implements Pattern {
117
132
  return parent.getPatternsAfter(this);
118
133
  }
119
134
 
120
- return []
135
+ return [];
121
136
  }
122
137
 
123
138
  getNextPatterns(): Pattern[] {
@@ -125,11 +140,14 @@ export class Not implements Pattern {
125
140
  return [];
126
141
  }
127
142
 
128
- return this.parent.getPatternsAfter(this)
143
+ return this.parent.getPatternsAfter(this);
129
144
  }
130
145
 
131
146
  find(predicate: (p: Pattern) => boolean): Pattern | null {
132
147
  return predicate(this._children[0]) ? this._children[0] : null;
133
148
  }
134
149
 
150
+ isEqual(pattern: Not): boolean {
151
+ return pattern.type === this.type && this.children.every((c, index) => c.isEqual(pattern.children[index]));
152
+ }
135
153
  }
@@ -0,0 +1,164 @@
1
+ import { Literal } from "./Literal";
2
+ import { Not } from "./Not";
3
+ import { Optional } from "./Optional";
4
+ import { Pattern } from "./Pattern";
5
+ import { Sequence } from "./Sequence";
6
+
7
+ describe("Optional", () => {
8
+ test("Match", () => {
9
+ const text = new Literal("text", "Text");
10
+ const optional = new Optional("optional-text", text);
11
+ const result = optional.exec("Text");
12
+
13
+ expect(result?.ast?.value).toBe("Text");
14
+ expect(result.cursor.index).toBe(3);
15
+ });
16
+
17
+ test("No Match", () => {
18
+ const text = new Literal("text", "Text");
19
+ const optional = new Optional("optional-text", text);
20
+ const result = optional.exec("Bad Text");
21
+
22
+ expect(result.ast).toBe(null);
23
+ expect(result.cursor.index).toBe(0);
24
+ });
25
+
26
+ test("Test Match", () => {
27
+ const text = new Literal("text", "Text");
28
+ const optional = new Optional("optional-text", text);
29
+ const result = optional.test("Text");
30
+
31
+ expect(result).toBeTruthy();
32
+ });
33
+
34
+ test("Test No Match", () => {
35
+ const text = new Literal("text", "Text");
36
+ const optional = new Optional("optional-text", text);
37
+ const result = optional.test("Bad Text");
38
+
39
+ expect(result).toBeTruthy();
40
+ });
41
+
42
+ test("Clone", () => {
43
+ const a = new Literal("a", "A");
44
+ const optionalA = new Optional("optional-a", a);
45
+ const clone = optionalA.clone();
46
+
47
+ expect(clone.name).toBe("optional-a");
48
+ expect(clone).not.toBe(optionalA);
49
+ });
50
+
51
+ test("Tokens", () => {
52
+ const a = new Literal("a", "A");
53
+ const optionalA = new Optional("optional-a", a);
54
+ const tokens = optionalA.getTokens();
55
+ const nextTokens = optionalA.getTokensAfter(new Literal("bogus", "bogus"));
56
+ const emptyArray: string[] = [];
57
+
58
+ expect(tokens).toEqual(["A"]);
59
+ expect(nextTokens).toEqual(emptyArray);
60
+ });
61
+
62
+ test("Properties", () => {
63
+ const a = new Literal("a", "A");
64
+ const optionalA = new Optional("optional-a", a);
65
+
66
+ expect(optionalA.type).toBe("optional");
67
+ expect(optionalA.name).toBe("optional-a");
68
+ expect(optionalA.parent).toBeNull();
69
+ expect(optionalA.children[0].name).toBe("a");
70
+ });
71
+
72
+ test("Get Next Tokens", () => {
73
+ const optionalAboutUs = new Optional("optional-about-us", new Literal("about-us", "About Us"));
74
+ const sequence = new Sequence("sequence", [optionalAboutUs, new Literal("about-them", "About Them")]);
75
+
76
+ const cloneOptionalAboutUs = sequence.find(p => p.name === "optional-about-us") as Pattern;
77
+ const nextTokens = cloneOptionalAboutUs.getNextTokens() || [];
78
+
79
+ expect(nextTokens[0]).toBe("About Them");
80
+ });
81
+
82
+ test("Get Next Tokens With No Parent", () => {
83
+ const optionalAboutUs = new Optional("optional-about-us", new Literal("about-us", "About Us"));
84
+ const nextTokens = optionalAboutUs.getNextTokens() || [];
85
+
86
+ expect(nextTokens.length).toBe(0);
87
+ });
88
+
89
+ test("Get Tokens", () => {
90
+ const optionalAboutUs = new Optional("optional-about-us", new Literal("about-us", "About Us"));
91
+ const sequence = new Sequence("sequence", [optionalAboutUs, new Literal("about-them", "About Them")]);
92
+
93
+ const cloneAboutUs = sequence.find(p => p.name === "optional-about-us") as Pattern;
94
+ const nextTokens = cloneAboutUs.getTokens() || [];
95
+
96
+ expect(nextTokens[0]).toBe("About Us");
97
+ });
98
+
99
+ test("Get Tokens After", () => {
100
+ const optionalAboutUs = new Optional("optional-about-us", new Literal("about-us", "About Us"));
101
+ const sequence = new Sequence("sequence", [optionalAboutUs, new Literal("about-them", "About Them")]);
102
+ const optionalAboutUsClone = sequence.find(p => p.name === "optional-about-us") as Pattern;
103
+ const aboutUsClone = sequence.find(p => p.name === "about-us") as Pattern;
104
+ const nextTokens = optionalAboutUsClone.getTokensAfter(aboutUsClone) || [];
105
+
106
+ expect(nextTokens[0]).toBe("About Them");
107
+ });
108
+
109
+ test("Find Pattern", () => {
110
+ const optionalAboutUs = new Optional("optional-about-us", new Literal("about-us", "About Us"));
111
+ const child = optionalAboutUs.find(p => p.name === "about-us");
112
+
113
+ expect(child).not.toBeNull();
114
+ });
115
+
116
+ test("Get Patterns", () => {
117
+ const optionalAboutUs = new Optional("optional-about-us", new Literal("about-us", "About Us"));
118
+ const sequence = new Sequence("sequence", [optionalAboutUs, new Literal("about-them", "About Them")]);
119
+
120
+ const cloneNotAboutUs = sequence.find(p => p.name === "optional-about-us") as Pattern;
121
+ const nextPatterns = cloneNotAboutUs.getPatterns();
122
+ const expected = [sequence.find(p => p.name === "about-us")];
123
+
124
+ expect(nextPatterns).toEqual(expected);
125
+ });
126
+
127
+ test("Get Next Patterns", () => {
128
+ const optionalAboutUs = new Optional("optional-about-us", new Literal("about-us", "About Us"));
129
+ const sequence = new Sequence("sequence", [optionalAboutUs, new Literal("about-them", "About Them")]);
130
+
131
+ const cloneNotAboutUs = sequence.find(p => p.name === "optional-about-us") as Pattern;
132
+ const patterns = cloneNotAboutUs.getNextPatterns() || [];
133
+
134
+ expect(patterns.length).toBe(1);
135
+ expect(patterns[0].name).toBe("about-them");
136
+ });
137
+
138
+ test("Get Next Patterns With No Parent", () => {
139
+ const optionalAboutUs = new Optional("optional-about-us", new Literal("about-us", "About Us"));
140
+ const patterns = optionalAboutUs.getNextPatterns() || [];
141
+
142
+ expect(patterns.length).toBe(0);
143
+ });
144
+
145
+ test("Get Patterns After", () => {
146
+ const optionalAboutUs = new Optional("optional-about-us", new Literal("about-us", "About Us"));
147
+ const sequence = new Sequence("sequence", [optionalAboutUs, new Literal("about-them", "About Them")]);
148
+ const optionalAboutUsClone = sequence.find(p => p.name === "optional-about-us") as Pattern;
149
+ const aboutUsClone = sequence.find(p => p.name === "about-us") as Pattern;
150
+ const patterns = optionalAboutUsClone.getPatternsAfter(aboutUsClone) || [];
151
+
152
+ expect(patterns.length).toBe(1);
153
+ expect(patterns[0].name).toBe("about-them");
154
+ });
155
+
156
+ test("Get Patterns After With Null Parent", () => {
157
+ const optionalAboutUs = new Optional("optional-about-us", new Literal("about-us", "About Us"));
158
+ const aboutUsClone = optionalAboutUs.find(p => p.name === "about-us") as Pattern;
159
+ const patterns = optionalAboutUs.getPatternsAfter(aboutUsClone) || [];
160
+
161
+ expect(patterns.length).toBe(0);
162
+ });
163
+
164
+ });
@@ -0,0 +1,143 @@
1
+ import { Node } from "../ast/Node";
2
+ import { Cursor } from "./Cursor";
3
+ import { ParseResult } from "./ParseResult";
4
+ import { Pattern } from "./Pattern";
5
+
6
+ let idIndex = 0;
7
+
8
+ export class Optional implements Pattern {
9
+ private _id: string;
10
+ private _type: string;
11
+ private _name: string;
12
+ private _parent: Pattern | null;
13
+ private _children: Pattern[];
14
+
15
+ get id(): string {
16
+ return this._id;
17
+ }
18
+
19
+ get type(): string {
20
+ return this._type;
21
+ }
22
+
23
+ get name(): string {
24
+ return this._name;
25
+ }
26
+
27
+ get parent(): Pattern | null {
28
+ return this._parent;
29
+ }
30
+
31
+ set parent(pattern: Pattern | null) {
32
+ this._parent = pattern;
33
+ }
34
+
35
+ get children(): Pattern[] {
36
+ return this._children;
37
+ }
38
+
39
+ constructor(name: string, pattern: Pattern) {
40
+ this._id = `optional-${idIndex++}`;
41
+ this._type = "optional";
42
+ this._name = name;
43
+ this._parent = null;
44
+ this._children = [pattern.clone()];
45
+ this._children[0].parent = this;
46
+ }
47
+
48
+ test(text: string) {
49
+ const cursor = new Cursor(text);
50
+ this.parse(cursor);
51
+
52
+ return !cursor.hasError;
53
+ }
54
+
55
+ exec(text: string, record = false): ParseResult {
56
+ const cursor = new Cursor(text);
57
+ record && cursor.startRecording();
58
+
59
+ const ast = this.parse(cursor);
60
+
61
+ return {
62
+ ast: ast?.value === text ? ast : null,
63
+ cursor
64
+ };
65
+ }
66
+
67
+ parse(cursor: Cursor): Node | null {
68
+ cursor.startParseWith(this);
69
+
70
+ const firstIndex = cursor.index;
71
+ const node = this._children[0].parse(cursor);
72
+
73
+ if (cursor.hasError) {
74
+ cursor.resolveError();
75
+ cursor.moveTo(firstIndex);
76
+
77
+ cursor.endParse();
78
+ return null;
79
+ } else {
80
+ cursor.endParse();
81
+ return node;
82
+ }
83
+
84
+ }
85
+
86
+ clone(name = this._name): Pattern {
87
+ const optional = new Optional(name, this._children[0]);
88
+ optional._id = this._id;
89
+ return optional;
90
+ }
91
+
92
+ getTokens(): string[] {
93
+ return this._children[0].getTokens();
94
+ }
95
+
96
+ getTokensAfter(_childReference: Pattern): string[] {
97
+ const parent = this._parent;
98
+
99
+ if (parent != null) {
100
+ return parent.getTokensAfter(this);
101
+ }
102
+
103
+ return [];
104
+ }
105
+
106
+ getNextTokens(): string[] {
107
+ if (this.parent == null) {
108
+ return [];
109
+ }
110
+
111
+ return this.parent.getTokensAfter(this);
112
+ }
113
+
114
+ getPatterns(): Pattern[] {
115
+ return this._children[0].getPatterns();
116
+ }
117
+
118
+ getPatternsAfter(_childReference: Pattern): Pattern[] {
119
+ const parent = this._parent;
120
+
121
+ if (parent != null) {
122
+ return parent.getPatternsAfter(this);
123
+ }
124
+
125
+ return [];
126
+ }
127
+
128
+ getNextPatterns(): Pattern[] {
129
+ if (this.parent == null) {
130
+ return [];
131
+ }
132
+
133
+ return this.parent.getPatternsAfter(this);
134
+ }
135
+
136
+ find(predicate: (p: Pattern) => boolean): Pattern | null {
137
+ return predicate(this._children[0]) ? this._children[0] : null;
138
+ }
139
+
140
+ isEqual(pattern: Optional): boolean {
141
+ return pattern.type === this.type && this.children.every((c, index) => c.isEqual(pattern.children[index]));
142
+ }
143
+ }