clarity-pattern-parser 11.3.10 → 11.3.12

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 (106) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/dist/ast/Node.test.d.ts +1 -0
  3. package/dist/grammar/Grammar.test.d.ts +1 -0
  4. package/dist/grammar/patterns.test.d.ts +1 -0
  5. package/dist/index.browser.js +196 -113
  6. package/dist/index.browser.js.map +1 -1
  7. package/dist/index.esm.js +196 -113
  8. package/dist/index.esm.js.map +1 -1
  9. package/dist/index.js +196 -113
  10. package/dist/index.js.map +1 -1
  11. package/dist/intellisense/AutoComplete.d.ts +26 -8
  12. package/dist/intellisense/AutoComplete.test.d.ts +2 -0
  13. package/dist/intellisense/SuggestionOption.d.ts +18 -2
  14. package/dist/intellisense/css/cssValue.d.ts +3 -0
  15. package/dist/intellisense/css/divider.d.ts +3 -0
  16. package/dist/intellisense/css/hex.d.ts +3 -0
  17. package/dist/intellisense/css/method.d.ts +3 -0
  18. package/dist/intellisense/css/name.d.ts +3 -0
  19. package/dist/intellisense/css/number.d.ts +3 -0
  20. package/dist/intellisense/css/optionalSpaces.d.ts +2 -0
  21. package/dist/intellisense/css/spaces.d.ts +3 -0
  22. package/dist/intellisense/css/unit.d.ts +3 -0
  23. package/dist/intellisense/css/value.d.ts +3 -0
  24. package/dist/intellisense/css/values.d.ts +3 -0
  25. package/dist/intellisense/javascript/Javascript.test.d.ts +1 -0
  26. package/dist/intellisense/javascript/arrayLiteral.d.ts +2 -0
  27. package/dist/intellisense/javascript/assignment.d.ts +3 -0
  28. package/dist/intellisense/javascript/deleteStatement.d.ts +2 -0
  29. package/dist/intellisense/javascript/escapedCharacter.d.ts +3 -0
  30. package/dist/intellisense/javascript/exponent.d.ts +3 -0
  31. package/dist/intellisense/javascript/expression.d.ts +3 -0
  32. package/dist/intellisense/javascript/fraction.d.ts +3 -0
  33. package/dist/intellisense/javascript/infixOperator.d.ts +3 -0
  34. package/dist/intellisense/javascript/integer.d.ts +3 -0
  35. package/dist/intellisense/javascript/invocation.d.ts +2 -0
  36. package/dist/intellisense/javascript/keywords.d.ts +2 -0
  37. package/dist/intellisense/javascript/literal.d.ts +3 -0
  38. package/dist/intellisense/javascript/name.d.ts +2 -0
  39. package/dist/intellisense/javascript/numberLiteral.d.ts +2 -0
  40. package/dist/intellisense/javascript/objectAccess.d.ts +2 -0
  41. package/dist/intellisense/javascript/objectLiteral.d.ts +3 -0
  42. package/dist/intellisense/javascript/optionalSpaces.d.ts +2 -0
  43. package/dist/intellisense/javascript/parameters.d.ts +3 -0
  44. package/dist/intellisense/javascript/prefixOperator.d.ts +3 -0
  45. package/dist/intellisense/javascript/propertyAccess.d.ts +3 -0
  46. package/dist/intellisense/javascript/stringLiteral.d.ts +3 -0
  47. package/dist/patterns/Context.test.d.ts +1 -0
  48. package/dist/patterns/Cursor.test.d.ts +1 -0
  49. package/dist/patterns/CursorHistory.test.d.ts +1 -0
  50. package/dist/patterns/Expression.test.d.ts +1 -0
  51. package/dist/patterns/FiniteRepeat.test.d.ts +1 -0
  52. package/dist/patterns/InfiniteRepeat.test.d.ts +1 -0
  53. package/dist/patterns/Literal.test.d.ts +1 -0
  54. package/dist/patterns/Not.test.d.ts +1 -0
  55. package/dist/patterns/Optional.test.d.ts +1 -0
  56. package/dist/patterns/Options.test.d.ts +1 -0
  57. package/dist/patterns/PrecedenceTree.test.d.ts +1 -0
  58. package/dist/patterns/Reference.test.d.ts +1 -0
  59. package/dist/patterns/Regex.test.d.ts +1 -0
  60. package/dist/patterns/Repeat.test.d.ts +1 -0
  61. package/dist/patterns/Sequence.test.d.ts +1 -0
  62. package/dist/patterns/TakeUntil.test.d.ts +1 -0
  63. package/dist/query/query.test.d.ts +1 -0
  64. package/dist/query/selector.test.d.ts +1 -0
  65. package/jest.config.js +3 -1
  66. package/jest.coverage.config.js +2 -0
  67. package/package.json +7 -6
  68. package/src/grammar/Grammar.test.ts +1 -0
  69. package/src/grammar/Grammar.ts +2 -0
  70. package/src/grammar/patterns.test.ts +36 -0
  71. package/src/grammar/spec.md +233 -84
  72. package/src/intellisense/AutoComplete.test.ts +627 -158
  73. package/src/intellisense/AutoComplete.ts +233 -110
  74. package/src/intellisense/SuggestionOption.ts +25 -2
  75. package/src/intellisense/css/optionalSpaces.ts +1 -1
  76. package/src/intellisense/css/value.ts +1 -1
  77. package/src/patterns/Expression.ts +4 -2
  78. package/src/patterns/Literal.test.ts +1 -1
  79. package/src/patterns/Options.test.ts +1 -1
  80. package/src/patterns/PrecedenceTree.ts +31 -7
  81. package/src/patterns/Reference.test.ts +1 -1
  82. package/src/patterns/Regex.test.ts +3 -3
  83. package/src/patterns/Sequence.test.ts +1 -1
  84. package/tsconfig.json +1 -1
  85. package/dist/Cursor.d.ts +0 -37
  86. package/dist/CursorHistory.d.ts +0 -27
  87. package/dist/TextSuggester.d.ts +0 -48
  88. package/dist/ast/Visitor.d.ts +0 -31
  89. package/dist/grammar/patterns/andLiteral.d.ts +0 -4
  90. package/dist/grammar/patterns/inlinePattern.d.ts +0 -1
  91. package/dist/grammar/patterns/orLiteral.d.ts +0 -2
  92. package/dist/patterns/And.d.ts +0 -41
  93. package/dist/patterns/DepthCache.d.ts +0 -6
  94. package/dist/patterns/ExpressionPattern.d.ts +0 -66
  95. package/dist/patterns/LookAhead.d.ts +0 -8
  96. package/dist/patterns/Or.d.ts +0 -36
  97. package/dist/patterns/Recursive.d.ts +0 -12
  98. package/dist/patterns/RightAssociatedPattern.d.ts +0 -31
  99. package/dist/patterns/arePatternsEqual.d.ts +0 -2
  100. package/dist/patterns/getNextPattern.d.ts +0 -2
  101. package/dist/types.d.ts +0 -797
  102. package/src/generator/generator.test.ts +0 -27
  103. package/src/generator/generator.ts +0 -103
  104. package/src/generator/igenerator.ts +0 -6
  105. package/src/generator/ivisitor.ts +0 -23
  106. package/src/generator/typescriptVisitor.ts +0 -158
@@ -3,7 +3,8 @@ import { Match } from "../patterns/CursorHistory";
3
3
  import { ParseError } from "../patterns/ParseError";
4
4
  import { Pattern } from "../patterns/Pattern";
5
5
  import { Suggestion } from "./Suggestion";
6
- import { SuggestionOption } from "./SuggestionOption";
6
+ import { SuggestionSegment, SuggestionOption, CompositeSuggestion } from "./SuggestionOption";
7
+
7
8
 
8
9
  export interface AutoCompleteOptions {
9
10
  /**
@@ -17,6 +18,12 @@ export interface AutoCompleteOptions {
17
18
  * and the string array are the tokens suggested for that pattern.
18
19
  */
19
20
  customTokens?: Record<string, string[]>;
21
+ /**
22
+ * Suggestions may share the same text but differ in their suggestionSequence.
23
+ * By default, duplicates are removed and only the first instance is kept.
24
+ * Disabling deduplication allows all distinct instances to be returned together.
25
+ */
26
+ disableDedupe?: boolean;
20
27
  }
21
28
 
22
29
  const defaultOptions = { greedyPatternNames: [], customTokens: {} };
@@ -33,6 +40,10 @@ export class AutoComplete {
33
40
  this._text = "";
34
41
  }
35
42
 
43
+ suggestFor(text: string): Suggestion {
44
+ return this.suggestForWithCursor(new Cursor(text));
45
+ }
46
+
36
47
  suggestForWithCursor(cursor: Cursor): Suggestion {
37
48
  cursor.moveTo(0);
38
49
 
@@ -41,14 +52,17 @@ export class AutoComplete {
41
52
  this._cursor.startRecording();
42
53
 
43
54
  if (cursor.length === 0) {
44
- return {
55
+
56
+ const suggestion: Suggestion = {
45
57
  isComplete: false,
46
- options: this._createSuggestionsFromRoot(),
58
+ options: this._createSuggestionOptionsFromMatch(),
47
59
  error: new ParseError(0, 0, this._pattern),
48
60
  errorAtIndex: 0,
49
61
  cursor,
50
62
  ast: null
51
- };
63
+ }
64
+
65
+ return suggestion;
52
66
  }
53
67
 
54
68
  let errorAtIndex = null;
@@ -56,7 +70,7 @@ export class AutoComplete {
56
70
 
57
71
  const ast = this._pattern.parse(this._cursor);
58
72
  const isComplete = ast?.value === this._text;
59
- const options = this._getAllOptions();
73
+ const options = this._getAllSuggestionsOptions();
60
74
 
61
75
  if (!isComplete && options.length > 0 && !this._cursor.hasError) {
62
76
  const startIndex = options.reduce((lowestIndex, o) => {
@@ -111,161 +125,269 @@ export class AutoComplete {
111
125
  return 0;
112
126
  }
113
127
 
114
- suggestFor(text: string): Suggestion {
115
- return this.suggestForWithCursor(new Cursor(text));
116
- }
117
128
 
118
- private _getAllOptions() {
119
- const errorMatches = this._getOptionsFromErrors();
120
- const leafMatches = this._cursor.leafMatches.map((m) => this._createSuggestionsFromMatch(m)).flat();
121
- const finalResults: SuggestionOption[] = [];
122
129
 
123
- [...leafMatches, ...errorMatches].forEach(m => {
130
+ private _getAllSuggestionsOptions() {
131
+
132
+ const errorMatchOptions = this._createSuggestionOptionsFromErrors();
133
+ const leafMatchOptions = this._cursor.leafMatches.map((m) => this._createSuggestionOptionsFromMatch(m)).flat();
134
+
135
+ const finalResults: SuggestionOption[] = [];
136
+ [...leafMatchOptions, ...errorMatchOptions].forEach(m => {
124
137
  const index = finalResults.findIndex(f => m.text === f.text);
125
138
  if (index === -1) {
126
139
  finalResults.push(m);
127
140
  }
128
141
  });
129
142
 
130
- return finalResults;
143
+ return getFurthestOptions(finalResults);
131
144
  }
132
145
 
133
- private _getOptionsFromErrors() {
146
+ private _createSuggestionOptionsFromErrors() {
134
147
  // These errored because the length of the string.
135
148
  const errors = this._cursor.errors.filter(e => e.lastIndex === this._cursor.length);
136
- const suggestions = errors.map(e => {
137
- const tokens = this._getTokensForPattern(e.pattern);
138
- const adjustedTokens: Set<string> = new Set();
139
- const currentText = this._cursor.getChars(e.startIndex, e.lastIndex);
140
-
141
- tokens.forEach((token)=>{
142
- if (token.startsWith(currentText) && token.length > currentText.length){
143
- const difference = token.length - currentText.length;
144
- const suggestedText = token.slice(-difference);
145
- adjustedTokens.add(suggestedText);
146
- }
147
- });
149
+
150
+ const errorSuggestionOptions = errors.map(parseError => {
148
151
 
149
- return Array.from(adjustedTokens).map(t=>{
150
- return {
151
- text: t,
152
- startIndex: e.lastIndex,
153
- }
154
- });
155
- });
152
+ const currentText = this._cursor.getChars(parseError.startIndex, parseError.lastIndex);
153
+
154
+ const compositeSuggestions = this._getCompositeSuggestionsForPattern(parseError.pattern);
155
+ const trimmedErrorCompositeSuggestions = this._trimSuggestionsByExistingText(currentText, compositeSuggestions);
156
+
157
+ return this._createSuggestions(parseError.lastIndex, trimmedErrorCompositeSuggestions);
158
+ }).flat();
159
+
160
+ const dedupedErrorSuggestionOptions = this._deDupeCompositeSuggestions(errorSuggestionOptions);
156
161
 
157
- return suggestions.flat();
162
+ return dedupedErrorSuggestionOptions
158
163
  }
159
164
 
160
- private _createSuggestionsFromRoot(): SuggestionOption[] {
161
- const suggestions: SuggestionOption[] = [];
162
- const tokens = [...this._pattern.getTokens(),... this._getTokensForPattern(this._pattern)];
163
165
 
164
- for (const token of tokens) {
165
- if (suggestions.findIndex(s => s.text === token) === -1) {
166
- suggestions.push(this._createSuggestion("", token));
167
- }
166
+ private _createSuggestionOptionsFromMatch(match?: Match): SuggestionOption[] {
167
+
168
+ if (match?.pattern == null) {
169
+ const compositeSuggestions = this._getCompositeSuggestionsForPattern(this._pattern);
170
+ return this._createSuggestions(-1, compositeSuggestions);
168
171
  }
169
172
 
170
- return suggestions;
171
- }
173
+ if ( match?.node != null) {
174
+ const currentText = this._text.slice(match.node.startIndex,match.node.endIndex)
175
+
176
+
177
+ /**Captures suggestions for a "completed" match pattern that still has existing possible suggestions.
178
+ * particularly relevant when working with set/custom tokens.
179
+ */
180
+ const matchCompositeSuggestions = this._getCompositeSuggestionsForPattern(match.pattern)
181
+ const trimmedMatchCompositeSuggestions = this._trimSuggestionsByExistingText(currentText, matchCompositeSuggestions)
172
182
 
173
- private _createSuggestionsFromMatch(match: Match): SuggestionOption[] {
174
- if (match.pattern == null) {
175
- return this._createSuggestions(-1, this._getTokensForPattern(this._pattern));
176
- }
183
+
184
+ const leafPatterns = match.pattern.getNextPatterns();
185
+ const leafCompositeSuggestions = leafPatterns.flatMap(leafPattern =>
186
+ this._getCompositeSuggestionsForPattern(leafPattern)
187
+ );
177
188
 
178
- if ( match.node != null) {
179
- const textStartingMatch = this._text.slice(match.node.startIndex,match.node.endIndex)
180
- const currentPatternsTokens = this._getTokensForPattern(match.pattern);
181
- /**
182
- * Compares tokens to current text and extracts remainder tokens
183
- * - IE. **currentText:** *abc*, **baseToken:** *abcdef*, **trailingToken:** *def*
184
- */
185
- const trailingTokens = currentPatternsTokens.reduce<string[]>((acc, token) => {
186
- if (token.startsWith(textStartingMatch)) {
187
- const sliced = token.slice(textStartingMatch.length);
188
- if (sliced !== '') {
189
- acc.push(sliced);
190
- }
191
- }
192
- return acc;
193
- }, []);
194
-
195
- const leafPatterns = match.pattern.getNextPatterns();
196
- const leafTokens = leafPatterns.reduce((acc: string[], leafPattern) => {
197
- acc.push(...this._getTokensForPattern(leafPattern));
198
- return acc;
199
- }, []);
200
-
201
- const allTokens = [...trailingTokens,...leafTokens]
202
- return this._createSuggestions(match.node.lastIndex, allTokens);
189
+ const allCompositeSuggestions = [...leafCompositeSuggestions,...trimmedMatchCompositeSuggestions,]
190
+
191
+ const dedupedCompositeSuggestions = this._deDupeCompositeSuggestions(allCompositeSuggestions);
192
+
193
+ return this._createSuggestions(match.node.lastIndex, dedupedCompositeSuggestions);
203
194
  } else {
204
195
  return [];
205
196
  }
206
197
  }
207
198
 
208
- private _getTokensForPattern(pattern: Pattern) {
209
- const augmentedTokens = this._getAugmentedTokens(pattern);
199
+ /**
200
+ * Compares suggestions with provided text and removes completed sub-sequences and preceding text
201
+ * - IE. **currentText:** *abc*, **sequence:** *[{ab}{cd}{ef}*
202
+ * - refines to {d}{ef}
203
+ */
204
+ private _trimSuggestionsByExistingText(currentText: string, compositeSuggestions: CompositeSuggestion[]): CompositeSuggestion[] {
205
+
206
+ const trimmedSuggestions = compositeSuggestions.reduce<CompositeSuggestion[]>((acc, compositeSuggestion) => {
207
+ if (compositeSuggestion.text.startsWith(currentText)) {
208
+
209
+ const filteredSegments = this._filterCompletedSubSegments(currentText, compositeSuggestion);
210
+ const slicedSuggestionText = compositeSuggestion.text.slice(currentText.length);
211
+
212
+ if (slicedSuggestionText !== '') {
213
+ const refinedCompositeSuggestion: CompositeSuggestion = {
214
+ text: slicedSuggestionText,
215
+ suggestionSequence: filteredSegments,
216
+ }
217
+
218
+ acc.push(refinedCompositeSuggestion);
219
+ }
220
+ }
221
+ return acc;
222
+ }, []);
223
+
224
+ return trimmedSuggestions
225
+ }
226
+
227
+ /** Removed segments already accounted for in the existing text.
228
+ * ie. sequence pattern segments ≈ [{look}, {an example}, {phrase}]
229
+ * fullText = "look an"
230
+ * remove {look} segment as its already been completed by the existing text.
231
+ */
232
+ private _filterCompletedSubSegments(currentText: string, compositeSuggestion: CompositeSuggestion){
233
+
234
+ let elementsToRemove: SuggestionSegment [] = [];
235
+ let workingText = currentText;
236
+
237
+ compositeSuggestion.suggestionSequence.forEach(segment => {
238
+ /**sub segment has been completed, remove it from the sequence */
239
+ if(workingText.startsWith(segment.text)){
240
+ workingText = workingText.slice(segment.text.length);
241
+ elementsToRemove.push (segment);
242
+ }
243
+ })
244
+
245
+ const filteredSegments = compositeSuggestion.suggestionSequence.filter(segment => !elementsToRemove.includes (segment) );
246
+
247
+ return filteredSegments
248
+ }
249
+
250
+ private _getCompositeSuggestionsForPattern(pattern: Pattern):CompositeSuggestion[] {
251
+
252
+ const suggestionsToReturn:CompositeSuggestion[] = [];
253
+
254
+ const leafPatterns = pattern.getPatterns();
255
+ // for when pattern has no leafPatterns and only returns itself
256
+ if(leafPatterns.length === 1 && leafPatterns[0].id === pattern.id) {
257
+
258
+ const currentCustomTokens = this._getCustomTokens(pattern);
259
+ const currentTokens = pattern.getTokens();
260
+ const allTokens = [...currentCustomTokens, ...currentTokens];
261
+
262
+ const leafCompositeSuggestions: CompositeSuggestion[] = allTokens.map(token => {
263
+
264
+ const segment:SuggestionSegment = {
265
+ text: token,
266
+ pattern: pattern,
267
+ }
268
+
269
+ const compositeSuggestion:CompositeSuggestion = {
270
+ text: token,
271
+ suggestionSequence: [segment],
272
+ }
273
+ return compositeSuggestion;
274
+ })
275
+ suggestionsToReturn.push(...leafCompositeSuggestions);
276
+
277
+ }else{
278
+
279
+ const currentCustomTokens = this._getCustomTokens(pattern);
280
+
281
+ const patternsSuggestionList = currentCustomTokens.map(token => {
282
+ const segment:SuggestionSegment = {
283
+ text: token,
284
+ pattern: pattern,
285
+ }
286
+
287
+ const patternSuggestion:CompositeSuggestion = {
288
+ text: token,
289
+ suggestionSequence: [segment],
290
+ }
291
+ return patternSuggestion;
292
+ })
293
+
294
+ const leafCompositeSuggestions = leafPatterns.map(lp => this._getCompositeSuggestionsForPattern(lp)).flat();
295
+
296
+ suggestionsToReturn.push(...patternsSuggestionList, ...leafCompositeSuggestions);
297
+ }
210
298
 
211
299
  if (this._options.greedyPatternNames != null && this._options.greedyPatternNames.includes(pattern.name)) {
300
+
212
301
  const nextPatterns = pattern.getNextPatterns();
213
- const nextPatternTokens = nextPatterns.reduce((acc: string[], pattern) => {
214
- acc.push(...this._getTokensForPattern(pattern));
302
+
303
+ const nextPatternedTokensList = nextPatterns.reduce<CompositeSuggestion[]>((acc, pattern) => {
304
+ const patternedTokensList = this._getCompositeSuggestionsForPattern(pattern);
305
+ acc.push(...patternedTokensList);
306
+
215
307
  return acc;
216
308
  }, []);
217
- // using set to prevent duplicates
218
- const tokens = new Set<string>();
219
309
 
220
- for (const token of augmentedTokens) {
221
- for (const nextPatternToken of nextPatternTokens) {
222
- tokens.add(token + nextPatternToken);
310
+ const compositeSuggestionList:CompositeSuggestion[] = [];
311
+
312
+ for (const currentSuggestion of suggestionsToReturn) {
313
+ for (const nextSuggestionWithSubElements of nextPatternedTokensList) {
314
+
315
+ const augmentedTokenWithPattern:CompositeSuggestion = {
316
+ text: currentSuggestion.text + nextSuggestionWithSubElements.text,
317
+ suggestionSequence: [...currentSuggestion.suggestionSequence,...nextSuggestionWithSubElements.suggestionSequence ],
318
+ }
319
+
320
+ compositeSuggestionList.push(augmentedTokenWithPattern);
223
321
  }
224
322
  }
225
323
 
226
- return [...tokens]
227
- } else {
228
- return augmentedTokens;
324
+ return compositeSuggestionList;
325
+
326
+ } else {
327
+
328
+ const dedupedSuggestions = this._deDupeCompositeSuggestions(suggestionsToReturn);
329
+ return dedupedSuggestions;
229
330
  }
230
331
  }
231
332
 
232
- private _getAugmentedTokens(pattern: Pattern) {
233
- const customTokensMap: any = this._options.customTokens || {};
234
- const leafPatterns = pattern.getPatterns();
333
+ private _getCustomTokens(pattern: Pattern) {
235
334
 
236
- /** Using Set to
237
- * - prevent duplicates
238
- * - prevent mutation of original customTokensMap
239
- */
240
- const customTokensForPattern = new Set<string>(customTokensMap[pattern.name] ?? []);
335
+ const customTokensMap: Record<string, string[]> = this._options.customTokens || {};
336
+ const customTokens = customTokensMap[pattern.name] ?? [];
241
337
 
242
- for (const lp of leafPatterns) {
243
- const augmentedTokens = customTokensMap[lp.name] ?? [];
244
- const lpsCombinedTokens = [...lp.getTokens(), ...augmentedTokens];
338
+ const allTokens = [...customTokens];
245
339
 
246
- for (const token of lpsCombinedTokens) {
247
- customTokensForPattern.add(token);
340
+ return allTokens;
248
341
  }
342
+
343
+
344
+
345
+ private _deDupeCompositeSuggestions<T extends CompositeSuggestion>(suggestions: T[]): T[] {
346
+
347
+ if (this._options.disableDedupe) {
348
+ return suggestions;
349
+ }
350
+
351
+ const seen = new Set<string>();
352
+ const unique: T[] = [];
353
+
354
+ for (const suggestion of suggestions) {
355
+ // Create a unique key based on text and subElements
356
+ const subElementsKey = suggestion.suggestionSequence
357
+ .map(sub => ` ${sub.pattern.name} - ${sub.text} `)
358
+ .sort()
359
+ .join('|');
360
+
361
+
362
+ const key = `${suggestion.text}|${subElementsKey}`;
363
+
364
+ if (!seen.has(key)) {
365
+ seen.add(key);
366
+ unique.push(suggestion);
367
+ }
368
+ }
369
+
370
+ return unique;
249
371
  }
250
- return [...customTokensForPattern];
251
- }
252
372
 
253
- private _createSuggestions(lastIndex: number, tokens: string[]): SuggestionOption[] {
373
+ private _createSuggestions(lastIndex: number, compositeSuggestionList:CompositeSuggestion[]): SuggestionOption[] {
374
+
254
375
  let textToIndex = lastIndex === -1 ? "" : this._cursor.getChars(0, lastIndex);
255
376
  const suggestionStrings: string[] = [];
256
377
  const options: SuggestionOption[] = [];
257
378
 
258
- for (const token of tokens) {
379
+ for (const compositeSuggestion of compositeSuggestionList) {
259
380
  // concatenated for start index identification inside createSuggestion
260
- const suggestion = textToIndex + token;
261
- const alreadyExist = suggestionStrings.includes(suggestion);
262
- const isSameAsText = suggestion === this._text;
381
+ const existingTextWithSuggestion = textToIndex + compositeSuggestion.text;
263
382
 
264
- if ( !alreadyExist && !isSameAsText) {
265
- suggestionStrings.push(suggestion);
266
- const suggestionOption = this._createSuggestion(this._cursor.text, suggestion)
383
+ const alreadyExist = suggestionStrings.includes(existingTextWithSuggestion);
384
+ const isSameAsText = existingTextWithSuggestion === this._text;
385
+
386
+ // if ( !alreadyExist && !isSameAsText) {
387
+ suggestionStrings.push(existingTextWithSuggestion);
388
+ const suggestionOption = this._createSuggestionOption(this._cursor.text, existingTextWithSuggestion, compositeSuggestion.suggestionSequence);
267
389
  options.push(suggestionOption);
268
- }
390
+ // }
269
391
  }
270
392
 
271
393
  const reducedOptions = getFurthestOptions(options);
@@ -274,13 +396,14 @@ export class AutoComplete {
274
396
  return reducedOptions;
275
397
  }
276
398
 
277
- private _createSuggestion(fullText: string, suggestion: string): SuggestionOption {
399
+ private _createSuggestionOption(fullText: string, suggestion: string, segments: SuggestionSegment[]): SuggestionOption {
278
400
  const furthestMatch = findMatchIndex(suggestion, fullText);
279
401
  const text = suggestion.slice(furthestMatch);
280
402
 
281
403
  const option:SuggestionOption = {
282
404
  text: text,
283
405
  startIndex: furthestMatch,
406
+ suggestionSequence: segments,
284
407
  };
285
408
  return option
286
409
  }
@@ -1,4 +1,27 @@
1
- export interface SuggestionOption {
2
- text: string;
1
+ import { Pattern } from "../patterns/Pattern";
2
+
3
+
4
+ /**
5
+ * A CompositeSuggestion associated with a index to start at when inserting into existing text.
6
+ */
7
+ export interface SuggestionOption extends CompositeSuggestion {
3
8
  startIndex: number;
4
9
  }
10
+
11
+
12
+ /**
13
+ * Represents a composed suggestion, derived from potential next tokens defined by the pattern, and configured auto complete options
14
+ * text: represents the full composed text of the suggestion; a concatenated string of relevant suggestion segments.
15
+ */
16
+ export interface CompositeSuggestion{
17
+ text: string;
18
+ suggestionSequence: SuggestionSegment[];
19
+ }
20
+
21
+
22
+ /** A segment of a parent composite suggestion and a reference to the pattern that this segment token was derived from. */
23
+ export interface SuggestionSegment {
24
+ text: string;
25
+ pattern: Pattern;
26
+ }
27
+
@@ -1,5 +1,5 @@
1
1
  import spaces from "./spaces";
2
2
 
3
- const optionalSpaces = spaces.clone(spaces.name, true);
3
+ const optionalSpaces = spaces.clone(spaces.name);
4
4
 
5
5
  export default optionalSpaces;
@@ -5,6 +5,6 @@ import method from "./method";
5
5
  import name from "./name"
6
6
  import { Options } from "../../patterns/Options";
7
7
 
8
- const value = new Or("value", [hex, method, unit, number, name]);
8
+ const value = new Options("value", [hex, method, unit, number, name]);
9
9
 
10
10
  export default value;
@@ -112,7 +112,7 @@ export class Expression implements Pattern {
112
112
 
113
113
  private _organizePatterns(patterns: Pattern[]) {
114
114
  const finalPatterns: Pattern[] = [];
115
- patterns.forEach((pattern) => {
115
+ patterns.forEach((pattern, index) => {
116
116
 
117
117
  if (this._isAtom(pattern)) {
118
118
  const atom = pattern.clone();
@@ -127,6 +127,7 @@ export class Expression implements Pattern {
127
127
 
128
128
  prefix.parent = this;
129
129
 
130
+ this._precedenceMap[name] = index;
130
131
  this._prefixPatterns.push(prefix);
131
132
  this._prefixNames.push(name);
132
133
 
@@ -136,6 +137,7 @@ export class Expression implements Pattern {
136
137
  const postfix = this._extractPostfix(pattern);
137
138
  postfix.parent = this;
138
139
 
140
+ this._precedenceMap[name] = index;
139
141
  this._postfixPatterns.push(postfix);
140
142
  this._postfixNames.push(name);
141
143
 
@@ -145,7 +147,7 @@ export class Expression implements Pattern {
145
147
  const binary = this._extractBinary(pattern);
146
148
  binary.parent = this;
147
149
 
148
- this._precedenceMap[name] = this._binaryPatterns.length;
150
+ this._precedenceMap[name] = index;
149
151
  this._binaryPatterns.push(binary);
150
152
  this._binaryNames.push(name);
151
153
 
@@ -7,7 +7,7 @@ describe("Literal", () => {
7
7
  test("Empty Value", () => {
8
8
  expect(() => {
9
9
  new Literal("empty", "");
10
- }).toThrowError();
10
+ }).toThrow();
11
11
  });
12
12
 
13
13
  test("Successful Parse", () => {
@@ -13,7 +13,7 @@ describe("Options", () => {
13
13
  test("Empty Options", () => {
14
14
  expect(() => {
15
15
  new Options("bad", []);
16
- }).toThrowError();
16
+ }).toThrow();
17
17
  });
18
18
 
19
19
  test("One Option Successful", () => {
@@ -175,14 +175,38 @@ export class PrecedenceTree {
175
175
  private _compileAtomNode() {
176
176
  let node = this._atomNode;
177
177
 
178
- if (this._prefixNode != null && this._atomNode != null) {
179
- node = this._prefixNode.findRoot();
180
- this._prefixPlaceholder.replaceWith(this._atomNode);
181
- }
178
+ if (this._prefixNode != null && this._postfixNode != null && this._atomNode != null) {
179
+ let firstNode = this._prefixNode;
180
+ let secondNode = this._postfixNode;
181
+ let firstPlaceholder = this._prefixPlaceholder;
182
+ let secondPlaceholder = this._postfixPlaceholder;
183
+ const prefixName = this._prefixNode.name;
184
+ const postfixName = this._postfixNode.name;
185
+ const prefixPrecedence = this._getPrecedence(prefixName);
186
+ const postfixPrecedence = this._getPrecedence(postfixName);
187
+
188
+ // The Precedence is the index of the patterns, so its lower not higher :\
189
+ if (prefixPrecedence > postfixPrecedence) {
190
+ firstNode = this._postfixNode;
191
+ secondNode = this._prefixNode;
192
+ firstPlaceholder = this._postfixPlaceholder;
193
+ secondPlaceholder = this._prefixPlaceholder;
194
+ }
195
+
196
+ node = firstNode.findRoot();
197
+ firstPlaceholder.replaceWith(this._atomNode);
198
+ secondPlaceholder.replaceWith(node);
199
+ node = secondNode;
200
+ } else {
201
+ if (this._prefixNode != null && this._atomNode != null) {
202
+ node = this._prefixNode.findRoot();
203
+ this._prefixPlaceholder.replaceWith(this._atomNode);
204
+ }
182
205
 
183
- if (this._postfixNode != null && node != null) {
184
- this._postfixPlaceholder.replaceWith(node);
185
- node = this._postfixNode;
206
+ if (this._postfixNode != null && node != null) {
207
+ this._postfixPlaceholder.replaceWith(node);
208
+ node = this._postfixNode;
209
+ }
186
210
  }
187
211
 
188
212
  this._prefixNode = null;
@@ -50,7 +50,7 @@ describe("Reference", () => {
50
50
 
51
51
  expect(() => {
52
52
  ref.parse(new Cursor("text"));
53
- }).toThrowError();
53
+ }).toThrow();
54
54
  });
55
55
 
56
56
  test("Get Tokens", () => {
@@ -7,15 +7,15 @@ import { Pattern } from "./Pattern";
7
7
 
8
8
  describe("Regex", () => {
9
9
  test("Empty String", () => {
10
- expect(() => new Regex("empty", "")).toThrowError();
10
+ expect(() => new Regex("empty", "")).toThrow();
11
11
  });
12
12
 
13
13
  test("Starts With ^", () => {
14
- expect(() => new Regex("carrot", "^")).toThrowError();
14
+ expect(() => new Regex("carrot", "^")).toThrow();
15
15
  });
16
16
 
17
17
  test("Ends With $", () => {
18
- expect(() => new Regex("money", ".$")).toThrowError();
18
+ expect(() => new Regex("money", ".$")).toThrow();
19
19
  });
20
20
 
21
21
  test("Successful Parse", () => {
@@ -9,7 +9,7 @@ describe("Sequence", () => {
9
9
  test("No Patterns", () => {
10
10
  expect(() => {
11
11
  new Sequence("empty", []);
12
- }).toThrowError();
12
+ }).toThrow();
13
13
  });
14
14
 
15
15
  test("One Pattern Match Successful", () => {
package/tsconfig.json CHANGED
@@ -17,5 +17,5 @@
17
17
  "strictNullChecks": true
18
18
  },
19
19
  "exclude": ["node_modules"],
20
- "files": ["./src/index.ts"]
20
+ "include": ["src"]
21
21
  }