Skip to content

Commit b5b9e16

Browse files
Improved error parsing when using verilator (#491)
* Improved error parsing when using verilator * Major: stderr passthrough Improved problem matcher regex. Now importaint sections are named Added better tagging for output blocks. Added changes to changelog Smaller stuff: changed container for messages to be a Map fixed regex that replaced slashes in windows paths removed old code
1 parent 416807d commit b5b9e16

File tree

2 files changed

+139
-19
lines changed

2 files changed

+139
-19
lines changed

CHANGELOG.md

+11-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,17 @@
22

33
All notable changes to this project will be documented in this file.
44

5-
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)\
6+
7+
## [Unreleased]
8+
9+
## Added
10+
- Verilator linter stderr passthrough [#489](https://github.com/mshr-h/vscode-verilog-hdl-support/issues/489)
11+
- When linting using Verilator, all detected problems are highlighted (By default it's just current file and it's dependencies. Verilator launch config can be adjusted in linting settings)
12+
13+
## Fixed
14+
- Imroved regex matcher for Verilator output
15+
- Verilator output blocks are correctly tagged with `[error]` or `[warning]`
616

717
## [1.14.2] - 2024-07-24
818

src/linter/VerilatorLinter.ts

+128-18
Original file line numberDiff line numberDiff line change
@@ -103,46 +103,156 @@ export default class VerilatorLinter extends BaseLinter {
103103
this.logger.info('[verilator] command: ' + command);
104104
this.logger.info('[verilator] cwd : ' + cwd);
105105

106+
107+
106108
var _: child.ChildProcess = child.exec(
107109
command,
108110
{ cwd: cwd },
109111
(_error: Error, _stdout: string, stderr: string) => {
110-
let diagnostics: vscode.Diagnostic[] = [];
111-
stderr.split(/\r?\n/g).forEach((line, _) => {
112-
if (line.search("No such file or directory") >= 0 || line.search("Not a directory") >= 0 || line.search("command not found") >= 0) {
113-
this.logger.error(`Could not execute command: ${command}`);
114-
return;
112+
113+
// basically DiagnosticsCollection but with ability to append diag lists
114+
let filesDiag = new Map();
115+
116+
stderr.split(/\r?\n/g).forEach((line, _, stderrLines) => {
117+
118+
119+
// if lineIndex is 0 and it doesn't start with %Error or %Warning,
120+
// the whole loop would skip
121+
// and it is probably a system error (wrong file name/directory/something)
122+
let lastDiagMessageType: string = "Error";
123+
124+
// parsing previous lines for message type
125+
// shouldn't be more than 5 or so
126+
for (let lineIndex = _; lineIndex >= 0; lineIndex--)
127+
{
128+
if (stderrLines[lineIndex].startsWith("%Error"))
129+
{
130+
lastDiagMessageType = "Error";
131+
break;
132+
}
133+
if (stderrLines[lineIndex].startsWith("%Warning"))
134+
{
135+
lastDiagMessageType = "Warning";
136+
break;
137+
}
115138
}
116139

117-
if (!line.startsWith('%') || line.indexOf(docUri) <= 0) {
140+
// first line would be normal stderr output like "directory name is invalid"
141+
// others are verilator sort of "highlighting" the issue, the block with "^~~~~"
142+
// this can actually be used for better error/warning highlighting
143+
144+
// also this might have some false positives
145+
// probably something like "stderr passthrough setting" would be a good idea
146+
if (!line.startsWith('%')) {
147+
148+
// allows for persistent
149+
if (lastDiagMessageType === 'Warning') { this.logger.warn(line); }
150+
else { this.logger.error(line); }
118151
return;
119152
}
120153

121-
let rex = line.match(
122-
/%(\w+)(-[A-Z0-9_]+)?:\s*(\w+:)?(?:[^:]+):\s*(\d+):(?:\s*(\d+):)?\s*(\s*.+)/
154+
155+
// important match sections are named now:
156+
// severity - Error or Warning
157+
// errorCode - error code, if there is one, something like PINNOTFOUND
158+
// filePath - full path to the file, including it's name and extension
159+
// lineNumber - line number
160+
// columNumber - columnNumber
161+
// verboseError - error elaboration by verilator
162+
163+
let errorParserRegex = new RegExp(
164+
/%(?<severity>\w+)/.source + // matches "%Warning" or "%Error"
165+
166+
// this matches errorcode with "-" before it, but the "-" doesn't go into ErrorCode match group
167+
/(-(?<errorCode>[A-Z0-9]+))?/.source + // matches error code like -PINNOTFOUND
168+
169+
/: /.source + // ": " before file path or error message
170+
171+
// this one's a bit of a mess, but apparently one can't cleanly split regex match group between lines
172+
// and this is a large group since it matches file path and line and column numbers which may not exist at all
173+
174+
// note: end of file path is detected using file extension at the end of it
175+
// this also allows for spaces in path.
176+
// (neiter Linux, nor Windows actually prohibits it, and Verilator handles spaces just fine)
177+
// In my testing, didn't lead cause any problems, but it potentially can
178+
// extension names are placed so that longest one is first and has highest priority
179+
180+
/((?<filePath>(\S| )+(?<fileExtension>(\.svh)|(\.sv)|(\.SV)|(\.vh)|(\.vl)|(\.v))):((?<lineNumber>\d+):)?((?<columnNumber>\d+):)? )?/.source +
181+
182+
// matches error message produced by Verilator
183+
/(?<verboseError>.*)/.source
184+
, "g"
123185
);
124186

187+
let rex = errorParserRegex.exec(line);
188+
189+
// stderr passthrough
190+
// probably better toggled with a parameter
191+
if (rex.groups["severity"] === "Error") { this.logger.error(line); }
192+
else if (rex.groups["severity"] === "Warning") { this.logger.warn(line); }
193+
194+
// theoretically, this shoudn't "fire", but just in case
195+
else { this.logger.error(line); }
196+
197+
198+
199+
200+
// vscode problems are tied to files
201+
// if there isn't a file name, no point going further
202+
if (!rex.groups["filePath"]) {
203+
return;
204+
}
205+
206+
// replacing "\\" and "\" with "/" for consistency
207+
if (isWindows)
208+
{
209+
rex.groups["filePath"] = rex.groups["filePath"].replace(/(\\\\)|(\\)/g, "/");
210+
}
211+
212+
// if there isn't a list of errors for this file already, it
213+
// needs to be created
214+
if (!filesDiag.has(rex.groups["filePath"]))
215+
{
216+
filesDiag.set(rex.groups["filePath"], []);
217+
}
218+
219+
125220
if (rex && rex[0].length > 0) {
126-
let lineNum = Number(rex[4]) - 1;
127-
let colNum = Number(rex[5]) - 1;
128-
// Type of warning is in rex[2]
221+
let lineNum = Number(rex.groups["lineNumber"]) - 1;
222+
let colNum = Number(rex.groups["columnNumber"]) - 1;
223+
129224
colNum = isNaN(colNum) ? 0 : colNum; // for older Verilator versions (< 4.030 ~ish)
130225

131226
if (!isNaN(lineNum)) {
132-
diagnostics.push({
133-
severity: this.convertToSeverity(rex[1]),
227+
228+
// appending diagnostic message to an array of messages
229+
// tied to a file
230+
filesDiag.get(rex.groups["filePath"]).push({
231+
severity: this.convertToSeverity(rex.groups["severity"]),
134232
range: new vscode.Range(lineNum, colNum, lineNum, Number.MAX_VALUE),
135-
message: rex[6],
136-
code: 'verilator',
233+
message: rex.groups["verboseError"],
234+
code: rex.groups["errorCode"],
137235
source: 'verilator',
138236
});
237+
139238
}
140239
return;
141240
}
142-
this.logger.warn('[verilator] failed to parse error: ' + line);
143241
});
144-
this.logger.info(`[verilator] ${diagnostics.length} errors/warnings returned`);
145-
this.diagnosticCollection.set(doc.uri, diagnostics);
242+
243+
// since error parsing has been redone "from the ground up"
244+
// earlier errors are discarded
245+
this.diagnosticCollection.clear();
246+
247+
filesDiag.forEach((issuesArray, fileName) =>
248+
{
249+
let fileURI = vscode.Uri.file(fileName);
250+
this.diagnosticCollection.set(
251+
fileURI,
252+
issuesArray
253+
);
254+
}
255+
);
146256
}
147257
);
148258
}

0 commit comments

Comments
 (0)