Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/parser.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 { AstNode, AstNodeKind, BracketAstNode, InvalidBracketAstNode, ListAstNode, PairAstNode, TextAstNode } from './ast.js';
7
import { BeforeEditPositionMapper, TextEditInfo } from './beforeEditPositionMapper.js';
8
import { SmallImmutableSet } from './smallImmutableSet.js';
9
import { lengthIsZero, lengthLessThan } from './length.js';
10
import { concat23Trees, concat23TreesOfSameHeight } from './concat23Trees.js';
11
import { NodeReader } from './nodeReader.js';
12
import { OpeningBracketId, Tokenizer, TokenKind } from './tokenizer.js';
13
14
/**
15
* Non incrementally built ASTs are immutable.
16
*/
17
export function parseDocument(tokenizer: Tokenizer, edits: TextEditInfo[], oldNode: AstNode | undefined, createImmutableLists: boolean): AstNode {
18
const parser = new Parser(tokenizer, edits, oldNode, createImmutableLists);
19
return parser.parseDocument();
20
}
21
22
/**
23
* Non incrementally built ASTs are immutable.
24
*/
25
class Parser {
26
private readonly oldNodeReader?: NodeReader;
27
private readonly positionMapper: BeforeEditPositionMapper;
28
private _itemsConstructed: number = 0;
29
private _itemsFromCache: number = 0;
30
31
/**
32
* Reports how many nodes were constructed in the last parse operation.
33
*/
34
get nodesConstructed() {
35
return this._itemsConstructed;
36
}
37
38
/**
39
* Reports how many nodes were reused in the last parse operation.
40
*/
41
get nodesReused() {
42
return this._itemsFromCache;
43
}
44
45
constructor(
46
private readonly tokenizer: Tokenizer,
47
edits: TextEditInfo[],
48
oldNode: AstNode | undefined,
49
private readonly createImmutableLists: boolean,
50
) {
51
if (oldNode && createImmutableLists) {
52
throw new Error('Not supported');
53
}
54
55
this.oldNodeReader = oldNode ? new NodeReader(oldNode) : undefined;
56
this.positionMapper = new BeforeEditPositionMapper(edits);
57
}
58
59
parseDocument(): AstNode {
60
this._itemsConstructed = 0;
61
this._itemsFromCache = 0;
62
63
let result = this.parseList(SmallImmutableSet.getEmpty(), 0);
64
if (!result) {
65
result = ListAstNode.getEmpty();
66
}
67
68
return result;
69
}
70
71
private parseList(
72
openedBracketIds: SmallImmutableSet<OpeningBracketId>,
73
level: number,
74
): AstNode | null {
75
const items: AstNode[] = [];
76
77
while (true) {
78
let child = this.tryReadChildFromCache(openedBracketIds);
79
80
if (!child) {
81
const token = this.tokenizer.peek();
82
if (
83
!token ||
84
(token.kind === TokenKind.ClosingBracket &&
85
token.bracketIds.intersects(openedBracketIds))
86
) {
87
break;
88
}
89
90
child = this.parseChild(openedBracketIds, level + 1);
91
}
92
93
if (child.kind === AstNodeKind.List && child.childrenLength === 0) {
94
continue;
95
}
96
97
items.push(child);
98
}
99
100
// When there is no oldNodeReader, all items are created from scratch and must have the same height.
101
const result = this.oldNodeReader ? concat23Trees(items) : concat23TreesOfSameHeight(items, this.createImmutableLists);
102
return result;
103
}
104
105
private tryReadChildFromCache(openedBracketIds: SmallImmutableSet<number>): AstNode | undefined {
106
if (this.oldNodeReader) {
107
const maxCacheableLength = this.positionMapper.getDistanceToNextChange(this.tokenizer.offset);
108
if (maxCacheableLength === null || !lengthIsZero(maxCacheableLength)) {
109
const cachedNode = this.oldNodeReader.readLongestNodeAt(this.positionMapper.getOffsetBeforeChange(this.tokenizer.offset), curNode => {
110
// The edit could extend the ending token, thus we cannot re-use nodes that touch the edit.
111
// If there is no edit anymore, we can re-use the node in any case.
112
if (maxCacheableLength !== null && !lengthLessThan(curNode.length, maxCacheableLength)) {
113
// Either the node contains edited text or touches edited text.
114
// In the latter case, brackets might have been extended (`end` -> `ending`), so even touching nodes cannot be reused.
115
return false;
116
}
117
const canBeReused = curNode.canBeReused(openedBracketIds);
118
return canBeReused;
119
});
120
121
if (cachedNode) {
122
this._itemsFromCache++;
123
this.tokenizer.skip(cachedNode.length);
124
return cachedNode;
125
}
126
}
127
}
128
return undefined;
129
}
130
131
private parseChild(
132
openedBracketIds: SmallImmutableSet<number>,
133
level: number,
134
): AstNode {
135
this._itemsConstructed++;
136
137
const token = this.tokenizer.read()!;
138
139
switch (token.kind) {
140
case TokenKind.ClosingBracket:
141
return new InvalidBracketAstNode(token.bracketIds, token.length);
142
143
case TokenKind.Text:
144
return token.astNode as TextAstNode;
145
146
case TokenKind.OpeningBracket: {
147
if (level > 300) {
148
// To prevent stack overflows
149
return new TextAstNode(token.length);
150
}
151
152
const set = openedBracketIds.merge(token.bracketIds);
153
const child = this.parseList(set, level + 1);
154
155
const nextToken = this.tokenizer.peek();
156
if (
157
nextToken &&
158
nextToken.kind === TokenKind.ClosingBracket &&
159
(nextToken.bracketId === token.bracketId || nextToken.bracketIds.intersects(token.bracketIds))
160
) {
161
this.tokenizer.read();
162
return PairAstNode.create(
163
token.astNode as BracketAstNode,
164
child,
165
nextToken.astNode as BracketAstNode
166
);
167
} else {
168
return PairAstNode.create(
169
token.astNode as BracketAstNode,
170
child,
171
null
172
);
173
}
174
}
175
default:
176
throw new Error('unexpected');
177
}
178
}
179
}
180
181