diff --git a/Cargo.toml b/Cargo.toml index 0f04e45..541b80a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,5 @@ edition = "2021" num = "0.4" clap = { version = "3.1.8", features = ["derive"] } chrono = "0.4" -itertools = "0.10.3" \ No newline at end of file +itertools = "0.10.3" +backtrace = "0.3" \ No newline at end of file diff --git a/src/vcd/parse/combinator_atoms.rs b/src/vcd/parse/combinator_atoms.rs index 32813fc..db77e94 100644 --- a/src/vcd/parse/combinator_atoms.rs +++ b/src/vcd/parse/combinator_atoms.rs @@ -76,9 +76,7 @@ pub(super) fn ident( ) -> Result<(), String> { // let keyword = "module"; - let err : Result<(), String> = Err(format!("reached end of file without parser leaving ident")); - let word = word_reader.next_word(); - let (word, cursor) = word.ok_or(err).unwrap(); + let (word, cursor) = word_reader.next_word()?; if word == keyword { return Ok(()) diff --git a/src/vcd/parse/events.rs b/src/vcd/parse/events.rs index 3b484c4..14bb81c 100644 --- a/src/vcd/parse/events.rs +++ b/src/vcd/parse/events.rs @@ -84,9 +84,10 @@ pub(super) fn parse_events<'a>( loop { let next_word = word_reader.next_word(); - // if we've reached the end of the file, then there is obviously + // The following is the only case where eof is not an error. + // If we've reached the end of the file, then there is obviously // nothing left to do... - if next_word.is_none() {break}; + if next_word.is_err() {break}; let (word, cursor) = next_word.unwrap(); let Cursor(Line(_), Word(word_in_line_idx)) = cursor; diff --git a/src/vcd/parse/metadata.rs b/src/vcd/parse/metadata.rs index 08cfd22..7f62fcf 100644 --- a/src/vcd/parse/metadata.rs +++ b/src/vcd/parse/metadata.rs @@ -17,8 +17,7 @@ pub(super) fn parse_date( let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; if !days.contains(&word) { - let msg = format!("Error near {}:{}. Reached end of file without \ - terminating parser", file!(), line!()); + let msg = format!("Error near {}:{}.", file!(), line!()); let msg2 = format!("{word} is not a valid weekday : expected one of {days:?}\n"); let msg3 = format!("failure location: {cursor:?}"); return Err(format!("{}{}{}", msg, msg2, msg3)) @@ -38,8 +37,7 @@ pub(super) fn parse_date( ]; if !months.contains(&word) { - let msg = format!("Error near {}:{}. Reached end of file without \ - terminating parser", file!(), line!()); + let msg = format!("Error near {}:{}.", file!(), line!()); let msg2 = format!("{word} is not a valid month : expected one of {months:?}\n"); let msg3 = format!("failure location: {cursor:?}"); return Err(format!("{}{}{}", msg, msg2, msg3)) @@ -54,12 +52,11 @@ pub(super) fn parse_date( let date : u8 = match word.to_string().parse() { Ok(date) => date, - Err(_) => {return Err("".to_string())} + Err(e) => {return Err(format!("Error near {}:{}. {e}", file!(), line!()))} }; if date > 31 { - let msg = format!("Error near {}:{}. Reached end of file without \ - terminating parser", file!(), line!()); + let msg = format!("Error near {}:{}.", file!(), line!()); let msg2 = format!("{word} is not a valid date : must be between 0 and 31\n"); let msg3 = format!("failure location: {cursor:?}"); return Err(format!("{}{}{}", msg, msg2, msg3)) @@ -77,7 +74,7 @@ pub(super) fn parse_date( res.assert_match()?; let hh : u8 = res.matched.to_string() .parse() - .map_err(|_| "failed to parse".to_string())?; + .map_err(|e| format!("Error near {}:{}. {e}", file!(), line!()))?; if hh > 23 { let msg = format!("Error near {}:{}.", file!(), line!()); @@ -92,7 +89,7 @@ pub(super) fn parse_date( res.assert_match()?; let mm : u8 = res.matched.to_string() .parse() - .map_err(|_| "failed to parse".to_string())?; + .map_err(|e| format!("Error near {}:{}. {e}", file!(), line!()))?; if mm > 60 { let msg = format!("Error near {}:{}.", file!(), line!()); @@ -107,7 +104,7 @@ pub(super) fn parse_date( let residual = &res.residual[1..]; // chop of colon which is at index 0 let ss : u8 = residual.to_string() .parse() - .map_err(|_| "failed to parse".to_string())?; + .map_err(|e| format!("Error near {}:{}. {e}", file!(), line!()))?; if ss > 60 { let msg = format!("Error near {}:{}.", file!(), line!()); @@ -132,7 +129,7 @@ pub(super) fn parse_date( return Ok(full_date.unwrap()) } - Err("failed to parse date".to_string()) + Err(format!("Error near {}:{}. Failed to parse date.", file!(), line!())) } @@ -140,16 +137,7 @@ pub(super) fn parse_version(word_reader : &mut WordReader) -> Result Result Result<(Option, Timescale), String> { - let err_msg = format!("Error near {}:{}. No more words left in vcd file.", - file!(), line!()); - - // we might see `scalarunit $end` or `scalar unit $end` + // we might see `1ps $end` or `1 ps $end` // first get timescale - let (word, _) = word_reader.next_word().ok_or(&err_msg)?; + let (word, _) = word_reader.next_word()?; let word = word.to_string(); let ParseResult{matched, residual} = take_while(word.as_str(), digit); let scalar = matched; let scalar : u32 = scalar.to_string().parse() - .map_err(|_| &err_msg)?; + .map_err(|e| format!("Error near {}:{}. {e}", file!(), line!()))?; let timescale = { if residual == "" { - let (word, _) = word_reader.next_word().ok_or(&err_msg)?; + let (word, _) = word_reader.next_word()?; let unit = match word { "fs" => {Ok(Timescale::Fs)} "ps" => {Ok(Timescale::Ps)} @@ -189,19 +174,20 @@ pub(super) fn parse_timescale(word_reader : &mut WordReader) -> Result<(Option {Ok(Timescale::Us)} "ms" => {Ok(Timescale::Ms)} "s" => {Ok(Timescale::S)} - _ => {Err(err_msg.to_string())} + _ => {Err(format!("Error near {}:{}. Unknown unit {word}.", file!(), line!()))} }.unwrap(); (Some(scalar), unit) } else { let unit = match residual { + "fs" => {Ok(Timescale::Fs)} "ps" => {Ok(Timescale::Ps)} "ns" => {Ok(Timescale::Ns)} "us" => {Ok(Timescale::Us)} "ms" => {Ok(Timescale::Ms)} "s" => {Ok(Timescale::S)} - _ => {Err(err_msg.to_string())} + _ => {Err(format!("Error near {}:{}. Unknown unit {residual}.", file!(), line!()))} }.unwrap(); (Some(scalar), unit) @@ -209,7 +195,7 @@ pub(super) fn parse_timescale(word_reader : &mut WordReader) -> Result<(Option Result<(Option Result { - let err_msg = format!("Error near {}:{}. No more words left in vcd file.", - file!(), line!()); let mut metadata = Metadata { date : None, @@ -228,7 +212,7 @@ pub(super) fn parse_metadata(word_reader : &mut WordReader) -> Result Result = Vec::new(); for _ in 0..5 { - let (word, cursor) = word_reader.next_word().expect(err_msg.as_str()); + let (word, cursor) = word_reader.next_word()?; let word = word.to_string(); match word.as_str() { "$end" => { diff --git a/src/vcd/parse/scopes.rs b/src/vcd/parse/scopes.rs index 4e43a36..fa62188 100644 --- a/src/vcd/parse/scopes.rs +++ b/src/vcd/parse/scopes.rs @@ -8,8 +8,7 @@ pub(super) fn parse_var<'a>( vcd : &'a mut VCD, signal_map : &mut HashMap ) -> Result<(), String> { - let err = format!("Error near {}:{}. No more words left in vcd file.", file!(), line!()); - let (word, cursor) = word_reader.next_word().ok_or(&err)?; + let (word, cursor) = word_reader.next_word()?; let expected_types = ["integer", "parameter", "real", "reg", "string", "wire", "tri1", "time"]; // $var parameter 3 a IDLE $end @@ -24,12 +23,14 @@ pub(super) fn parse_var<'a>( "tri1" => {Ok(SigType::Tri1)} "time" => {Ok(SigType::Time)} _ => { - let err = format!("found keyword `{word}` but expected one of {expected_types:?} on {cursor:?}"); + let err = format!("Error near {}:{} \ + found keyword `{word}` but expected one of \ + {expected_types:?} on {cursor:?}", file!(), line!()); Err(err) } }?; - let (word, cursor) = word_reader.next_word().ok_or(&err)?; + let (word, cursor) = word_reader.next_word()?; let parse_err = format!("failed to parse as usize on {cursor:?}"); // $var parameter 3 a IDLE $end @@ -48,14 +49,14 @@ pub(super) fn parse_var<'a>( // $var parameter 3 a IDLE $end // ^ - signal_alias - let (word, _) = word_reader.next_word().ok_or(&err)?; + let (word, _) = word_reader.next_word()?; let signal_alias = word.to_string(); // $var parameter 3 a IDLE $end // ^^^^ - full_signal_name(can extend until $end) let mut full_signal_name = Vec::::new(); loop { - let (word, _) = word_reader.next_word().ok_or(&err)?; + let (word, _) = word_reader.next_word()?; match word { "$end" => {break} _ => {full_signal_name.push(word.to_string())} @@ -145,15 +146,7 @@ fn parse_orphaned_vars<'a>( parse_var(word_reader, scope_idx, vcd, signal_map)?; loop { - let next_word = word_reader.next_word(); - - // we shouldn't reach the end of the file here... - if next_word.is_none() { - let err = format!("Error near {}:{}. No more words left in vcd file.", file!(), line!()); - Err(err)?; - }; - - let (word, cursor) = next_word.unwrap(); + let (word, cursor) = word_reader.next_word()?; match word { "$var" => { @@ -161,9 +154,9 @@ fn parse_orphaned_vars<'a>( } "$scope" => {break} _ => { - let (f, l )= (file!(), line!()); - let msg = format!("Error near {f}:{l}.\ - Expected $scope or $var, found {word} at {cursor:?}"); + let msg = format!("Error near {}:{}.\ + Expected $scope or $var, found \ + {word} at {cursor:?}", file!(), line!()); Err(msg)?; } }; @@ -181,20 +174,21 @@ pub(super) fn parse_signal_tree<'a>( // $scope module reg_mag_i $end // ^^^^^^ - module keyword - let err = format!("Error near {}:{}. No more words left in vcd file.", file!(), line!()); - let (keyword, cursor) = word_reader.next_word().ok_or(&err)?; + let (keyword, cursor) = word_reader.next_word()?; let expected = ["module", "begin", "task", "function"]; if expected.contains(&keyword) { Ok(()) } else { - let err = format!("found keyword `{keyword}` but expected one of `{expected:?}` on {cursor:?}"); + let err = format!("Error near {}:{}. \ + found keyword `{keyword}` but expected one of \ + {expected:?} on {cursor:?}", file!(), line!()); Err(err) }?; // $scope module reg_mag_i $end // ^^^^^^^^^ - scope name - let (scope_name, _) = word_reader.next_word().ok_or(&err)?; + let (scope_name, _) = word_reader.next_word()?; let curr_scope_idx = ScopeIdx(vcd.all_scopes.len()); @@ -227,7 +221,7 @@ pub(super) fn parse_signal_tree<'a>( ident(word_reader, "$end")?; loop { - let (word, cursor) = word_reader.next_word().ok_or(&err)?; + let (word, cursor) = word_reader.next_word()?; let ParseResult{matched, residual} = tag(word, "$"); match matched { // we hope that this word starts with a `$` @@ -251,13 +245,18 @@ pub(super) fn parse_signal_tree<'a>( } } _ => { - let err = format!("found keyword `{residual}` but expected `$scope`, `$var`, `$comment`, or `$upscope` on {cursor:?}"); + let err = format!("Error near {}:{}. \ + found keyword `{residual}` but expected \ + `$scope`, `$var`, `$comment`, or `$upscope` \ + on {cursor:?}", file!(), line!()); return Err(err) } } } _ => { - let err = format!("found keyword `{matched}` but expected `$` on {cursor:?}"); + let err = format!("Error near {}:{}. \ + found keyword `{matched}` but \ + expected `$` on {cursor:?}", file!(), line!()); return Err(err) } } @@ -272,8 +271,7 @@ pub(super) fn parse_scopes<'a>( signal_map : &mut HashMap ) -> Result<(), String> { // get the current word - let err = format!("Error near {}:{}. No more words left in vcd file.", file!(), line!()); - let (word, _) = word_reader.curr_word().ok_or(&err)?; + let (word, _) = word_reader.curr_word()?; // we may have orphaned vars that occur before the first scope if word == "$var" { @@ -281,16 +279,16 @@ pub(super) fn parse_scopes<'a>( } // get the current word - let (word, cursor) = word_reader.curr_word().ok_or(&err)?; + let (word, cursor) = word_reader.curr_word()?; // the current word should be "scope", as `parse_orphaned_vars`(if it // was called), should have terminated upon encountering "$scope". // If `parse_orphaned_vars` was not called, `parse_scopes` should still // have only been called if the caller encountered the word "$scope" if word != "$scope" { - let (f, l )= (file!(), line!()); - let msg = format!("Error near {f}:{l}.\ - Expected $scope or $var, found {word} at {cursor:?}"); + let msg = format!("Error near {}:{}.\ + Expected $scope or $var, found \ + {word} at {cursor:?}", file!(), line!()); return Err(msg) } @@ -305,7 +303,7 @@ pub(super) fn parse_scopes<'a>( // because this loop gets a word from `next_word` instead of // `curr_word()`. loop { - let (word, cursor) = word_reader.next_word().ok_or(&err)?; + let (word, cursor) = word_reader.next_word()?; match word { "$scope" => { parse_signal_tree(word_reader, None, vcd, signal_map)?; @@ -322,8 +320,9 @@ pub(super) fn parse_scopes<'a>( } } _ => { - let err = format!("found keyword `{word}` but expected one \ - of `{expected_keywords:?}` on {cursor:?}"); + let err = format!("Error near {}:{} \ + found keyword `{word}` but expected one of \ + {expected_keywords:?} on {cursor:?}", file!(), line!()); return Err(err) } diff --git a/src/vcd/reader.rs b/src/vcd/reader.rs index 7ae33e7..0548677 100644 --- a/src/vcd/reader.rs +++ b/src/vcd/reader.rs @@ -5,12 +5,16 @@ use std::str; use std::io::prelude::*; use std::io; +use backtrace::{ Backtrace, BacktraceFrame, BacktraceSymbol }; + #[derive(Debug, Clone)] pub(super) struct Line(pub(super) usize); #[derive(Debug, Clone)] pub(super) struct Word(pub(super) usize); #[derive(Debug, Clone)] pub(super) struct Cursor(pub(super) Line, pub(super) Word); +#[derive(Debug)] +pub(super) enum FileStatus{Eof} pub struct WordReader { reader : io::BufReader, @@ -21,6 +25,7 @@ pub struct WordReader { curr_slice : Option<(*const u8, usize, Cursor)>, } + impl WordReader { pub(super) fn new(file : File) -> WordReader { let reader = io::BufReader::new(file); @@ -34,13 +39,18 @@ impl WordReader { } } - pub(super) fn next_word(&mut self) -> Option<(&str, Cursor)> { - // if there are no more words, attempt to read more content + + pub(super) fn next_word(&mut self) -> Result<(&str, Cursor), FileStatus> { + + // although reaching the eof is not technically an error, in most cases, + // we treat it like one in the rest of the codebase. + + // if there are no more words in the buffer, attempt to read more content // from the file if self.str_slices.is_empty() { self.buffers.clear(); - if self.eof {return None} + if self.eof {return Err(FileStatus::Eof)} let num_buffers = 10; @@ -70,7 +80,7 @@ impl WordReader { // if after we've attempted to read in more content from the file, // there are still no words... if self.str_slices.is_empty() { - return None + return Err(FileStatus::Eof) } // if we make it here, we return the next word @@ -78,21 +88,53 @@ impl WordReader { let (ptr, len, position) = self.str_slices.pop_front().unwrap(); let slice = slice::from_raw_parts(ptr, len); self.curr_slice = Some((ptr, len, position.clone())); - return Some((str::from_utf8(slice).unwrap(), position)); + return Ok((str::from_utf8(slice).unwrap(), position)); }; } - pub(super) fn curr_word(&mut self) -> Option<(&str, Cursor)> { + pub(super) fn curr_word(&mut self) -> Result<(&str, Cursor), FileStatus> { match &self.curr_slice { Some(slice) => { unsafe { let (ptr, len, position) = slice.clone(); let slice = slice::from_raw_parts(ptr, len); - Some((str::from_utf8(slice).unwrap(), position)) + Ok((str::from_utf8(slice).unwrap(), position)) } } - None => {None} + None => {Err(FileStatus::Eof)} + } + } +} + +fn previous_symbol(level: u32) -> Option { + let (trace, curr_file, curr_line) = (Backtrace::new(), file!(), line!()); + let frames = trace.frames(); + frames.iter() + .flat_map(BacktraceFrame::symbols) + .skip_while(|s| s.filename().map(|p| !p.ends_with(curr_file)).unwrap_or(true) + || s.lineno() != Some(curr_line)) + .nth(1 + level as usize).cloned() +} + +impl From for String { + fn from(f: FileStatus) -> String { + let sym = previous_symbol(1); + let filename = sym + .as_ref() + .and_then(BacktraceSymbol::filename) + .map_or(None, |path| {path.to_str()}) + .unwrap_or("(Couldn't determine filename)"); + let lineno = sym + .as_ref() + .and_then(BacktraceSymbol::lineno) + .map_or(None, |path| {Some(path.to_string())}) + .unwrap_or("(Couldn't determine line number)".to_string()); + + match f { + FileStatus::Eof => format!( + "Error near {filename}:{lineno} \ + No more words left in vcd file."), } } } \ No newline at end of file diff --git a/test-vcd-files/VCD_file_with_errors.vcd b/test-vcd-files/VCD_file_with_errors.vcd new file mode 100644 index 0000000..6be0c1a --- /dev/null +++ b/test-vcd-files/VCD_file_with_errors.vcd @@ -0,0 +1,92 @@ +$date +Thu Dec 17 17:19:03 2020 +$end +$version +Aldec HDL Simulator Version 10.03.3558 +$end +$timescale +1 ps +$end + +$scope module tb $end + +$scope module t $end +$var wire 1 ! CLK $end +$var wire 1 " LED $end +$var wire 1 # PIN_10 $end +$var wire 1 $ PIN_11 $end +$var wire 1 % PIN_12 $end +$var wire 1 & PIN_13 $end +$var wire 1 ' SPI_In $end +$var wire 1 ( SPI_Out $end +$var wire 1 ) SPI_Data_Available $end +$var wire 1 * RegMap_In $end +$var wire 1 + RegMap_Out $end +$var wire 1 , RegMap_Data_Available $end +$var wire 8 - AddrBus [7:0] $end +$var wire 8 . DataBus [7:0] $end + +$scope module controller $end +$var wire 1 ! clk $end +$var wire 1 " LED $end +$var wire 8 . DataBus [7:0] $end +$var wire 1 ) SPI_Data_Available $end +$var wire 1 , RegMap_Data_available $end +$var wire 8 / addr [7:0] $end +$var wire 8 0 data [7:0] $end +$var wire 1 1 BusActive $end +$var reg 8 2 AddrBus [7:0] $end +$var reg 1 3 SPI_In $end +$var reg 1 4 SPI_Out $end +$var reg 1 5 RegMap_In $end +$var reg 1 6 RegMap_Out $end +$var reg 1 7 LED_state $end +$var reg 3 8 block [2:0] $end +$var reg 3 9 doing [2:0] $end +$var parameter 3 : IDLE $end +$var parameter 3 ; READ_ADDR $end +$var parameter 3 < READ_DATA $end +$var parameter 3 = TX $end +$var parameter 3 > SPI $end +$upscope $end + + +$scope module reg_mag_i $end +$var wire 1 ! clk $end +$var wire 1 + RegMap_Out $end +$var wire 1 * RegMap_In $end +$var wire 8 - AddrBus [7:0] $end +$var wire 8 . DataBus [7:0] $end +$var wire 1 ? r_w $end +$var wire 1 @ outputData $end +$var wire 1 A inputData $end +$var reg 1 B RegMap_Data_Available $end +$var reg 8 C inData [7:0] $end +$var reg 8 D inAddr [7:0] $end +$var reg 8 E outData [7:0] $end +$var reg 1 F addr_rcv $end +$var reg 1 G data_rcv $end +$var reg 2 H state [1:0] $end +$var parameter 2 I INIT $end +$var parameter 2 J IDLE $end +$var parameter 2 K RX $end +$var parameter 2 L TX $end +$var parameter 32 M MAXADDRESS $end +$upscope $end + + +$scope module SPI_i $end +$var wire 1 ! clk $end +$var wire 1 # SCK $end +$var wire 1 $ SSEL $end +$var wire 1 % MOSI $end +$var wire 1 & MISO $end +$var wire 1 ' SPI_In $end +$var wire 1 ( SPI_Out $end +$var wire 8 - AddrBus [7:0] $end +$var wire 8 . DataBus [7:0] $end +$var wire 1 N SCK_risingedge $end +$var wire 1 O SCK_fallingedge $end +$var wire 1 P SSEL_active $end +$var wire 1 Q MOSI_data $end +$var reg 1 R SPI_Data_Available $end \ No newline at end of file