Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/platform/parser/node/parserWithCaching.ts
13401 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 Parser = require('web-tree-sitter');
7
import { DisposablesLRUCache } from '../../../util/common/cache';
8
import { IDisposable } from '../../../util/vs/base/common/lifecycle';
9
import { LanguageLoader } from './languageLoader';
10
import { WASMLanguage } from './treeSitterLanguages';
11
12
export class ParserWithCaching implements IDisposable {
13
14
public static INSTANCE = new ParserWithCaching();
15
16
static CACHE_SIZE_PER_LANGUAGE = 5;
17
18
private readonly caches: Map<WASMLanguage, DisposablesLRUCache<CacheableParseTree>>;
19
private readonly languageLoader: LanguageLoader;
20
private _parser: Parser | null;
21
22
constructor() {
23
this.caches = new Map<WASMLanguage, DisposablesLRUCache<CacheableParseTree>>();
24
this.languageLoader = new LanguageLoader();
25
this._parser = null;
26
}
27
28
/** @remarks must not be called before `Parser.init()` */
29
private get parser() {
30
if (!this._parser) {
31
this._parser = new Parser();
32
}
33
return this._parser;
34
}
35
36
/**
37
* @remarks Do not `delete()` the returned parse tree manually.
38
*/
39
async parse(lang: WASMLanguage, source: string): Promise<ParseTreeReference> {
40
41
await Parser.init();
42
43
const cache = this.getParseTreeCache(lang);
44
45
let cacheEntry = cache.get(source);
46
if (cacheEntry) {
47
return cacheEntry.createReference();
48
}
49
50
const parserLang = await this.languageLoader.loadLanguage(lang);
51
this.parser.setLanguage(parserLang);
52
53
// check again the cache, maybe someone else has already parsed the source during the await
54
cacheEntry = cache.get(source);
55
if (cacheEntry) {
56
return cacheEntry.createReference();
57
}
58
59
const parseTree = this.parser.parse(source);
60
cacheEntry = new CacheableParseTree(parseTree);
61
cache.put(source, cacheEntry);
62
63
return cacheEntry.createReference();
64
}
65
66
dispose() {
67
if (this._parser) {
68
this.parser.delete();
69
this._parser = null;
70
}
71
for (const cache of this.caches.values()) {
72
cache.dispose();
73
}
74
}
75
76
private getParseTreeCache(lang: WASMLanguage) {
77
let cache = this.caches.get(lang);
78
if (!cache) {
79
cache = new DisposablesLRUCache<CacheableParseTree>(ParserWithCaching.CACHE_SIZE_PER_LANGUAGE);
80
this.caches.set(lang, cache);
81
}
82
return cache;
83
}
84
}
85
86
/**
87
* A parse tree that can be cached (i.e. it can be referenced multiple
88
* times and will be disppsed when it is evicted from cache and all
89
* references to it are also disposed.
90
*/
91
class CacheableParseTree implements IDisposable {
92
93
private readonly _tree: RefCountedParseTree;
94
95
constructor(tree: Parser.Tree) {
96
this._tree = new RefCountedParseTree(tree);
97
}
98
99
dispose(): void {
100
this._tree.deref();
101
}
102
103
createReference(): ParseTreeReference {
104
return new ParseTreeReference(this._tree);
105
}
106
}
107
108
/**
109
* A reference to a parse tree.
110
* You must call `dispose()` when you're done with it.
111
*/
112
export class ParseTreeReference implements IDisposable {
113
114
public get tree() {
115
return this._parseTree.tree;
116
}
117
118
constructor(
119
private readonly _parseTree: RefCountedParseTree
120
) {
121
this._parseTree.ref();
122
}
123
124
dispose(): void {
125
this._parseTree.deref();
126
}
127
}
128
129
/**
130
* Will dispose the referenced parse tree when the ref count reaches 0.
131
* The ref count is initialized to 1.
132
*/
133
class RefCountedParseTree {
134
135
private _refCount = 1;
136
137
public get tree(): Parser.Tree {
138
if (this._refCount === 0) {
139
throw new Error(`Cannot access disposed RefCountedParseTree`);
140
}
141
return this._tree;
142
}
143
144
constructor(
145
private readonly _tree: Parser.Tree
146
) { }
147
148
ref(): void {
149
if (this._refCount === 0) {
150
throw new Error(`Cannot ref disposed RefCountedParseTree`);
151
}
152
this._refCount++;
153
}
154
155
deref(): void {
156
if (this._refCount === 0) {
157
throw new Error(`Cannot deref disposed RefCountedParseTree`);
158
}
159
this._refCount--;
160
if (this._refCount === 0) {
161
this._tree.delete();
162
}
163
}
164
}
165
166
export function _dispose() {
167
ParserWithCaching.INSTANCE.dispose();
168
}
169
170
/**
171
* Parses the given source code and returns the root node of the resulting syntax tree.
172
*/
173
export function _parse(language: WASMLanguage, source: string): Promise<ParseTreeReference> {
174
return ParserWithCaching.INSTANCE.parse(language, source);
175
}
176
177