@@ -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 - Z 0 - 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 - 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"
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