vcd_ng/fastflow.rs
1//! Fast Flow: an ultrafast way to parse VCD signal parts.
2//!
3//! This module provides [`FastFlow`], a Reader-like iterator interface
4//! which is very similar to [`vcd_ng::Parser`], but faster.
5//!
6//! The magic behind this module is the reuse of buffer spaces to
7//! represent long signals, and the elimination of temporary memory
8//! allocations.
9//!
10//! Some compatibility has been sacrificed in return for speed. Notably:
11//! 0. We only support timestamps and value changes. All other operations
12//! are intentionally ignored and do not produce tokens.
13//! 1. We assert for newline characters after each timestamp `#xxx`
14//! as well as bit change lines.
15//! Previously, both newline and other whitespaces could be used.
16//! This helps us to identify timestamp from the middle of an input.
17//! 2. We do not support lines that are itself too long (which means
18//! EXTREMELY long, longer than the whole buffer size).
19//! This should not be a problem with a large buffer like 1MB,
20//! unless one user becomes insane and defines a million-sized
21//! bit vector.
22
23use crate::{ IdCode, InvalidData };
24use std::io::{ self, Read };
25use linereader::LineReader;
26
27/// An enum of tokens that fast flow supports.
28#[derive(Debug, PartialEq, Eq, Clone)]
29pub enum FastFlowToken<'i> {
30 Timestamp(u64),
31 Value(FFValueChange<'i>)
32}
33
34/// A value change token.
35///
36/// It uses a reference to the internal buffer of the [`FastFlow`]
37/// parser object to speed up the retrieval of signals.
38#[derive(Debug, PartialEq, Eq, Clone)]
39pub struct FFValueChange<'i> {
40 /// The symbolic index of the signal to change
41 pub id: IdCode,
42 /// A byte slice of signal changes. Each byte can be
43 /// `0`, `1`, `x`, or `z`.
44 pub bits: &'i [u8]
45}
46
47/// Fast token stream of timestamp and value changes.
48/// See the module-level documentation for details.
49pub struct FastFlow<R: Read> {
50 /// The line reader inner object
51 line_reader: LineReader<R>,
52 /// The bytes already read since the start.
53 bytes_read: usize
54}
55
56impl<R: Read> FastFlow<R> {
57 /// Create a new FastFlow from a Read object and a buffer size.
58 ///
59 /// It is recommended that we do NOT use BufReaders here, because
60 /// we have built-in buffers in FastFlow. Doubling the buffer
61 /// introduces unnecessary overhead.
62 pub fn new(source: R, buf_size: usize) -> FastFlow<R> {
63 FastFlow {
64 line_reader: LineReader::with_capacity(buf_size, source),
65 bytes_read: 0
66 }
67 }
68
69 /// Get number of bytes that have been read.
70 pub fn bytes_read(&self) -> usize {
71 self.bytes_read
72 }
73
74 /// Read a line.
75 /// This records the number of bytes read.
76 #[inline]
77 pub fn next_line<'i>(&'i mut self) -> io::Result<Option<&'i [u8]>> {
78 match self.line_reader.next_line() {
79 None => Ok(None),
80 Some(Err(e)) => Err(e),
81 Some(Ok(line)) => {
82 self.bytes_read += line.len();
83 Ok(Some(&line[..line.len() - 1]))
84 }
85 }
86 }
87
88 /// Skip a line.
89 #[inline]
90 pub fn skip_line(&mut self) -> io::Result<()> {
91 let _ = self.next_line()?;
92 Ok(())
93 }
94
95 /// Read a token.
96 pub fn next_token<'i>(&'i mut self) -> io::Result<Option<FastFlowToken<'i>>> {
97 while let Some(line) = unsafe {
98 // The following unsafe transform is NEEDED.
99 // If we use &'i mut self here, it will leave a footprint
100 // that marks lifetime 'i as *mutated* (exclusively used)
101 // inside the loop.
102 // As a result, we cannot return the value to outside.
103 // This is known as a limitation of current Rust borrow checker.
104 // See https://github.com/rust-lang/rust/issues/68117.
105 &mut *(self as *mut FastFlow<R>) // kicks off the lifetime
106 // We are safe as long as the reference next_line() returns
107 // can outlive 'i **in reality**.
108 }.next_line()? {
109 let line: &'i [u8] = line;
110 // ok, we are safe now with the assumption above.
111
112 if line.len() == 0 { continue }
113 return Ok(Some(match line[0] {
114 b'#' => FastFlowToken::Timestamp(
115 atoi_radix10::parse(&line[1..]).map_err(
116 |_| io::Error::from(InvalidData("parse timestamp failed")))?
117 ),
118 b'0' | b'1' | b'x' | b'z' => FastFlowToken::Value(
119 FFValueChange { id: IdCode::new(&line[1..])?,
120 bits: &line[0..1] }
121 ),
122 b'b' => match line.iter().rposition(|c| *c == b' ') {
123 Some(i) => FastFlowToken::Value(FFValueChange {
124 id: IdCode::new(&line[i + 1..])?,
125 bits: &line[1..i]
126 }),
127 None => return Err(
128 InvalidData("vec value w/o space").into())
129 },
130 b'$' | b'\t' | b' ' => continue,
131 _ => {
132 return Err(InvalidData(
133 "unexpected line in vcd, which is unrecognized \
134 by FastFlow. please try normal parser \
135 instead.").into())
136 }
137 }))
138 }
139 Ok(None) // EOF
140 }
141
142 /// Read the first complete timestamp token, skipping all other tokens
143 /// around it.
144 ///
145 /// This function looks for a "\n#" pattern. Thus, it will skip at
146 /// least one line in the input byte stream.
147 pub fn first_timestamp(&mut self) -> io::Result<Option<u64>> {
148 self.next_line()?;
149 while let Some(line) = self.next_line()? {
150 if line.len() == 0 || line[0] != b'#' { continue }
151 return Ok(Some(atoi_radix10::parse::<u64>(&line[1..]).map_err(
152 |_| io::Error::from(InvalidData("parse first timestamp failed")))?))
153 }
154 Ok(None) // EOF before even a first timestamp.
155 }
156
157 /// Unwraps this `FastFlow` and returns the underlying reader.
158 /// All unread buffered lines will be discarded.
159 pub fn into_inner(self) -> R {
160 self.line_reader.into_inner()
161 }
162}
163
164#[test]
165fn test_fastflow() {
166 let buf = br###"
167$enddefinitions $end
168
169#0
170$dumpvars
1710$Q
1720"#o
173#250
1741!
175b00000000000000000000000000000000 #0
176bxxxx $o
177"###;
178 let mut f = FastFlow::new(&buf[..], 64);
179 assert_eq!(f.first_timestamp().unwrap(), Some(0));
180 assert_eq!(f.next_token().unwrap(),
181 Some(FastFlowToken::Value(FFValueChange {
182 id: IdCode::new(&b"$Q"[..]).unwrap(), bits: b"0"
183 })));
184 assert_eq!(f.next_token().unwrap(),
185 Some(FastFlowToken::Value(FFValueChange {
186 id: IdCode::new(&b"\"#o"[..]).unwrap(), bits: b"0"
187 })));
188 assert_eq!(f.next_token().unwrap(),
189 Some(FastFlowToken::Timestamp(250)));
190 assert_eq!(f.next_token().unwrap(),
191 Some(FastFlowToken::Value(FFValueChange {
192 id: IdCode::new(&b"!"[..]).unwrap(), bits: b"1"
193 })));
194 assert_eq!(f.next_token().unwrap(),
195 Some(FastFlowToken::Value(FFValueChange {
196 id: IdCode::new(&b"#0"[..]).unwrap(), bits: b"00000000000000000000000000000000"
197 })));
198 assert_eq!(f.next_token().unwrap(),
199 Some(FastFlowToken::Value(FFValueChange {
200 id: IdCode::new(&b"$o"[..]).unwrap(), bits: b"xxxx"
201 })));
202 assert_eq!(f.next_token().unwrap(), None);
203 assert_eq!(f.bytes_read(), buf.len());
204}