refactor: Go to defination

This commit is contained in:
全卓 2022-11-15 14:37:44 +08:00
parent 95ccbd604d
commit 56db02cc7d
5 changed files with 304 additions and 24 deletions

View File

@ -264,6 +264,10 @@ connection.onDefinition((params: DefinitionParams) => {
const uri: string = params.textDocument.uri;
const tree = getFileContext(uri);
const definationListener = new DefinationListener(uri, topScope);
// clear refToDef and topScope first
refToDef.clear();
topScope.clear();
antlr4.tree.ParseTreeWalker.DEFAULT.walk(definationListener, tree);
const document = documents.get(uri);

View File

@ -0,0 +1,215 @@
import Token from "../parser/antlr4/Token";
import CMakeListener from "../parser/CMakeListener";
import CMakeParser from "../parser/CMakeParser";
import { DefinationListener, refToDef } from "./goToDefination";
import { FileScope, FunctionScope, MacroScope, Scope } from "./scope";
import { Sym, Type } from "./symbol";
import { getFileContext, getIncludeFileUri, getSubCMakeListsUri } from "../utils";
import antlr4 from "../parser/antlr4";
import ParseTree from "../parser/antlr4/tree/ParseTree";
export class FuncMacroListener extends CMakeListener {
private currentScope: Scope;
private funcMacroSym: Sym;
private inBody: boolean = false;
private parseTreeProperty = new Map<ParseTree, boolean>();
constructor(parent: Scope, symbol: Sym) {
super();
if (symbol.getType() === Type.Function) {
this.currentScope = new FunctionScope(parent);
} else {
this.currentScope = new MacroScope(parent);
}
this.funcMacroSym = symbol;
}
enterFunctionCmd(ctx: any): void {
const funcToken: Token = ctx.argument(0).start;
// token.line start from 1
if (funcToken.line - 1 === this.funcMacroSym.getLine() &&
funcToken.column === this.funcMacroSym.getColumn()) {
// we now enter the desired function
this.inBody = true;
}
}
exitEndFunctionCmd(ctx: any): void {
this.inBody = false;
}
enterMacroCmd(ctx: any): void {
this.enterFunctionCmd(ctx);
}
exitEndMacroCmd(ctx: any): void {
this.exitEndFunctionCmd(ctx);
}
enterSetCmd(ctx: any): void {
if (!this.inBody) {
return;
}
// create a variable symbol
const varToken: Token = ctx.argument(0).start;
const varSymbol: Sym = new Sym(varToken.text, Type.Variable,
this.funcMacroSym.getUri(), varToken.line - 1, varToken.column);
if (this.funcMacroSym.getType() === Type.Function) {
// define variable in function, add variable to function scope
this.currentScope.define(varSymbol);
} else {
// define variable in macro, add to parent scope
this.currentScope.getEnclosingScope().define(varSymbol);
}
}
enterOptionCmd(ctx: any): void {
this.enterSetCmd(ctx);
}
enterIncludeCmd(ctx: any): void {
if (!this.inBody) {
return;
}
const nameToken = ctx.argument(0).start;
const fileUri: string = getIncludeFileUri(this.funcMacroSym.getUri(), nameToken.text);
if (!fileUri) {
return;
}
// add included module to refDef
const refPos: string = this.funcMacroSym.getUri() + '_' + (nameToken.line - 1) + '_' +
nameToken.column + '_' + nameToken.text;
refToDef.set(refPos, {
uri: fileUri,
range: {
start: {
line: 0,
character: 0
},
end: {
line: Number.MAX_VALUE,
character: Number.MAX_VALUE
}
}
});
const tree = getFileContext(fileUri);
const definationListener = new DefinationListener(fileUri, this.currentScope);
antlr4.tree.ParseTreeWalker.DEFAULT.walk(definationListener, tree);
}
enterAddSubDirCmd(ctx: any): void {
this.parseTreeProperty.set(ctx, false);
if (!this.inBody) {
return;
}
const dirToken: Token = ctx.argument(0).start;
const fileUri: string = getSubCMakeListsUri(this.funcMacroSym.getUri(), dirToken.text);
if (!fileUri) {
return;
}
this.parseTreeProperty.set(ctx, true);
// add subdir CMakeLists.txt to refDef
const refPos: string = this.funcMacroSym.getUri() + '_' + (dirToken.line - 1) + '_' +
dirToken.column + '_' + dirToken.text;
refToDef.set(refPos, {
uri: fileUri,
range: {
start: {
line: 0,
character: 0
},
end: {
line: Number.MAX_VALUE,
character: Number.MAX_VALUE
}
}
});
const tree = getFileContext(fileUri);
const subDirScope: Scope = new FileScope(this.currentScope);
this.currentScope = subDirScope;
const definationListener = new DefinationListener(fileUri, subDirScope);
antlr4.tree.ParseTreeWalker.DEFAULT.walk(definationListener, tree);
}
exitAddSubDirCmd(ctx: any): void {
if (!this.inBody) {
return;
}
if (this.parseTreeProperty.get(ctx)) {
this.currentScope = this.currentScope.getEnclosingScope();
}
}
enterOtherCmd(ctx: any): void {
if (!this.inBody) {
return;
}
// command reference, resolve the defination
const cmdToken: Token = ctx.ID().symbol;
const symbol: Sym = this.currentScope.resolve(cmdToken.text, Type.Function);
if (symbol === null) {
return;
}
// token.line start from 1, so -1 first
const refPos: string = this.funcMacroSym.getUri() + '_' + (cmdToken.line - 1) + '_' +
cmdToken.column + '_' + cmdToken.text;
// add to refToDef
refToDef.set(refPos, symbol.getLocation());
}
enterArgument(ctx: any): void {
if (!this.inBody) {
return;
}
if (ctx.parent instanceof CMakeParser.FunctionCmdContext ||
ctx.parent instanceof CMakeParser.MacroCmdContext) {
if (ctx.getChildCount() !== 1) {
return;
}
// add function/macro argument to current scope
const token = ctx.start;
const varSymbol: Sym = new Sym(token.text, Type.Variable,
this.funcMacroSym.getUri(), token.line - 1, token.column);
this.currentScope.define(varSymbol);
// just return after parse function/macro formal parameter
return;
}
// find all variable reference, resolve the defination, add to refToDef
const argToken: Token = ctx.start;
const regexp: RegExp = /\${(.*?)}/g;
const matches = argToken.text.matchAll(regexp);
for (let match of matches) {
const varRef: string = match[1];
const symbol: Sym = this.currentScope.resolve(varRef, Type.Variable);
if (symbol === null) {
continue;
}
// token.line start from 1, so - 1 first
const refPos: string = this.funcMacroSym.getUri() + '_' + (argToken.line - 1) + '_' +
(argToken.column + match.index + 2) + '_' + varRef;
refToDef.set(refPos, symbol.getLocation());
}
}
}

View File

@ -1,13 +1,13 @@
import * as path from "path";
import { Location } from "vscode-languageserver-types";
import antlr4 from '../parser/antlr4/index.js';
import Token from "../parser/antlr4/Token";
import ParseTree from "../parser/antlr4/tree/ParseTree.js";
import CMakeListener from "../parser/CMakeListener";
import { getFileContext, getIncludeFileUri, getSubCMakeListsUri } from "../utils";
import { FuncMacroListener } from "./function";
import { FileScope, FunctionScope, Scope } from "./scope";
import { Sym, Type } from "./symbol";
export const definations: Map<string, Location> = new Map();
export const topScope: FileScope = new FileScope(null);
/**
@ -20,21 +20,19 @@ export const refToDef: Map<string, Location> = new Map();
export class DefinationListener extends CMakeListener {
private currentScope: Scope;
private inFunction = false;
private inBody = false;
private uri: string;
private parseTreeProperty = new Map<ParseTree, boolean>();
constructor(uri: string, scope: Scope) {
super();
this.uri = uri;
this.currentScope = scope;
}
enterFile(ctx: any): void {
}
enterFunctionCmd(ctx: any): void {
this.inFunction = true;
this.inBody = true;
// create a function symbol
const funcToken: Token = ctx.argument(0).start;
@ -43,28 +41,33 @@ export class DefinationListener extends CMakeListener {
// add to current scope
this.currentScope.define(funcSymbol);
// create a new function scope
const funcScope: Scope = new FunctionScope(this.currentScope);
this.currentScope = funcScope;
// add all remain arguments to function scope
ctx.argument().slice(1).forEach(element => {
const argToken = element.start;
const varSymbol: Sym = new Sym(argToken.text, Type.Variable,
this.uri, argToken.line - 1, argToken.column);
this.currentScope.define(varSymbol);
});
}
exitEndFunctionCmd(ctx: any): void {
this.inFunction = false;
this.inBody = false;
}
// restore the parent scope
this.currentScope = this.currentScope.getEnclosingScope();
enterMacroCmd(ctx: any): void {
this.inBody = true;
// create a macro symbol
const macroToken: Token = ctx.argument(0).start;
const macroSymbol: Sym = new Sym(macroToken.text, Type.Macro,
this.uri, macroToken.line - 1, macroToken.column);
// add macro to this scope
this.currentScope.define(macroSymbol);
}
exitEndMacroCmd(ctx: any): void {
this.inBody = false;
}
enterSetCmd(ctx: any): void {
if (this.inBody) {
return;
}
// create a variable symbol
const varToken: Token = ctx.argument(0).start;
const varSymbol: Sym = new Sym(varToken.text, Type.Variable,
@ -79,6 +82,10 @@ export class DefinationListener extends CMakeListener {
}
enterIncludeCmd(ctx: any): void {
if (this.inBody) {
return;
}
const nameToken = ctx.argument(0).start;
const fileUri: string = getIncludeFileUri(this.uri, nameToken.text);
if (!fileUri) {
@ -108,12 +115,20 @@ export class DefinationListener extends CMakeListener {
}
enterAddSubDirCmd(ctx: any): void {
this.parseTreeProperty.set(ctx, false);
if (this.inBody) {
return;
}
const dirToken: Token = ctx.argument(0).start;
const fileUri: string = getSubCMakeListsUri(this.uri, dirToken.text);
if (!fileUri) {
return;
}
this.parseTreeProperty.set(ctx, true);
// add subdir CMakeLists.txt to refDef
const refPos: string = this.uri + '_' + (dirToken.line - 1) + '_' +
dirToken.column + '_' + dirToken.text;
@ -139,25 +154,47 @@ export class DefinationListener extends CMakeListener {
}
exitAddSubDirCmd(ctx: any): void {
this.currentScope = this.currentScope.getEnclosingScope();
if (this.inBody) {
return;
}
if (this.parseTreeProperty.get(ctx)) {
this.currentScope = this.currentScope.getEnclosingScope();
}
}
enterOtherCmd(ctx: any): void {
if (this.inBody) {
return;
}
// command reference, resolve the defination
const cmdToken: Token = ctx.ID().symbol;
const symbol: Sym = this.currentScope.resolve(cmdToken.text, Type.Function);
if (symbol === null) {
return;
}
// token.line start from 1, so - 1 first
const refPos: string = this.uri + '_' + (cmdToken.line - 1) + '_' +
cmdToken.column + '_' + cmdToken.text;
// add to refToDef
refToDef.set(refPos, symbol.getLocation());
// parse the function body
const tree = getFileContext(symbol.getUri());
const functionListener = new FuncMacroListener(this.currentScope, symbol);
antlr4.tree.ParseTreeWalker.DEFAULT.walk(functionListener, tree);
}
enterArgument(ctx: any): void {
// If we are in function/macro body, just return
// parse function/macro content just delay to reference
if (this.inBody) {
return;
}
const count: number = ctx.getChildCount();
if (count !== 1) {
return;

View File

@ -28,6 +28,7 @@ export class Scope {
if (symbol.getType() === Type.Variable) {
this.variables.set(symbol.getName(), symbol);
} else {
// Functions and Macros all saved into 'commands'
this.commands.set(symbol.getName(), symbol);
}
@ -37,6 +38,11 @@ export class Scope {
getEnclosingScope(): Scope {
return this.enclosingScope;
}
clear(): void {
this.variables.clear();
this.commands.clear();
}
}
export class FileScope extends Scope {
@ -50,3 +56,9 @@ export class FunctionScope extends Scope {
super(enclosingScope);
}
}
export class MacroScope extends Scope {
constructor(enclosingScope: Scope) {
super(enclosingScope);
}
}

View File

@ -53,4 +53,16 @@ export class Sym {
}
};
}
getUri(): string {
return this.uri;
}
getLine(): number {
return this.line;
}
getColumn(): number {
return this.column;
}
}