diff --git a/server/src/server.ts b/server/src/server.ts index 7ffbf33..7828e9c 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -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); diff --git a/server/src/symbolTable/function.ts b/server/src/symbolTable/function.ts new file mode 100644 index 0000000..3fc2774 --- /dev/null +++ b/server/src/symbolTable/function.ts @@ -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(); + + 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()); + } + } +} \ No newline at end of file diff --git a/server/src/symbolTable/goToDefination.ts b/server/src/symbolTable/goToDefination.ts index c6c26b2..4f08c18 100644 --- a/server/src/symbolTable/goToDefination.ts +++ b/server/src/symbolTable/goToDefination.ts @@ -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 = new Map(); export const topScope: FileScope = new FileScope(null); /** @@ -20,21 +20,19 @@ export const refToDef: Map = new Map(); export class DefinationListener extends CMakeListener { private currentScope: Scope; - private inFunction = false; + private inBody = false; private uri: string; + private parseTreeProperty = new Map(); + 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; diff --git a/server/src/symbolTable/scope.ts b/server/src/symbolTable/scope.ts index 9e30f91..2f3e77b 100644 --- a/server/src/symbolTable/scope.ts +++ b/server/src/symbolTable/scope.ts @@ -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); + } +} diff --git a/server/src/symbolTable/symbol.ts b/server/src/symbolTable/symbol.ts index 76b5812..69f9cd6 100644 --- a/server/src/symbolTable/symbol.ts +++ b/server/src/symbolTable/symbol.ts @@ -53,4 +53,16 @@ export class Sym { } }; } + + getUri(): string { + return this.uri; + } + + getLine(): number { + return this.line; + } + + getColumn(): number { + return this.column; + } }