Path: blob/main/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/ast.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 { BugIndicatingError } from '../../../../../base/common/errors.js';6import { CursorColumns } from '../../../core/cursorColumns.js';7import { BracketKind } from '../../../languages/supports/languageBracketsConfiguration.js';8import { ITextModel } from '../../../model.js';9import { Length, lengthAdd, lengthGetLineCount, lengthToObj, lengthZero } from './length.js';10import { SmallImmutableSet } from './smallImmutableSet.js';11import { OpeningBracketId } from './tokenizer.js';1213export const enum AstNodeKind {14Text = 0,15Bracket = 1,16Pair = 2,17UnexpectedClosingBracket = 3,18List = 4,19}2021export type AstNode = PairAstNode | ListAstNode | BracketAstNode | InvalidBracketAstNode | TextAstNode;2223/**24* The base implementation for all AST nodes.25*/26abstract class BaseAstNode {27public abstract readonly kind: AstNodeKind;2829public abstract readonly childrenLength: number;3031/**32* Might return null even if {@link idx} is smaller than {@link BaseAstNode.childrenLength}.33*/34public abstract getChild(idx: number): AstNode | null;3536/**37* Try to avoid using this property, as implementations might need to allocate the resulting array.38*/39public abstract readonly children: readonly AstNode[];4041/**42* Represents the set of all (potentially) missing opening bracket ids in this node.43* E.g. in `{ ] ) }` that set is {`[`, `(` }.44*/45public abstract readonly missingOpeningBracketIds: SmallImmutableSet<OpeningBracketId>;4647/**48* In case of a list, determines the height of the (2,3) tree.49*/50public abstract readonly listHeight: number;5152protected _length: Length;5354/**55* The length of the entire node, which should equal the sum of lengths of all children.56*/57public get length(): Length {58return this._length;59}6061public constructor(length: Length) {62this._length = length;63}6465/**66* @param openBracketIds The set of all opening brackets that have not yet been closed.67*/68public abstract canBeReused(69openBracketIds: SmallImmutableSet<OpeningBracketId>70): boolean;7172/**73* Flattens all lists in this AST. Only for debugging.74*/75public abstract flattenLists(): AstNode;7677/**78* Creates a deep clone.79*/80public abstract deepClone(): AstNode;8182public abstract computeMinIndentation(offset: Length, textModel: ITextModel): number;83}8485/**86* Represents a bracket pair including its child (e.g. `{ ... }`).87* Might be unclosed.88* Immutable, if all children are immutable.89*/90export class PairAstNode extends BaseAstNode {91public static create(92openingBracket: BracketAstNode,93child: AstNode | null,94closingBracket: BracketAstNode | null95) {96let length = openingBracket.length;97if (child) {98length = lengthAdd(length, child.length);99}100if (closingBracket) {101length = lengthAdd(length, closingBracket.length);102}103return new PairAstNode(length, openingBracket, child, closingBracket, child ? child.missingOpeningBracketIds : SmallImmutableSet.getEmpty());104}105106public get kind(): AstNodeKind.Pair {107return AstNodeKind.Pair;108}109public get listHeight() {110return 0;111}112public get childrenLength(): number {113return 3;114}115public getChild(idx: number): AstNode | null {116switch (idx) {117case 0: return this.openingBracket;118case 1: return this.child;119case 2: return this.closingBracket;120}121throw new Error('Invalid child index');122}123124/**125* Avoid using this property, it allocates an array!126*/127public get children() {128const result: AstNode[] = [];129result.push(this.openingBracket);130if (this.child) {131result.push(this.child);132}133if (this.closingBracket) {134result.push(this.closingBracket);135}136return result;137}138139private constructor(140length: Length,141public readonly openingBracket: BracketAstNode,142public readonly child: AstNode | null,143public readonly closingBracket: BracketAstNode | null,144public readonly missingOpeningBracketIds: SmallImmutableSet<OpeningBracketId>145) {146super(length);147}148149public canBeReused(openBracketIds: SmallImmutableSet<OpeningBracketId>) {150if (this.closingBracket === null) {151// Unclosed pair ast nodes only152// end at the end of the document153// or when a parent node is closed.154155// This could be improved:156// Only return false if some next token is neither "undefined" nor a bracket that closes a parent.157158return false;159}160161if (openBracketIds.intersects(this.missingOpeningBracketIds)) {162return false;163}164165return true;166}167168public flattenLists(): PairAstNode {169return PairAstNode.create(170this.openingBracket.flattenLists(),171this.child && this.child.flattenLists(),172this.closingBracket && this.closingBracket.flattenLists()173);174}175176public deepClone(): PairAstNode {177return new PairAstNode(178this.length,179this.openingBracket.deepClone(),180this.child && this.child.deepClone(),181this.closingBracket && this.closingBracket.deepClone(),182this.missingOpeningBracketIds183);184}185186public computeMinIndentation(offset: Length, textModel: ITextModel): number {187return this.child ? this.child.computeMinIndentation(lengthAdd(offset, this.openingBracket.length), textModel) : Number.MAX_SAFE_INTEGER;188}189}190191export abstract class ListAstNode extends BaseAstNode {192/**193* This method uses more memory-efficient list nodes that can only store 2 or 3 children.194*/195public static create23(item1: AstNode, item2: AstNode, item3: AstNode | null, immutable: boolean = false): ListAstNode {196let length = item1.length;197let missingBracketIds = item1.missingOpeningBracketIds;198199if (item1.listHeight !== item2.listHeight) {200throw new Error('Invalid list heights');201}202203length = lengthAdd(length, item2.length);204missingBracketIds = missingBracketIds.merge(item2.missingOpeningBracketIds);205206if (item3) {207if (item1.listHeight !== item3.listHeight) {208throw new Error('Invalid list heights');209}210length = lengthAdd(length, item3.length);211missingBracketIds = missingBracketIds.merge(item3.missingOpeningBracketIds);212}213return immutable214? new Immutable23ListAstNode(length, item1.listHeight + 1, item1, item2, item3, missingBracketIds)215: new TwoThreeListAstNode(length, item1.listHeight + 1, item1, item2, item3, missingBracketIds);216}217218public static create(items: AstNode[], immutable: boolean = false): ListAstNode {219if (items.length === 0) {220return this.getEmpty();221} else {222let length = items[0].length;223let unopenedBrackets = items[0].missingOpeningBracketIds;224for (let i = 1; i < items.length; i++) {225length = lengthAdd(length, items[i].length);226unopenedBrackets = unopenedBrackets.merge(items[i].missingOpeningBracketIds);227}228return immutable229? new ImmutableArrayListAstNode(length, items[0].listHeight + 1, items, unopenedBrackets)230: new ArrayListAstNode(length, items[0].listHeight + 1, items, unopenedBrackets);231}232}233234public static getEmpty() {235return new ImmutableArrayListAstNode(lengthZero, 0, [], SmallImmutableSet.getEmpty());236}237238public get kind(): AstNodeKind.List {239return AstNodeKind.List;240}241242public get missingOpeningBracketIds(): SmallImmutableSet<OpeningBracketId> {243return this._missingOpeningBracketIds;244}245246private cachedMinIndentation: number = -1;247248/**249* Use ListAstNode.create.250*/251constructor(252length: Length,253public readonly listHeight: number,254private _missingOpeningBracketIds: SmallImmutableSet<OpeningBracketId>255) {256super(length);257}258259protected throwIfImmutable(): void {260// NOOP261}262263protected abstract setChild(idx: number, child: AstNode): void;264265public makeLastElementMutable(): AstNode | undefined {266this.throwIfImmutable();267const childCount = this.childrenLength;268if (childCount === 0) {269return undefined;270}271const lastChild = this.getChild(childCount - 1)!;272const mutable = lastChild.kind === AstNodeKind.List ? lastChild.toMutable() : lastChild;273if (lastChild !== mutable) {274this.setChild(childCount - 1, mutable);275}276return mutable;277}278279public makeFirstElementMutable(): AstNode | undefined {280this.throwIfImmutable();281const childCount = this.childrenLength;282if (childCount === 0) {283return undefined;284}285const firstChild = this.getChild(0)!;286const mutable = firstChild.kind === AstNodeKind.List ? firstChild.toMutable() : firstChild;287if (firstChild !== mutable) {288this.setChild(0, mutable);289}290return mutable;291}292293public canBeReused(openBracketIds: SmallImmutableSet<OpeningBracketId>): boolean {294if (openBracketIds.intersects(this.missingOpeningBracketIds)) {295return false;296}297298if (this.childrenLength === 0) {299// Don't reuse empty lists.300return false;301}302303let lastChild: ListAstNode = this;304while (lastChild.kind === AstNodeKind.List) {305const lastLength = lastChild.childrenLength;306if (lastLength === 0) {307// Empty lists should never be contained in other lists.308throw new BugIndicatingError();309}310lastChild = lastChild.getChild(lastLength - 1) as ListAstNode;311}312313return lastChild.canBeReused(openBracketIds);314}315316public handleChildrenChanged(): void {317this.throwIfImmutable();318319const count = this.childrenLength;320321let length = this.getChild(0)!.length;322let unopenedBrackets = this.getChild(0)!.missingOpeningBracketIds;323324for (let i = 1; i < count; i++) {325const child = this.getChild(i)!;326length = lengthAdd(length, child.length);327unopenedBrackets = unopenedBrackets.merge(child.missingOpeningBracketIds);328}329330this._length = length;331this._missingOpeningBracketIds = unopenedBrackets;332this.cachedMinIndentation = -1;333}334335public flattenLists(): ListAstNode {336const items: AstNode[] = [];337for (const c of this.children) {338const normalized = c.flattenLists();339if (normalized.kind === AstNodeKind.List) {340items.push(...normalized.children);341} else {342items.push(normalized);343}344}345return ListAstNode.create(items);346}347348public computeMinIndentation(offset: Length, textModel: ITextModel): number {349if (this.cachedMinIndentation !== -1) {350return this.cachedMinIndentation;351}352353let minIndentation = Number.MAX_SAFE_INTEGER;354let childOffset = offset;355for (let i = 0; i < this.childrenLength; i++) {356const child = this.getChild(i);357if (child) {358minIndentation = Math.min(minIndentation, child.computeMinIndentation(childOffset, textModel));359childOffset = lengthAdd(childOffset, child.length);360}361}362363this.cachedMinIndentation = minIndentation;364return minIndentation;365}366367/**368* Creates a shallow clone that is mutable, or itself if it is already mutable.369*/370public abstract toMutable(): ListAstNode;371372public abstract appendChildOfSameHeight(node: AstNode): void;373public abstract unappendChild(): AstNode | undefined;374public abstract prependChildOfSameHeight(node: AstNode): void;375public abstract unprependChild(): AstNode | undefined;376}377378class TwoThreeListAstNode extends ListAstNode {379public get childrenLength(): number {380return this._item3 !== null ? 3 : 2;381}382public getChild(idx: number): AstNode | null {383switch (idx) {384case 0: return this._item1;385case 1: return this._item2;386case 2: return this._item3;387}388throw new Error('Invalid child index');389}390protected setChild(idx: number, node: AstNode): void {391switch (idx) {392case 0: this._item1 = node; return;393case 1: this._item2 = node; return;394case 2: this._item3 = node; return;395}396throw new Error('Invalid child index');397}398399public get children(): readonly AstNode[] {400return this._item3 ? [this._item1, this._item2, this._item3] : [this._item1, this._item2];401}402403public get item1(): AstNode {404return this._item1;405}406public get item2(): AstNode {407return this._item2;408}409public get item3(): AstNode | null {410return this._item3;411}412413public constructor(414length: Length,415listHeight: number,416private _item1: AstNode,417private _item2: AstNode,418private _item3: AstNode | null,419missingOpeningBracketIds: SmallImmutableSet<OpeningBracketId>420) {421super(length, listHeight, missingOpeningBracketIds);422}423424public deepClone(): ListAstNode {425return new TwoThreeListAstNode(426this.length,427this.listHeight,428this._item1.deepClone(),429this._item2.deepClone(),430this._item3 ? this._item3.deepClone() : null,431this.missingOpeningBracketIds432);433}434435public appendChildOfSameHeight(node: AstNode): void {436if (this._item3) {437throw new Error('Cannot append to a full (2,3) tree node');438}439this.throwIfImmutable();440this._item3 = node;441this.handleChildrenChanged();442}443444public unappendChild(): AstNode | undefined {445if (!this._item3) {446throw new Error('Cannot remove from a non-full (2,3) tree node');447}448this.throwIfImmutable();449const result = this._item3;450this._item3 = null;451this.handleChildrenChanged();452return result;453}454455public prependChildOfSameHeight(node: AstNode): void {456if (this._item3) {457throw new Error('Cannot prepend to a full (2,3) tree node');458}459this.throwIfImmutable();460this._item3 = this._item2;461this._item2 = this._item1;462this._item1 = node;463this.handleChildrenChanged();464}465466public unprependChild(): AstNode | undefined {467if (!this._item3) {468throw new Error('Cannot remove from a non-full (2,3) tree node');469}470this.throwIfImmutable();471const result = this._item1;472this._item1 = this._item2;473this._item2 = this._item3;474this._item3 = null;475476this.handleChildrenChanged();477return result;478}479480override toMutable(): ListAstNode {481return this;482}483}484485/**486* Immutable, if all children are immutable.487*/488class Immutable23ListAstNode extends TwoThreeListAstNode {489override toMutable(): ListAstNode {490return new TwoThreeListAstNode(this.length, this.listHeight, this.item1, this.item2, this.item3, this.missingOpeningBracketIds);491}492493protected override throwIfImmutable(): void {494throw new Error('this instance is immutable');495}496}497498/**499* For debugging.500*/501class ArrayListAstNode extends ListAstNode {502get childrenLength(): number {503return this._children.length;504}505getChild(idx: number): AstNode | null {506return this._children[idx];507}508protected setChild(idx: number, child: AstNode): void {509this._children[idx] = child;510}511get children(): readonly AstNode[] {512return this._children;513}514515constructor(516length: Length,517listHeight: number,518private readonly _children: AstNode[],519missingOpeningBracketIds: SmallImmutableSet<OpeningBracketId>520) {521super(length, listHeight, missingOpeningBracketIds);522}523524deepClone(): ListAstNode {525const children = new Array<AstNode>(this._children.length);526for (let i = 0; i < this._children.length; i++) {527children[i] = this._children[i].deepClone();528}529return new ArrayListAstNode(this.length, this.listHeight, children, this.missingOpeningBracketIds);530}531532public appendChildOfSameHeight(node: AstNode): void {533this.throwIfImmutable();534this._children.push(node);535this.handleChildrenChanged();536}537538public unappendChild(): AstNode | undefined {539this.throwIfImmutable();540const item = this._children.pop();541this.handleChildrenChanged();542return item;543}544545public prependChildOfSameHeight(node: AstNode): void {546this.throwIfImmutable();547this._children.unshift(node);548this.handleChildrenChanged();549}550551public unprependChild(): AstNode | undefined {552this.throwIfImmutable();553const item = this._children.shift();554this.handleChildrenChanged();555return item;556}557558public override toMutable(): ListAstNode {559return this;560}561}562563/**564* Immutable, if all children are immutable.565*/566class ImmutableArrayListAstNode extends ArrayListAstNode {567override toMutable(): ListAstNode {568return new ArrayListAstNode(this.length, this.listHeight, [...this.children], this.missingOpeningBracketIds);569}570571protected override throwIfImmutable(): void {572throw new Error('this instance is immutable');573}574}575576const emptyArray: readonly AstNode[] = [];577578abstract class ImmutableLeafAstNode extends BaseAstNode {579public get listHeight() {580return 0;581}582public get childrenLength(): number {583return 0;584}585public getChild(idx: number): AstNode | null {586return null;587}588public get children(): readonly AstNode[] {589return emptyArray;590}591592public flattenLists(): this & AstNode {593return this as this & AstNode;594}595public deepClone(): this & AstNode {596return this as this & AstNode;597}598}599600export class TextAstNode extends ImmutableLeafAstNode {601public get kind(): AstNodeKind.Text {602return AstNodeKind.Text;603}604public get missingOpeningBracketIds(): SmallImmutableSet<OpeningBracketId> {605return SmallImmutableSet.getEmpty();606}607608public canBeReused(_openedBracketIds: SmallImmutableSet<OpeningBracketId>) {609return true;610}611612public computeMinIndentation(offset: Length, textModel: ITextModel): number {613const start = lengthToObj(offset);614// Text ast nodes don't have partial indentation (ensured by the tokenizer).615// Thus, if this text node does not start at column 0, the first line cannot have any indentation at all.616const startLineNumber = (start.columnCount === 0 ? start.lineCount : start.lineCount + 1) + 1;617const endLineNumber = lengthGetLineCount(lengthAdd(offset, this.length)) + 1;618619let result = Number.MAX_SAFE_INTEGER;620621for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {622const firstNonWsColumn = textModel.getLineFirstNonWhitespaceColumn(lineNumber);623const lineContent = textModel.getLineContent(lineNumber);624if (firstNonWsColumn === 0) {625continue;626}627628const visibleColumn = CursorColumns.visibleColumnFromColumn(lineContent, firstNonWsColumn, textModel.getOptions().tabSize)!;629result = Math.min(result, visibleColumn);630}631632return result;633}634}635636export class BracketAstNode extends ImmutableLeafAstNode {637public static create(638length: Length,639bracketInfo: BracketKind,640bracketIds: SmallImmutableSet<OpeningBracketId>641): BracketAstNode {642const node = new BracketAstNode(length, bracketInfo, bracketIds);643return node;644}645646public get kind(): AstNodeKind.Bracket {647return AstNodeKind.Bracket;648}649650public get missingOpeningBracketIds(): SmallImmutableSet<OpeningBracketId> {651return SmallImmutableSet.getEmpty();652}653654private constructor(655length: Length,656public readonly bracketInfo: BracketKind,657/**658* In case of a opening bracket, this is the id of the opening bracket.659* In case of a closing bracket, this contains the ids of all opening brackets it can close.660*/661public readonly bracketIds: SmallImmutableSet<OpeningBracketId>662) {663super(length);664}665666public get text() {667return this.bracketInfo.bracketText;668}669670public get languageId() {671return this.bracketInfo.languageId;672}673674public canBeReused(_openedBracketIds: SmallImmutableSet<OpeningBracketId>) {675// These nodes could be reused,676// but not in a general way.677// Their parent may be reused.678return false;679}680681public computeMinIndentation(offset: Length, textModel: ITextModel): number {682return Number.MAX_SAFE_INTEGER;683}684}685686export class InvalidBracketAstNode extends ImmutableLeafAstNode {687public get kind(): AstNodeKind.UnexpectedClosingBracket {688return AstNodeKind.UnexpectedClosingBracket;689}690691public readonly missingOpeningBracketIds: SmallImmutableSet<OpeningBracketId>;692693public constructor(closingBrackets: SmallImmutableSet<OpeningBracketId>, length: Length) {694super(length);695this.missingOpeningBracketIds = closingBrackets;696}697698public canBeReused(openedBracketIds: SmallImmutableSet<OpeningBracketId>) {699return !openedBracketIds.intersects(this.missingOpeningBracketIds);700}701702public computeMinIndentation(offset: Length, textModel: ITextModel): number {703return Number.MAX_SAFE_INTEGER;704}705}706707708