diff --git a/README.md b/README.md index 6146e63..c5315b3 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,11 @@ The first build of the program may take some time. You can run all the tests with ``cargo test`` # TODO - - [ ] support multiple root scopes - [ ] support parsing dates with commas - [ ] Fix warning especially usage and restriction warnings once I'm able to successfully parse all sample VCDs. + - [ ] Should be able to load waveform whilst viewing it live. + - could be quite challenging to implement for various reasons - [ ] Consolidate error messages and add cursors throughout. - [ ] Consider what to do with don't care values diff --git a/src/vcd/parse.rs b/src/vcd/parse.rs index 0195d6c..c7821f1 100644 --- a/src/vcd/parse.rs +++ b/src/vcd/parse.rs @@ -1,8 +1,5 @@ -use chrono::prelude::*; -use itertools::Itertools; use std::{fs::File}; use std::collections::{BTreeMap, HashMap}; -use ::function_name::named; use super::*; @@ -15,242 +12,8 @@ use types::*; mod metadata; use metadata::*; -#[named] -fn parse_var<'a>( - word_reader : &mut WordReader, - parent_scope_idx : Scope_Idx, - vcd : &'a mut VCD, - signal_map : &mut HashMap -) -> Result<(), String> { - let err = format!("reached end of file without parser leaving {}", function_name!()); - let (word, cursor) = word_reader.next_word().ok_or(&err)?; - let expected_types = "[integer, parameter, real, reg, string, wire]"; - - // $var parameter 3 a IDLE $end - // ^^^^^^^^^ - var_type - let var_type = match word { - "integer" => {Ok(Sig_Type::Integer)} - "parameter" => {Ok(Sig_Type::Parameter)} - "real" => {Ok(Sig_Type::Real)} - "reg" => {Ok(Sig_Type::Reg)} - "string" => {Ok(Sig_Type::Str)} - "wire" => {Ok(Sig_Type::Wire)} - "tri1" => {Ok(Sig_Type::Tri1)} - "time" => {Ok(Sig_Type::Time)} - _ => { - let err = format!("found keyword `{word}` but expected one of {expected_types} on {cursor:?}"); - Err(err) - } - }?; - - let (word, cursor) = word_reader.next_word().ok_or(&err)?; - let parse_err = format!("failed to parse as usize on {cursor:?}"); - - // $var parameter 3 a IDLE $end - // ^ - no_bits - let no_bits = match var_type { - Sig_Type::Integer | Sig_Type::Parameter | - Sig_Type::Real | Sig_Type::Reg | - Sig_Type::Wire => { - let no_bits = word.parse::().expect(parse_err.as_str()); - Some(no_bits) - } - // for strings, we don't really care what the number of bits is - _ => {None} - }; - - // $var parameter 3 a IDLE $end - // ^ - signal_alias - let (word, cursor) = word_reader.next_word().ok_or(&err)?; - 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, cursor) = word_reader.next_word().ok_or(&err)?; - match word { - "$end" => {break} - _ => {full_signal_name.push(word.to_string())} - } - } - let full_signal_name = full_signal_name.join(" "); - - // Is the current variable an alias to a signal already encountered? - // if so, handle ref_signal_idx accordingly, if not, add signal to hash - // map - let (signal, signal_idx) = match signal_map.get(&signal_alias) { - Some(ref_signal_idx) => { - let signal_idx = Signal_Idx(vcd.all_signals.len()); - let signal = Signal::Alias{ - name: full_signal_name, - signal_alias: *ref_signal_idx}; - (signal, signal_idx) - } - None => { - let signal_idx = Signal_Idx(vcd.all_signals.len()); - signal_map.insert(signal_alias.to_string(), signal_idx); - let signal = Signal::Data{ - name: full_signal_name, - sig_type: var_type, - num_bits: no_bits, - self_idx: signal_idx, - timeline: BTreeMap::new(), - scope_parent: parent_scope_idx }; - (signal, signal_idx) - } - }; - - vcd.all_signals.push(signal); - let Scope_Idx(parent_scope_idx_usize) = parent_scope_idx; - let parent_scope = vcd.all_scopes.get_mut(parent_scope_idx_usize).unwrap(); - parent_scope.child_signals.push(signal_idx); - - Ok(()) -} - -#[named] -fn parse_signal_tree<'a>( - word_reader : &mut WordReader, - parent_scope_idx : Option, - vcd : &'a mut VCD, - signal_map : &mut HashMap -) -> Result<(), String> { - - // $scope module reg_mag_i $end - // ^^^^^^ - module keyword - let err = format!("reached end of file without parser leaving {}", function_name!()); - let (keyword, cursor) = word_reader.next_word().ok_or(&err)?; - - // TODO : just check if keyword is in expected - let expected = ["module", "begin", "task", "function"]; - match keyword { - "module" => {Ok(())} - "begin" => {Ok(())} - "task" => {Ok(())} - "function" => {Ok(())} - _ => { - let err = format!("found keyword `{keyword}` but expected one of `{expected:?}` on {cursor:?}"); - Err(err) - } - }.unwrap(); - - // $scope module reg_mag_i $end - // ^^^^^^^^^ - scope name - let (scope_name, _) = word_reader.next_word().ok_or(&err)?; - - let curr_scope_idx = Scope_Idx(vcd.all_scopes.len()); - - // register this scope as a child of the current parent scope - // if there is a parent scope, or else we register this scope as - // root scope - match parent_scope_idx { - Some(Scope_Idx(parent_scope_idx)) => { - let parent_scope = vcd.all_scopes.get_mut(parent_scope_idx).unwrap(); - parent_scope.child_scopes.push(curr_scope_idx); - } - None => { - vcd.scope_roots.push(curr_scope_idx) - } - } - - // add this scope to list of existing scopes - vcd.all_scopes.push( - Scope { - name: scope_name.to_string(), - parent_idx: parent_scope_idx, - self_idx: curr_scope_idx, - child_signals: vec![], - child_scopes: vec![] - } - ); - - // $scope module reg_mag_i $end - // ^^^^ - end keyword - ident(word_reader, "$end")?; - - let err = format!("reached end of file without parser leaving {}", function_name!()); - loop { - let (word, cursor) = word_reader.next_word().ok_or(&err)?; - let ParseResult{matched, residual} = tag(word, "$"); - match matched { - // we hope that this word stars with a `$` - "$" => { - match residual { - "scope" => { - // recursive - parse inside of current scope tree - parse_signal_tree(word_reader, Some(curr_scope_idx), vcd, signal_map)?; - } - "var" => { - parse_var(word_reader, curr_scope_idx, vcd, signal_map)?; - } - "upscope" => { - ident(word_reader, "$end")?; - break - } - // we ignore comments - "comment" => { - loop { - if ident(word_reader, "$end").is_ok() {break} - } - } - _ => { - let err = format!("found keyword `{residual}` but expected `$scope`, `$var`, `$comment`, or `$upscope` on {cursor:?}"); - return Err(err) - } - } - } - _ => { - let err = format!("found keyword `{matched}` but expected `$` on {cursor:?}"); - return Err(err) - } - } - } - - // TODO : remove the following Ok(()) once we add loop above - Ok(()) -} - -#[named] -fn parse_scopes<'a>( - word_reader : &mut WordReader, - parent_scope_idx : Option, - vcd : &'a mut VCD, - signal_map : &mut HashMap -) -> Result<(), String> { - // we've already seen `$scope`, so here we just jump right in - parse_signal_tree(word_reader, None, vcd, signal_map)?; - - let err = format!("reached end of file without parser leaving {}", function_name!()); - let expected_keywords = ["$scope", "$enddefinitions"]; - - loop { - let (word, cursor) = word_reader.next_word().ok_or(&err)?; - match word { - "$scope" => { - parse_signal_tree(word_reader, None, vcd, signal_map)?; - } - "$enddefinitions" => { - ident(word_reader, "$end")?; - break - } - // we ignore comments - "comment" => { - loop { - if ident(word_reader, "$end").is_ok() {break} - } - } - _ => { - let err = format!("found keyword `{word}` but expected oneof `{expected_keywords:?}` on {cursor:?}"); - return Err(err) - - } - } - } - - Ok(()) -} - +mod scopes; +use scopes::*; pub fn parse_vcd(file : File) -> Result { let mut word_gen = WordReader::new(file); @@ -309,9 +72,7 @@ mod tests { #[test] fn scopes() { - // TODO: eventually, once all dates pass, merge the following - // two loops - // testing dates + // see if we can parse all signal trees successfully for file_name in test::files { let file = File::open(file_name).unwrap(); let vcd = parse_vcd(file); diff --git a/src/vcd/parse/scopes.rs b/src/vcd/parse/scopes.rs new file mode 100644 index 0000000..b1a1f1e --- /dev/null +++ b/src/vcd/parse/scopes.rs @@ -0,0 +1,235 @@ +//! part of the vcd parser that handles parsing the signal tree and +//! building the resulting signal tree +use function_name::named; + +use super::*; + +#[named] +pub(super) fn parse_var<'a>( + word_reader : &mut WordReader, + parent_scope_idx : Scope_Idx, + vcd : &'a mut VCD, + signal_map : &mut HashMap +) -> Result<(), String> { + let err = format!("reached end of file without parser leaving {}", function_name!()); + let (word, cursor) = word_reader.next_word().ok_or(&err)?; + let expected_types = ["integer", "parameter", "real", "reg", "string", "wire", "tri1", "time"]; + + // $var parameter 3 a IDLE $end + // ^^^^^^^^^ - var_type + let var_type = match word { + "integer" => {Ok(Sig_Type::Integer)} + "parameter" => {Ok(Sig_Type::Parameter)} + "real" => {Ok(Sig_Type::Real)} + "reg" => {Ok(Sig_Type::Reg)} + "string" => {Ok(Sig_Type::Str)} + "wire" => {Ok(Sig_Type::Wire)} + "tri1" => {Ok(Sig_Type::Tri1)} + "time" => {Ok(Sig_Type::Time)} + _ => { + let err = format!("found keyword `{word}` but expected one of {expected_types:?} on {cursor:?}"); + Err(err) + } + }?; + + let (word, cursor) = word_reader.next_word().ok_or(&err)?; + let parse_err = format!("failed to parse as usize on {cursor:?}"); + + // $var parameter 3 a IDLE $end + // ^ - no_bits + let no_bits = match var_type { + Sig_Type::Integer | Sig_Type::Parameter | + Sig_Type::Real | Sig_Type::Reg | + Sig_Type::Wire => { + let no_bits = word.parse::().expect(parse_err.as_str()); + Some(no_bits) + } + // for strings, we don't really care what the number of bits is + _ => {None} + }; + + // $var parameter 3 a IDLE $end + // ^ - signal_alias + let (word, cursor) = word_reader.next_word().ok_or(&err)?; + 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, cursor) = word_reader.next_word().ok_or(&err)?; + match word { + "$end" => {break} + _ => {full_signal_name.push(word.to_string())} + } + } + let full_signal_name = full_signal_name.join(" "); + + // Is the current variable an alias to a signal already encountered? + // if so, handle ref_signal_idx accordingly, if not, add signal to hash + // map + let (signal, signal_idx) = match signal_map.get(&signal_alias) { + Some(ref_signal_idx) => { + let signal_idx = Signal_Idx(vcd.all_signals.len()); + let signal = Signal::Alias{ + name: full_signal_name, + signal_alias: *ref_signal_idx}; + (signal, signal_idx) + } + None => { + let signal_idx = Signal_Idx(vcd.all_signals.len()); + signal_map.insert(signal_alias.to_string(), signal_idx); + let signal = Signal::Data{ + name: full_signal_name, + sig_type: var_type, + num_bits: no_bits, + self_idx: signal_idx, + timeline: BTreeMap::new(), + scope_parent: parent_scope_idx }; + (signal, signal_idx) + } + }; + + vcd.all_signals.push(signal); + let Scope_Idx(parent_scope_idx_usize) = parent_scope_idx; + let parent_scope = vcd.all_scopes.get_mut(parent_scope_idx_usize).unwrap(); + parent_scope.child_signals.push(signal_idx); + + Ok(()) +} + +#[named] +pub(super) fn parse_signal_tree<'a>( + word_reader : &mut WordReader, + parent_scope_idx : Option, + vcd : &'a mut VCD, + signal_map : &mut HashMap +) -> Result<(), String> { + + // $scope module reg_mag_i $end + // ^^^^^^ - module keyword + let err = format!("reached end of file without parser leaving {}", function_name!()); + let (keyword, cursor) = word_reader.next_word().ok_or(&err)?; + + 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:?}"); + Err(err) + }?; + + // $scope module reg_mag_i $end + // ^^^^^^^^^ - scope name + let (scope_name, _) = word_reader.next_word().ok_or(&err)?; + + let curr_scope_idx = Scope_Idx(vcd.all_scopes.len()); + + // register this scope as a child of the current parent scope + // if there is a parent scope, or else we register this scope as + // root scope + match parent_scope_idx { + Some(Scope_Idx(parent_scope_idx)) => { + let parent_scope = vcd.all_scopes.get_mut(parent_scope_idx).unwrap(); + parent_scope.child_scopes.push(curr_scope_idx); + } + None => { + vcd.scope_roots.push(curr_scope_idx) + } + } + + // add this scope to list of existing scopes + vcd.all_scopes.push( + Scope { + name: scope_name.to_string(), + parent_idx: parent_scope_idx, + self_idx: curr_scope_idx, + child_signals: vec![], + child_scopes: vec![] + } + ); + + // $scope module reg_mag_i $end + // ^^^^ - end keyword + ident(word_reader, "$end")?; + + let err = format!("reached end of file without parser leaving {}", function_name!()); + loop { + let (word, cursor) = word_reader.next_word().ok_or(&err)?; + let ParseResult{matched, residual} = tag(word, "$"); + match matched { + // we hope that this word stars with a `$` + "$" => { + match residual { + "scope" => { + // recursive - parse inside of current scope tree + parse_signal_tree(word_reader, Some(curr_scope_idx), vcd, signal_map)?; + } + "var" => { + parse_var(word_reader, curr_scope_idx, vcd, signal_map)?; + } + "upscope" => { + ident(word_reader, "$end")?; + break + } + // we ignore comments + "comment" => { + loop { + if ident(word_reader, "$end").is_ok() {break} + } + } + _ => { + let err = format!("found keyword `{residual}` but expected `$scope`, `$var`, `$comment`, or `$upscope` on {cursor:?}"); + return Err(err) + } + } + } + _ => { + let err = format!("found keyword `{matched}` but expected `$` on {cursor:?}"); + return Err(err) + } + } + } + + Ok(()) +} + +#[named] +pub(super) fn parse_scopes<'a>( + word_reader : &mut WordReader, + parent_scope_idx : Option, + vcd : &'a mut VCD, + signal_map : &mut HashMap +) -> Result<(), String> { + // we've already seen `$scope`, so here we just jump right in + parse_signal_tree(word_reader, None, vcd, signal_map)?; + + let err = format!("reached end of file without parser leaving {}", function_name!()); + let expected_keywords = ["$scope", "$enddefinitions"]; + + loop { + let (word, cursor) = word_reader.next_word().ok_or(&err)?; + match word { + "$scope" => { + parse_signal_tree(word_reader, None, vcd, signal_map)?; + } + "$enddefinitions" => { + ident(word_reader, "$end")?; + break + } + // we ignore comments + "comment" => { + loop { + if ident(word_reader, "$end").is_ok() {break} + } + } + _ => { + let err = format!("found keyword `{word}` but expected oneof `{expected_keywords:?}` on {cursor:?}"); + return Err(err) + + } + } + } + + Ok(()) +} \ No newline at end of file