clarity-pattern-parser 8.2.1 → 8.3.1

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.
@@ -13,7 +13,7 @@ describe("Grammar", () => {
13
13
  name = "John"
14
14
  `;
15
15
 
16
- const patterns = Grammar.parse(expression);
16
+ const patterns = Grammar.parseString(expression);
17
17
  const namePattern = patterns.get("name");
18
18
  const expected = new Literal("name", "John");
19
19
 
@@ -25,7 +25,7 @@ describe("Grammar", () => {
25
25
  name = /\\w/
26
26
  `;
27
27
 
28
- const patterns = Grammar.parse(expression);
28
+ const patterns = Grammar.parseString(expression);
29
29
  const pattern = patterns.get("name");
30
30
  const name = new Regex("name", "\\w");
31
31
 
@@ -39,7 +39,7 @@ describe("Grammar", () => {
39
39
  names = john | jane
40
40
  `;
41
41
 
42
- const patterns = Grammar.parse(expression);
42
+ const patterns = Grammar.parseString(expression);
43
43
  const pattern = patterns.get("names");
44
44
  const john = new Literal("john", "John");
45
45
  const jane = new Literal("jane", "Jane");
@@ -56,7 +56,7 @@ describe("Grammar", () => {
56
56
  full-name = first-name & space & last-name
57
57
  `;
58
58
 
59
- const patterns = Grammar.parse(expression);
59
+ const patterns = Grammar.parseString(expression);
60
60
  const pattern = patterns.get("full-name");
61
61
  const space = new Literal("space", " ");
62
62
  const firstName = new Regex("first-name", "\\w");
@@ -76,7 +76,7 @@ describe("Grammar", () => {
76
76
  full-name = first-name & space & middle-name-with-space? & last-name
77
77
  `;
78
78
 
79
- const patterns = Grammar.parse(expression);
79
+ const patterns = Grammar.parseString(expression);
80
80
  const pattern = patterns.get("full-name");
81
81
  const space = new Literal("space", " ");
82
82
  const firstName = new Regex("first-name", "\\w");
@@ -99,7 +99,7 @@ describe("Grammar", () => {
99
99
  full-name = !jack & first-name & space & middle-name-with-space? & last-name
100
100
  `;
101
101
 
102
- const patterns = Grammar.parse(expression);
102
+ const patterns = Grammar.parseString(expression);
103
103
  const pattern = patterns.get("full-name");
104
104
  const space = new Literal("space", " ");
105
105
  const firstName = new Regex("first-name", "\\w");
@@ -119,7 +119,7 @@ describe("Grammar", () => {
119
119
  digits = (digit)+
120
120
  `;
121
121
 
122
- const patterns = Grammar.parse(expression);
122
+ const patterns = Grammar.parseString(expression);
123
123
  const pattern = patterns.get("digits");
124
124
  const digit = new Regex("digit", "\\d");
125
125
  const digits = new Repeat("digits", digit);
@@ -133,7 +133,7 @@ describe("Grammar", () => {
133
133
  digits = (digit)*
134
134
  `;
135
135
 
136
- const patterns = Grammar.parse(expression);
136
+ const patterns = Grammar.parseString(expression);
137
137
  const pattern = patterns.get("digits");
138
138
  const digit = new Regex("digit", "\\d");
139
139
  const digits = new Repeat("digits", digit, { min: 0 });
@@ -147,7 +147,7 @@ describe("Grammar", () => {
147
147
  digits = (digit){1,}
148
148
  `;
149
149
 
150
- const patterns = Grammar.parse(expression);
150
+ const patterns = Grammar.parseString(expression);
151
151
  const pattern = patterns.get("digits");
152
152
  const digit = new Regex("digit", "\\d+");
153
153
  const digits = new Repeat("digits", digit, { min: 1 });
@@ -161,7 +161,7 @@ describe("Grammar", () => {
161
161
  digits = (digit){1,3}
162
162
  `;
163
163
 
164
- const patterns = Grammar.parse(expression);
164
+ const patterns = Grammar.parseString(expression);
165
165
  const pattern = patterns.get("digits");
166
166
  const digit = new Regex("digit", "\\d+");
167
167
  const digits = new Repeat("digits", digit, { min: 1, max: 3 });
@@ -175,7 +175,7 @@ describe("Grammar", () => {
175
175
  digits = (digit){,3}
176
176
  `;
177
177
 
178
- const patterns = Grammar.parse(expression);
178
+ const patterns = Grammar.parseString(expression);
179
179
  const pattern = patterns.get("digits");
180
180
  const digit = new Regex("digit", "\\d+");
181
181
  const digits = new Repeat("digits", digit, { min: 0, max: 3 });
@@ -189,7 +189,7 @@ describe("Grammar", () => {
189
189
  digits = (digit){3}
190
190
  `;
191
191
 
192
- const patterns = Grammar.parse(expression);
192
+ const patterns = Grammar.parseString(expression);
193
193
  const pattern = patterns.get("digits");
194
194
  const digit = new Regex("digit", "\\d+");
195
195
  const digits = new Repeat("digits", digit, { min: 3, max: 3 });
@@ -204,7 +204,7 @@ describe("Grammar", () => {
204
204
  digits = (digit, comma){3}
205
205
  `;
206
206
 
207
- const patterns = Grammar.parse(expression);
207
+ const patterns = Grammar.parseString(expression);
208
208
  const pattern = patterns.get("digits");
209
209
  const digit = new Regex("digit", "\\d+");
210
210
  const divider = new Literal("comma", ",");
@@ -220,7 +220,7 @@ describe("Grammar", () => {
220
220
  digits = (digit, comma){3} -t
221
221
  `;
222
222
 
223
- const patterns = Grammar.parse(expression);
223
+ const patterns = Grammar.parseString(expression);
224
224
  const pattern = patterns.get("digits");
225
225
  const digit = new Regex("digit", "\\d+");
226
226
  const divider = new Literal("comma", ",");
@@ -236,7 +236,7 @@ describe("Grammar", () => {
236
236
  digits = (digit?, comma)+
237
237
  `;
238
238
 
239
- const patterns = Grammar.parse(expression);
239
+ const patterns = Grammar.parseString(expression);
240
240
  const pattern = patterns.get("digits") as Pattern;
241
241
  const digit = new Regex("digit", "\\d+", true);
242
242
  const comma = new Literal("comma", ",");
@@ -257,7 +257,7 @@ describe("Grammar", () => {
257
257
  array = open-bracket & spaces? & array-items? & spaces? & close-bracket
258
258
  `;
259
259
 
260
- const patterns = Grammar.parse(expression);
260
+ const patterns = Grammar.parseString(expression);
261
261
  const pattern = patterns.get("array") as Pattern;
262
262
 
263
263
  let text = "[1, []]";
@@ -272,8 +272,8 @@ describe("Grammar", () => {
272
272
  alias = name
273
273
  `;
274
274
 
275
- const patterns = Grammar.parse(expression);
276
-
275
+ const patterns = Grammar.parseString(expression);
276
+
277
277
  const name = patterns.get("name");
278
278
  const expectedName = new Regex("name", "regex");
279
279
 
@@ -288,7 +288,7 @@ describe("Grammar", () => {
288
288
 
289
289
  expect(() => {
290
290
  const expression = `Just Junk`;
291
- Grammar.parse(expression);
291
+ Grammar.parseString(expression);
292
292
  }).toThrowError("[Parse Error] Found: 'Just Junk', expected: ' ='.");
293
293
 
294
294
  });
@@ -299,8 +299,54 @@ describe("Grammar", () => {
299
299
  const expression = `name = /\\w/
300
300
  age = /()
301
301
  `;
302
- Grammar.parse(expression);
302
+ Grammar.parseString(expression);
303
303
  }).toThrowError("[Parse Error] Found: ' age = /()\n ', expected: ' age = !' or ' age = (' or ' age = [Pattern Name]' or ' age = [Regular Expression]' or ' age = [String]'.")
304
304
 
305
305
  });
306
+
307
+ test("Import", async () => {
308
+ const importExpression = `first-name = "John"`;
309
+ const expression = `
310
+ import { first-name } from "some/path/to/file.cpat"
311
+ last-name = "Doe"
312
+ space = " "
313
+ full-name = first-name & space & last-name
314
+ `
315
+ function resolveImport(path: string) {
316
+ expect(path).toBe("some/path/to/file.cpat");
317
+ return Promise.resolve({ expression: importExpression, path });
318
+ }
319
+
320
+ const patterns = await Grammar.parse(expression, { resolveImport });
321
+ const fullname = patterns.get("full-name") as Pattern;
322
+ const result = fullname.exec("John Doe");
323
+
324
+ expect(result?.ast?.value).toBe("John Doe");
325
+ });
326
+
327
+ test("Imports", async () => {
328
+ const importExpression = `first-name = "John"`;
329
+ const spaceExpression = `space = " "`
330
+
331
+ const pathMap: Record<string, string> = {
332
+ "space.cpat": spaceExpression,
333
+ "first-name.cpat": importExpression
334
+ };
335
+
336
+ const expression = `
337
+ import { first-name } from "first-name.cpat"
338
+ import { space } from "space.cpat"
339
+ last-name = "Doe"
340
+ full-name = first-name & space & last-name
341
+ `
342
+ function resolveImport(path: string) {
343
+ return Promise.resolve({ expression: pathMap[path], path });
344
+ }
345
+
346
+ const patterns = await Grammar.parse(expression, { resolveImport });
347
+ const fullname = patterns.get("full-name") as Pattern;
348
+ const result = fullname.exec("John Doe");
349
+ expect(result?.ast?.value).toBe("John Doe");
350
+ });
351
+
306
352
  });
@@ -12,13 +12,36 @@ import { AutoComplete } from "../intellisense/AutoComplete";
12
12
 
13
13
  class ParseContext {
14
14
  patternsByName = new Map<string, Pattern>();
15
+ importedPatternsByName = new Map<string, Pattern>();
16
+ }
17
+
18
+ function defaultImportResolver(_path: string, _basePath: string | null): Promise<GrammarFile> {
19
+ throw new Error("No import resolver supplied.");
20
+ }
21
+
22
+ export interface GrammarMeta {
23
+ originPath?: string;
24
+ }
25
+
26
+ export interface GrammarFile {
27
+ path: string;
28
+ expression: string;
29
+ }
30
+
31
+ export interface GrammarOptions {
32
+ resolveImport?: (path: string, originPath: string | null) => Promise<GrammarFile>;
33
+ meta?: GrammarMeta | null;
15
34
  }
16
35
 
17
36
  export class Grammar {
37
+ private _meta: GrammarMeta | null;
38
+ private _resolveImport: (path: string, basePath: string | null) => Promise<GrammarFile>;
18
39
  private _parseContext: ParseContext;
19
40
  private _autoComplete: AutoComplete;
20
41
 
21
- constructor() {
42
+ constructor(options: GrammarOptions = {}) {
43
+ this._meta = options.meta == null ? null : options.meta;
44
+ this._resolveImport = options.resolveImport == null ? defaultImportResolver : options.resolveImport;
22
45
  this._parseContext = new ParseContext();
23
46
  this._autoComplete = new AutoComplete(grammar, {
24
47
  greedyPatternNames: ["spaces", "optional-spaces", "whitespace", "new-line"],
@@ -31,14 +54,39 @@ export class Grammar {
31
54
  });
32
55
  }
33
56
 
34
- parse(expression: string) {
57
+ async import(path: string) {
58
+ const grammarFile = await this._resolveImport(path, null);
59
+ const grammar = new Grammar({ resolveImport: this._resolveImport, meta: { originPath: grammarFile.path } });
60
+
61
+ return grammar.parse(grammarFile.expression);
62
+ }
63
+
64
+ async parse(expression: string) {
35
65
  this._parseContext = new ParseContext();
36
- this._tryToParse(expression);
66
+ const ast = this._tryToParse(expression);
67
+
68
+ await this._resolveImports(ast);
69
+ this._buildPatterns(ast);
70
+ this._cleanAst(ast);
37
71
 
38
72
  return this._parseContext.patternsByName;
39
73
  }
40
74
 
41
- private _tryToParse(expression: string) {
75
+ parseString(expression: string) {
76
+ this._parseContext = new ParseContext();
77
+ const ast = this._tryToParse(expression);
78
+
79
+ if (this._hasImports(ast)) {
80
+ throw new Error("Cannot use imports on parseString, use parse instead.");
81
+ }
82
+
83
+ this._buildPatterns(ast);
84
+ this._cleanAst(ast);
85
+
86
+ return this._parseContext.patternsByName;
87
+ }
88
+
89
+ private _tryToParse(expression: string): Node {
42
90
  const { ast, cursor, options, isComplete } = this._autoComplete.suggestFor(expression);
43
91
 
44
92
  if (!isComplete) {
@@ -54,9 +102,16 @@ export class Grammar {
54
102
  }
55
103
 
56
104
  // If it is complete it will always have a node. So we have to cast it.
57
- this._cleanAst(ast as Node);
58
- this._buildPatterns(ast as Node);
105
+ return ast as Node;
106
+ }
107
+
108
+ private _hasImports(ast: Node) {
109
+ const importBlock = ast.find(n => n.name === "import-block");
110
+ if (importBlock == null) {
111
+ return false;
112
+ }
59
113
 
114
+ return importBlock && importBlock.children.length > 0;
60
115
  }
61
116
 
62
117
  private _cleanAst(ast: Node) {
@@ -99,10 +154,48 @@ export class Grammar {
99
154
  this._buildAlias(n)
100
155
  break;
101
156
  }
157
+ default: {
158
+ break;
159
+ }
102
160
  }
103
161
  });
104
162
  }
105
163
 
164
+ private async _resolveImports(ast: Node) {
165
+ const parseContext = this._parseContext;
166
+ const importStatements = ast.findAll(n => n.name === "import-statement");
167
+
168
+ for (const importStatement of importStatements) {
169
+ const urlNode = importStatement.find(n => n.name === "url") as Node;
170
+
171
+ const url = urlNode.value.slice(1, -1);
172
+ const grammarFile = await this._resolveImport(url, this._meta?.originPath || null);
173
+ const grammar = new Grammar({ resolveImport: this._resolveImport, meta: { originPath: grammarFile.path } });
174
+
175
+ try {
176
+ const patterns = await grammar.parse(grammarFile.expression);
177
+ const importNames = importStatement.findAll(n => n.name === "import-name").map(n => n.value);
178
+
179
+ importNames.forEach((importName) => {
180
+ if (parseContext.importedPatternsByName.has(importName)) {
181
+ throw new Error(`'${importName}' was already used within another import.`);
182
+ }
183
+
184
+ const pattern = patterns.get(importName);
185
+ if (pattern == null) {
186
+ throw new Error(`Couldn't find pattern with name: ${importName}, from import: ${url}.`);
187
+ }
188
+
189
+ parseContext.importedPatternsByName.set(importName, pattern);
190
+ })
191
+
192
+ } catch (e: any) {
193
+ throw new Error(`Failed loading expression from: "${url}". Error details: "${e.message}"`);
194
+ }
195
+
196
+ }
197
+ }
198
+
106
199
  private _buildLiteral(statementNode: Node) {
107
200
  const nameNode = statementNode.find(n => n.name === "name") as Node;
108
201
  const literalNode = statementNode.find(n => n.name === "literal") as Node;
@@ -137,7 +230,11 @@ export class Grammar {
137
230
  }
138
231
 
139
232
  private _getPattern(name: string) {
140
- const pattern = this._parseContext.patternsByName.get(name);
233
+ let pattern = this._parseContext.patternsByName.get(name);
234
+
235
+ if (pattern == null) {
236
+ pattern = this._parseContext.importedPatternsByName.get(name);
237
+ }
141
238
 
142
239
  if (pattern == null) {
143
240
  return new Reference(name);
@@ -241,9 +338,19 @@ export class Grammar {
241
338
  this._parseContext.patternsByName.set(name, alias)
242
339
  }
243
340
 
244
- static parse(expression: string) {
245
- const grammar = new Grammar();
341
+ static parse(expression: string, options?: GrammarOptions) {
342
+ const grammar = new Grammar(options);
246
343
  return grammar.parse(expression);
247
344
  }
248
345
 
346
+ static import(path: string, options?: GrammarOptions) {
347
+ const grammar = new Grammar(options);
348
+ return grammar.import(path);
349
+ }
350
+
351
+ static parseString(expression: string) {
352
+ const grammar = new Grammar();
353
+ return grammar.parseString(expression);
354
+ }
355
+
249
356
  }
@@ -3,17 +3,20 @@ import { Regex } from "../../patterns/Regex";
3
3
  import { Repeat } from "../../patterns/Repeat";
4
4
  import { comment } from "./comment";
5
5
  import { statement } from "./statement";
6
+ import { importStatement } from './import';
6
7
 
7
- const whitespace = new Regex("whitespace", "[ \\t]+");
8
+ const whitespace = new Regex("whitespace", "[ \\t]+((\\r?\\n)+)?");
8
9
  const newLine = new Regex("new-line", "(\\r?\\n)+");
9
10
 
10
11
  whitespace.setTokens([" "]);
11
- newLine.setTokens(["\n"])
12
+ newLine.setTokens(["\n"]);
12
13
 
13
14
  const line = new Or("line", [
15
+ newLine,
16
+ whitespace,
14
17
  comment,
15
- statement,
16
- whitespace
17
- ], true);
18
+ importStatement,
19
+ statement
20
+ ]);
18
21
 
19
- export const grammar = new Repeat("grammar", line, { divider: newLine });
22
+ export const grammar = new Repeat("grammer", line);
@@ -0,0 +1,29 @@
1
+ import { And } from "../../patterns/And";
2
+ import { Repeat } from "../../patterns/Repeat";
3
+ import { Literal } from "../../patterns/Literal";
4
+ import { Regex } from "../../patterns/Regex";
5
+ import { literal } from "./literal";
6
+
7
+ const spaces = new Regex("spaces", "\\s+", true);
8
+ const importNameDivider = new Regex("import-name-divider", "(\\s+)?,(\\s+)?");
9
+ const importKeyword = new Literal("import", "import");
10
+ const fromKeyword = new Literal("from", "from");
11
+ const openBracket = new Literal("open-bracket", "{");
12
+ const closeBracket = new Literal("close-bracket", "}");
13
+ const name = new Regex("import-name", "[^}\\s,]+");
14
+ const importedNames = new Repeat("imported-names", name, { divider: importNameDivider });
15
+ const optionalSpaces = spaces.clone("optional-spaces", true);
16
+
17
+ export const importStatement = new And("import-statement", [
18
+ importKeyword,
19
+ optionalSpaces,
20
+ openBracket,
21
+ optionalSpaces,
22
+ importedNames,
23
+ optionalSpaces,
24
+ closeBracket,
25
+ optionalSpaces,
26
+ fromKeyword,
27
+ spaces,
28
+ literal.clone("url"),
29
+ ]);
@@ -69,7 +69,6 @@ digits = (digit)*
69
69
  ```
70
70
 
71
71
  ### Zero Or More Pattern
72
-
73
72
  ```
74
73
  digit = /\d/
75
74
  comma = ","
@@ -138,5 +137,30 @@ digits = (digit, comma){1,3}
138
137
  ```
139
138
  digit = /\d/
140
139
  comma = ","
141
- digits = (digit, comma){1,3}
140
+ digits = (digit, comma){1,}
141
+ ```
142
+
143
+ ### Import
144
+ ```
145
+ import { spaces } from "https://some.cdn.com/some/spaces.cpat"
146
+
147
+ first-name = "John"
148
+ last-name = "Doe"
149
+ full-name = first-name & spaces & last-name
150
+ ```
151
+
152
+ ### Muliple Named Imports
153
+ ```
154
+ import { spaces, first-name } from "https://some.cdn.com/some/spaces.cpat"
155
+
156
+ last-name = "Doe"
157
+ full-name = first-name & spaces & last-name
158
+ ```
159
+
160
+ ### Muliple Imports
161
+ ```
162
+ import { spaces, first-name } from "https://some.cdn.com/some/patterns.cpat"
163
+ import { last-name } from "https://some.cdn.com/some/last-name.cpat"
164
+
165
+ full-name = first-name & spaces & last-name
142
166
  ```
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@ import { Pattern } from "./patterns/Pattern";
15
15
  import { Reference } from "./patterns/Reference";
16
16
  import { CursorHistory, Match } from "./patterns/CursorHistory";
17
17
  import { ParseResult } from "./patterns/ParseResult";
18
+ import { grammar } from "./grammar/patterns/grammar";
18
19
 
19
20
  export {
20
21
  Node,
@@ -36,4 +37,5 @@ export {
36
37
  Reference,
37
38
  Regex,
38
39
  Repeat,
40
+ grammar
39
41
  };
@@ -190,7 +190,6 @@ export class And implements Pattern {
190
190
  const children = filterOutNull(this._nodes);
191
191
 
192
192
  const lastIndex = children[children.length - 1].lastIndex;
193
- const value = cursor.getChars(this._firstIndex, lastIndex);
194
193
 
195
194
  cursor.moveTo(lastIndex)
196
195
 
@@ -250,15 +249,12 @@ export class And implements Pattern {
250
249
 
251
250
  getPatternsAfter(childReference: Pattern): Pattern[] {
252
251
  const patterns: Pattern[] = [];
253
- let nextSibling: Pattern | null = null;
254
252
  let nextSiblingIndex = -1;
255
253
  let index = -1;
256
254
 
257
255
  for (let i = 0; i < this._children.length; i++) {
258
256
  if (this._children[i] === childReference) {
259
- if (i + 1 < this._children.length) {
260
- nextSibling = this._children[i + 1];
261
- }
257
+
262
258
  nextSiblingIndex = i + 1;
263
259
  index = i;
264
260
  break;
@@ -111,8 +111,9 @@ export class FiniteRepeat implements Pattern {
111
111
  }
112
112
 
113
113
  if (matchCount < this._min) {
114
+ const lastIndex = cursor.index;
114
115
  cursor.moveTo(startIndex);
115
- cursor.recordErrorAt(startIndex, startIndex, this);
116
+ cursor.recordErrorAt(startIndex, lastIndex, this);
116
117
  return null;
117
118
  } else if (nodes.length === 0) {
118
119
  cursor.resolveError();
@@ -109,29 +109,6 @@ describe("InfiniteRepeat", () => {
109
109
  expect(cursor.hasError).toBeFalsy()
110
110
  });
111
111
 
112
- test("Optional Repeating Pattern", () => {
113
- const digit = new Regex("digit", "\\d+", true);
114
- const divider = new Regex("divider", "\\s");
115
- const integer = new InfiniteRepeat("number", digit, { divider });
116
- const cursor = new Cursor(
117
- "\n" +
118
- "3\n" +
119
- "\n" +
120
- "\n"
121
- );
122
- const result = integer.parse(cursor);
123
- const expected = new Node("infinite-repeat", "number", 0, 4, [
124
- new Node("regex", "divider", 0, 0, [], "\n"),
125
- new Node("regex", "digit", 1, 1, [], "3"),
126
- new Node("regex", "divider", 2, 2, [], "\n"),
127
- new Node("regex", "divider", 3, 3, [], "\n"),
128
- new Node("regex", "divider", 4, 4, [], "\n"),
129
- ]);
130
-
131
- expect(result).toEqual(expected)
132
- expect(cursor.hasError).toBeFalsy()
133
- });
134
-
135
112
  test("Failed (Optional)", () => {
136
113
  const digit = new Regex("digit", "\\d");
137
114
  const integer = new InfiniteRepeat("number", digit, { min: 0 });
@@ -293,4 +270,13 @@ describe("InfiniteRepeat", () => {
293
270
 
294
271
  expect(clone).toEqual(expected);
295
272
  });
273
+
274
+ test("No Results, min is 0", () => {
275
+ const numbers = new InfiniteRepeat("numbers", new Regex("number", "\\d"), { divider: new Literal(",", ","), min: 0 });
276
+ const result = numbers.exec("j");
277
+ expect(result.ast).toBeNull();
278
+ expect(result.cursor.index).toBe(0);
279
+ });
280
+
281
+
296
282
  });
@@ -56,9 +56,9 @@ export class InfiniteRepeat implements Pattern {
56
56
  let children: Pattern[];
57
57
 
58
58
  if (divider != null) {
59
- children = [pattern.clone(), divider.clone(divider.name, false)]
59
+ children = [pattern.clone(pattern.name, false), divider.clone(divider.name, false)]
60
60
  } else {
61
- children = [pattern.clone()]
61
+ children = [pattern.clone(pattern.name, false)]
62
62
  }
63
63
 
64
64
  this._assignChildrenToParent(children);
@@ -208,6 +208,11 @@ export class InfiniteRepeat implements Pattern {
208
208
  cursor.moveTo(dividerNode.firstIndex);
209
209
  }
210
210
 
211
+ // if (this._nodes.length === 0) {
212
+ // cursor.moveTo(this._firstIndex);
213
+ // return null;
214
+ // }
215
+
211
216
  const lastIndex = this._nodes[this._nodes.length - 1].lastIndex;
212
217
  cursor.moveTo(lastIndex);
213
218