@imc-trading/svlangserver
Version:
A language server for systemverilog
187 lines (186 loc) • 7.67 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VerilatorDiagnostics = void 0;
const vscode_languageserver_1 = require("vscode-languageserver");
const genutils_1 = require("./genutils");
const child = require("child_process");
const path = require('path');
class VerilatorDiagnostics {
constructor(indexer) {
this._command = "";
this._defines = [];
this._optionsFile = "";
this._alreadyRunning = new Map();
this._fileWaiting = new Map();
this._freeTmpFileNums = [];
this._totalTmpFileNums = 0;
this._indexer = indexer;
this._tmpDir = genutils_1.getTmpDirSync();
}
setCommand(cmd) {
this._command = cmd;
}
setOptionsFile(file) {
this._optionsFile = file;
}
setDefines(defines) {
this._defines = defines || [];
}
_getFreeTmpFileNum() {
if (this._freeTmpFileNums.length <= 0) {
this._freeTmpFileNums.push(this._totalTmpFileNums++);
}
return this._freeTmpFileNums.shift();
}
_lintImmediate(file, text) {
let _kill = () => {
let [proc, statusRef] = this._alreadyRunning.get(file);
statusRef[0] = false;
proc.kill();
};
if (this._alreadyRunning.has(file)) {
//ConnectionLogger.log(`DEBUG: Killing already running command to start a new one`);
_kill();
}
return new Promise((resolve) => {
let actFile = text == undefined ? file : path.join(this._tmpDir.name, "sources", file);
let optionsFile = this._optionsFile;
let vcTmpFileNum;
if (text != undefined) {
genutils_1.fsWriteFileSync(actFile, text);
// if file write takes too long and another process started in the interim
if (this._alreadyRunning.has(file)) {
//ConnectionLogger.log(`DEBUG: Killing already running command to start a new one`);
_kill();
}
if (this._indexer.fileHasPkg(file)) {
let vcFileContent = this._indexer.getOptionsFileContent()
.map(line => { return (line == file) ? actFile : line; })
.join('\n');
vcTmpFileNum = this._getFreeTmpFileNum();
let tmpVcFile = path.join(this._tmpDir.name, "vcfiles", `lint${vcTmpFileNum}.vc`);
genutils_1.fsWriteFileSync(tmpVcFile, vcFileContent);
optionsFile = tmpVcFile;
// if file write takes too long and another process started in the interim
if (this._alreadyRunning.has(file)) {
//ConnectionLogger.log(`DEBUG: Killing already running command to start a new one`);
_kill();
}
}
}
let definesArg = this._defines.length > 0 ? this._defines.map(d => ` +define+${d}`).join('') : "";
let optionsFileArg = optionsFile ? ' -f ' + optionsFile : "";
let actFileArg = (this._indexer.fileHasPkg(file)) ? "" : " " + actFile;
let command = this._command + definesArg + optionsFileArg + actFileArg;
let statusRef = [true];
//ConnectionLogger.log(`DEBUG: verilator command ${command}`);
this._alreadyRunning.set(file, [
child.exec(command, (error, stdout, stderr) => {
if (optionsFile != this._optionsFile) {
this._freeTmpFileNums.push(vcTmpFileNum);
}
if (statusRef[0]) {
this._alreadyRunning.delete(file);
resolve(this._parseDiagnostics(error, stdout, stderr, actFile));
}
else {
resolve([]);
}
}),
statusRef
]);
});
}
lint(file, text) {
try {
if (text == undefined) {
return this._lintImmediate(file, text)
.catch(error => {
genutils_1.ConnectionLogger.error(error);
return [];
});
}
else {
if (this._fileWaiting.has(file)) {
let [waitTimer, resolver] = this._fileWaiting.get(file);
clearTimeout(waitTimer);
resolver(false);
}
return new Promise(resolve => {
this._fileWaiting.set(file, [setTimeout(resolve, 1000, true), resolve]);
}).then((success) => {
if (!!success) {
this._fileWaiting.delete(file);
return this._lintImmediate(file, text);
}
return [];
}).catch(error => {
genutils_1.ConnectionLogger.error(error);
return [];
});
}
}
catch (error) {
genutils_1.ConnectionLogger.error(error);
return Promise.resolve([]);
}
}
_parseDiagnostics(error, stdout, stderr, file) {
let diagnostics = [];
let lines = stderr.split(/\r?\n/g);
// RegExp expression for matching Verilator messages
// Group 1: Severity
// Group 2: Type (optional)
// Group 3: Filename
// Group 4: Line number
// Group 5: Column number (optional)
// Group 6: Message
let regex = new RegExp(String.raw `%(Error|Warning)(-[A-Z0-9_]+)?: (` + file.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') + String.raw `):(\d+):(?:(\d+):)? (.*)`, 'i');
// Parse output lines
lines.forEach((line, i) => {
let terms = line.match(regex);
if (terms != null) {
let severity = this._getSeverity(terms[1]);
let message = "";
let lineNum = parseInt(terms[4]) - 1;
let colNum = 0;
if (terms[5]) {
colNum = parseInt(terms[5]) - 1;
}
message = terms[6];
for (let whitelistedMessage of VerilatorDiagnostics._whitelistedMessages) {
if (message.search(whitelistedMessage) >= 0) {
return;
}
}
if ((lineNum != NaN) && (colNum != NaN)) {
diagnostics.push({
severity: severity,
range: vscode_languageserver_1.Range.create(lineNum, colNum, lineNum, Number.MAX_VALUE),
message: message,
code: 'verilator',
source: 'verilator'
});
}
}
});
return diagnostics;
}
_getSeverity(severityString) {
let result = vscode_languageserver_1.DiagnosticSeverity.Information;
if (severityString.startsWith('Error')) {
result = vscode_languageserver_1.DiagnosticSeverity.Error;
}
else if (severityString.startsWith('Warning')) {
result = vscode_languageserver_1.DiagnosticSeverity.Warning;
}
return result;
}
cleanupTmpFiles() {
this._tmpDir.removeCallback();
}
}
exports.VerilatorDiagnostics = VerilatorDiagnostics;
VerilatorDiagnostics._whitelistedMessages = [
/Unsupported: Interfaced port on top level module/i
];