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