clarity-pattern-parser 11.0.21 → 11.0.23
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/dist/grammar/Grammar.d.ts +0 -1
- package/dist/index.browser.js +280 -256
- package/dist/index.browser.js.map +1 -1
- package/dist/index.esm.js +280 -256
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +280 -256
- package/dist/index.js.map +1 -1
- package/dist/patterns/Sequence.d.ts +2 -1
- package/dist/patterns/generate_error_message.d.ts +3 -0
- package/package.json +1 -1
- package/src/grammar/Grammar.ts +11 -22
- package/src/patterns/Literal.ts +4 -0
- package/src/patterns/Reference.ts +3 -1
- package/src/patterns/Sequence.test.ts +11 -0
- package/src/patterns/Sequence.ts +13 -4
- package/src/patterns/generate_error_message.ts +33 -0
- package/src/query/query.test.ts +174 -0
- package/src/query/query.ts +152 -16
- package/src/query/selector.test.ts +262 -0
- package/src/query/selector.ts +280 -0
- package/src/query/selector_parser.ts +44 -3
|
@@ -13,7 +13,6 @@ export declare class Grammar {
|
|
|
13
13
|
private _originResource?;
|
|
14
14
|
private _resolveImport;
|
|
15
15
|
private _parseContext;
|
|
16
|
-
private _autoComplete;
|
|
17
16
|
constructor(options?: GrammarOptions);
|
|
18
17
|
import(path: string): Promise<Record<string, Pattern>>;
|
|
19
18
|
parse(expression: string): Promise<Record<string, Pattern>>;
|
package/dist/index.browser.js
CHANGED
|
@@ -606,6 +606,9 @@
|
|
|
606
606
|
_tryToParse(cursor) {
|
|
607
607
|
let passed = false;
|
|
608
608
|
const literalRuneLength = this._runes.length;
|
|
609
|
+
if (!cursor.hasNext) {
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
609
612
|
for (let i = 0; i < literalRuneLength; i++) {
|
|
610
613
|
const literalRune = this._runes[i];
|
|
611
614
|
const cursorRune = cursor.currentChar;
|
|
@@ -871,6 +874,7 @@
|
|
|
871
874
|
}
|
|
872
875
|
_cacheAncestors(id) {
|
|
873
876
|
if (!this._cachedAncestors) {
|
|
877
|
+
this._cachedAncestors = true;
|
|
874
878
|
let pattern = this.parent;
|
|
875
879
|
while (pattern != null) {
|
|
876
880
|
if (pattern.id === id) {
|
|
@@ -883,7 +887,7 @@
|
|
|
883
887
|
_isBeyondRecursiveAllowance() {
|
|
884
888
|
let depth = 0;
|
|
885
889
|
for (let pattern of this._recursiveAncestors) {
|
|
886
|
-
if (pattern.
|
|
890
|
+
if (pattern.startedOnIndex === this.startedOnIndex) {
|
|
887
891
|
depth++;
|
|
888
892
|
if (depth > 0) {
|
|
889
893
|
return true;
|
|
@@ -1769,7 +1773,7 @@
|
|
|
1769
1773
|
else {
|
|
1770
1774
|
// We are at the end of the text, it may still be valid, if all the
|
|
1771
1775
|
// following patterns are optional.
|
|
1772
|
-
if (this.
|
|
1776
|
+
if (this._areRemainingPatternsOptional(i)) {
|
|
1773
1777
|
passed = true;
|
|
1774
1778
|
break;
|
|
1775
1779
|
}
|
|
@@ -1788,7 +1792,7 @@
|
|
|
1788
1792
|
else {
|
|
1789
1793
|
// If we don't have any results from what we parsed then record error.
|
|
1790
1794
|
const lastNode = this.getLastValidNode();
|
|
1791
|
-
if (lastNode === null) {
|
|
1795
|
+
if (lastNode === null && !this._areAllPatternsOptional()) {
|
|
1792
1796
|
cursor.recordErrorAt(this._firstIndex, cursor.index, this);
|
|
1793
1797
|
break;
|
|
1794
1798
|
}
|
|
@@ -1812,7 +1816,10 @@
|
|
|
1812
1816
|
}
|
|
1813
1817
|
return nodes[nodes.length - 1];
|
|
1814
1818
|
}
|
|
1815
|
-
|
|
1819
|
+
_areAllPatternsOptional() {
|
|
1820
|
+
return this._areRemainingPatternsOptional(-1);
|
|
1821
|
+
}
|
|
1822
|
+
_areRemainingPatternsOptional(fromIndex) {
|
|
1816
1823
|
const startOnIndex = fromIndex + 1;
|
|
1817
1824
|
const length = this._children.length;
|
|
1818
1825
|
for (let i = startOnIndex; i < length; i++) {
|
|
@@ -1825,6 +1832,10 @@
|
|
|
1825
1832
|
}
|
|
1826
1833
|
createNode(cursor) {
|
|
1827
1834
|
const children = filterOutNull(this._nodes);
|
|
1835
|
+
if (children.length === 0) {
|
|
1836
|
+
cursor.moveTo(this._firstIndex);
|
|
1837
|
+
return null;
|
|
1838
|
+
}
|
|
1828
1839
|
const lastIndex = children[children.length - 1].lastIndex;
|
|
1829
1840
|
cursor.moveTo(lastIndex);
|
|
1830
1841
|
return new Node("sequence", this._name, this._firstIndex, lastIndex, children);
|
|
@@ -2368,239 +2379,6 @@
|
|
|
2368
2379
|
}
|
|
2369
2380
|
}
|
|
2370
2381
|
|
|
2371
|
-
const defaultOptions = { greedyPatternNames: [], customTokens: {} };
|
|
2372
|
-
class AutoComplete {
|
|
2373
|
-
constructor(pattern, options = defaultOptions) {
|
|
2374
|
-
this._pattern = pattern;
|
|
2375
|
-
this._options = options;
|
|
2376
|
-
this._text = "";
|
|
2377
|
-
}
|
|
2378
|
-
suggestForWithCursor(cursor) {
|
|
2379
|
-
cursor.moveTo(0);
|
|
2380
|
-
this._cursor = cursor;
|
|
2381
|
-
this._text = cursor.text;
|
|
2382
|
-
this._cursor.startRecording();
|
|
2383
|
-
if (cursor.length === 0) {
|
|
2384
|
-
return {
|
|
2385
|
-
isComplete: false,
|
|
2386
|
-
options: this._createSuggestionsFromRoot(),
|
|
2387
|
-
error: new ParseError(0, 0, this._pattern),
|
|
2388
|
-
errorAtIndex: 0,
|
|
2389
|
-
cursor,
|
|
2390
|
-
ast: null
|
|
2391
|
-
};
|
|
2392
|
-
}
|
|
2393
|
-
let errorAtIndex = null;
|
|
2394
|
-
let error = null;
|
|
2395
|
-
const ast = this._pattern.parse(this._cursor);
|
|
2396
|
-
const isComplete = (ast === null || ast === void 0 ? void 0 : ast.value) === this._text;
|
|
2397
|
-
const options = this._getAllOptions();
|
|
2398
|
-
if (!isComplete && options.length > 0 && !this._cursor.hasError) {
|
|
2399
|
-
const startIndex = options.reduce((lowestIndex, o) => {
|
|
2400
|
-
return Math.min(lowestIndex, o.startIndex);
|
|
2401
|
-
}, Infinity);
|
|
2402
|
-
const lastIndex = cursor.getLastIndex() + 1;
|
|
2403
|
-
error = new ParseError(startIndex, lastIndex, this._pattern);
|
|
2404
|
-
errorAtIndex = startIndex;
|
|
2405
|
-
}
|
|
2406
|
-
else if (!isComplete && options.length === 0 && ast != null) {
|
|
2407
|
-
const startIndex = ast.endIndex;
|
|
2408
|
-
const lastIndex = cursor.getLastIndex() + 1;
|
|
2409
|
-
error = new ParseError(startIndex, lastIndex, this._pattern);
|
|
2410
|
-
errorAtIndex = startIndex;
|
|
2411
|
-
}
|
|
2412
|
-
else if (!isComplete && this._cursor.hasError && this._cursor.furthestError != null) {
|
|
2413
|
-
errorAtIndex = this.getFurthestPosition(cursor);
|
|
2414
|
-
error = new ParseError(errorAtIndex, errorAtIndex, this._pattern);
|
|
2415
|
-
}
|
|
2416
|
-
return {
|
|
2417
|
-
isComplete: isComplete,
|
|
2418
|
-
options: options,
|
|
2419
|
-
error,
|
|
2420
|
-
errorAtIndex,
|
|
2421
|
-
cursor: cursor,
|
|
2422
|
-
ast,
|
|
2423
|
-
};
|
|
2424
|
-
}
|
|
2425
|
-
getFurthestPosition(cursor) {
|
|
2426
|
-
const furthestError = cursor.furthestError;
|
|
2427
|
-
const furthestMatch = cursor.allMatchedNodes[cursor.allMatchedNodes.length - 1];
|
|
2428
|
-
if (furthestError && furthestMatch) {
|
|
2429
|
-
if (furthestError.lastIndex > furthestMatch.endIndex) {
|
|
2430
|
-
return furthestMatch.endIndex;
|
|
2431
|
-
}
|
|
2432
|
-
else {
|
|
2433
|
-
return furthestError.lastIndex;
|
|
2434
|
-
}
|
|
2435
|
-
}
|
|
2436
|
-
if (furthestError == null && furthestMatch != null) {
|
|
2437
|
-
return furthestMatch.endIndex;
|
|
2438
|
-
}
|
|
2439
|
-
if (furthestMatch == null && furthestError != null) {
|
|
2440
|
-
return furthestError.lastIndex;
|
|
2441
|
-
}
|
|
2442
|
-
return 0;
|
|
2443
|
-
}
|
|
2444
|
-
suggestFor(text) {
|
|
2445
|
-
return this.suggestForWithCursor(new Cursor(text));
|
|
2446
|
-
}
|
|
2447
|
-
_getAllOptions() {
|
|
2448
|
-
const errorMatches = this._getOptionsFromErrors();
|
|
2449
|
-
const leafMatches = this._cursor.leafMatches.map((m) => this._createSuggestionsFromMatch(m)).flat();
|
|
2450
|
-
const finalResults = [];
|
|
2451
|
-
[...leafMatches, ...errorMatches].forEach(m => {
|
|
2452
|
-
const index = finalResults.findIndex(f => m.text === f.text);
|
|
2453
|
-
if (index === -1) {
|
|
2454
|
-
finalResults.push(m);
|
|
2455
|
-
}
|
|
2456
|
-
});
|
|
2457
|
-
return finalResults;
|
|
2458
|
-
}
|
|
2459
|
-
_getOptionsFromErrors() {
|
|
2460
|
-
// These errored because the length of the string.
|
|
2461
|
-
const errors = this._cursor.errors.filter(e => e.lastIndex === this._cursor.length);
|
|
2462
|
-
const suggestions = errors.map(e => {
|
|
2463
|
-
const tokens = this._getTokensForPattern(e.pattern);
|
|
2464
|
-
const adjustedTokens = new Set();
|
|
2465
|
-
const currentText = this._cursor.getChars(e.startIndex, e.lastIndex);
|
|
2466
|
-
tokens.forEach((token) => {
|
|
2467
|
-
if (token.startsWith(currentText) && token.length > currentText.length) {
|
|
2468
|
-
const difference = token.length - currentText.length;
|
|
2469
|
-
const suggestedText = token.slice(-difference);
|
|
2470
|
-
adjustedTokens.add(suggestedText);
|
|
2471
|
-
}
|
|
2472
|
-
});
|
|
2473
|
-
return Array.from(adjustedTokens).map(t => {
|
|
2474
|
-
return {
|
|
2475
|
-
text: t,
|
|
2476
|
-
startIndex: e.lastIndex,
|
|
2477
|
-
};
|
|
2478
|
-
});
|
|
2479
|
-
});
|
|
2480
|
-
return suggestions.flat();
|
|
2481
|
-
}
|
|
2482
|
-
_createSuggestionsFromRoot() {
|
|
2483
|
-
const suggestions = [];
|
|
2484
|
-
const tokens = this._pattern.getTokens();
|
|
2485
|
-
for (const token of tokens) {
|
|
2486
|
-
if (suggestions.findIndex(s => s.text === token) === -1) {
|
|
2487
|
-
suggestions.push(this._createSuggestion("", token));
|
|
2488
|
-
}
|
|
2489
|
-
}
|
|
2490
|
-
return suggestions;
|
|
2491
|
-
}
|
|
2492
|
-
_createSuggestionsFromMatch(match) {
|
|
2493
|
-
if (match.pattern == null) {
|
|
2494
|
-
return this._createSuggestions(-1, this._getTokensForPattern(this._pattern));
|
|
2495
|
-
}
|
|
2496
|
-
const leafPattern = match.pattern;
|
|
2497
|
-
const parent = match.pattern.parent;
|
|
2498
|
-
if (parent !== null && match.node != null) {
|
|
2499
|
-
const patterns = leafPattern.getNextPatterns();
|
|
2500
|
-
const tokens = patterns.reduce((acc, pattern) => {
|
|
2501
|
-
acc.push(...this._getTokensForPattern(pattern));
|
|
2502
|
-
return acc;
|
|
2503
|
-
}, []);
|
|
2504
|
-
return this._createSuggestions(match.node.lastIndex, tokens);
|
|
2505
|
-
}
|
|
2506
|
-
else {
|
|
2507
|
-
return [];
|
|
2508
|
-
}
|
|
2509
|
-
}
|
|
2510
|
-
_getTokensForPattern(pattern) {
|
|
2511
|
-
const augmentedTokens = this._getAugmentedTokens(pattern);
|
|
2512
|
-
if (this._options.greedyPatternNames != null && this._options.greedyPatternNames.includes(pattern.name)) {
|
|
2513
|
-
const nextPatterns = pattern.getNextPatterns();
|
|
2514
|
-
const tokens = [];
|
|
2515
|
-
const nextPatternTokens = nextPatterns.reduce((acc, pattern) => {
|
|
2516
|
-
acc.push(...this._getTokensForPattern(pattern));
|
|
2517
|
-
return acc;
|
|
2518
|
-
}, []);
|
|
2519
|
-
for (let token of augmentedTokens) {
|
|
2520
|
-
for (let nextPatternToken of nextPatternTokens) {
|
|
2521
|
-
tokens.push(token + nextPatternToken);
|
|
2522
|
-
}
|
|
2523
|
-
}
|
|
2524
|
-
return tokens;
|
|
2525
|
-
}
|
|
2526
|
-
else {
|
|
2527
|
-
return augmentedTokens;
|
|
2528
|
-
}
|
|
2529
|
-
}
|
|
2530
|
-
_getAugmentedTokens(pattern) {
|
|
2531
|
-
const customTokensMap = this._options.customTokens || {};
|
|
2532
|
-
const leafPatterns = pattern.getPatterns();
|
|
2533
|
-
const tokens = customTokensMap[pattern.name] || [];
|
|
2534
|
-
leafPatterns.forEach(p => {
|
|
2535
|
-
const augmentedTokens = customTokensMap[p.name] || [];
|
|
2536
|
-
tokens.push(...p.getTokens(), ...augmentedTokens);
|
|
2537
|
-
});
|
|
2538
|
-
return tokens;
|
|
2539
|
-
}
|
|
2540
|
-
_createSuggestions(lastIndex, tokens) {
|
|
2541
|
-
let substring = lastIndex === -1 ? "" : this._cursor.getChars(0, lastIndex);
|
|
2542
|
-
const suggestionStrings = [];
|
|
2543
|
-
const options = [];
|
|
2544
|
-
for (const token of tokens) {
|
|
2545
|
-
const suggestion = substring + token;
|
|
2546
|
-
const startsWith = suggestion.startsWith(substring);
|
|
2547
|
-
const alreadyExist = suggestionStrings.includes(suggestion);
|
|
2548
|
-
const isSameAsText = suggestion === this._text;
|
|
2549
|
-
if (startsWith && !alreadyExist && !isSameAsText) {
|
|
2550
|
-
suggestionStrings.push(suggestion);
|
|
2551
|
-
options.push(this._createSuggestion(this._cursor.text, suggestion));
|
|
2552
|
-
}
|
|
2553
|
-
}
|
|
2554
|
-
const reducedOptions = getFurthestOptions(options);
|
|
2555
|
-
reducedOptions.sort((a, b) => a.text.localeCompare(b.text));
|
|
2556
|
-
return reducedOptions;
|
|
2557
|
-
}
|
|
2558
|
-
_createSuggestion(fullText, suggestion) {
|
|
2559
|
-
const furthestMatch = findMatchIndex(suggestion, fullText);
|
|
2560
|
-
const text = suggestion.slice(furthestMatch);
|
|
2561
|
-
return {
|
|
2562
|
-
text: text,
|
|
2563
|
-
startIndex: furthestMatch,
|
|
2564
|
-
};
|
|
2565
|
-
}
|
|
2566
|
-
static suggestFor(text, pattern, options) {
|
|
2567
|
-
return new AutoComplete(pattern, options).suggestFor(text);
|
|
2568
|
-
}
|
|
2569
|
-
static suggestForWithCursor(cursor, pattern, options) {
|
|
2570
|
-
return new AutoComplete(pattern, options).suggestForWithCursor(cursor);
|
|
2571
|
-
}
|
|
2572
|
-
}
|
|
2573
|
-
function findMatchIndex(str1, str2) {
|
|
2574
|
-
let matchCount = 0;
|
|
2575
|
-
let minLength = str1.length;
|
|
2576
|
-
if (str2.length < minLength) {
|
|
2577
|
-
minLength = str2.length;
|
|
2578
|
-
}
|
|
2579
|
-
for (let i = 0; i < minLength; i++) {
|
|
2580
|
-
if (str1[i] === str2[i]) {
|
|
2581
|
-
matchCount++;
|
|
2582
|
-
}
|
|
2583
|
-
else {
|
|
2584
|
-
break;
|
|
2585
|
-
}
|
|
2586
|
-
}
|
|
2587
|
-
return matchCount;
|
|
2588
|
-
}
|
|
2589
|
-
function getFurthestOptions(options) {
|
|
2590
|
-
let furthestOptions = [];
|
|
2591
|
-
let furthestIndex = -1;
|
|
2592
|
-
for (const option of options) {
|
|
2593
|
-
if (option.startIndex > furthestIndex) {
|
|
2594
|
-
furthestIndex = option.startIndex;
|
|
2595
|
-
furthestOptions = [];
|
|
2596
|
-
}
|
|
2597
|
-
if (option.startIndex === furthestIndex) {
|
|
2598
|
-
furthestOptions.push(option);
|
|
2599
|
-
}
|
|
2600
|
-
}
|
|
2601
|
-
return furthestOptions;
|
|
2602
|
-
}
|
|
2603
|
-
|
|
2604
2382
|
let contextId = 0;
|
|
2605
2383
|
class Context {
|
|
2606
2384
|
get id() {
|
|
@@ -3363,6 +3141,30 @@
|
|
|
3363
3141
|
}
|
|
3364
3142
|
}
|
|
3365
3143
|
|
|
3144
|
+
function generateErrorMessage(pattern, cursor) {
|
|
3145
|
+
const furthestMatch = cursor.leafMatch;
|
|
3146
|
+
if (furthestMatch == null || furthestMatch.node == null || furthestMatch.pattern == null) {
|
|
3147
|
+
const suggestions = cleanSuggestions(pattern.getTokens()).join(", ");
|
|
3148
|
+
return `Error at line 1, column 1. Hint: ${suggestions}`;
|
|
3149
|
+
}
|
|
3150
|
+
const endIndex = furthestMatch.node.endIndex;
|
|
3151
|
+
if (endIndex === 0) {
|
|
3152
|
+
const suggestions = cleanSuggestions(pattern.getTokens()).join(", ");
|
|
3153
|
+
return `Error at line 1, column 1. Hint: ${suggestions}`;
|
|
3154
|
+
}
|
|
3155
|
+
const lastPattern = furthestMatch.pattern;
|
|
3156
|
+
const suggestions = cleanSuggestions(lastPattern.getTokens());
|
|
3157
|
+
const strUpToError = cursor.getChars(0, endIndex);
|
|
3158
|
+
const lines = strUpToError.split("\n");
|
|
3159
|
+
const lastLine = lines[lines.length - 1];
|
|
3160
|
+
const line = lines.length;
|
|
3161
|
+
const column = lastLine.length;
|
|
3162
|
+
return `Error at line ${line}, column ${column}. Hint: ${suggestions}`;
|
|
3163
|
+
}
|
|
3164
|
+
function cleanSuggestions(suggestions) {
|
|
3165
|
+
return suggestions.map(s => s.trim()).filter(s => s.length > 0);
|
|
3166
|
+
}
|
|
3167
|
+
|
|
3366
3168
|
let anonymousIndexId = 0;
|
|
3367
3169
|
const patternNodes = {
|
|
3368
3170
|
"literal": true,
|
|
@@ -3390,9 +3192,6 @@
|
|
|
3390
3192
|
this._originResource = (options === null || options === void 0 ? void 0 : options.originResource) == null ? null : options.originResource;
|
|
3391
3193
|
this._resolveImport = options.resolveImport == null ? defaultImportResolver : options.resolveImport;
|
|
3392
3194
|
this._parseContext = new ParseContext(this._params);
|
|
3393
|
-
this._autoComplete = new AutoComplete(grammar, {
|
|
3394
|
-
greedyPatternNames: ["spaces", "optional-spaces", "whitespace", "new-line"],
|
|
3395
|
-
});
|
|
3396
3195
|
}
|
|
3397
3196
|
import(path) {
|
|
3398
3197
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -3432,19 +3231,11 @@
|
|
|
3432
3231
|
return patterns;
|
|
3433
3232
|
}
|
|
3434
3233
|
_tryToParse(expression) {
|
|
3435
|
-
const { ast, cursor
|
|
3436
|
-
if (
|
|
3437
|
-
const
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
const expectedTexts = "'" + options.map(o => {
|
|
3441
|
-
const startText = text.slice(Math.max(o.startIndex - 10), o.startIndex);
|
|
3442
|
-
return startText + o.text;
|
|
3443
|
-
}).join("' or '") + "'";
|
|
3444
|
-
const message = `[Parse Error] Found: '${foundText}', expected: ${expectedTexts}.`;
|
|
3445
|
-
throw new Error(message);
|
|
3446
|
-
}
|
|
3447
|
-
// If it is complete it will always have a node. So we have to cast it.
|
|
3234
|
+
const { ast, cursor } = grammar.exec(expression, true);
|
|
3235
|
+
if (ast == null) {
|
|
3236
|
+
const message = generateErrorMessage(grammar, cursor);
|
|
3237
|
+
throw new Error(`[Invalid Grammar] ${message}`);
|
|
3238
|
+
}
|
|
3448
3239
|
return ast;
|
|
3449
3240
|
}
|
|
3450
3241
|
_hasImports(ast) {
|
|
@@ -3580,9 +3371,9 @@
|
|
|
3580
3371
|
const firstChild = pattern.children[0];
|
|
3581
3372
|
const lastChild = pattern.children[pattern.children.length - 1];
|
|
3582
3373
|
const isLongEnough = pattern.children.length >= 2;
|
|
3583
|
-
return
|
|
3584
|
-
(firstChild.name === name
|
|
3585
|
-
|
|
3374
|
+
return pattern.type === "sequence" && isLongEnough &&
|
|
3375
|
+
(firstChild.name === name ||
|
|
3376
|
+
lastChild.name === name);
|
|
3586
3377
|
}
|
|
3587
3378
|
_buildPattern(node) {
|
|
3588
3379
|
const type = node.name;
|
|
@@ -3811,6 +3602,239 @@
|
|
|
3811
3602
|
}
|
|
3812
3603
|
}
|
|
3813
3604
|
|
|
3605
|
+
const defaultOptions = { greedyPatternNames: [], customTokens: {} };
|
|
3606
|
+
class AutoComplete {
|
|
3607
|
+
constructor(pattern, options = defaultOptions) {
|
|
3608
|
+
this._pattern = pattern;
|
|
3609
|
+
this._options = options;
|
|
3610
|
+
this._text = "";
|
|
3611
|
+
}
|
|
3612
|
+
suggestForWithCursor(cursor) {
|
|
3613
|
+
cursor.moveTo(0);
|
|
3614
|
+
this._cursor = cursor;
|
|
3615
|
+
this._text = cursor.text;
|
|
3616
|
+
this._cursor.startRecording();
|
|
3617
|
+
if (cursor.length === 0) {
|
|
3618
|
+
return {
|
|
3619
|
+
isComplete: false,
|
|
3620
|
+
options: this._createSuggestionsFromRoot(),
|
|
3621
|
+
error: new ParseError(0, 0, this._pattern),
|
|
3622
|
+
errorAtIndex: 0,
|
|
3623
|
+
cursor,
|
|
3624
|
+
ast: null
|
|
3625
|
+
};
|
|
3626
|
+
}
|
|
3627
|
+
let errorAtIndex = null;
|
|
3628
|
+
let error = null;
|
|
3629
|
+
const ast = this._pattern.parse(this._cursor);
|
|
3630
|
+
const isComplete = (ast === null || ast === void 0 ? void 0 : ast.value) === this._text;
|
|
3631
|
+
const options = this._getAllOptions();
|
|
3632
|
+
if (!isComplete && options.length > 0 && !this._cursor.hasError) {
|
|
3633
|
+
const startIndex = options.reduce((lowestIndex, o) => {
|
|
3634
|
+
return Math.min(lowestIndex, o.startIndex);
|
|
3635
|
+
}, Infinity);
|
|
3636
|
+
const lastIndex = cursor.getLastIndex() + 1;
|
|
3637
|
+
error = new ParseError(startIndex, lastIndex, this._pattern);
|
|
3638
|
+
errorAtIndex = startIndex;
|
|
3639
|
+
}
|
|
3640
|
+
else if (!isComplete && options.length === 0 && ast != null) {
|
|
3641
|
+
const startIndex = ast.endIndex;
|
|
3642
|
+
const lastIndex = cursor.getLastIndex() + 1;
|
|
3643
|
+
error = new ParseError(startIndex, lastIndex, this._pattern);
|
|
3644
|
+
errorAtIndex = startIndex;
|
|
3645
|
+
}
|
|
3646
|
+
else if (!isComplete && this._cursor.hasError && this._cursor.furthestError != null) {
|
|
3647
|
+
errorAtIndex = this.getFurthestPosition(cursor);
|
|
3648
|
+
error = new ParseError(errorAtIndex, errorAtIndex, this._pattern);
|
|
3649
|
+
}
|
|
3650
|
+
return {
|
|
3651
|
+
isComplete: isComplete,
|
|
3652
|
+
options: options,
|
|
3653
|
+
error,
|
|
3654
|
+
errorAtIndex,
|
|
3655
|
+
cursor: cursor,
|
|
3656
|
+
ast,
|
|
3657
|
+
};
|
|
3658
|
+
}
|
|
3659
|
+
getFurthestPosition(cursor) {
|
|
3660
|
+
const furthestError = cursor.furthestError;
|
|
3661
|
+
const furthestMatch = cursor.allMatchedNodes[cursor.allMatchedNodes.length - 1];
|
|
3662
|
+
if (furthestError && furthestMatch) {
|
|
3663
|
+
if (furthestError.lastIndex > furthestMatch.endIndex) {
|
|
3664
|
+
return furthestMatch.endIndex;
|
|
3665
|
+
}
|
|
3666
|
+
else {
|
|
3667
|
+
return furthestError.lastIndex;
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
if (furthestError == null && furthestMatch != null) {
|
|
3671
|
+
return furthestMatch.endIndex;
|
|
3672
|
+
}
|
|
3673
|
+
if (furthestMatch == null && furthestError != null) {
|
|
3674
|
+
return furthestError.lastIndex;
|
|
3675
|
+
}
|
|
3676
|
+
return 0;
|
|
3677
|
+
}
|
|
3678
|
+
suggestFor(text) {
|
|
3679
|
+
return this.suggestForWithCursor(new Cursor(text));
|
|
3680
|
+
}
|
|
3681
|
+
_getAllOptions() {
|
|
3682
|
+
const errorMatches = this._getOptionsFromErrors();
|
|
3683
|
+
const leafMatches = this._cursor.leafMatches.map((m) => this._createSuggestionsFromMatch(m)).flat();
|
|
3684
|
+
const finalResults = [];
|
|
3685
|
+
[...leafMatches, ...errorMatches].forEach(m => {
|
|
3686
|
+
const index = finalResults.findIndex(f => m.text === f.text);
|
|
3687
|
+
if (index === -1) {
|
|
3688
|
+
finalResults.push(m);
|
|
3689
|
+
}
|
|
3690
|
+
});
|
|
3691
|
+
return finalResults;
|
|
3692
|
+
}
|
|
3693
|
+
_getOptionsFromErrors() {
|
|
3694
|
+
// These errored because the length of the string.
|
|
3695
|
+
const errors = this._cursor.errors.filter(e => e.lastIndex === this._cursor.length);
|
|
3696
|
+
const suggestions = errors.map(e => {
|
|
3697
|
+
const tokens = this._getTokensForPattern(e.pattern);
|
|
3698
|
+
const adjustedTokens = new Set();
|
|
3699
|
+
const currentText = this._cursor.getChars(e.startIndex, e.lastIndex);
|
|
3700
|
+
tokens.forEach((token) => {
|
|
3701
|
+
if (token.startsWith(currentText) && token.length > currentText.length) {
|
|
3702
|
+
const difference = token.length - currentText.length;
|
|
3703
|
+
const suggestedText = token.slice(-difference);
|
|
3704
|
+
adjustedTokens.add(suggestedText);
|
|
3705
|
+
}
|
|
3706
|
+
});
|
|
3707
|
+
return Array.from(adjustedTokens).map(t => {
|
|
3708
|
+
return {
|
|
3709
|
+
text: t,
|
|
3710
|
+
startIndex: e.lastIndex,
|
|
3711
|
+
};
|
|
3712
|
+
});
|
|
3713
|
+
});
|
|
3714
|
+
return suggestions.flat();
|
|
3715
|
+
}
|
|
3716
|
+
_createSuggestionsFromRoot() {
|
|
3717
|
+
const suggestions = [];
|
|
3718
|
+
const tokens = this._pattern.getTokens();
|
|
3719
|
+
for (const token of tokens) {
|
|
3720
|
+
if (suggestions.findIndex(s => s.text === token) === -1) {
|
|
3721
|
+
suggestions.push(this._createSuggestion("", token));
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
return suggestions;
|
|
3725
|
+
}
|
|
3726
|
+
_createSuggestionsFromMatch(match) {
|
|
3727
|
+
if (match.pattern == null) {
|
|
3728
|
+
return this._createSuggestions(-1, this._getTokensForPattern(this._pattern));
|
|
3729
|
+
}
|
|
3730
|
+
const leafPattern = match.pattern;
|
|
3731
|
+
const parent = match.pattern.parent;
|
|
3732
|
+
if (parent !== null && match.node != null) {
|
|
3733
|
+
const patterns = leafPattern.getNextPatterns();
|
|
3734
|
+
const tokens = patterns.reduce((acc, pattern) => {
|
|
3735
|
+
acc.push(...this._getTokensForPattern(pattern));
|
|
3736
|
+
return acc;
|
|
3737
|
+
}, []);
|
|
3738
|
+
return this._createSuggestions(match.node.lastIndex, tokens);
|
|
3739
|
+
}
|
|
3740
|
+
else {
|
|
3741
|
+
return [];
|
|
3742
|
+
}
|
|
3743
|
+
}
|
|
3744
|
+
_getTokensForPattern(pattern) {
|
|
3745
|
+
const augmentedTokens = this._getAugmentedTokens(pattern);
|
|
3746
|
+
if (this._options.greedyPatternNames != null && this._options.greedyPatternNames.includes(pattern.name)) {
|
|
3747
|
+
const nextPatterns = pattern.getNextPatterns();
|
|
3748
|
+
const tokens = [];
|
|
3749
|
+
const nextPatternTokens = nextPatterns.reduce((acc, pattern) => {
|
|
3750
|
+
acc.push(...this._getTokensForPattern(pattern));
|
|
3751
|
+
return acc;
|
|
3752
|
+
}, []);
|
|
3753
|
+
for (let token of augmentedTokens) {
|
|
3754
|
+
for (let nextPatternToken of nextPatternTokens) {
|
|
3755
|
+
tokens.push(token + nextPatternToken);
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3758
|
+
return tokens;
|
|
3759
|
+
}
|
|
3760
|
+
else {
|
|
3761
|
+
return augmentedTokens;
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
_getAugmentedTokens(pattern) {
|
|
3765
|
+
const customTokensMap = this._options.customTokens || {};
|
|
3766
|
+
const leafPatterns = pattern.getPatterns();
|
|
3767
|
+
const tokens = customTokensMap[pattern.name] || [];
|
|
3768
|
+
leafPatterns.forEach(p => {
|
|
3769
|
+
const augmentedTokens = customTokensMap[p.name] || [];
|
|
3770
|
+
tokens.push(...p.getTokens(), ...augmentedTokens);
|
|
3771
|
+
});
|
|
3772
|
+
return tokens;
|
|
3773
|
+
}
|
|
3774
|
+
_createSuggestions(lastIndex, tokens) {
|
|
3775
|
+
let substring = lastIndex === -1 ? "" : this._cursor.getChars(0, lastIndex);
|
|
3776
|
+
const suggestionStrings = [];
|
|
3777
|
+
const options = [];
|
|
3778
|
+
for (const token of tokens) {
|
|
3779
|
+
const suggestion = substring + token;
|
|
3780
|
+
const startsWith = suggestion.startsWith(substring);
|
|
3781
|
+
const alreadyExist = suggestionStrings.includes(suggestion);
|
|
3782
|
+
const isSameAsText = suggestion === this._text;
|
|
3783
|
+
if (startsWith && !alreadyExist && !isSameAsText) {
|
|
3784
|
+
suggestionStrings.push(suggestion);
|
|
3785
|
+
options.push(this._createSuggestion(this._cursor.text, suggestion));
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
const reducedOptions = getFurthestOptions(options);
|
|
3789
|
+
reducedOptions.sort((a, b) => a.text.localeCompare(b.text));
|
|
3790
|
+
return reducedOptions;
|
|
3791
|
+
}
|
|
3792
|
+
_createSuggestion(fullText, suggestion) {
|
|
3793
|
+
const furthestMatch = findMatchIndex(suggestion, fullText);
|
|
3794
|
+
const text = suggestion.slice(furthestMatch);
|
|
3795
|
+
return {
|
|
3796
|
+
text: text,
|
|
3797
|
+
startIndex: furthestMatch,
|
|
3798
|
+
};
|
|
3799
|
+
}
|
|
3800
|
+
static suggestFor(text, pattern, options) {
|
|
3801
|
+
return new AutoComplete(pattern, options).suggestFor(text);
|
|
3802
|
+
}
|
|
3803
|
+
static suggestForWithCursor(cursor, pattern, options) {
|
|
3804
|
+
return new AutoComplete(pattern, options).suggestForWithCursor(cursor);
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
function findMatchIndex(str1, str2) {
|
|
3808
|
+
let matchCount = 0;
|
|
3809
|
+
let minLength = str1.length;
|
|
3810
|
+
if (str2.length < minLength) {
|
|
3811
|
+
minLength = str2.length;
|
|
3812
|
+
}
|
|
3813
|
+
for (let i = 0; i < minLength; i++) {
|
|
3814
|
+
if (str1[i] === str2[i]) {
|
|
3815
|
+
matchCount++;
|
|
3816
|
+
}
|
|
3817
|
+
else {
|
|
3818
|
+
break;
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
return matchCount;
|
|
3822
|
+
}
|
|
3823
|
+
function getFurthestOptions(options) {
|
|
3824
|
+
let furthestOptions = [];
|
|
3825
|
+
let furthestIndex = -1;
|
|
3826
|
+
for (const option of options) {
|
|
3827
|
+
if (option.startIndex > furthestIndex) {
|
|
3828
|
+
furthestIndex = option.startIndex;
|
|
3829
|
+
furthestOptions = [];
|
|
3830
|
+
}
|
|
3831
|
+
if (option.startIndex === furthestIndex) {
|
|
3832
|
+
furthestOptions.push(option);
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
return furthestOptions;
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3814
3838
|
const kebabRegex = /-([a-z])/g; // Define the regex once
|
|
3815
3839
|
function kebabToCamelCase(str) {
|
|
3816
3840
|
return str.replace(kebabRegex, (_, char) => char.toUpperCase());
|