import * as vscode from "vscode"; import * as fs from 'fs'; import { LspOutput, ReportType, opeParam } from "../../../global"; import { Path } from "../../../../resources/hdlParser"; import { hdlFile, hdlPath } from "../../../hdlFs"; import { easyExec } from "../../../global/util"; import { BaseLinter } from "./base"; import { HdlLangID } from "../../../global/enum"; class ModelsimLinter implements BaseLinter { diagnostic: vscode.DiagnosticCollection; executableFileMap: Map = new Map(); executableInvokeNameMap: Map = new Map(); linterArgsMap: Map = new Map(); constructor() { this.diagnostic = vscode.languages.createDiagnosticCollection(); // configure map for executable file name this.executableFileMap.set(HdlLangID.Verilog, 'vlog'); this.executableFileMap.set(HdlLangID.Vhdl, 'vcom'); this.executableFileMap.set(HdlLangID.SystemVerilog, 'vlog'); this.executableFileMap.set(HdlLangID.Unknown, undefined); // configure map for argruments when lintering this.linterArgsMap.set(HdlLangID.Verilog, ['-quiet', '-nologo']); this.linterArgsMap.set(HdlLangID.Vhdl, ['-quiet', '-nologo', '-2008']); this.linterArgsMap.set(HdlLangID.SystemVerilog, ['-quiet', '-nolog', '-sv']); this.linterArgsMap.set(HdlLangID.Unknown, []); } async lint(document: vscode.TextDocument) { const filePath = hdlPath.toSlash(document.fileName); const langID = hdlFile.getLanguageId(filePath); // acquire install path const linterArgs = this.linterArgsMap.get(langID); if (linterArgs === undefined) { return; } const args = [filePath, ...linterArgs]; const executor = this.executableInvokeNameMap.get(langID); if (executor !== undefined) { const { stdout } = await easyExec(executor, args); if (stdout.length > 0) { const diagnostics = this.provideDiagnostics(document, stdout); this.diagnostic.set(document.uri, diagnostics); } } else { LspOutput.report('modelsim linter is not available, please check prj.modelsim.install.path in your setting!', ReportType.Error, true); } } async remove(uri: vscode.Uri) { this.diagnostic.delete(uri); } /** * @param document * @param stdout stdout from xvlog * @returns { vscode.Diagnostic[] } linter info */ private provideDiagnostics(document: vscode.TextDocument, stdout: string): vscode.Diagnostic[] { const diagnostics = []; for (const line of stdout.split(/\r?\n/g)) { const tokens = line.split(/(Error|Warning).+?(?: *?(?:.+?(?:\\|\/))+.+?\((\d+?)\):|)(?: *?near "(.+?)":|)(?: *?\((.+?)\)|) +?(.+)/gm); const headerInfo = tokens[1]; if (headerInfo === 'Error') { const errorLine = parseInt(tokens[2]) - 1; const syntaxInfo = tokens[5]; LspOutput.report(` line: ${errorLine}, info: ${syntaxInfo}`, ReportType.Run); const range = this.makeCorrectRange(document, errorLine, syntaxInfo); const diag = new vscode.Diagnostic(range, syntaxInfo, vscode.DiagnosticSeverity.Error); diagnostics.push(diag); } else if (headerInfo === 'Warning') { const errorLine = parseInt(tokens[2]) - 1; const syntaxInfo = tokens[5]; LspOutput.report(` line: ${errorLine}, info: ${syntaxInfo}`, ReportType.Run); const range = this.makeCorrectRange(document, errorLine, syntaxInfo); const diag = new vscode.Diagnostic(range, syntaxInfo, vscode.DiagnosticSeverity.Warning); diagnostics.push(diag); } } return diagnostics; } private makeCorrectRange(document: vscode.TextDocument, line: number, syntaxInfo: string): vscode.Range { // extract all the words like 'adawwd' in a syntax info const singleQuoteWords = syntaxInfo.match(/'([^']*)'/g); if (singleQuoteWords && singleQuoteWords.length > 0) { const targetWord = singleQuoteWords.map(val => val.replace(/'/g, ''))[0]; // find range of target word const textLine = document.lineAt(line); const text = textLine.text; const startCharacter = text.indexOf(targetWord); if (startCharacter > -1) { const endCharacter = startCharacter + targetWord.length; const range = new vscode.Range( new vscode.Position(line, startCharacter), new vscode.Position(line, endCharacter) ); return range; } } // else target the first word in the line return this.makeCommonRange(document, line, syntaxInfo); } private makeCommonRange(document: vscode.TextDocument, line: number, syntaxInfo: string): vscode.Range { const startPosition = new vscode.Position(line, 0); const wordRange = document.getWordRangeAtPosition(startPosition, /[`_0-9a-zA-Z]+/); if (wordRange) { return wordRange; } else { return new vscode.Range(startPosition, startPosition); } } public getExecutableFilePath(langID: HdlLangID): string | Path | undefined { // modelsim install path stored in prj.modelsim.install.path const modelsimConfig = vscode.workspace.getConfiguration('digital-ide.prj.modelsim'); const modelsimInstallPath = modelsimConfig.get('install.path', ''); const executorName = this.executableFileMap.get(langID); if (executorName === undefined) { return undefined; } // e.g. vlog.exe in windows, vlog in linux const fullExecutorName = opeParam.os === 'win32' ? executorName + '.exe' : executorName; if (modelsimInstallPath.trim() === '' || !fs.existsSync(modelsimInstallPath)) { LspOutput.report(`User's modelsim Install Path "${modelsimInstallPath}", which is invalid. Use ${executorName} in default.`, ReportType.Warn); LspOutput.report('If you have doubts, check prj.modelsim.install.path in setting', ReportType.Warn); return executorName; } else { LspOutput.report(`User's modelsim Install Path "${modelsimInstallPath}", which is invalid`); const executorPath = hdlPath.join( hdlPath.toSlash(modelsimInstallPath), fullExecutorName ); // prevent path like C://stupid name/xxx/xxx/bin // blank space const safeExecutorPath = '"' + executorPath + '"'; return safeExecutorPath; } } public async setExecutableFilePath(executorPath: string | Path | undefined, langID: HdlLangID): Promise { if (executorPath === undefined) { return false; } const { stderr } = await easyExec(executorPath, []); if (stderr.length === 0) { this.executableInvokeNameMap.set(langID, executorPath); LspOutput.report(`success to verify ${executorPath}, linter from modelsim is ready to go!`, ReportType.Launch); return true; } else { this.executableInvokeNameMap.set(langID, undefined); LspOutput.report(`Fail to execute ${executorPath}! Reason: ${stderr}`, ReportType.Error, true); return false; } } public async initialise(langID: HdlLangID): Promise { const executorPath = this.getExecutableFilePath(langID); return this.setExecutableFilePath(executorPath, langID); } } const modelsimLinter = new ModelsimLinter(); export { modelsimLinter, ModelsimLinter };