quaigh 0.0.6

Logic optimization library
Documentation
//! IO for test patterns

use std::io::{BufRead, BufReader, Read, Write};

/// Read test patterns in Atalanta format
///
/// Each pattern may contain multiple timesteps. For each timestep, the value of each circuit input is given.
/// The patterns are formatted as follows:
/// ```text
///     * This is a comment
///
///     * Input pattern with five input values set to zero
///     1: 00000
///
///     * The index increments at each pattern
///     2: 00000
///
///     * A pattern that contains three timesteps
///     3: 01110 00111 01000
///
///     * The index is optional when reading patterns
///     01110 00111 01000
/// ```
pub fn read_patterns<R: Read>(r: R) -> Result<Vec<Vec<Vec<bool>>>, String> {
    let mut ret = Vec::new();
    let mut pattern_ind: usize = 1;
    let mut line_ind = 0;
    for l in BufReader::new(r).lines() {
        if let Ok(s) = l {
            line_ind += 1;
            let t = s.trim();
            if t.is_empty() || t.starts_with('*') {
                continue;
            }
            let sp = t.split(':').collect::<Vec<_>>();
            if sp.len() >= 3 || sp.is_empty() {
                return Err(
                    "Expected line of the form INDEX: TIMESTEP_1 TIMESTEP_2 ... TIMESTEP_N"
                        .to_owned(),
                );
            }
            if sp.len() == 2 {
                let parse_ind = sp[0].trim().parse::<usize>();
                if parse_ind.is_err() || parse_ind.unwrap() != pattern_ind {
                    println!(
                        "Index {} on a line does not match expected {}",
                        sp[0], pattern_ind
                    );
                }
            }
            let patterns = if sp.len() == 2 {
                sp[1].split_whitespace()
            } else {
                sp[0].split_whitespace()
            };
            let mut invalid = false;
            let mut seq_ret = Vec::new();
            for p in patterns {
                let mut comb_ret = Vec::new();
                for c in p.chars() {
                    if c == '0' {
                        comb_ret.push(false);
                    } else if c == '1' {
                        comb_ret.push(true);
                    } else if !invalid {
                        invalid = true;
                        println!("Ignoring line {line_ind} with invalid characters");
                    }
                }
                seq_ret.push(comb_ret);
            }
            if !invalid {
                ret.push(seq_ret);
                pattern_ind += 1;
            }
        }
    }
    Ok(ret)
}

/// Write test patterns in Atalanta format
///
/// Each pattern may contain multiple timesteps. For each timestep, the value of each circuit input is given.
/// The patterns are formatted as follows:
/// ```text
///     * This is a comment
///
///     * Input pattern with five input values set to zero
///     1: 00000
///
///     * The index increments at each pattern
///     2: 00000
///
///     * A pattern that contains three timesteps
///     3: 01110 00111 01000
/// ```
pub fn write_patterns<W: Write>(w: &mut W, patterns: &Vec<Vec<Vec<bool>>>) {
    writeln!(w, "* Test pattern file").unwrap();
    writeln!(w, "* generated by quaigh").unwrap();
    for (i, v) in patterns.iter().enumerate() {
        write!(w, "{}:", i + 1).unwrap();
        for seq_pattern in v {
            write!(w, " ").unwrap();
            for inp_value in seq_pattern {
                write!(w, "{}", if *inp_value { "1" } else { "0" }).unwrap();
            }
        }
        writeln!(w).unwrap();
    }
}

mod test {
    #[test]
    fn test_read_pattern() {
        let example = "  * comment1
*comment2
1: 00000 00000
2: 01010  11111\t11111
3:
:00000
*comment 3
5: 00000
00110";
        let patterns = super::read_patterns(example.as_bytes()).unwrap();
        assert_eq!(patterns.len(), 6);
        assert_eq!(
            patterns[0],
            vec![
                vec![false, false, false, false, false],
                vec![false, false, false, false, false]
            ]
        );
        assert_eq!(
            patterns[1],
            vec![
                vec![false, true, false, true, false],
                vec![true, true, true, true, true],
                vec![true, true, true, true, true]
            ]
        );
        assert_eq!(patterns[2], Vec::<Vec<bool>>::new());
        assert_eq!(patterns[3], vec![vec![false, false, false, false, false],]);
        assert_eq!(patterns[4], vec![vec![false, false, false, false, false],]);
        assert_eq!(patterns[5], vec![vec![false, false, true, true, false],]);
    }

    #[test]
    fn test_write_pattern() {
        use std::io::BufWriter;

        let example = vec![
            vec![vec![false, true], vec![true, false]],
            vec![vec![true, true]],
        ];
        let mut buf = BufWriter::new(Vec::new());
        super::write_patterns(&mut buf, &example);
        let s = String::from_utf8(buf.into_inner().unwrap()).unwrap();
        assert_eq!(
            s,
            "* Test pattern file
* generated by quaigh
1: 01 10
2: 11
"
        );
    }
}