FastWaveBackend/src/vcd/parse.rs

398 lines
14 KiB
Rust
Raw Normal View History

2022-06-09 01:45:47 +00:00
use chrono::prelude::*;
use itertools::Itertools;
2022-06-04 01:06:46 +00:00
use std::fs::File;
use ::function_name::named;
2022-06-19 13:44:57 +00:00
use super::*;
2022-06-13 02:52:24 +00:00
2022-06-19 13:44:57 +00:00
mod combinator_atoms;
use combinator_atoms::*;
2022-06-04 01:06:46 +00:00
2022-06-19 13:44:57 +00:00
mod types;
use types::*;
2022-06-04 01:06:46 +00:00
#[named]
2022-06-09 01:45:47 +00:00
fn parse_date(
2022-06-11 04:01:53 +00:00
word_and_ctx1 : (&str, &Cursor),
word_and_ctx2 : (&str, &Cursor),
word_and_ctx3 : (&str, &Cursor),
word_and_ctx4 : (&str, &Cursor),
word_and_ctx5 : (&str, &Cursor),
2022-06-09 01:45:47 +00:00
) -> Result<DateTime<Utc>, String> {
2022-06-04 01:06:46 +00:00
let day = {
// check for another word in the file
2022-06-09 01:45:47 +00:00
let (word, cursor) = word_and_ctx1;
2022-06-04 01:06:46 +00:00
let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
if !days.contains(&word) {
let msg = format!("reached end of file without parser leaving {}\n", function_name!());
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))
}
word.to_string()
};
let month = {
// check for another word in the file
2022-06-09 01:45:47 +00:00
let (word, cursor) = word_and_ctx2;
2022-06-04 01:06:46 +00:00
let months = [
"Jan", "Feb", "Mar", "Apr",
"May", "Jun", "Jul", "Aug",
"Sept", "Oct", "Nov", "Dec",
];
if !months.contains(&word) {
let msg = format!("reached end of file without parser leaving {}\n", function_name!());
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))
}
word.to_string()
};
let date = {
// check for another word in the file
2022-06-09 01:45:47 +00:00
let (word, cursor) = word_and_ctx3;
2022-06-04 01:06:46 +00:00
2022-06-11 04:01:53 +00:00
let date : u8 = match word.to_string().parse() {
Ok(date) => date,
Err(_) => {return Err("".to_string())}
};
2022-06-04 01:06:46 +00:00
if date > 31 {
let msg = format!("reached end of file without parser leaving {}\n", function_name!());
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))
}
2022-06-11 04:01:53 +00:00
date.to_string()
2022-06-04 01:06:46 +00:00
};
let (hh, mm, ss) = {
2022-06-09 01:45:47 +00:00
// get hour
let (word, cursor) = word_and_ctx4;
2022-06-04 01:06:46 +00:00
let res = take_until(word, b':');
res.assert_match()?;
let hh : u8 = res.matched.to_string()
2022-06-11 04:01:53 +00:00
.parse()
.map_err(|_| "failed to parse".to_string())?;
2022-06-04 01:06:46 +00:00
2022-06-09 01:45:47 +00:00
if hh > 23 {
2022-06-04 01:06:46 +00:00
let msg = format!("reached end of file without parser leaving {}\n", function_name!());
2022-06-09 01:45:47 +00:00
let msg2 = format!("{hh} is not a valid hour : must be between 0 and 23\n");
2022-06-04 01:06:46 +00:00
let msg3 = format!("failure location: {cursor:?}");
return Err(format!("{}{}{}", msg, msg2, msg3))
}
2022-06-09 01:45:47 +00:00
// get minute
let word = &res.residual[1..]; // chop off colon which is at index 0
let res = take_until(word, b':');
res.assert_match()?;
let mm : u8 = res.matched.to_string()
2022-06-11 04:01:53 +00:00
.parse()
.map_err(|_| "failed to parse".to_string())?;
2022-06-04 01:06:46 +00:00
2022-06-09 01:45:47 +00:00
if mm > 60 {
let msg = format!("reached end of file without parser leaving {}\n", function_name!());
let msg2 = format!("{mm} is not a valid minute : must be between 0 and 60\n");
let msg3 = format!("failure location: {cursor:?}");
return Err(format!("{}{}{}", msg, msg2, msg3))
}
2022-06-04 01:06:46 +00:00
2022-06-09 01:45:47 +00:00
// get second
2022-06-11 04:01:53 +00:00
// let ss : u8 = remainder.to_string().parse().unwrap();
res.assert_residual()?;
let residual = &res.residual[1..]; // chop of colon which is at index 0
let ss : u8 = residual.to_string()
2022-06-11 04:01:53 +00:00
.parse()
.map_err(|_| "failed to parse".to_string())?;
2022-06-04 01:06:46 +00:00
2022-06-09 01:45:47 +00:00
if ss > 60 {
let msg = format!("reached end of file without parser leaving {}\n", function_name!());
let msg2 = format!("{ss} is not a valid second : must be between 0 and 60\n");
let msg3 = format!("failure location: {cursor:?}");
return Err(format!("{}{}{}", msg, msg2, msg3))
}
(hh.to_string(), mm.to_string(), ss.to_string())
};
2022-06-04 01:06:46 +00:00
2022-06-09 01:45:47 +00:00
let year = {
// check for another word in the file
let (word, cursor) = word_and_ctx5;
word.to_string()
};
2022-06-04 01:06:46 +00:00
2022-06-11 04:01:53 +00:00
// unfortunately, the minutes, seconds, and hour could occur in an
// unexpected order
2022-06-12 19:32:00 +00:00
let full_date = format!("{day} {month} {date} {hh}:{mm}:{ss} {year}");
2022-06-11 04:01:53 +00:00
let full_date = Utc.datetime_from_str(full_date.as_str(), "%a %b %e %T %Y");
if full_date.is_ok() {
return Ok(full_date.unwrap())
}
2022-06-12 19:32:00 +00:00
Err("failed to parse date".to_string())
2022-06-04 01:06:46 +00:00
}
2022-06-12 19:32:00 +00:00
#[named]
fn parse_version(word_reader : &mut WordReader) -> Result<Version, String> {
let mut version = String::new();
loop {
let word = word_reader.next_word();
// if there isn't another word left in the file, then we exit
if word.is_none() {
return Err(format!("reached end of file without parser leaving {}", function_name!()))
}
let (word, cursor) = word.unwrap();
if word == "$end" {
// truncate trailing whitespace
let version = version[0..(version.len() - 1)].to_string();
return Ok(Version(version))
}
else {
version.push_str(word);
version.push_str(" ");
}
}
}
2022-06-13 02:52:24 +00:00
#[named]
fn parse_timescale(word_reader : &mut WordReader) -> Result<(Option<u32>, Timescale), String> {
let err_msg = format!("failed in {}", function_name!());
// we might see `scalarunit $end` or `scalar unit $end`
// first get timescale
let (word, cursor) = word_reader.next_word().ok_or(&err_msg)?;
let word = word.to_string();
let ParseResult{matched, residual} = take_while(word.as_str(), digit);
let scalar = matched;
2022-06-13 02:52:24 +00:00
let scalar : u32 = scalar.to_string().parse()
.map_err(|_| &err_msg)?;
let timescale = {
if residual == "" {
let (word, cursor) = word_reader.next_word().ok_or(&err_msg)?;
let unit = match word {
2022-06-18 05:00:01 +00:00
"fs" => {Ok(Timescale::fs)}
2022-06-13 02:52:24 +00:00
"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())}
}.unwrap();
(Some(scalar), unit)
}
else {
let unit = match residual {
"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())}
}.unwrap();
(Some(scalar), unit)
}
};
// then check for the `$end` keyword
let (end, cursor) = word_reader.next_word().ok_or(&err_msg)?;
2022-06-19 13:44:57 +00:00
tag(end, "$end").assert_match()?;
2022-06-13 02:52:24 +00:00
return Ok(timescale);
Err("".to_string())
}
2022-06-04 01:06:46 +00:00
#[named]
2022-06-11 04:01:53 +00:00
fn parse_metadata(word_reader : &mut WordReader) -> Result<Metadata, String> {
let err_msg = format!("reached end of file without parser leaving {}", function_name!());
2022-06-11 04:01:53 +00:00
let mut metadata = Metadata {
2022-06-09 01:45:47 +00:00
date : None,
version : None,
timescale : (None, Timescale::unit)
};
2022-06-04 01:06:46 +00:00
loop {
// check for another word in the file
let (word, cursor) = word_reader.next_word().ok_or(&err_msg)?;
2022-06-04 01:06:46 +00:00
let ParseResult{matched, residual} = tag(word, "$");
match matched {
2022-06-04 01:06:46 +00:00
// we hope that this word stars with a `$`
"$" => {
match residual {
2022-06-09 01:45:47 +00:00
"date" => {
let err_msg = format!("reached end of file without parser leaving {}", function_name!());
// a date is typically composed of the 5 following words which can
// occur in any order:
// {Day, Month, Date(number in month), hh:mm:ss, year}.
// Thus, we must lookahead read the 5 next words, and try our date
// parser on 5! = 120 permutations of the 5 words.
//
2022-06-11 04:01:53 +00:00
// It is also possible that within each permutation, the hours,
// minutes, and seconds could be in an unusual order, which means
// that we may search up to 6 different permutations oh hh::mm:ss,
// for an upper bound total of 720 permutations
//
2022-06-09 01:45:47 +00:00
// While looking ahead, if one of the 5 words in `$end`, we have to
// immediately stop trying to get more words.
let mut found_end = false;
let mut lookahead_5_words : Vec<(String, Cursor)> = Vec::new();
for word in 0..5 {
let (word, cursor) = word_reader.next_word().expect(err_msg.as_str());
let word = word.to_string();
match word.as_str() {
"$end" => {
found_end = true;
break;
}
_ => {
lookahead_5_words.push((word, cursor));
}
};
}
// we no longer attempt to parse date if we weren't able to lookahead 5
// words
if found_end {continue}
2022-06-11 04:01:53 +00:00
let permutations = lookahead_5_words
.iter()
.permutations(lookahead_5_words.len());
// go ahead and search for a match amongst permuted date text
for mut permutations in permutations {
let (w1, s1) = permutations.pop().unwrap();
let arg_1 = (&w1[..], s1);
let (w2, s2) = permutations.pop().unwrap();
let arg_2 = (&w2[..], s2);
let (w3, s3) = permutations.pop().unwrap();
let arg_3 = (&w3[..], s3);
let (w4, s4) = permutations.pop().unwrap();
let arg_4 = (&w4[..], s4);
let (w5, s5) = permutations.pop().unwrap();
let arg_5 = (&w5[..], s5);
let parsed_date = parse_date(arg_1, arg_2, arg_3, arg_4, arg_5);
// store date and exit loop if a match is found
if parsed_date.is_ok() {
metadata.date = Some(parsed_date.unwrap());
break
}
}
2022-06-09 01:45:47 +00:00
}
2022-06-12 19:32:00 +00:00
"version" => {
let version = parse_version(word_reader);
if version.is_ok() {
metadata.version = Some(version.unwrap());
}
}
2022-06-13 02:52:24 +00:00
"timescale" => {
let timescale = parse_timescale(word_reader);
if timescale.is_ok() {
metadata.timescale = timescale.unwrap();
}
}
2022-06-11 04:01:53 +00:00
// in VCDs, the scope keyword indicates the end of the metadata section
2022-06-09 01:45:47 +00:00
"scope" => {break}
2022-06-11 04:01:53 +00:00
// we keep searching for words until we've found one of the following
// keywords, ["version", "timescale", "scope"]
2022-06-04 01:06:46 +00:00
_ => {}
}
}
// if word does not start with `$`, then we keep looping
_ => {}
2022-06-04 01:06:46 +00:00
}
}
2022-06-11 04:01:53 +00:00
return Ok(metadata)
2022-06-04 01:06:46 +00:00
}
pub fn parse_vcd(file : File) {
let mut word_gen = WordReader::new(file);
2022-06-11 04:01:53 +00:00
let header = parse_metadata(&mut word_gen).unwrap();
2022-06-09 01:45:47 +00:00
dbg!(header);
2022-06-18 05:00:01 +00:00
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
#[test]
fn headers() {
let files = vec![
"./test-vcd-files/aldec/SPI_Write.vcd",
"./test-vcd-files/ghdl/alu.vcd",
"./test-vcd-files/ghdl/idea.vcd",
"./test-vcd-files/ghdl/pcpu.vcd",
"./test-vcd-files/gtkwave-analyzer/perm_current.vcd",
"./test-vcd-files/icarus/CPU.vcd",
"./test-vcd-files/icarus/rv32_soc_TB.vcd",
"./test-vcd-files/icarus/test1.vcd",
"./test-vcd-files/model-sim/CPU_Design.msim.vcd",
"./test-vcd-files/model-sim/clkdiv2n_tb.vcd",
"./test-vcd-files/my-hdl/Simple_Memory.vcd",
"./test-vcd-files/my-hdl/sigmoid_tb.vcd",
"./test-vcd-files/my-hdl/top.vcd",
// "./test-vcd-files/ncsim/ffdiv_32bit_tb.vcd",
// "./test-vcd-files/quartus/mipsHardware.vcd",
// "./test-vcd-files/quartus/wave_registradores.vcd",
"./test-vcd-files/questa-sim/dump.vcd",
"./test-vcd-files/questa-sim/test.vcd",
"./test-vcd-files/riviera-pro/dump.vcd",
// "./test-vcd-files/systemc/waveform.vcd",
// "./test-vcd-files/treadle/GCD.vcd",
"./test-vcd-files/vcs/Apb_slave_uvm_new.vcd",
"./test-vcd-files/vcs/datapath_log.vcd",
"./test-vcd-files/vcs/processor.vcd",
"./test-vcd-files/verilator/swerv1.vcd",
"./test-vcd-files/verilator/vlt_dump.vcd",
// "./test-vcd-files/vivado/iladata.vcd",
"./test-vcd-files/xilinx_isim/test.vcd",
"./test-vcd-files/xilinx_isim/test1.vcd",
"./test-vcd-files/xilinx_isim/test2x2_regex22_string1.vcd"
];
for file in files {
let metadata = parse_metadata(
&mut WordReader::new(
File::open(file)
.unwrap()
)
);
assert!(metadata.is_ok());
assert!(metadata.unwrap().date.is_some());
}
}
2022-06-04 01:06:46 +00:00
}