@@ -103,82 +103,156 @@ export default class VerilatorLinter extends BaseLinter {
103
103
this . logger . info ( '[verilator] command: ' + command ) ;
104
104
this . logger . info ( '[verilator] cwd : ' + cwd ) ;
105
105
106
+
107
+
106
108
var _ : child . ChildProcess = child . exec (
107
109
command ,
108
110
{ cwd : cwd } ,
109
111
( _error : Error , _stdout : string , stderr : string ) => {
110
- //let diagnostics: vscode.Diagnostic[] = [];
111
112
112
113
// basically DiagnosticsCollection but with ability to append diag lists
113
- let filesDiag = { } ;
114
- let error_warning_counter = 0 ;
115
- stderr . split ( / \r ? \n / g) . forEach ( ( line , _ ) => {
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
+ }
138
+ }
116
139
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
117
146
if ( ! line . startsWith ( '%' ) ) {
118
- this . logger . error ( line ) ;
147
+
148
+ // allows for persistent
149
+ if ( lastDiagMessageType === 'Warning' ) { this . logger . warn ( line ) ; }
150
+ else { this . logger . error ( line ) ; }
119
151
return ;
120
152
}
121
153
122
- // for this regex, match sections are:
123
- // 1 - severity (warning/error)
124
- // 3 - error/warning code (EOFNEWLINE, ...),
125
- // 5 - file path
126
- // 9 - line number
127
- // 11 - column number
128
- // 12 - message
129
154
130
- let rex = line . match (
131
- / ( \w + ) ( - ( [ A - Z 0 - 9 ] + ) ) ? : ( ( \S + ( ( \. s v ) | ( \. v ) ) ) : ( \d + ) : ( ( \d + ) : ) ? ) ? ( .* ) /
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 - Z 0 - 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 > ( \. s v h ) | ( \. s v ) | ( \. S V ) | ( \. v h ) | ( \. v l ) | ( \. v ) ) ) : ( (?< lineNumber > \d + ) : ) ? ( (?< columnNumber > \d + ) : ) ? ) ? / . source +
181
+
182
+ // matches error message produced by Verilator
183
+ / (?< verboseError > .* ) / . source
184
+ , "g"
132
185
) ;
133
186
134
- // vscode problems are tied to files, so if there is no file name, no point adding
135
- if ( ! rex [ 5 ] ) { return ; }
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
+ }
136
205
137
206
// replacing "\\" and "\" with "/" for consistency
138
207
if ( isWindows )
139
208
{
140
- rex [ 5 ] = rex [ 5 ] . replace ( / ( \\ \\ ) | ( \\ ) / , "/" ) ;
209
+ rex . groups [ "filePath" ] = rex . groups [ "filePath" ] . replace ( / ( \\ \\ ) | ( \\ ) / g , "/" ) ;
141
210
}
142
211
143
- // if no errors for this file, new list needs to be created
144
- if ( ! ( rex [ 5 ] in Object . keys ( filesDiag ) ) )
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" ] ) )
145
215
{
146
- filesDiag [ rex [ 5 ] ] = [ ] ;
216
+ filesDiag . set ( rex . groups [ "filePath" ] , [ ] ) ;
147
217
}
148
218
149
219
150
220
if ( rex && rex [ 0 ] . length > 0 ) {
151
- let lineNum = Number ( rex [ 9 ] ) - 1 ;
152
- let colNum = Number ( rex [ 11 ] ) - 1 ;
153
- // Type of warning is in rex[2]
221
+ let lineNum = Number ( rex . groups [ "lineNumber" ] ) - 1 ;
222
+ let colNum = Number ( rex . groups [ "columnNumber" ] ) - 1 ;
223
+
154
224
colNum = isNaN ( colNum ) ? 0 : colNum ; // for older Verilator versions (< 4.030 ~ish)
155
225
156
226
if ( ! isNaN ( lineNum ) ) {
157
- filesDiag [ rex [ 5 ] ] . push ( {
158
- 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" ] ) ,
159
232
range : new vscode . Range ( lineNum , colNum , lineNum , Number . MAX_VALUE ) ,
160
- message : rex [ 12 ] ,
161
- code : rex [ 3 ] ,
233
+ message : rex . groups [ "verboseError" ] ,
234
+ code : rex . groups [ "errorCode" ] ,
162
235
source : 'verilator' ,
163
236
} ) ;
164
237
165
- error_warning_counter ++ ;
166
238
}
167
239
return ;
168
240
}
169
- this . logger . warn ( '[verilator] failed to parse error: ' + line ) ;
170
241
} ) ;
171
- this . logger . info ( `[verilator] ${ error_warning_counter } errors/warnings returned` ) ;
172
- this . diagnosticCollection . clear ( )
173
- for ( let fileName in filesDiag )
174
- {
175
- let fileURI = vscode . Uri . file ( fileName ) ;
176
- // adding diag info for each file
177
- this . diagnosticCollection . set (
178
- fileURI ,
179
- filesDiag [ fileName ]
180
- ) ;
181
- }
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
+ ) ;
182
256
}
183
257
) ;
184
258
}
0 commit comments