Path: blob/main/extensions/copilot/src/platform/parser/node/parserWithCaching.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import Parser = require('web-tree-sitter');6import { DisposablesLRUCache } from '../../../util/common/cache';7import { IDisposable } from '../../../util/vs/base/common/lifecycle';8import { LanguageLoader } from './languageLoader';9import { WASMLanguage } from './treeSitterLanguages';1011export class ParserWithCaching implements IDisposable {1213public static INSTANCE = new ParserWithCaching();1415static CACHE_SIZE_PER_LANGUAGE = 5;1617private readonly caches: Map<WASMLanguage, DisposablesLRUCache<CacheableParseTree>>;18private readonly languageLoader: LanguageLoader;19private _parser: Parser | null;2021constructor() {22this.caches = new Map<WASMLanguage, DisposablesLRUCache<CacheableParseTree>>();23this.languageLoader = new LanguageLoader();24this._parser = null;25}2627/** @remarks must not be called before `Parser.init()` */28private get parser() {29if (!this._parser) {30this._parser = new Parser();31}32return this._parser;33}3435/**36* @remarks Do not `delete()` the returned parse tree manually.37*/38async parse(lang: WASMLanguage, source: string): Promise<ParseTreeReference> {3940await Parser.init();4142const cache = this.getParseTreeCache(lang);4344let cacheEntry = cache.get(source);45if (cacheEntry) {46return cacheEntry.createReference();47}4849const parserLang = await this.languageLoader.loadLanguage(lang);50this.parser.setLanguage(parserLang);5152// check again the cache, maybe someone else has already parsed the source during the await53cacheEntry = cache.get(source);54if (cacheEntry) {55return cacheEntry.createReference();56}5758const parseTree = this.parser.parse(source);59cacheEntry = new CacheableParseTree(parseTree);60cache.put(source, cacheEntry);6162return cacheEntry.createReference();63}6465dispose() {66if (this._parser) {67this.parser.delete();68this._parser = null;69}70for (const cache of this.caches.values()) {71cache.dispose();72}73}7475private getParseTreeCache(lang: WASMLanguage) {76let cache = this.caches.get(lang);77if (!cache) {78cache = new DisposablesLRUCache<CacheableParseTree>(ParserWithCaching.CACHE_SIZE_PER_LANGUAGE);79this.caches.set(lang, cache);80}81return cache;82}83}8485/**86* A parse tree that can be cached (i.e. it can be referenced multiple87* times and will be disppsed when it is evicted from cache and all88* references to it are also disposed.89*/90class CacheableParseTree implements IDisposable {9192private readonly _tree: RefCountedParseTree;9394constructor(tree: Parser.Tree) {95this._tree = new RefCountedParseTree(tree);96}9798dispose(): void {99this._tree.deref();100}101102createReference(): ParseTreeReference {103return new ParseTreeReference(this._tree);104}105}106107/**108* A reference to a parse tree.109* You must call `dispose()` when you're done with it.110*/111export class ParseTreeReference implements IDisposable {112113public get tree() {114return this._parseTree.tree;115}116117constructor(118private readonly _parseTree: RefCountedParseTree119) {120this._parseTree.ref();121}122123dispose(): void {124this._parseTree.deref();125}126}127128/**129* Will dispose the referenced parse tree when the ref count reaches 0.130* The ref count is initialized to 1.131*/132class RefCountedParseTree {133134private _refCount = 1;135136public get tree(): Parser.Tree {137if (this._refCount === 0) {138throw new Error(`Cannot access disposed RefCountedParseTree`);139}140return this._tree;141}142143constructor(144private readonly _tree: Parser.Tree145) { }146147ref(): void {148if (this._refCount === 0) {149throw new Error(`Cannot ref disposed RefCountedParseTree`);150}151this._refCount++;152}153154deref(): void {155if (this._refCount === 0) {156throw new Error(`Cannot deref disposed RefCountedParseTree`);157}158this._refCount--;159if (this._refCount === 0) {160this._tree.delete();161}162}163}164165export function _dispose() {166ParserWithCaching.INSTANCE.dispose();167}168169/**170* Parses the given source code and returns the root node of the resulting syntax tree.171*/172export function _parse(language: WASMLanguage, source: string): Promise<ParseTreeReference> {173return ParserWithCaching.INSTANCE.parse(language, source);174}175176177