Path: blob/main/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/parser.ts
3296 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 { AstNode, AstNodeKind, BracketAstNode, InvalidBracketAstNode, ListAstNode, PairAstNode, TextAstNode } from './ast.js';6import { BeforeEditPositionMapper, TextEditInfo } from './beforeEditPositionMapper.js';7import { SmallImmutableSet } from './smallImmutableSet.js';8import { lengthIsZero, lengthLessThan } from './length.js';9import { concat23Trees, concat23TreesOfSameHeight } from './concat23Trees.js';10import { NodeReader } from './nodeReader.js';11import { OpeningBracketId, Tokenizer, TokenKind } from './tokenizer.js';1213/**14* Non incrementally built ASTs are immutable.15*/16export function parseDocument(tokenizer: Tokenizer, edits: TextEditInfo[], oldNode: AstNode | undefined, createImmutableLists: boolean): AstNode {17const parser = new Parser(tokenizer, edits, oldNode, createImmutableLists);18return parser.parseDocument();19}2021/**22* Non incrementally built ASTs are immutable.23*/24class Parser {25private readonly oldNodeReader?: NodeReader;26private readonly positionMapper: BeforeEditPositionMapper;27private _itemsConstructed: number = 0;28private _itemsFromCache: number = 0;2930/**31* Reports how many nodes were constructed in the last parse operation.32*/33get nodesConstructed() {34return this._itemsConstructed;35}3637/**38* Reports how many nodes were reused in the last parse operation.39*/40get nodesReused() {41return this._itemsFromCache;42}4344constructor(45private readonly tokenizer: Tokenizer,46edits: TextEditInfo[],47oldNode: AstNode | undefined,48private readonly createImmutableLists: boolean,49) {50if (oldNode && createImmutableLists) {51throw new Error('Not supported');52}5354this.oldNodeReader = oldNode ? new NodeReader(oldNode) : undefined;55this.positionMapper = new BeforeEditPositionMapper(edits);56}5758parseDocument(): AstNode {59this._itemsConstructed = 0;60this._itemsFromCache = 0;6162let result = this.parseList(SmallImmutableSet.getEmpty(), 0);63if (!result) {64result = ListAstNode.getEmpty();65}6667return result;68}6970private parseList(71openedBracketIds: SmallImmutableSet<OpeningBracketId>,72level: number,73): AstNode | null {74const items: AstNode[] = [];7576while (true) {77let child = this.tryReadChildFromCache(openedBracketIds);7879if (!child) {80const token = this.tokenizer.peek();81if (82!token ||83(token.kind === TokenKind.ClosingBracket &&84token.bracketIds.intersects(openedBracketIds))85) {86break;87}8889child = this.parseChild(openedBracketIds, level + 1);90}9192if (child.kind === AstNodeKind.List && child.childrenLength === 0) {93continue;94}9596items.push(child);97}9899// When there is no oldNodeReader, all items are created from scratch and must have the same height.100const result = this.oldNodeReader ? concat23Trees(items) : concat23TreesOfSameHeight(items, this.createImmutableLists);101return result;102}103104private tryReadChildFromCache(openedBracketIds: SmallImmutableSet<number>): AstNode | undefined {105if (this.oldNodeReader) {106const maxCacheableLength = this.positionMapper.getDistanceToNextChange(this.tokenizer.offset);107if (maxCacheableLength === null || !lengthIsZero(maxCacheableLength)) {108const cachedNode = this.oldNodeReader.readLongestNodeAt(this.positionMapper.getOffsetBeforeChange(this.tokenizer.offset), curNode => {109// The edit could extend the ending token, thus we cannot re-use nodes that touch the edit.110// If there is no edit anymore, we can re-use the node in any case.111if (maxCacheableLength !== null && !lengthLessThan(curNode.length, maxCacheableLength)) {112// Either the node contains edited text or touches edited text.113// In the latter case, brackets might have been extended (`end` -> `ending`), so even touching nodes cannot be reused.114return false;115}116const canBeReused = curNode.canBeReused(openedBracketIds);117return canBeReused;118});119120if (cachedNode) {121this._itemsFromCache++;122this.tokenizer.skip(cachedNode.length);123return cachedNode;124}125}126}127return undefined;128}129130private parseChild(131openedBracketIds: SmallImmutableSet<number>,132level: number,133): AstNode {134this._itemsConstructed++;135136const token = this.tokenizer.read()!;137138switch (token.kind) {139case TokenKind.ClosingBracket:140return new InvalidBracketAstNode(token.bracketIds, token.length);141142case TokenKind.Text:143return token.astNode as TextAstNode;144145case TokenKind.OpeningBracket: {146if (level > 300) {147// To prevent stack overflows148return new TextAstNode(token.length);149}150151const set = openedBracketIds.merge(token.bracketIds);152const child = this.parseList(set, level + 1);153154const nextToken = this.tokenizer.peek();155if (156nextToken &&157nextToken.kind === TokenKind.ClosingBracket &&158(nextToken.bracketId === token.bracketId || nextToken.bracketIds.intersects(token.bracketIds))159) {160this.tokenizer.read();161return PairAstNode.create(162token.astNode as BracketAstNode,163child,164nextToken.astNode as BracketAstNode165);166} else {167return PairAstNode.create(168token.astNode as BracketAstNode,169child,170null171);172}173}174default:175throw new Error('unexpected');176}177}178}179180181