clarity-pattern-parser 4.0.2 → 5.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 (132) hide show
  1. package/README.md +176 -1
  2. package/TODO.md +22 -2
  3. package/dist/ast/Node.d.ts +43 -11
  4. package/dist/ast/Visitor.d.ts +31 -31
  5. package/dist/index.browser.js +1248 -1495
  6. package/dist/index.browser.js.map +1 -1
  7. package/dist/index.d.ts +12 -16
  8. package/dist/index.esm.js +1218 -1460
  9. package/dist/index.esm.js.map +1 -1
  10. package/dist/index.js +1217 -1464
  11. package/dist/index.js.map +1 -1
  12. package/dist/patterns/And.d.ts +37 -24
  13. package/dist/patterns/Cursor.d.ts +35 -0
  14. package/dist/patterns/CursorHistory.d.ts +30 -0
  15. package/dist/patterns/Literal.d.ts +36 -19
  16. package/dist/patterns/Not.d.ts +26 -11
  17. package/dist/patterns/Or.d.ts +31 -22
  18. package/dist/patterns/ParseError.d.ts +6 -8
  19. package/dist/patterns/ParseResult.d.ts +6 -0
  20. package/dist/patterns/Pattern.d.ts +17 -26
  21. package/dist/patterns/Reference.d.ts +31 -12
  22. package/dist/patterns/Regex.d.ts +42 -21
  23. package/dist/patterns/Repeat.d.ts +38 -20
  24. package/dist/patterns/clonePatterns.d.ts +2 -0
  25. package/dist/patterns/filterOutNull.d.ts +2 -0
  26. package/dist/patterns/findPattern.d.ts +2 -0
  27. package/dist/patterns/getNextPattern.d.ts +2 -0
  28. package/jest.config.js +2 -1
  29. package/package.json +4 -5
  30. package/rollup.config.js +1 -1
  31. package/src/ast/Node.test.ts +254 -0
  32. package/src/ast/Node.ts +171 -23
  33. package/src/index.ts +11 -19
  34. package/src/intellisense/AutoComplete.test.ts +72 -0
  35. package/src/intellisense/AutoComplete.ts +146 -0
  36. package/src/intellisense/Suggestion.ts +13 -0
  37. package/src/intellisense/SuggestionOption.ts +4 -0
  38. package/src/{tests/cssPatterns → intellisense/css}/cssValue.ts +1 -1
  39. package/src/{tests/cssPatterns → intellisense/css}/divider.ts +2 -1
  40. package/src/intellisense/css/hex.ts +6 -0
  41. package/src/{tests/cssPatterns → intellisense/css}/method.ts +8 -9
  42. package/src/intellisense/css/name.ts +5 -0
  43. package/src/{tests/javascriptPatterns → intellisense/css}/number.ts +3 -3
  44. package/src/intellisense/css/spaces.ts +6 -0
  45. package/src/intellisense/css/unit.ts +10 -0
  46. package/src/{tests/cssPatterns → intellisense/css}/value.ts +1 -1
  47. package/src/{tests/cssPatterns → intellisense/css}/values.ts +1 -1
  48. package/src/intellisense/javascript/Javascript.test.ts +203 -0
  49. package/src/intellisense/javascript/arrayLiteral.ts +25 -0
  50. package/src/intellisense/javascript/deleteStatement.ts +14 -0
  51. package/src/intellisense/javascript/escapedCharacter.ts +50 -0
  52. package/src/intellisense/javascript/exponent.ts +26 -0
  53. package/src/intellisense/javascript/expression.ts +87 -0
  54. package/src/intellisense/javascript/expressionStatement.ts +29 -0
  55. package/src/intellisense/javascript/fraction.ts +13 -0
  56. package/src/intellisense/javascript/infixOperator.ts +36 -0
  57. package/src/intellisense/javascript/integer.ts +7 -0
  58. package/src/intellisense/javascript/invocation.ts +28 -0
  59. package/src/intellisense/javascript/literal.ts +14 -0
  60. package/src/intellisense/javascript/name.ts +3 -0
  61. package/src/intellisense/javascript/numberLiteral.ts +10 -0
  62. package/src/intellisense/javascript/objectLiteral.ts +30 -0
  63. package/src/intellisense/javascript/optionalSpaces.ts +3 -0
  64. package/src/intellisense/javascript/parameters.ts +20 -0
  65. package/src/intellisense/javascript/prefixOperator.ts +13 -0
  66. package/src/intellisense/javascript/propertyAccess.ts +23 -0
  67. package/src/intellisense/javascript/stringLiteral.ts +28 -0
  68. package/src/patterns/And.test.ts +299 -0
  69. package/src/patterns/And.ts +222 -119
  70. package/src/patterns/Cursor.test.ts +93 -0
  71. package/src/patterns/Cursor.ts +130 -0
  72. package/src/patterns/CursorHistory.test.ts +54 -0
  73. package/src/patterns/CursorHistory.ts +95 -0
  74. package/src/patterns/Literal.test.ts +134 -0
  75. package/src/patterns/Literal.ts +151 -61
  76. package/src/patterns/Not.test.ts +88 -0
  77. package/src/patterns/Not.ts +74 -33
  78. package/src/patterns/Or.test.ts +105 -0
  79. package/src/patterns/Or.ts +106 -98
  80. package/src/patterns/ParseError.ts +3 -7
  81. package/src/patterns/ParseResult.ts +7 -0
  82. package/src/patterns/Pattern.ts +18 -150
  83. package/src/patterns/Reference.test.ts +104 -0
  84. package/src/patterns/Reference.ts +94 -94
  85. package/src/patterns/Regex.test.ts +101 -0
  86. package/src/patterns/Regex.ts +129 -60
  87. package/src/patterns/Repeat.test.ts +196 -0
  88. package/src/patterns/Repeat.ts +208 -104
  89. package/src/patterns/clonePatterns.ts +5 -0
  90. package/src/patterns/filterOutNull.ts +13 -0
  91. package/src/patterns/findPattern.ts +25 -0
  92. package/src/patterns/getNextPattern.test.ts +39 -0
  93. package/src/patterns/getNextPattern.ts +18 -0
  94. package/src/Cursor.ts +0 -141
  95. package/src/CursorHistory.ts +0 -146
  96. package/src/TextSuggester.ts +0 -317
  97. package/src/ast/Visitor.ts +0 -271
  98. package/src/patterns/LookAhead.ts +0 -32
  99. package/src/patterns/Recursive.ts +0 -92
  100. package/src/tests/And.test.ts +0 -180
  101. package/src/tests/ComplexExamples.test.ts +0 -86
  102. package/src/tests/CssPatterns.test.ts +0 -90
  103. package/src/tests/CursorHistory.test.ts +0 -107
  104. package/src/tests/Cusor.test.ts +0 -174
  105. package/src/tests/HtmlPatterns.test.ts +0 -34
  106. package/src/tests/Literal.test.ts +0 -79
  107. package/src/tests/LookAhead.test.ts +0 -44
  108. package/src/tests/Not.test.ts +0 -51
  109. package/src/tests/Or.test.ts +0 -113
  110. package/src/tests/Pattern.test.ts +0 -290
  111. package/src/tests/Recursive.test.ts +0 -64
  112. package/src/tests/Reference.test.ts +0 -16
  113. package/src/tests/Repeat.test.ts +0 -75
  114. package/src/tests/SpeedTest.test.ts +0 -31
  115. package/src/tests/TextSuggester.test.ts +0 -297
  116. package/src/tests/Visitor.test.ts +0 -331
  117. package/src/tests/cssPatterns/hex.ts +0 -5
  118. package/src/tests/cssPatterns/name.ts +0 -5
  119. package/src/tests/cssPatterns/number.ts +0 -8
  120. package/src/tests/cssPatterns/spaces.ts +0 -5
  121. package/src/tests/cssPatterns/unit.ts +0 -8
  122. package/src/tests/htmlPatterns/element.ts +0 -49
  123. package/src/tests/javascriptPatterns/boolean.ts +0 -10
  124. package/src/tests/javascriptPatterns/json.ts +0 -67
  125. package/src/tests/javascriptPatterns/name.ts +0 -5
  126. package/src/tests/javascriptPatterns/objectLiteral.ts +0 -40
  127. package/src/tests/javascriptPatterns/string.ts +0 -84
  128. package/src/tests/javascriptPatterns/unit.ts +0 -8
  129. package/src/tests/javascriptPatterns/whitespace.ts +0 -44
  130. package/src/tests/naturalLanguage/filter.ts +0 -37
  131. package/src/tests/patterns/sentence.ts +0 -37
  132. /package/src/{tests/cssPatterns → intellisense/css}/optionalSpaces.ts +0 -0
@@ -0,0 +1,95 @@
1
+ import { Node } from "../ast/Node";
2
+ import { ParseError } from "./ParseError";
3
+ import { Pattern } from "./Pattern";
4
+
5
+ export interface Match {
6
+ pattern: Pattern | null;
7
+ node: Node | null;
8
+ }
9
+
10
+ export class CursorHistory {
11
+ private _isRecording: boolean = false;
12
+ private _leafMatch: Match = { pattern: null, node: null };
13
+ private _furthestError: ParseError | null = null;
14
+ private _currentError: ParseError | null = null;
15
+ private _rootMatch: Match = { pattern: null, node: null };
16
+ private _patterns: Pattern[] = [];
17
+ private _nodes: Node[] = [];
18
+ private _errors: ParseError[] = [];
19
+
20
+ get leafMatch(): Match {
21
+ return this._leafMatch;
22
+ }
23
+
24
+ get furthestError(): ParseError | null {
25
+ return this._furthestError;
26
+ }
27
+
28
+ get isRecording(): boolean {
29
+ return this._isRecording;
30
+ }
31
+
32
+ get errors(): ParseError[] {
33
+ return this._errors;
34
+ }
35
+
36
+ get error(): ParseError | null {
37
+ return this._currentError
38
+ }
39
+
40
+ get nodes(): Node[] {
41
+ return this._nodes;
42
+ }
43
+
44
+ get patterns(): Pattern[] {
45
+ return this._patterns;
46
+ }
47
+
48
+ get rootMatch(): Match {
49
+ return this._rootMatch;
50
+ }
51
+
52
+ recordMatch(pattern: Pattern, node: Node): void {
53
+ if (this._isRecording) {
54
+ this._patterns.push(pattern);
55
+ this._nodes.push(node);
56
+ }
57
+
58
+ this._rootMatch.pattern = pattern;
59
+ this._rootMatch.node = node;
60
+
61
+ const isFurthestMatch =
62
+ this._leafMatch.node === null || node.lastIndex > this._leafMatch.node.lastIndex;
63
+
64
+ if (isFurthestMatch) {
65
+ this._leafMatch.pattern = pattern;
66
+ this._leafMatch.node = node;
67
+ }
68
+ }
69
+
70
+ recordErrorAt(index: number, pattern: Pattern): void {
71
+ const error = new ParseError(index, pattern);
72
+ this._currentError = error;
73
+
74
+ if (this._furthestError === null || index > this._furthestError.index) {
75
+ this._furthestError = error;
76
+ }
77
+
78
+ if (this._isRecording) {
79
+ this._errors.push(error);
80
+ }
81
+ }
82
+
83
+ startRecording(): void {
84
+ this._isRecording = true;
85
+ }
86
+
87
+ stopRecording(): void {
88
+ this._isRecording = false;
89
+ }
90
+
91
+ resolveError() {
92
+ this._currentError = null;
93
+ }
94
+
95
+ }
@@ -0,0 +1,134 @@
1
+ import { Node } from "../ast/Node";
2
+ import { And } from "./And";
3
+ import { Cursor } from "./Cursor";
4
+ import { Literal } from "./Literal"
5
+
6
+ describe("Literal", () => {
7
+ test("Empty Value", () => {
8
+ expect(() => {
9
+ new Literal("empty", "")
10
+ }).toThrowError()
11
+ });
12
+
13
+ test("Successful Parse", () => {
14
+ const literal = new Literal("greeting", "Hello World!");
15
+
16
+ const cursor = new Cursor("Hello World!");
17
+ const result = literal.parse(cursor);
18
+ const expected = new Node("literal", "greeting", 0, 11, [], "Hello World!");
19
+
20
+ expect(result).toEqual(expected);
21
+ expect(cursor.index).toBe(11);
22
+ expect(cursor.error).toBeNull();
23
+ expect(cursor.leafMatch.node).toEqual(expected)
24
+ expect(cursor.leafMatch.pattern).toBe(literal)
25
+ expect(cursor.rootMatch.node).toEqual(expected)
26
+ expect(cursor.rootMatch.pattern).toBe(literal)
27
+ });
28
+
29
+ test("Failed Parse", () => {
30
+ const literal = new Literal("greeting", "Hello World!");
31
+
32
+ const cursor = new Cursor("Hello Saturn!");
33
+ const result = literal.parse(cursor);
34
+
35
+ expect(result).toEqual(null);
36
+ expect(cursor.index).toBe(6);
37
+ expect(cursor.error?.index).toBe(6);
38
+ expect(cursor.error?.pattern).toBe(literal)
39
+ });
40
+
41
+ test("Failed Parse Because End Of Text", () => {
42
+ const literal = new Literal("greeting", "Hello World!");
43
+
44
+ const cursor = new Cursor("Hello World");
45
+ const result = literal.parse(cursor);
46
+
47
+ expect(result).toEqual(null);
48
+ expect(cursor.index).toBe(10);
49
+ expect(cursor.error?.index).toBe(10);
50
+ expect(cursor.error?.pattern).toBe(literal)
51
+ });
52
+
53
+ test("Failed Parse (Optional)", () => {
54
+ const literal = new Literal("greeting", "Hello World!", true);
55
+
56
+ const cursor = new Cursor("Hello Saturn!");
57
+ const result = literal.parse(cursor);
58
+
59
+ expect(result).toEqual(null);
60
+ expect(cursor.index).toBe(0);
61
+ expect(cursor.error).toBeNull();
62
+ });
63
+
64
+ test("Clone", () => {
65
+ const literal = new Literal("greeting", "Hello World!");
66
+ const clone = literal.clone();
67
+
68
+ expect(clone.name).toBe("greeting");
69
+ expect(clone).not.toBe(literal);
70
+ });
71
+
72
+ test("Get Tokens", () => {
73
+ const parent = new And("parent", [
74
+ new Literal("a", "A"),
75
+ new Literal("b", "B")
76
+ ]);
77
+
78
+ const a = parent.children[0] as Literal;
79
+ const b = parent.children[1];
80
+
81
+ let tokens = a.getTokens();
82
+ let expectedTokens = ["A"];
83
+
84
+ expect(tokens).toEqual(expectedTokens);
85
+
86
+ a.enableContextualTokenAggregation();
87
+
88
+ tokens = a.getTokens();
89
+ expectedTokens = ["AB"];
90
+
91
+ expect(tokens).toEqual(expectedTokens);
92
+
93
+ a.disableContextualTokenAggregation();
94
+
95
+ tokens = a.getTokens();
96
+ expectedTokens = ["A"];
97
+
98
+ expect(tokens).toEqual(expectedTokens);
99
+ });
100
+
101
+ test("Get Next Tokens", () => {
102
+ const literal = new Literal("a", "A");
103
+ const tokens = literal.getNextTokens(new Literal("bogus", "bogus"));
104
+ const expected: string[] = [];
105
+
106
+ expect(tokens).toEqual(expected)
107
+ });
108
+
109
+ test("Get Next Pattern", () => {
110
+ const parent = new And("parent", [
111
+ new Literal("a", "A"),
112
+ new Literal("b", "B")
113
+ ]);
114
+
115
+ const nextPattern = parent.children[0].getNextPattern();
116
+
117
+ expect(nextPattern?.name).toBe("b")
118
+ });
119
+
120
+ test("Properties", () => {
121
+ const literal = new Literal("a", "A");
122
+
123
+ expect(literal.type).toBe("literal");
124
+ expect(literal.name).toBe("a");
125
+ expect(literal.parent).toBeNull();
126
+ expect(literal.children).toEqual([]);
127
+ });
128
+
129
+ test("Parse Text", () => {
130
+ const literal = new Literal("a", "A");
131
+ const { ast: result } = literal.parseText("B");
132
+ expect(result).toBeNull()
133
+ });
134
+ });
@@ -1,91 +1,181 @@
1
- import ParseError from "./ParseError";
2
- import Node from "../ast/Node";
3
- import Pattern from "./Pattern";
4
- import Cursor from "../Cursor";
1
+ import { Node } from "../ast/Node";
2
+ import { Cursor } from "./Cursor";
3
+ import { getNextPattern } from "./getNextPattern";
4
+ import { Pattern } from "./Pattern";
5
5
 
6
- export default class Literal extends Pattern {
7
- public literal: string;
8
- public node: Node | null = null;
9
- public cursor!: Cursor;
10
- public mark: number = 0;
11
- public substring: string = "";
6
+ export class Literal implements Pattern {
7
+ private _type: string;
8
+ private _name: string;
9
+ private _parent: Pattern | null;
10
+ private _isOptional: boolean;
11
+ private _literal: string;
12
+ private _runes: string[];
13
+ private _firstIndex: number;
14
+ private _lastIndex: number;
15
+ private _hasContextualTokenAggregation: boolean;
16
+ private _isRetrievingContextualTokens: boolean;
12
17
 
13
- constructor(name: string, literal: string, isOptional = false) {
14
- super("literal", name, [], isOptional);
15
- this.literal = literal;
16
- this.assertArguments();
18
+ get type(): string {
19
+ return this._type;
17
20
  }
18
21
 
19
- parse(cursor: Cursor) {
20
- this.resetState(cursor);
21
- this.tryToParse();
22
+ get name(): string {
23
+ return this._name;
24
+ }
22
25
 
23
- return this.node;
26
+ get parent(): Pattern | null {
27
+ return this._parent;
24
28
  }
25
29
 
26
- clone(name?: string, isOptional?: boolean) {
27
- if (name == null) {
28
- name = this.name;
29
- }
30
+ set parent(pattern: Pattern | null) {
31
+ this._parent = pattern;
32
+ }
30
33
 
31
- if (isOptional == null) {
32
- isOptional = this._isOptional;
33
- }
34
-
35
- return new Literal(name, this.literal, isOptional);
34
+ get children(): Pattern[] {
35
+ return [];
36
36
  }
37
37
 
38
- getTokens() {
39
- return [this.literal];
38
+ get isOptional(): boolean {
39
+ return this._isOptional;
40
40
  }
41
41
 
42
- private assertArguments() {
43
- if (this.literal.length < 1) {
44
- throw new Error(
45
- "Invalid Arguments: The `literal` argument needs to be at least one character long."
46
- );
42
+ constructor(name: string, value: string, isOptional = false) {
43
+ if (value.length === 0){
44
+ throw new Error("Value Cannot be empty.");
47
45
  }
46
+
47
+ this._type = "literal";
48
+ this._name = name;
49
+ this._literal = value;
50
+ this._runes = Array.from(value);
51
+ this._isOptional = isOptional;
52
+ this._parent = null;
53
+ this._firstIndex = 0;
54
+ this._lastIndex = 0;
55
+ this._hasContextualTokenAggregation = false;
56
+ this._isRetrievingContextualTokens = false;
48
57
  }
49
58
 
50
- private resetState(cursor: Cursor) {
51
- this.cursor = cursor;
52
- this.mark = this.cursor.mark();
53
- this.substring = this.cursor.text.substring(
54
- this.mark,
55
- this.mark + this.literal.length
56
- );
57
- this.node = null;
59
+ parseText(text: string) {
60
+ const cursor = new Cursor(text);
61
+ const ast = this.parse(cursor)
62
+
63
+ return {
64
+ ast,
65
+ cursor
66
+ };
58
67
  }
59
68
 
60
- private tryToParse() {
61
- if (this.substring === this.literal) {
62
- this.processResult();
63
- } else {
64
- this.processError();
69
+ parse(cursor: Cursor): Node | null {
70
+ this._firstIndex = cursor.index;
71
+
72
+ const passed = this._tryToParse(cursor);
73
+
74
+ if (passed) {
75
+ cursor.resolveError();
76
+ const node = this._createNode();
77
+ cursor.recordMatch(this, node);
78
+
79
+ return node;
65
80
  }
81
+
82
+ if (!this._isOptional) {
83
+ cursor.recordErrorAt(cursor.index, this)
84
+ return null;
85
+ }
86
+
87
+ cursor.resolveError();
88
+ cursor.moveTo(this._firstIndex);
89
+ return null;
66
90
  }
67
91
 
68
- private processError() {
69
- this.node = null;
92
+ private _tryToParse(cursor: Cursor): boolean {
93
+ let passed = false;
94
+ const literalRuneLength = this._runes.length;
70
95
 
71
- if (!this._isOptional) {
72
- const message = `ParseError: Expected '${this.literal}' but found '${this.substring}'.`;
73
- const parseError = new ParseError(message, this.cursor.getIndex(), this);
74
- this.cursor.throwError(parseError);
96
+ for (let i = 0; i < literalRuneLength; i++) {
97
+ const literalRune = this._runes[i];
98
+ const cursorRune = cursor.currentChar;
99
+
100
+ if (literalRune !== cursorRune) {
101
+ break
102
+ }
103
+
104
+ if (i + 1 === literalRuneLength) {
105
+ this._lastIndex = this._firstIndex + this._literal.length - 1;
106
+ passed = true;
107
+ break;
108
+ }
109
+
110
+ if (!cursor.hasNext()) {
111
+ break;
112
+ }
113
+
114
+ cursor.next();
75
115
  }
116
+
117
+ return passed
76
118
  }
77
119
 
78
- private processResult() {
79
- this.node = new Node(
120
+ private _createNode(): Node {
121
+ return new Node(
80
122
  "literal",
81
- this.name,
82
- this.mark,
83
- this.mark + this.literal.length - 1,
123
+ this._name,
124
+ this._firstIndex,
125
+ this._lastIndex,
84
126
  [],
85
- this.substring
127
+ this._literal
86
128
  );
129
+ }
87
130
 
88
- this.cursor.index = this.node.endIndex;
89
- this.cursor.addMatch(this, this.node);
131
+ clone(name = this._name, isOptional = this._isOptional): Pattern {
132
+ const clone = new Literal(name, this._literal, isOptional);
133
+ clone._hasContextualTokenAggregation = this._hasContextualTokenAggregation;
134
+ return clone;
90
135
  }
136
+
137
+ getTokens(): string[] {
138
+ const parent = this._parent;
139
+
140
+ if (
141
+ this._hasContextualTokenAggregation &&
142
+ parent != null &&
143
+ !this._isRetrievingContextualTokens
144
+ ) {
145
+ this._isRetrievingContextualTokens = true;
146
+
147
+ const aggregateTokens: string[] = [];
148
+ const nextTokens = parent.getNextTokens(this);
149
+
150
+ for (const nextToken of nextTokens) {
151
+ aggregateTokens.push(this._literal + nextToken);
152
+ }
153
+
154
+ this._isRetrievingContextualTokens = false;
155
+ return aggregateTokens;
156
+ } else {
157
+ return [this._literal];
158
+ }
159
+ }
160
+
161
+ getNextTokens(_lastMatched: Pattern): string[] {
162
+ return [];
163
+ }
164
+
165
+ getNextPattern(): Pattern | null {
166
+ return getNextPattern(this)
167
+ }
168
+
169
+ findPattern(_isMatch: (p: Pattern) => boolean): Pattern | null {
170
+ return null;
171
+ }
172
+
173
+ enableContextualTokenAggregation(): void {
174
+ this._hasContextualTokenAggregation = true;
175
+ }
176
+
177
+ disableContextualTokenAggregation(): void {
178
+ this._hasContextualTokenAggregation = false;
179
+ }
180
+
91
181
  }
@@ -0,0 +1,88 @@
1
+ import { And } from "./And";
2
+ import { Cursor } from "./Cursor";
3
+ import { Literal } from "./Literal";
4
+ import { Not } from "./Not";
5
+
6
+ describe("Not", () => {
7
+ test("Parse Successfully", () => {
8
+ const a = new Literal("a", "A");
9
+ const notA = new Not("not-a", a);
10
+ const cursor = new Cursor("B");
11
+ const result = notA.parse(cursor);
12
+
13
+ expect(result).toBeNull();
14
+ expect(cursor.hasError).toBeFalsy();
15
+ });
16
+
17
+ test("Parse Failed", () => {
18
+ const a = new Literal("a", "A");
19
+ const notA = new Not("not-a", a);
20
+ const cursor = new Cursor("A");
21
+ const result = notA.parse(cursor);
22
+
23
+ expect(result).toBeNull();
24
+ expect(cursor.hasError).toBeTruthy();
25
+ });
26
+
27
+ test("Clone", () => {
28
+ const a = new Literal("a", "A");
29
+ const notA = new Not("not-a", a);
30
+ const clone = notA.clone();
31
+
32
+ expect(clone.name).toBe("not-a");
33
+ expect(clone).not.toBe(notA)
34
+ });
35
+
36
+ test("Tokens", () => {
37
+ const a = new Literal("a", "A");
38
+ const notA = new Not("not-a", a);
39
+ const tokens = notA.getTokens();
40
+ const nextTokens = notA.getNextTokens(new Literal("bogus", "bogus"))
41
+ const emptyArray: string[] = [];
42
+
43
+ expect(tokens).toEqual(emptyArray);
44
+ expect(nextTokens).toEqual(emptyArray);
45
+ });
46
+
47
+ test("Properties", () => {
48
+ const a = new Literal("a", "A");
49
+ const notA = new Not("not-a", a);
50
+
51
+ expect(notA.type).toBe("not");
52
+ expect(notA.name).toBe("not-a");
53
+ expect(notA.parent).toBeNull();
54
+ expect(notA.children[0].name).toBe("a");
55
+ expect(notA.isOptional).toBeFalsy();
56
+ });
57
+
58
+ test("Not A Not", () => {
59
+ const a = new Literal("a", "A");
60
+ const notA = new Not("not-a", a);
61
+ const notnotA = new Not("not-not-a", notA);
62
+
63
+ const cursor = new Cursor("A");
64
+ const result = notnotA.parse(cursor);
65
+
66
+ expect(result).toBeNull();
67
+ expect(cursor.hasError).toBeFalsy();
68
+ });
69
+
70
+ test("Parse Text", () => {
71
+ const a = new Literal("a", "A");
72
+ const notA = new Not("not-a", a);
73
+ const { ast: result } = notA.parseText("A");
74
+
75
+ expect(result).toBeNull();
76
+ });
77
+
78
+ test("Get Next Pattern", () => {
79
+ const a = new Literal("a", "A");
80
+ const b = new Literal("b", "B");
81
+ const notA = new Not("not-a", a);
82
+
83
+ const parent = new And("parent", [notA, b]);
84
+ const nextPattern = parent.children[0].getNextPattern();
85
+
86
+ expect(nextPattern?.name).toBe("b");
87
+ });
88
+ });
@@ -1,50 +1,91 @@
1
- import Pattern from "./Pattern";
2
- import ParseError from "./ParseError";
3
- import Cursor from "../Cursor";
1
+ import { Node } from "../ast/Node";
2
+ import { Cursor } from "./Cursor";
3
+ import { getNextPattern } from "./getNextPattern";
4
+ import { Pattern } from "./Pattern";
4
5
 
5
- export default class Not extends Pattern {
6
- public cursor!: Cursor;
7
- public mark: number = 0;
6
+ export class Not implements Pattern {
7
+ private _type: string;
8
+ private _name: string;
9
+ private _parent: Pattern | null;
10
+ private _children: Pattern[];
8
11
 
9
- constructor(pattern: Pattern) {
10
- super("not", `not-${pattern.name}`, [pattern]);
11
- this._isOptional = true;
12
+ get type(): string {
13
+ return this._type;
12
14
  }
13
15
 
14
- parse(cursor: Cursor) {
15
- this.cursor = cursor;
16
- this.mark = cursor.mark();
17
- this.tryToParse();
18
- return null;
16
+ get name(): string {
17
+ return this._name;
18
+ }
19
+
20
+ get isOptional(): boolean {
21
+ return false;
22
+ }
23
+
24
+ get parent(): Pattern | null {
25
+ return this._parent;
26
+ }
27
+
28
+ set parent(pattern: Pattern | null) {
29
+ this._parent = pattern;
30
+ }
31
+
32
+ get children(): Pattern[] {
33
+ return this._children;
34
+ }
35
+
36
+ constructor(name: string, pattern: Pattern) {
37
+ this._type = "not";
38
+ this._name = name;
39
+ this._parent = null;
40
+ this._children = [pattern.clone(pattern.name, false)];
41
+ this._children[0].parent = this;
42
+ }
43
+
44
+ parseText(text: string) {
45
+ const cursor = new Cursor(text);
46
+ const ast = this.parse(cursor)
47
+
48
+ return {
49
+ ast,
50
+ cursor
51
+ };
19
52
  }
20
53
 
21
- private tryToParse() {
22
- const mark = this.cursor.mark();
23
- this.children[0].parse(this.cursor);
54
+ parse(cursor: Cursor): Node | null {
55
+ const firstIndex = cursor.index;
56
+ this._children[0].parse(cursor);
24
57
 
25
- if (this.cursor.hasUnresolvedError()) {
26
- this.cursor.resolveError();
27
- this.cursor.moveToMark(mark);
58
+ if (cursor.hasError) {
59
+ cursor.resolveError();
60
+ cursor.moveTo(firstIndex);
28
61
  } else {
29
- this.cursor.moveToMark(mark);
30
- const parseError = new ParseError(
31
- `Match invalid pattern: ${this.children[0].name}.`,
32
- this.mark,
33
- this
34
- );
35
- this.cursor.throwError(parseError);
62
+ cursor.moveTo(firstIndex);
63
+ cursor.resolveError();
64
+ cursor.recordErrorAt(firstIndex, this);
36
65
  }
66
+
67
+ return null;
37
68
  }
38
69
 
39
- clone(name?: string) {
40
- if (name == null) {
41
- name = this.name;
42
- }
70
+ clone(name = this._name): Pattern {
71
+ const not = new Not(name, this._children[0]);
72
+ return not;
73
+ }
43
74
 
44
- return new Not(this.children[0]);
75
+ getNextPattern(): Pattern | null {
76
+ return getNextPattern(this)
45
77
  }
46
78
 
47
- getTokens() {
79
+ getTokens(): string[] {
48
80
  return [];
49
81
  }
82
+
83
+ getNextTokens(_lastMatched: Pattern): string[] {
84
+ return [];
85
+ }
86
+
87
+ findPattern(isMatch: (p: Pattern) => boolean): Pattern | null {
88
+ return isMatch(this._children[0]) ? this._children[0] : null;
89
+ }
90
+
50
91
  }