Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/ast.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { BugIndicatingError } from '../../../../../base/common/errors.js';
7
import { CursorColumns } from '../../../core/cursorColumns.js';
8
import { BracketKind } from '../../../languages/supports/languageBracketsConfiguration.js';
9
import { ITextModel } from '../../../model.js';
10
import { Length, lengthAdd, lengthGetLineCount, lengthToObj, lengthZero } from './length.js';
11
import { SmallImmutableSet } from './smallImmutableSet.js';
12
import { OpeningBracketId } from './tokenizer.js';
13
14
export const enum AstNodeKind {
15
Text = 0,
16
Bracket = 1,
17
Pair = 2,
18
UnexpectedClosingBracket = 3,
19
List = 4,
20
}
21
22
export type AstNode = PairAstNode | ListAstNode | BracketAstNode | InvalidBracketAstNode | TextAstNode;
23
24
/**
25
* The base implementation for all AST nodes.
26
*/
27
abstract class BaseAstNode {
28
public abstract readonly kind: AstNodeKind;
29
30
public abstract readonly childrenLength: number;
31
32
/**
33
* Might return null even if {@link idx} is smaller than {@link BaseAstNode.childrenLength}.
34
*/
35
public abstract getChild(idx: number): AstNode | null;
36
37
/**
38
* Try to avoid using this property, as implementations might need to allocate the resulting array.
39
*/
40
public abstract readonly children: readonly AstNode[];
41
42
/**
43
* Represents the set of all (potentially) missing opening bracket ids in this node.
44
* E.g. in `{ ] ) }` that set is {`[`, `(` }.
45
*/
46
public abstract readonly missingOpeningBracketIds: SmallImmutableSet<OpeningBracketId>;
47
48
/**
49
* In case of a list, determines the height of the (2,3) tree.
50
*/
51
public abstract readonly listHeight: number;
52
53
protected _length: Length;
54
55
/**
56
* The length of the entire node, which should equal the sum of lengths of all children.
57
*/
58
public get length(): Length {
59
return this._length;
60
}
61
62
public constructor(length: Length) {
63
this._length = length;
64
}
65
66
/**
67
* @param openBracketIds The set of all opening brackets that have not yet been closed.
68
*/
69
public abstract canBeReused(
70
openBracketIds: SmallImmutableSet<OpeningBracketId>
71
): boolean;
72
73
/**
74
* Flattens all lists in this AST. Only for debugging.
75
*/
76
public abstract flattenLists(): AstNode;
77
78
/**
79
* Creates a deep clone.
80
*/
81
public abstract deepClone(): AstNode;
82
83
public abstract computeMinIndentation(offset: Length, textModel: ITextModel): number;
84
}
85
86
/**
87
* Represents a bracket pair including its child (e.g. `{ ... }`).
88
* Might be unclosed.
89
* Immutable, if all children are immutable.
90
*/
91
export class PairAstNode extends BaseAstNode {
92
public static create(
93
openingBracket: BracketAstNode,
94
child: AstNode | null,
95
closingBracket: BracketAstNode | null
96
) {
97
let length = openingBracket.length;
98
if (child) {
99
length = lengthAdd(length, child.length);
100
}
101
if (closingBracket) {
102
length = lengthAdd(length, closingBracket.length);
103
}
104
return new PairAstNode(length, openingBracket, child, closingBracket, child ? child.missingOpeningBracketIds : SmallImmutableSet.getEmpty());
105
}
106
107
public get kind(): AstNodeKind.Pair {
108
return AstNodeKind.Pair;
109
}
110
public get listHeight() {
111
return 0;
112
}
113
public get childrenLength(): number {
114
return 3;
115
}
116
public getChild(idx: number): AstNode | null {
117
switch (idx) {
118
case 0: return this.openingBracket;
119
case 1: return this.child;
120
case 2: return this.closingBracket;
121
}
122
throw new Error('Invalid child index');
123
}
124
125
/**
126
* Avoid using this property, it allocates an array!
127
*/
128
public get children() {
129
const result: AstNode[] = [];
130
result.push(this.openingBracket);
131
if (this.child) {
132
result.push(this.child);
133
}
134
if (this.closingBracket) {
135
result.push(this.closingBracket);
136
}
137
return result;
138
}
139
140
private constructor(
141
length: Length,
142
public readonly openingBracket: BracketAstNode,
143
public readonly child: AstNode | null,
144
public readonly closingBracket: BracketAstNode | null,
145
public readonly missingOpeningBracketIds: SmallImmutableSet<OpeningBracketId>
146
) {
147
super(length);
148
}
149
150
public canBeReused(openBracketIds: SmallImmutableSet<OpeningBracketId>) {
151
if (this.closingBracket === null) {
152
// Unclosed pair ast nodes only
153
// end at the end of the document
154
// or when a parent node is closed.
155
156
// This could be improved:
157
// Only return false if some next token is neither "undefined" nor a bracket that closes a parent.
158
159
return false;
160
}
161
162
if (openBracketIds.intersects(this.missingOpeningBracketIds)) {
163
return false;
164
}
165
166
return true;
167
}
168
169
public flattenLists(): PairAstNode {
170
return PairAstNode.create(
171
this.openingBracket.flattenLists(),
172
this.child && this.child.flattenLists(),
173
this.closingBracket && this.closingBracket.flattenLists()
174
);
175
}
176
177
public deepClone(): PairAstNode {
178
return new PairAstNode(
179
this.length,
180
this.openingBracket.deepClone(),
181
this.child && this.child.deepClone(),
182
this.closingBracket && this.closingBracket.deepClone(),
183
this.missingOpeningBracketIds
184
);
185
}
186
187
public computeMinIndentation(offset: Length, textModel: ITextModel): number {
188
return this.child ? this.child.computeMinIndentation(lengthAdd(offset, this.openingBracket.length), textModel) : Number.MAX_SAFE_INTEGER;
189
}
190
}
191
192
export abstract class ListAstNode extends BaseAstNode {
193
/**
194
* This method uses more memory-efficient list nodes that can only store 2 or 3 children.
195
*/
196
public static create23(item1: AstNode, item2: AstNode, item3: AstNode | null, immutable: boolean = false): ListAstNode {
197
let length = item1.length;
198
let missingBracketIds = item1.missingOpeningBracketIds;
199
200
if (item1.listHeight !== item2.listHeight) {
201
throw new Error('Invalid list heights');
202
}
203
204
length = lengthAdd(length, item2.length);
205
missingBracketIds = missingBracketIds.merge(item2.missingOpeningBracketIds);
206
207
if (item3) {
208
if (item1.listHeight !== item3.listHeight) {
209
throw new Error('Invalid list heights');
210
}
211
length = lengthAdd(length, item3.length);
212
missingBracketIds = missingBracketIds.merge(item3.missingOpeningBracketIds);
213
}
214
return immutable
215
? new Immutable23ListAstNode(length, item1.listHeight + 1, item1, item2, item3, missingBracketIds)
216
: new TwoThreeListAstNode(length, item1.listHeight + 1, item1, item2, item3, missingBracketIds);
217
}
218
219
public static create(items: AstNode[], immutable: boolean = false): ListAstNode {
220
if (items.length === 0) {
221
return this.getEmpty();
222
} else {
223
let length = items[0].length;
224
let unopenedBrackets = items[0].missingOpeningBracketIds;
225
for (let i = 1; i < items.length; i++) {
226
length = lengthAdd(length, items[i].length);
227
unopenedBrackets = unopenedBrackets.merge(items[i].missingOpeningBracketIds);
228
}
229
return immutable
230
? new ImmutableArrayListAstNode(length, items[0].listHeight + 1, items, unopenedBrackets)
231
: new ArrayListAstNode(length, items[0].listHeight + 1, items, unopenedBrackets);
232
}
233
}
234
235
public static getEmpty() {
236
return new ImmutableArrayListAstNode(lengthZero, 0, [], SmallImmutableSet.getEmpty());
237
}
238
239
public get kind(): AstNodeKind.List {
240
return AstNodeKind.List;
241
}
242
243
public get missingOpeningBracketIds(): SmallImmutableSet<OpeningBracketId> {
244
return this._missingOpeningBracketIds;
245
}
246
247
private cachedMinIndentation: number = -1;
248
249
/**
250
* Use ListAstNode.create.
251
*/
252
constructor(
253
length: Length,
254
public readonly listHeight: number,
255
private _missingOpeningBracketIds: SmallImmutableSet<OpeningBracketId>
256
) {
257
super(length);
258
}
259
260
protected throwIfImmutable(): void {
261
// NOOP
262
}
263
264
protected abstract setChild(idx: number, child: AstNode): void;
265
266
public makeLastElementMutable(): AstNode | undefined {
267
this.throwIfImmutable();
268
const childCount = this.childrenLength;
269
if (childCount === 0) {
270
return undefined;
271
}
272
const lastChild = this.getChild(childCount - 1)!;
273
const mutable = lastChild.kind === AstNodeKind.List ? lastChild.toMutable() : lastChild;
274
if (lastChild !== mutable) {
275
this.setChild(childCount - 1, mutable);
276
}
277
return mutable;
278
}
279
280
public makeFirstElementMutable(): AstNode | undefined {
281
this.throwIfImmutable();
282
const childCount = this.childrenLength;
283
if (childCount === 0) {
284
return undefined;
285
}
286
const firstChild = this.getChild(0)!;
287
const mutable = firstChild.kind === AstNodeKind.List ? firstChild.toMutable() : firstChild;
288
if (firstChild !== mutable) {
289
this.setChild(0, mutable);
290
}
291
return mutable;
292
}
293
294
public canBeReused(openBracketIds: SmallImmutableSet<OpeningBracketId>): boolean {
295
if (openBracketIds.intersects(this.missingOpeningBracketIds)) {
296
return false;
297
}
298
299
if (this.childrenLength === 0) {
300
// Don't reuse empty lists.
301
return false;
302
}
303
304
let lastChild: ListAstNode = this;
305
while (lastChild.kind === AstNodeKind.List) {
306
const lastLength = lastChild.childrenLength;
307
if (lastLength === 0) {
308
// Empty lists should never be contained in other lists.
309
throw new BugIndicatingError();
310
}
311
lastChild = lastChild.getChild(lastLength - 1) as ListAstNode;
312
}
313
314
return lastChild.canBeReused(openBracketIds);
315
}
316
317
public handleChildrenChanged(): void {
318
this.throwIfImmutable();
319
320
const count = this.childrenLength;
321
322
let length = this.getChild(0)!.length;
323
let unopenedBrackets = this.getChild(0)!.missingOpeningBracketIds;
324
325
for (let i = 1; i < count; i++) {
326
const child = this.getChild(i)!;
327
length = lengthAdd(length, child.length);
328
unopenedBrackets = unopenedBrackets.merge(child.missingOpeningBracketIds);
329
}
330
331
this._length = length;
332
this._missingOpeningBracketIds = unopenedBrackets;
333
this.cachedMinIndentation = -1;
334
}
335
336
public flattenLists(): ListAstNode {
337
const items: AstNode[] = [];
338
for (const c of this.children) {
339
const normalized = c.flattenLists();
340
if (normalized.kind === AstNodeKind.List) {
341
items.push(...normalized.children);
342
} else {
343
items.push(normalized);
344
}
345
}
346
return ListAstNode.create(items);
347
}
348
349
public computeMinIndentation(offset: Length, textModel: ITextModel): number {
350
if (this.cachedMinIndentation !== -1) {
351
return this.cachedMinIndentation;
352
}
353
354
let minIndentation = Number.MAX_SAFE_INTEGER;
355
let childOffset = offset;
356
for (let i = 0; i < this.childrenLength; i++) {
357
const child = this.getChild(i);
358
if (child) {
359
minIndentation = Math.min(minIndentation, child.computeMinIndentation(childOffset, textModel));
360
childOffset = lengthAdd(childOffset, child.length);
361
}
362
}
363
364
this.cachedMinIndentation = minIndentation;
365
return minIndentation;
366
}
367
368
/**
369
* Creates a shallow clone that is mutable, or itself if it is already mutable.
370
*/
371
public abstract toMutable(): ListAstNode;
372
373
public abstract appendChildOfSameHeight(node: AstNode): void;
374
public abstract unappendChild(): AstNode | undefined;
375
public abstract prependChildOfSameHeight(node: AstNode): void;
376
public abstract unprependChild(): AstNode | undefined;
377
}
378
379
class TwoThreeListAstNode extends ListAstNode {
380
public get childrenLength(): number {
381
return this._item3 !== null ? 3 : 2;
382
}
383
public getChild(idx: number): AstNode | null {
384
switch (idx) {
385
case 0: return this._item1;
386
case 1: return this._item2;
387
case 2: return this._item3;
388
}
389
throw new Error('Invalid child index');
390
}
391
protected setChild(idx: number, node: AstNode): void {
392
switch (idx) {
393
case 0: this._item1 = node; return;
394
case 1: this._item2 = node; return;
395
case 2: this._item3 = node; return;
396
}
397
throw new Error('Invalid child index');
398
}
399
400
public get children(): readonly AstNode[] {
401
return this._item3 ? [this._item1, this._item2, this._item3] : [this._item1, this._item2];
402
}
403
404
public get item1(): AstNode {
405
return this._item1;
406
}
407
public get item2(): AstNode {
408
return this._item2;
409
}
410
public get item3(): AstNode | null {
411
return this._item3;
412
}
413
414
public constructor(
415
length: Length,
416
listHeight: number,
417
private _item1: AstNode,
418
private _item2: AstNode,
419
private _item3: AstNode | null,
420
missingOpeningBracketIds: SmallImmutableSet<OpeningBracketId>
421
) {
422
super(length, listHeight, missingOpeningBracketIds);
423
}
424
425
public deepClone(): ListAstNode {
426
return new TwoThreeListAstNode(
427
this.length,
428
this.listHeight,
429
this._item1.deepClone(),
430
this._item2.deepClone(),
431
this._item3 ? this._item3.deepClone() : null,
432
this.missingOpeningBracketIds
433
);
434
}
435
436
public appendChildOfSameHeight(node: AstNode): void {
437
if (this._item3) {
438
throw new Error('Cannot append to a full (2,3) tree node');
439
}
440
this.throwIfImmutable();
441
this._item3 = node;
442
this.handleChildrenChanged();
443
}
444
445
public unappendChild(): AstNode | undefined {
446
if (!this._item3) {
447
throw new Error('Cannot remove from a non-full (2,3) tree node');
448
}
449
this.throwIfImmutable();
450
const result = this._item3;
451
this._item3 = null;
452
this.handleChildrenChanged();
453
return result;
454
}
455
456
public prependChildOfSameHeight(node: AstNode): void {
457
if (this._item3) {
458
throw new Error('Cannot prepend to a full (2,3) tree node');
459
}
460
this.throwIfImmutable();
461
this._item3 = this._item2;
462
this._item2 = this._item1;
463
this._item1 = node;
464
this.handleChildrenChanged();
465
}
466
467
public unprependChild(): AstNode | undefined {
468
if (!this._item3) {
469
throw new Error('Cannot remove from a non-full (2,3) tree node');
470
}
471
this.throwIfImmutable();
472
const result = this._item1;
473
this._item1 = this._item2;
474
this._item2 = this._item3;
475
this._item3 = null;
476
477
this.handleChildrenChanged();
478
return result;
479
}
480
481
override toMutable(): ListAstNode {
482
return this;
483
}
484
}
485
486
/**
487
* Immutable, if all children are immutable.
488
*/
489
class Immutable23ListAstNode extends TwoThreeListAstNode {
490
override toMutable(): ListAstNode {
491
return new TwoThreeListAstNode(this.length, this.listHeight, this.item1, this.item2, this.item3, this.missingOpeningBracketIds);
492
}
493
494
protected override throwIfImmutable(): void {
495
throw new Error('this instance is immutable');
496
}
497
}
498
499
/**
500
* For debugging.
501
*/
502
class ArrayListAstNode extends ListAstNode {
503
get childrenLength(): number {
504
return this._children.length;
505
}
506
getChild(idx: number): AstNode | null {
507
return this._children[idx];
508
}
509
protected setChild(idx: number, child: AstNode): void {
510
this._children[idx] = child;
511
}
512
get children(): readonly AstNode[] {
513
return this._children;
514
}
515
516
constructor(
517
length: Length,
518
listHeight: number,
519
private readonly _children: AstNode[],
520
missingOpeningBracketIds: SmallImmutableSet<OpeningBracketId>
521
) {
522
super(length, listHeight, missingOpeningBracketIds);
523
}
524
525
deepClone(): ListAstNode {
526
const children = new Array<AstNode>(this._children.length);
527
for (let i = 0; i < this._children.length; i++) {
528
children[i] = this._children[i].deepClone();
529
}
530
return new ArrayListAstNode(this.length, this.listHeight, children, this.missingOpeningBracketIds);
531
}
532
533
public appendChildOfSameHeight(node: AstNode): void {
534
this.throwIfImmutable();
535
this._children.push(node);
536
this.handleChildrenChanged();
537
}
538
539
public unappendChild(): AstNode | undefined {
540
this.throwIfImmutable();
541
const item = this._children.pop();
542
this.handleChildrenChanged();
543
return item;
544
}
545
546
public prependChildOfSameHeight(node: AstNode): void {
547
this.throwIfImmutable();
548
this._children.unshift(node);
549
this.handleChildrenChanged();
550
}
551
552
public unprependChild(): AstNode | undefined {
553
this.throwIfImmutable();
554
const item = this._children.shift();
555
this.handleChildrenChanged();
556
return item;
557
}
558
559
public override toMutable(): ListAstNode {
560
return this;
561
}
562
}
563
564
/**
565
* Immutable, if all children are immutable.
566
*/
567
class ImmutableArrayListAstNode extends ArrayListAstNode {
568
override toMutable(): ListAstNode {
569
return new ArrayListAstNode(this.length, this.listHeight, [...this.children], this.missingOpeningBracketIds);
570
}
571
572
protected override throwIfImmutable(): void {
573
throw new Error('this instance is immutable');
574
}
575
}
576
577
const emptyArray: readonly AstNode[] = [];
578
579
abstract class ImmutableLeafAstNode extends BaseAstNode {
580
public get listHeight() {
581
return 0;
582
}
583
public get childrenLength(): number {
584
return 0;
585
}
586
public getChild(idx: number): AstNode | null {
587
return null;
588
}
589
public get children(): readonly AstNode[] {
590
return emptyArray;
591
}
592
593
public flattenLists(): this & AstNode {
594
return this as this & AstNode;
595
}
596
public deepClone(): this & AstNode {
597
return this as this & AstNode;
598
}
599
}
600
601
export class TextAstNode extends ImmutableLeafAstNode {
602
public get kind(): AstNodeKind.Text {
603
return AstNodeKind.Text;
604
}
605
public get missingOpeningBracketIds(): SmallImmutableSet<OpeningBracketId> {
606
return SmallImmutableSet.getEmpty();
607
}
608
609
public canBeReused(_openedBracketIds: SmallImmutableSet<OpeningBracketId>) {
610
return true;
611
}
612
613
public computeMinIndentation(offset: Length, textModel: ITextModel): number {
614
const start = lengthToObj(offset);
615
// Text ast nodes don't have partial indentation (ensured by the tokenizer).
616
// Thus, if this text node does not start at column 0, the first line cannot have any indentation at all.
617
const startLineNumber = (start.columnCount === 0 ? start.lineCount : start.lineCount + 1) + 1;
618
const endLineNumber = lengthGetLineCount(lengthAdd(offset, this.length)) + 1;
619
620
let result = Number.MAX_SAFE_INTEGER;
621
622
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
623
const firstNonWsColumn = textModel.getLineFirstNonWhitespaceColumn(lineNumber);
624
const lineContent = textModel.getLineContent(lineNumber);
625
if (firstNonWsColumn === 0) {
626
continue;
627
}
628
629
const visibleColumn = CursorColumns.visibleColumnFromColumn(lineContent, firstNonWsColumn, textModel.getOptions().tabSize)!;
630
result = Math.min(result, visibleColumn);
631
}
632
633
return result;
634
}
635
}
636
637
export class BracketAstNode extends ImmutableLeafAstNode {
638
public static create(
639
length: Length,
640
bracketInfo: BracketKind,
641
bracketIds: SmallImmutableSet<OpeningBracketId>
642
): BracketAstNode {
643
const node = new BracketAstNode(length, bracketInfo, bracketIds);
644
return node;
645
}
646
647
public get kind(): AstNodeKind.Bracket {
648
return AstNodeKind.Bracket;
649
}
650
651
public get missingOpeningBracketIds(): SmallImmutableSet<OpeningBracketId> {
652
return SmallImmutableSet.getEmpty();
653
}
654
655
private constructor(
656
length: Length,
657
public readonly bracketInfo: BracketKind,
658
/**
659
* In case of a opening bracket, this is the id of the opening bracket.
660
* In case of a closing bracket, this contains the ids of all opening brackets it can close.
661
*/
662
public readonly bracketIds: SmallImmutableSet<OpeningBracketId>
663
) {
664
super(length);
665
}
666
667
public get text() {
668
return this.bracketInfo.bracketText;
669
}
670
671
public get languageId() {
672
return this.bracketInfo.languageId;
673
}
674
675
public canBeReused(_openedBracketIds: SmallImmutableSet<OpeningBracketId>) {
676
// These nodes could be reused,
677
// but not in a general way.
678
// Their parent may be reused.
679
return false;
680
}
681
682
public computeMinIndentation(offset: Length, textModel: ITextModel): number {
683
return Number.MAX_SAFE_INTEGER;
684
}
685
}
686
687
export class InvalidBracketAstNode extends ImmutableLeafAstNode {
688
public get kind(): AstNodeKind.UnexpectedClosingBracket {
689
return AstNodeKind.UnexpectedClosingBracket;
690
}
691
692
public readonly missingOpeningBracketIds: SmallImmutableSet<OpeningBracketId>;
693
694
public constructor(closingBrackets: SmallImmutableSet<OpeningBracketId>, length: Length) {
695
super(length);
696
this.missingOpeningBracketIds = closingBrackets;
697
}
698
699
public canBeReused(openedBracketIds: SmallImmutableSet<OpeningBracketId>) {
700
return !openedBracketIds.intersects(this.missingOpeningBracketIds);
701
}
702
703
public computeMinIndentation(offset: Length, textModel: ITextModel): number {
704
return Number.MAX_SAFE_INTEGER;
705
}
706
}
707
708