clarity-pattern-parser 11.4.1 → 11.4.2
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/index.browser.js +75 -45
- package/dist/index.browser.js.map +1 -1
- package/dist/index.esm.js +75 -45
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +75 -45
- package/dist/index.js.map +1 -1
- package/dist/intellisense/AutoComplete.d.ts +1 -1
- package/dist/patterns/Cursor.d.ts +7 -2
- package/dist/patterns/Literal.d.ts +0 -2
- package/package.json +1 -1
- package/src/intellisense/AutoComplete.test.ts +3 -3
- package/src/intellisense/AutoComplete.ts +86 -86
- package/src/intellisense/javascript/stringLiteral.ts +2 -2
- package/src/patterns/Cursor.test.ts +10 -2
- package/src/patterns/Cursor.ts +52 -12
- package/src/patterns/Literal.test.ts +41 -1
- package/src/patterns/Literal.ts +20 -28
- package/src/patterns/Regex.ts +6 -6
- package/src/patterns/TakeUntil.ts +1 -1
- package/src/patterns/generate_error_message.ts +1 -1
- package/tsconfig.json +1 -1
|
@@ -42,7 +42,7 @@ export declare class AutoComplete {
|
|
|
42
42
|
* ie. sequence pattern segments ≈ [{look}, {an example}, {phrase}]
|
|
43
43
|
* fullText = "look an"
|
|
44
44
|
* remove {look} segment as its already been completed by the existing text.
|
|
45
|
-
|
|
45
|
+
*/
|
|
46
46
|
private _filterCompletedSubSegments;
|
|
47
47
|
private _getCompositeSuggestionsForPattern;
|
|
48
48
|
private _getCustomTokens;
|
|
@@ -3,7 +3,9 @@ import { Match } from "./CursorHistory";
|
|
|
3
3
|
import { ParseError } from "./ParseError";
|
|
4
4
|
import { Pattern } from "./Pattern";
|
|
5
5
|
export declare class Cursor {
|
|
6
|
-
private
|
|
6
|
+
private _text;
|
|
7
|
+
private _charSize;
|
|
8
|
+
private _charMap;
|
|
7
9
|
private _index;
|
|
8
10
|
private _length;
|
|
9
11
|
private _history;
|
|
@@ -33,10 +35,13 @@ export declare class Cursor {
|
|
|
33
35
|
moveToFirstChar(): void;
|
|
34
36
|
moveToLastChar(): void;
|
|
35
37
|
getLastIndex(): number;
|
|
36
|
-
|
|
38
|
+
substring(first: number, last: number): string;
|
|
37
39
|
recordMatch(pattern: Pattern, node: Node): void;
|
|
38
40
|
recordErrorAt(startIndex: number, lastIndex: number, onPattern: Pattern): void;
|
|
39
41
|
resolveError(): void;
|
|
40
42
|
startRecording(): void;
|
|
41
43
|
stopRecording(): void;
|
|
44
|
+
getCharStartIndex(index: number): number;
|
|
45
|
+
getCharEndIndex(index: number): number;
|
|
46
|
+
getCharLastIndex(index: number): number;
|
|
42
47
|
}
|
|
@@ -8,10 +8,8 @@ export declare class Literal implements Pattern {
|
|
|
8
8
|
private _name;
|
|
9
9
|
private _parent;
|
|
10
10
|
private _token;
|
|
11
|
-
private _runes;
|
|
12
11
|
private _firstIndex;
|
|
13
12
|
private _lastIndex;
|
|
14
|
-
private _endIndex;
|
|
15
13
|
get id(): string;
|
|
16
14
|
get type(): string;
|
|
17
15
|
get name(): string;
|
package/package.json
CHANGED
|
@@ -194,7 +194,7 @@ describe("AutoComplete", () => {
|
|
|
194
194
|
|
|
195
195
|
expect(result.ast).toBeNull();
|
|
196
196
|
optionsMatchExpected(result.options, expectedOptions);
|
|
197
|
-
expect(result.errorAtIndex).toBe(text.length);
|
|
197
|
+
expect(result.errorAtIndex).toBe(text.length - 1);
|
|
198
198
|
expect(result.isComplete).toBeFalsy();
|
|
199
199
|
expect(result.cursor).not.toBeNull();
|
|
200
200
|
});
|
|
@@ -313,7 +313,7 @@ describe("AutoComplete", () => {
|
|
|
313
313
|
|
|
314
314
|
expect(result.ast).toBeNull();
|
|
315
315
|
optionsMatchExpected(result.options, expectedOptions);
|
|
316
|
-
expect(result.errorAtIndex).toBe(
|
|
316
|
+
expect(result.errorAtIndex).toBe(1);
|
|
317
317
|
expect(result.isComplete).toBeFalsy();
|
|
318
318
|
expect(result.cursor).not.toBeNull();
|
|
319
319
|
});
|
|
@@ -944,7 +944,7 @@ describe("AutoComplete", () => {
|
|
|
944
944
|
|
|
945
945
|
optionsMatchExpected(suggestion.options, expected);
|
|
946
946
|
|
|
947
|
-
expect(suggestion.error?.lastIndex).toBe(
|
|
947
|
+
expect(suggestion.error?.lastIndex).toBe(1);
|
|
948
948
|
});
|
|
949
949
|
|
|
950
950
|
test("Recursion With And", () => {
|
|
@@ -107,7 +107,7 @@ export class AutoComplete {
|
|
|
107
107
|
const furthestMatch = cursor.allMatchedNodes[cursor.allMatchedNodes.length - 1];
|
|
108
108
|
|
|
109
109
|
if (furthestError && furthestMatch) {
|
|
110
|
-
if (furthestMatch.endIndex
|
|
110
|
+
if (furthestMatch.endIndex > furthestError.lastIndex) {
|
|
111
111
|
return furthestMatch.endIndex;
|
|
112
112
|
} else {
|
|
113
113
|
return furthestError.lastIndex;
|
|
@@ -131,7 +131,7 @@ export class AutoComplete {
|
|
|
131
131
|
|
|
132
132
|
const errorMatchOptions = this._createSuggestionOptionsFromErrors();
|
|
133
133
|
const leafMatchOptions = this._cursor.leafMatches.map((m) => this._createSuggestionOptionsFromMatch(m)).flat();
|
|
134
|
-
|
|
134
|
+
|
|
135
135
|
const finalResults: SuggestionOption[] = [];
|
|
136
136
|
[...leafMatchOptions, ...errorMatchOptions].forEach(m => {
|
|
137
137
|
const index = finalResults.findIndex(f => m.text === f.text);
|
|
@@ -145,15 +145,15 @@ export class AutoComplete {
|
|
|
145
145
|
|
|
146
146
|
private _createSuggestionOptionsFromErrors() {
|
|
147
147
|
// These errored because the length of the string.
|
|
148
|
-
const errors = this._cursor.errors.filter(e => e.lastIndex === this._cursor.length);
|
|
149
|
-
|
|
148
|
+
const errors = this._cursor.errors.filter(e => e.lastIndex === this._cursor.length - 1);
|
|
149
|
+
|
|
150
150
|
const errorSuggestionOptions = errors.map(parseError => {
|
|
151
151
|
|
|
152
|
-
const currentText = this._cursor.
|
|
153
|
-
|
|
152
|
+
const currentText = this._cursor.substring(parseError.startIndex, parseError.lastIndex);
|
|
153
|
+
|
|
154
154
|
const compositeSuggestions = this._getCompositeSuggestionsForPattern(parseError.pattern);
|
|
155
155
|
const trimmedErrorCompositeSuggestions = this._trimSuggestionsByExistingText(currentText, compositeSuggestions);
|
|
156
|
-
|
|
156
|
+
|
|
157
157
|
return this._createSuggestions(parseError.lastIndex, trimmedErrorCompositeSuggestions);
|
|
158
158
|
}).flat();
|
|
159
159
|
|
|
@@ -170,25 +170,25 @@ export class AutoComplete {
|
|
|
170
170
|
return this._createSuggestions(-1, compositeSuggestions);
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
if (
|
|
174
|
-
const currentText = this._text.slice(match.node.startIndex,match.node.endIndex)
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
if (match?.node != null) {
|
|
174
|
+
const currentText = this._text.slice(match.node.startIndex, match.node.endIndex)
|
|
175
|
+
|
|
176
|
+
|
|
177
177
|
/**Captures suggestions for a "completed" match pattern that still has existing possible suggestions.
|
|
178
178
|
* particularly relevant when working with set/custom tokens.
|
|
179
179
|
*/
|
|
180
180
|
const matchCompositeSuggestions = this._getCompositeSuggestionsForPattern(match.pattern)
|
|
181
|
-
const trimmedMatchCompositeSuggestions =
|
|
181
|
+
const trimmedMatchCompositeSuggestions = this._trimSuggestionsByExistingText(currentText, matchCompositeSuggestions)
|
|
182
|
+
|
|
182
183
|
|
|
183
|
-
|
|
184
184
|
const leafPatterns = match.pattern.getNextPatterns();
|
|
185
|
-
const leafCompositeSuggestions = leafPatterns.flatMap(leafPattern =>
|
|
186
|
-
|
|
187
|
-
|
|
185
|
+
const leafCompositeSuggestions = leafPatterns.flatMap(leafPattern =>
|
|
186
|
+
this._getCompositeSuggestionsForPattern(leafPattern)
|
|
187
|
+
);
|
|
188
188
|
|
|
189
|
-
|
|
189
|
+
const allCompositeSuggestions = [...leafCompositeSuggestions, ...trimmedMatchCompositeSuggestions,]
|
|
190
190
|
|
|
191
|
-
|
|
191
|
+
const dedupedCompositeSuggestions = this._deDupeCompositeSuggestions(allCompositeSuggestions);
|
|
192
192
|
|
|
193
193
|
return this._createSuggestions(match.node.lastIndex, dedupedCompositeSuggestions);
|
|
194
194
|
} else {
|
|
@@ -202,58 +202,58 @@ export class AutoComplete {
|
|
|
202
202
|
* - refines to {d}{ef}
|
|
203
203
|
*/
|
|
204
204
|
private _trimSuggestionsByExistingText(currentText: string, compositeSuggestions: CompositeSuggestion[]): CompositeSuggestion[] {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
acc.push(refinedCompositeSuggestion);
|
|
219
|
-
}
|
|
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,
|
|
220
216
|
}
|
|
221
|
-
return acc;
|
|
222
|
-
}, []);
|
|
223
217
|
|
|
224
|
-
|
|
218
|
+
acc.push(refinedCompositeSuggestion);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return acc;
|
|
222
|
+
}, []);
|
|
223
|
+
|
|
224
|
+
return trimmedSuggestions
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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[] {
|
|
249
251
|
|
|
250
|
-
|
|
252
|
+
const suggestionsToReturn: CompositeSuggestion[] = [];
|
|
251
253
|
|
|
252
|
-
const suggestionsToReturn:CompositeSuggestion[] = [];
|
|
253
|
-
|
|
254
254
|
const leafPatterns = pattern.getPatterns();
|
|
255
255
|
// for when pattern has no leafPatterns and only returns itself
|
|
256
|
-
if(leafPatterns.length === 1 && leafPatterns[0].id === pattern.id) {
|
|
256
|
+
if (leafPatterns.length === 1 && leafPatterns[0].id === pattern.id) {
|
|
257
257
|
|
|
258
258
|
const currentCustomTokens = this._getCustomTokens(pattern);
|
|
259
259
|
const currentTokens = pattern.getTokens();
|
|
@@ -261,12 +261,12 @@ export class AutoComplete {
|
|
|
261
261
|
|
|
262
262
|
const leafCompositeSuggestions: CompositeSuggestion[] = allTokens.map(token => {
|
|
263
263
|
|
|
264
|
-
const segment:SuggestionSegment = {
|
|
264
|
+
const segment: SuggestionSegment = {
|
|
265
265
|
text: token,
|
|
266
266
|
pattern: pattern,
|
|
267
267
|
}
|
|
268
|
-
|
|
269
|
-
const compositeSuggestion:CompositeSuggestion = {
|
|
268
|
+
|
|
269
|
+
const compositeSuggestion: CompositeSuggestion = {
|
|
270
270
|
text: token,
|
|
271
271
|
suggestionSequence: [segment],
|
|
272
272
|
}
|
|
@@ -274,17 +274,17 @@ export class AutoComplete {
|
|
|
274
274
|
})
|
|
275
275
|
suggestionsToReturn.push(...leafCompositeSuggestions);
|
|
276
276
|
|
|
277
|
-
}else{
|
|
277
|
+
} else {
|
|
278
278
|
|
|
279
279
|
const currentCustomTokens = this._getCustomTokens(pattern);
|
|
280
280
|
|
|
281
281
|
const patternsSuggestionList = currentCustomTokens.map(token => {
|
|
282
|
-
const segment:SuggestionSegment = {
|
|
282
|
+
const segment: SuggestionSegment = {
|
|
283
283
|
text: token,
|
|
284
284
|
pattern: pattern,
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
-
const patternSuggestion:CompositeSuggestion = {
|
|
287
|
+
const patternSuggestion: CompositeSuggestion = {
|
|
288
288
|
text: token,
|
|
289
289
|
suggestionSequence: [segment],
|
|
290
290
|
}
|
|
@@ -306,15 +306,15 @@ export class AutoComplete {
|
|
|
306
306
|
|
|
307
307
|
return acc;
|
|
308
308
|
}, []);
|
|
309
|
-
|
|
310
|
-
const compositeSuggestionList:CompositeSuggestion[] = [];
|
|
311
|
-
|
|
309
|
+
|
|
310
|
+
const compositeSuggestionList: CompositeSuggestion[] = [];
|
|
311
|
+
|
|
312
312
|
for (const currentSuggestion of suggestionsToReturn) {
|
|
313
|
-
for (const nextSuggestionWithSubElements of nextPatternedTokensList) {
|
|
313
|
+
for (const nextSuggestionWithSubElements of nextPatternedTokensList) {
|
|
314
314
|
|
|
315
|
-
const augmentedTokenWithPattern:CompositeSuggestion = {
|
|
315
|
+
const augmentedTokenWithPattern: CompositeSuggestion = {
|
|
316
316
|
text: currentSuggestion.text + nextSuggestionWithSubElements.text,
|
|
317
|
-
suggestionSequence: [...currentSuggestion.suggestionSequence
|
|
317
|
+
suggestionSequence: [...currentSuggestion.suggestionSequence, ...nextSuggestionWithSubElements.suggestionSequence],
|
|
318
318
|
}
|
|
319
319
|
|
|
320
320
|
compositeSuggestionList.push(augmentedTokenWithPattern);
|
|
@@ -323,7 +323,7 @@ export class AutoComplete {
|
|
|
323
323
|
|
|
324
324
|
return compositeSuggestionList;
|
|
325
325
|
|
|
326
|
-
} else {
|
|
326
|
+
} else {
|
|
327
327
|
|
|
328
328
|
const dedupedSuggestions = this._deDupeCompositeSuggestions(suggestionsToReturn);
|
|
329
329
|
return dedupedSuggestions;
|
|
@@ -332,16 +332,16 @@ export class AutoComplete {
|
|
|
332
332
|
|
|
333
333
|
private _getCustomTokens(pattern: Pattern) {
|
|
334
334
|
|
|
335
|
-
|
|
336
|
-
|
|
335
|
+
const customTokensMap: Record<string, string[]> = this._options.customTokens || {};
|
|
336
|
+
const customTokens = customTokensMap[pattern.name] ?? [];
|
|
337
337
|
|
|
338
|
-
|
|
338
|
+
const allTokens = [...customTokens];
|
|
339
339
|
|
|
340
|
-
|
|
340
|
+
return allTokens;
|
|
341
341
|
}
|
|
342
342
|
|
|
343
343
|
|
|
344
|
-
|
|
344
|
+
|
|
345
345
|
private _deDupeCompositeSuggestions<T extends CompositeSuggestion>(suggestions: T[]): T[] {
|
|
346
346
|
|
|
347
347
|
if (this._options.disableDedupe) {
|
|
@@ -370,9 +370,9 @@ export class AutoComplete {
|
|
|
370
370
|
return unique;
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
-
private _createSuggestions(lastIndex: number, compositeSuggestionList:CompositeSuggestion[]): SuggestionOption[] {
|
|
373
|
+
private _createSuggestions(lastIndex: number, compositeSuggestionList: CompositeSuggestion[]): SuggestionOption[] {
|
|
374
374
|
|
|
375
|
-
let textToIndex = lastIndex === -1 ? "" : this._cursor.
|
|
375
|
+
let textToIndex = lastIndex === -1 ? "" : this._cursor.substring(0, lastIndex);
|
|
376
376
|
const suggestionStrings: string[] = [];
|
|
377
377
|
const options: SuggestionOption[] = [];
|
|
378
378
|
|
|
@@ -384,9 +384,9 @@ export class AutoComplete {
|
|
|
384
384
|
const isSameAsText = existingTextWithSuggestion === this._text;
|
|
385
385
|
|
|
386
386
|
// if ( !alreadyExist && !isSameAsText) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
387
|
+
suggestionStrings.push(existingTextWithSuggestion);
|
|
388
|
+
const suggestionOption = this._createSuggestionOption(this._cursor.text, existingTextWithSuggestion, compositeSuggestion.suggestionSequence);
|
|
389
|
+
options.push(suggestionOption);
|
|
390
390
|
// }
|
|
391
391
|
}
|
|
392
392
|
|
|
@@ -400,7 +400,7 @@ export class AutoComplete {
|
|
|
400
400
|
const furthestMatch = findMatchIndex(suggestion, fullText);
|
|
401
401
|
const text = suggestion.slice(furthestMatch);
|
|
402
402
|
|
|
403
|
-
const option:SuggestionOption = {
|
|
403
|
+
const option: SuggestionOption = {
|
|
404
404
|
text: text,
|
|
405
405
|
startIndex: furthestMatch,
|
|
406
406
|
suggestionSequence: segments,
|
|
@@ -10,7 +10,7 @@ const doubleQuoteStringLiteral = new Sequence("double-string-literal", [
|
|
|
10
10
|
new Literal("double-quote", "\""),
|
|
11
11
|
new Optional("optional-characters", new Repeat("characters",
|
|
12
12
|
new Options("characters", [
|
|
13
|
-
new Regex("normal-characters",
|
|
13
|
+
new Regex("normal-characters", '[^"]+'),
|
|
14
14
|
escapedCharacter
|
|
15
15
|
])
|
|
16
16
|
)),
|
|
@@ -21,7 +21,7 @@ const singleQuoteStringLiteral = new Sequence("single-string-literal", [
|
|
|
21
21
|
new Literal("single-quote", "'"),
|
|
22
22
|
new Optional("optional-characters", new Repeat("characters",
|
|
23
23
|
new Options("characters", [
|
|
24
|
-
new Regex("normal-characters", "[
|
|
24
|
+
new Regex("normal-characters", "[^']+"),
|
|
25
25
|
escapedCharacter
|
|
26
26
|
]),
|
|
27
27
|
)),
|
|
@@ -27,7 +27,7 @@ describe("Cursor", () => {
|
|
|
27
27
|
cursor.previous();
|
|
28
28
|
expect(cursor.index).toBe(0);
|
|
29
29
|
|
|
30
|
-
const text = cursor.
|
|
30
|
+
const text = cursor.substring(0, 1);
|
|
31
31
|
expect(text).toBe("");
|
|
32
32
|
});
|
|
33
33
|
|
|
@@ -107,7 +107,7 @@ describe("Cursor", () => {
|
|
|
107
107
|
|
|
108
108
|
test("Text Information", () => {
|
|
109
109
|
const cursor = new Cursor("Hello World!");
|
|
110
|
-
const hello = cursor.
|
|
110
|
+
const hello = cursor.substring(0, 4);
|
|
111
111
|
|
|
112
112
|
expect(hello).toBe("Hello");
|
|
113
113
|
expect(cursor.length).toBe(12);
|
|
@@ -153,4 +153,12 @@ describe("Cursor", () => {
|
|
|
153
153
|
expect(records[2].pattern.name).toBe("first-names");
|
|
154
154
|
});
|
|
155
155
|
|
|
156
|
+
test("Text with Emojis", () => {
|
|
157
|
+
const cursor = new Cursor("🔴 World!");
|
|
158
|
+
expect(cursor.currentChar).toBe("🔴");
|
|
159
|
+
cursor.next();
|
|
160
|
+
expect(cursor.currentChar).toBe(" ");
|
|
161
|
+
expect(cursor.length).toBe(9);
|
|
162
|
+
});
|
|
163
|
+
|
|
156
164
|
});
|
package/src/patterns/Cursor.ts
CHANGED
|
@@ -3,14 +3,18 @@ import { CursorHistory, Match } from "./CursorHistory";
|
|
|
3
3
|
import { ParseError } from "./ParseError";
|
|
4
4
|
import { Pattern } from "./Pattern";
|
|
5
5
|
|
|
6
|
+
const segmenter = new Intl.Segmenter("und", { granularity: "grapheme" });
|
|
7
|
+
|
|
6
8
|
export class Cursor {
|
|
7
|
-
private
|
|
9
|
+
private _text: string;
|
|
10
|
+
private _charSize: number[];
|
|
11
|
+
private _charMap: number[];
|
|
8
12
|
private _index: number;
|
|
9
13
|
private _length: number;
|
|
10
14
|
private _history: CursorHistory;
|
|
11
15
|
|
|
12
16
|
get text(): string {
|
|
13
|
-
return this.
|
|
17
|
+
return this._text;
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
get isOnFirst(): boolean {
|
|
@@ -74,39 +78,62 @@ export class Cursor {
|
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
get currentChar(): string {
|
|
77
|
-
|
|
81
|
+
const index = this.getCharStartIndex(this._index);
|
|
82
|
+
return this.text.slice(index, index + this._charSize[index]);
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
constructor(text: string) {
|
|
81
|
-
this.
|
|
86
|
+
this._text = text;
|
|
87
|
+
this._length = text.length;
|
|
88
|
+
this._charSize = [];
|
|
89
|
+
this._charMap = [];
|
|
90
|
+
|
|
82
91
|
this._index = 0;
|
|
83
|
-
this._length = this._chars.length;
|
|
84
92
|
this._history = new CursorHistory();
|
|
93
|
+
|
|
94
|
+
let index = 0;
|
|
95
|
+
for (const segment of segmenter.segment(text)) {
|
|
96
|
+
const size = segment.segment.length;
|
|
97
|
+
for (let i = 0; i < size; i++) {
|
|
98
|
+
this._charMap.push(index);
|
|
99
|
+
this._charSize.push(size);
|
|
100
|
+
}
|
|
101
|
+
index += size;
|
|
102
|
+
}
|
|
85
103
|
}
|
|
86
104
|
|
|
87
105
|
hasNext(): boolean {
|
|
88
|
-
|
|
106
|
+
const index = this._charMap[this._index];
|
|
107
|
+
const charSize = this._charSize[index];
|
|
108
|
+
return index + charSize < this._length;
|
|
89
109
|
}
|
|
90
110
|
|
|
91
111
|
next(): void {
|
|
92
112
|
if (this.hasNext()) {
|
|
93
|
-
this._index
|
|
113
|
+
const index = this._charMap[this._index];
|
|
114
|
+
const size = this._charSize[index];
|
|
115
|
+
this.moveTo(index + size);
|
|
94
116
|
}
|
|
95
117
|
}
|
|
96
118
|
|
|
97
119
|
hasPrevious(): boolean {
|
|
98
|
-
|
|
120
|
+
const index = this._charMap[this._index];
|
|
121
|
+
const previousIndex = this._charMap[index - 1] ?? -1;
|
|
122
|
+
|
|
123
|
+
return previousIndex >= 0;
|
|
99
124
|
}
|
|
100
125
|
|
|
101
126
|
previous(): void {
|
|
102
127
|
if (this.hasPrevious()) {
|
|
103
|
-
this._index
|
|
128
|
+
const index = this._charMap[this._index];
|
|
129
|
+
const previousIndex = this._charMap[index - 1] ?? -1;;
|
|
130
|
+
this.moveTo(previousIndex);
|
|
104
131
|
}
|
|
105
132
|
}
|
|
106
133
|
|
|
107
134
|
moveTo(position: number): void {
|
|
108
135
|
if (position >= 0 && position < this._length) {
|
|
109
|
-
this._index = position;
|
|
136
|
+
this._index = this._charMap[position];
|
|
110
137
|
}
|
|
111
138
|
}
|
|
112
139
|
|
|
@@ -122,8 +149,8 @@ export class Cursor {
|
|
|
122
149
|
return this._length - 1;
|
|
123
150
|
}
|
|
124
151
|
|
|
125
|
-
|
|
126
|
-
return this.
|
|
152
|
+
substring(first: number, last: number): string {
|
|
153
|
+
return this._text.slice(first, last + 1);
|
|
127
154
|
}
|
|
128
155
|
|
|
129
156
|
recordMatch(pattern: Pattern, node: Node): void {
|
|
@@ -146,4 +173,17 @@ export class Cursor {
|
|
|
146
173
|
this._history.stopRecording();
|
|
147
174
|
}
|
|
148
175
|
|
|
176
|
+
getCharStartIndex(index: number): number {
|
|
177
|
+
return this._charMap[index];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
getCharEndIndex(index: number): number {
|
|
181
|
+
let startIndex = this.getCharStartIndex(index);
|
|
182
|
+
return startIndex + this._charSize[startIndex] ?? 1;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
getCharLastIndex(index: number): number {
|
|
186
|
+
return this.getCharEndIndex(index) - 1 ?? 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
149
189
|
}
|
|
@@ -48,7 +48,7 @@ describe("Literal", () => {
|
|
|
48
48
|
expect(result).toEqual(null);
|
|
49
49
|
expect(cursor.index).toBe(10);
|
|
50
50
|
expect(cursor.error?.startIndex).toBe(0);
|
|
51
|
-
expect(cursor.error?.lastIndex).toBe(
|
|
51
|
+
expect(cursor.error?.lastIndex).toBe(10);
|
|
52
52
|
expect(cursor.error?.pattern).toBe(literal);
|
|
53
53
|
});
|
|
54
54
|
|
|
@@ -163,4 +163,44 @@ describe("Literal", () => {
|
|
|
163
163
|
|
|
164
164
|
expect(pattern).toBeNull();
|
|
165
165
|
});
|
|
166
|
+
|
|
167
|
+
test("Unicode", () => {
|
|
168
|
+
const literal = new Literal("a", "🔴");
|
|
169
|
+
const cursor = new Cursor("🔴");
|
|
170
|
+
const result = literal.parse(cursor);
|
|
171
|
+
expect(result).not.toBeNull();
|
|
172
|
+
expect(cursor.index).toBe(0);
|
|
173
|
+
expect(result?.toString()).toBe("🔴");
|
|
174
|
+
expect(cursor.error).toBeNull();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("Unicode In Middle", () => {
|
|
178
|
+
const literal = new Literal("a", "|🔴|");
|
|
179
|
+
const cursor = new Cursor("|🔴|");
|
|
180
|
+
const result = literal.parse(cursor);
|
|
181
|
+
expect(result).not.toBeNull();
|
|
182
|
+
expect(cursor.index).toBe(3);
|
|
183
|
+
expect(result?.toString()).toBe("|🔴|");
|
|
184
|
+
expect(cursor.error).toBeNull();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("Unicode At the End", () => {
|
|
188
|
+
const literal = new Literal("a", "|🔴");
|
|
189
|
+
const cursor = new Cursor("|🔴");
|
|
190
|
+
const result = literal.parse(cursor);
|
|
191
|
+
expect(result).not.toBeNull();
|
|
192
|
+
expect(cursor.index).toBe(1);
|
|
193
|
+
expect(result?.toString()).toBe("|🔴");
|
|
194
|
+
expect(cursor.error).toBeNull();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("Unicode At the Beginning", () => {
|
|
198
|
+
const literal = new Literal("a", "🔴|");
|
|
199
|
+
const cursor = new Cursor("🔴|");
|
|
200
|
+
const result = literal.parse(cursor);
|
|
201
|
+
expect(result).not.toBeNull();
|
|
202
|
+
expect(cursor.index).toBe(2);
|
|
203
|
+
expect(result?.toString()).toBe("🔴|");
|
|
204
|
+
expect(cursor.error).toBeNull();
|
|
205
|
+
});
|
|
166
206
|
});
|