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.
- package/TODO.md +4 -1
- package/dist/grammar/Grammar.d.ts +18 -10
- package/dist/grammar/patterns/andLiteral.d.ts +2 -0
- package/dist/grammar/patterns/anonymousPattern.d.ts +2 -0
- package/dist/grammar/patterns/inlinePattern.d.ts +1 -0
- package/dist/grammar/patterns/literals.d.ts +3 -0
- package/dist/grammar/patterns/pattern.d.ts +2 -2
- package/dist/grammar/patterns.d.ts +2 -0
- package/dist/index.browser.js +472 -185
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.esm.js +471 -186
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +472 -185
- package/dist/index.js.map +1 -1
- package/dist/patterns/And.d.ts +4 -1
- package/dist/patterns/Cursor.d.ts +5 -0
- package/dist/patterns/CursorHistory.d.ts +7 -0
- package/dist/patterns/FiniteRepeat.d.ts +4 -1
- package/dist/patterns/InfiniteRepeat.d.ts +5 -4
- package/dist/patterns/Literal.d.ts +6 -5
- package/dist/patterns/Not.d.ts +5 -4
- package/dist/patterns/Or.d.ts +5 -4
- package/dist/patterns/Pattern.d.ts +4 -2
- package/dist/patterns/Reference.d.ts +5 -4
- package/dist/patterns/Regex.d.ts +5 -4
- package/dist/patterns/Repeat.d.ts +3 -0
- package/dist/patterns/arePatternsEqual.d.ts +2 -0
- package/package.json +1 -1
- package/src/grammar/Grammar.test.ts +126 -72
- package/src/grammar/Grammar.ts +241 -158
- package/src/grammar/patterns/anonymousPattern.ts +23 -0
- package/src/grammar/patterns/body.ts +9 -6
- package/src/grammar/patterns/comment.ts +3 -2
- package/src/grammar/patterns/grammar.ts +15 -12
- package/src/grammar/patterns/import.ts +18 -12
- package/src/grammar/patterns/literal.ts +2 -3
- package/src/grammar/patterns/literals.ts +20 -0
- package/src/grammar/patterns/optionsLiteral.ts +19 -0
- package/src/grammar/patterns/pattern.ts +23 -9
- package/src/grammar/patterns/regexLiteral.ts +1 -0
- package/src/grammar/patterns/repeatLiteral.ts +30 -25
- package/src/grammar/patterns/sequenceLiteral.ts +24 -0
- package/src/grammar/patterns/spaces.ts +8 -6
- package/src/grammar/patterns/statement.ts +8 -20
- package/src/grammar/patterns.test.ts +38 -0
- package/src/grammar/patterns.ts +24 -0
- package/src/grammar/spec.md +4 -12
- package/src/index.ts +11 -5
- package/src/intellisense/AutoComplete.test.ts +41 -40
- package/src/intellisense/css/method.ts +2 -2
- package/src/intellisense/css/unit.ts +2 -2
- package/src/intellisense/css/value.ts +1 -1
- package/src/intellisense/javascript/Javascript.test.ts +31 -32
- package/src/intellisense/javascript/arrayLiteral.ts +7 -6
- package/src/intellisense/javascript/assignment.ts +6 -6
- package/src/intellisense/javascript/deleteStatement.ts +2 -2
- package/src/intellisense/javascript/escapedCharacter.ts +6 -6
- package/src/intellisense/javascript/exponent.ts +6 -6
- package/src/intellisense/javascript/expression.ts +18 -17
- package/src/intellisense/javascript/fraction.ts +3 -3
- package/src/intellisense/javascript/infixOperator.ts +10 -10
- package/src/intellisense/javascript/integer.ts +1 -1
- package/src/intellisense/javascript/invocation.ts +8 -7
- package/src/intellisense/javascript/literal.ts +3 -3
- package/src/intellisense/javascript/numberLiteral.ts +5 -4
- package/src/intellisense/javascript/objectAccess.ts +2 -3
- package/src/intellisense/javascript/objectLiteral.ts +8 -7
- package/src/intellisense/javascript/optionalSpaces.ts +2 -1
- package/src/intellisense/javascript/parameters.ts +5 -5
- package/src/intellisense/javascript/prefixOperator.ts +3 -4
- package/src/intellisense/javascript/propertyAccess.ts +9 -8
- package/src/intellisense/javascript/stringLiteral.ts +14 -15
- package/src/patterns/Cursor.ts +42 -4
- package/src/patterns/CursorHistory.ts +20 -4
- package/src/patterns/FiniteRepeat.test.ts +52 -51
- package/src/patterns/FiniteRepeat.ts +60 -38
- package/src/patterns/InfiniteRepeat.test.ts +36 -49
- package/src/patterns/InfiniteRepeat.ts +70 -37
- package/src/patterns/Literal.test.ts +16 -27
- package/src/patterns/Literal.ts +34 -27
- package/src/patterns/Not.test.ts +7 -7
- package/src/patterns/Not.ts +24 -6
- package/src/patterns/Optional.test.ts +164 -0
- package/src/patterns/Optional.ts +143 -0
- package/src/patterns/{Or.test.ts → Options.test.ts} +51 -49
- package/src/patterns/{Or.ts → Options.ts} +32 -23
- package/src/patterns/Pattern.ts +6 -5
- package/src/patterns/Reference.test.ts +21 -22
- package/src/patterns/Reference.ts +26 -15
- package/src/patterns/Regex.test.ts +15 -15
- package/src/patterns/Regex.ts +29 -19
- package/src/patterns/Repeat.test.ts +12 -22
- package/src/patterns/Repeat.ts +22 -21
- package/src/patterns/{And.test.ts → Sequence.test.ts} +78 -78
- package/src/patterns/{And.ts → Sequence.ts} +40 -29
- package/src/patterns/arePatternsEqual.ts +12 -0
- package/src/patterns/clonePatterns.ts +2 -2
- package/src/grammar/patterns/andLiteral.ts +0 -8
- package/src/grammar/patterns/orLiteral.ts +0 -8
package/src/patterns/Literal.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
87
|
-
|
|
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.
|
|
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.
|
|
136
|
+
this._text
|
|
134
137
|
);
|
|
135
138
|
}
|
|
136
139
|
|
|
137
|
-
clone(name = this._name
|
|
138
|
-
const clone = new Literal(name, this.
|
|
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.
|
|
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
|
}
|
package/src/patterns/Not.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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) || [];
|
package/src/patterns/Not.ts
CHANGED
|
@@ -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(
|
|
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
|
+
}
|