clarity-pattern-parser 11.0.22 → 11.0.24
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 +295 -271
- package/dist/index.browser.js.map +1 -1
- package/dist/index.esm.js +295 -271
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +295 -271
- package/dist/index.js.map +1 -1
- package/dist/patterns/Optional.d.ts +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 +8 -20
- package/src/patterns/FiniteRepeat.test.ts +2 -2
- package/src/patterns/Optional.test.ts +1 -1
- package/src/patterns/Optional.ts +5 -14
- 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/execPattern.ts +24 -8
- package/src/patterns/generate_error_message.ts +33 -0
- package/src/patterns/testPattern.ts +4 -7
- package/src/query/query.test.ts +175 -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
|
@@ -529,20 +529,31 @@
|
|
|
529
529
|
|
|
530
530
|
function execPattern(pattern, text, record = false) {
|
|
531
531
|
const cursor = new Cursor(text);
|
|
532
|
+
if (cursor.length === 0) {
|
|
533
|
+
return { ast: null, cursor };
|
|
534
|
+
}
|
|
532
535
|
record && cursor.startRecording();
|
|
533
|
-
|
|
534
|
-
const
|
|
536
|
+
let ast = pattern.parse(cursor);
|
|
537
|
+
const resultLength = ast == null ? 0 : ast.value.length;
|
|
538
|
+
if (ast != null) {
|
|
539
|
+
const isMatch = ast.value === text;
|
|
540
|
+
if (!isMatch && !cursor.hasError) {
|
|
541
|
+
ast = null;
|
|
542
|
+
cursor.recordErrorAt(resultLength, cursor.length, pattern);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
cursor.recordErrorAt(resultLength, cursor.length, pattern);
|
|
547
|
+
}
|
|
535
548
|
return {
|
|
536
|
-
ast:
|
|
549
|
+
ast: ast,
|
|
537
550
|
cursor
|
|
538
551
|
};
|
|
539
552
|
}
|
|
540
553
|
|
|
541
554
|
function testPattern(pattern, text, record = false) {
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
const ast = pattern.parse(cursor);
|
|
545
|
-
return (ast === null || ast === void 0 ? void 0 : ast.value.length) === text.length;
|
|
555
|
+
const result = execPattern(pattern, text, record);
|
|
556
|
+
return !result.cursor.hasError;
|
|
546
557
|
}
|
|
547
558
|
|
|
548
559
|
let idIndex$9 = 0;
|
|
@@ -871,6 +882,7 @@
|
|
|
871
882
|
}
|
|
872
883
|
_cacheAncestors(id) {
|
|
873
884
|
if (!this._cachedAncestors) {
|
|
885
|
+
this._cachedAncestors = true;
|
|
874
886
|
let pattern = this.parent;
|
|
875
887
|
while (pattern != null) {
|
|
876
888
|
if (pattern.id === id) {
|
|
@@ -883,7 +895,7 @@
|
|
|
883
895
|
_isBeyondRecursiveAllowance() {
|
|
884
896
|
let depth = 0;
|
|
885
897
|
for (let pattern of this._recursiveAncestors) {
|
|
886
|
-
if (pattern.
|
|
898
|
+
if (pattern.startedOnIndex === this.startedOnIndex) {
|
|
887
899
|
depth++;
|
|
888
900
|
if (depth > 0) {
|
|
889
901
|
return true;
|
|
@@ -1769,7 +1781,7 @@
|
|
|
1769
1781
|
else {
|
|
1770
1782
|
// We are at the end of the text, it may still be valid, if all the
|
|
1771
1783
|
// following patterns are optional.
|
|
1772
|
-
if (this.
|
|
1784
|
+
if (this._areRemainingPatternsOptional(i)) {
|
|
1773
1785
|
passed = true;
|
|
1774
1786
|
break;
|
|
1775
1787
|
}
|
|
@@ -1788,7 +1800,7 @@
|
|
|
1788
1800
|
else {
|
|
1789
1801
|
// If we don't have any results from what we parsed then record error.
|
|
1790
1802
|
const lastNode = this.getLastValidNode();
|
|
1791
|
-
if (lastNode === null) {
|
|
1803
|
+
if (lastNode === null && !this._areAllPatternsOptional()) {
|
|
1792
1804
|
cursor.recordErrorAt(this._firstIndex, cursor.index, this);
|
|
1793
1805
|
break;
|
|
1794
1806
|
}
|
|
@@ -1812,7 +1824,10 @@
|
|
|
1812
1824
|
}
|
|
1813
1825
|
return nodes[nodes.length - 1];
|
|
1814
1826
|
}
|
|
1815
|
-
|
|
1827
|
+
_areAllPatternsOptional() {
|
|
1828
|
+
return this._areRemainingPatternsOptional(-1);
|
|
1829
|
+
}
|
|
1830
|
+
_areRemainingPatternsOptional(fromIndex) {
|
|
1816
1831
|
const startOnIndex = fromIndex + 1;
|
|
1817
1832
|
const length = this._children.length;
|
|
1818
1833
|
for (let i = startOnIndex; i < length; i++) {
|
|
@@ -1825,6 +1840,10 @@
|
|
|
1825
1840
|
}
|
|
1826
1841
|
createNode(cursor) {
|
|
1827
1842
|
const children = filterOutNull(this._nodes);
|
|
1843
|
+
if (children.length === 0) {
|
|
1844
|
+
cursor.moveTo(this._firstIndex);
|
|
1845
|
+
return null;
|
|
1846
|
+
}
|
|
1828
1847
|
const lastIndex = children[children.length - 1].lastIndex;
|
|
1829
1848
|
cursor.moveTo(lastIndex);
|
|
1830
1849
|
return new Node("sequence", this._name, this._firstIndex, lastIndex, children);
|
|
@@ -1981,19 +2000,11 @@
|
|
|
1981
2000
|
this._children = [pattern.clone()];
|
|
1982
2001
|
this._children[0].parent = this;
|
|
1983
2002
|
}
|
|
1984
|
-
test(text) {
|
|
1985
|
-
|
|
1986
|
-
this.parse(cursor);
|
|
1987
|
-
return !cursor.hasError;
|
|
2003
|
+
test(text, record = false) {
|
|
2004
|
+
return testPattern(this, text, record);
|
|
1988
2005
|
}
|
|
1989
2006
|
exec(text, record = false) {
|
|
1990
|
-
|
|
1991
|
-
record && cursor.startRecording();
|
|
1992
|
-
const ast = this.parse(cursor);
|
|
1993
|
-
return {
|
|
1994
|
-
ast: (ast === null || ast === void 0 ? void 0 : ast.value) === text ? ast : null,
|
|
1995
|
-
cursor
|
|
1996
|
-
};
|
|
2007
|
+
return execPattern(this, text, record);
|
|
1997
2008
|
}
|
|
1998
2009
|
parse(cursor) {
|
|
1999
2010
|
const firstIndex = cursor.index;
|
|
@@ -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) {
|
|
@@ -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());
|