1use comp_cat_rs::collapse::free_category::FreeCategoryError;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub struct Width(u32);
25
26impl Width {
27 #[must_use]
29 pub fn new(bits: u32) -> Self {
30 Self(bits)
31 }
32
33 #[must_use]
35 pub fn bits(self) -> u32 {
36 self.0
37 }
38}
39
40impl core::fmt::Display for Width {
41 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
42 write!(f, "{} bit(s)", self.0)
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
51pub struct Cycle(u64);
52
53impl Cycle {
54 #[must_use]
56 pub fn new(index: u64) -> Self {
57 Self(index)
58 }
59
60 #[must_use]
62 pub fn index(self) -> u64 {
63 self.0
64 }
65}
66
67impl core::fmt::Display for Cycle {
68 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
69 write!(f, "cycle {}", self.0)
70 }
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Hash)]
79pub struct TypeName(String);
80
81impl TypeName {
82 pub fn new(name: impl Into<String>) -> Self {
84 Self(name.into())
85 }
86
87 #[must_use]
89 pub fn as_str(&self) -> &str {
90 &self.0
91 }
92}
93
94impl core::fmt::Display for TypeName {
95 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
96 f.write_str(&self.0)
97 }
98}
99
100#[derive(Debug, Clone, PartialEq, Eq, Hash)]
102pub struct SignalName(String);
103
104impl SignalName {
105 pub fn new(name: impl Into<String>) -> Self {
107 Self(name.into())
108 }
109
110 #[must_use]
112 pub fn as_str(&self) -> &str {
113 &self.0
114 }
115}
116
117impl core::fmt::Display for SignalName {
118 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
119 f.write_str(&self.0)
120 }
121}
122
123#[derive(Debug)]
144pub enum Error {
145 Io(std::io::Error),
147
148 Fmt(core::fmt::Error),
150
151 ParseInt(core::num::ParseIntError),
153
154 FreeCategory(FreeCategoryError),
156
157 WidthMismatch {
159 expected: Width,
161 actual: Width,
163 },
164
165 TypeMismatch {
167 expected: TypeName,
169 actual: TypeName,
171 },
172
173 ClockDomainMismatch,
175
176 UndefinedSignal {
178 name: SignalName,
180 },
181
182 ImmatureSim {
185 cycle: Cycle,
187 },
188
189 Overflow {
191 width: Width,
193 },
194}
195
196impl core::fmt::Display for Error {
197 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
198 match self {
199 Self::Io(e) => write!(f, "I/O error: {e}"),
200 Self::Fmt(e) => write!(f, "formatter error: {e}"),
201 Self::ParseInt(e) => write!(f, "parse-int error: {e}"),
202 Self::FreeCategory(e) => write!(f, "free-category error: {e}"),
203 Self::WidthMismatch { expected, actual } => {
204 write!(f, "width mismatch: expected {expected}, got {actual}")
205 }
206 Self::TypeMismatch { expected, actual } => {
207 write!(f, "type mismatch: expected {expected}, got {actual}")
208 }
209 Self::ClockDomainMismatch => f.write_str("clock domain mismatch"),
210 Self::UndefinedSignal { name } => write!(f, "undefined signal: {name}"),
211 Self::ImmatureSim { cycle } => {
212 write!(f, "simulation read before first clock edge at {cycle}")
213 }
214 Self::Overflow { width } => write!(f, "arithmetic overflow at {width}"),
215 }
216 }
217}
218
219impl std::error::Error for Error {
220 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
221 match self {
222 Self::Io(e) => Some(e),
223 Self::Fmt(e) => Some(e),
224 Self::ParseInt(e) => Some(e),
225 Self::FreeCategory(e) => Some(e),
226 Self::WidthMismatch { .. }
227 | Self::TypeMismatch { .. }
228 | Self::ClockDomainMismatch
229 | Self::UndefinedSignal { .. }
230 | Self::ImmatureSim { .. }
231 | Self::Overflow { .. } => None,
232 }
233 }
234}
235
236impl From<std::io::Error> for Error {
237 fn from(e: std::io::Error) -> Self {
238 Self::Io(e)
239 }
240}
241
242impl From<core::fmt::Error> for Error {
243 fn from(e: core::fmt::Error) -> Self {
244 Self::Fmt(e)
245 }
246}
247
248impl From<core::num::ParseIntError> for Error {
249 fn from(e: core::num::ParseIntError) -> Self {
250 Self::ParseInt(e)
251 }
252}
253
254impl From<FreeCategoryError> for Error {
255 fn from(e: FreeCategoryError) -> Self {
256 Self::FreeCategory(e)
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::{Cycle, Error, SignalName, TypeName, Width};
263
264 #[test]
265 fn width_mismatch_displays_both_widths() {
266 let e = Error::WidthMismatch {
267 expected: Width::new(8),
268 actual: Width::new(4),
269 };
270 assert_eq!(
271 e.to_string(),
272 "width mismatch: expected 8 bit(s), got 4 bit(s)",
273 );
274 }
275
276 #[test]
277 fn type_mismatch_displays_both_names() {
278 let e = Error::TypeMismatch {
279 expected: TypeName::new("Bits<8>"),
280 actual: TypeName::new("Bits<4>"),
281 };
282 assert_eq!(e.to_string(), "type mismatch: expected Bits<8>, got Bits<4>");
283 }
284
285 #[test]
286 fn clock_domain_mismatch_displays_message() {
287 assert_eq!(Error::ClockDomainMismatch.to_string(), "clock domain mismatch");
288 }
289
290 #[test]
291 fn undefined_signal_displays_name() {
292 let e = Error::UndefinedSignal {
293 name: SignalName::new("clk"),
294 };
295 assert_eq!(e.to_string(), "undefined signal: clk");
296 }
297
298 #[test]
299 fn immature_sim_displays_cycle() {
300 let e = Error::ImmatureSim { cycle: Cycle::new(0) };
301 assert_eq!(
302 e.to_string(),
303 "simulation read before first clock edge at cycle 0",
304 );
305 }
306
307 #[test]
308 fn overflow_displays_width() {
309 let e = Error::Overflow { width: Width::new(16) };
310 assert_eq!(e.to_string(), "arithmetic overflow at 16 bit(s)");
311 }
312
313 #[test]
314 fn from_io_error_wraps_without_loss() {
315 let io = std::io::Error::new(std::io::ErrorKind::NotFound, "nope");
316 let e: Error = io.into();
317 let msg = e.to_string();
318 assert!(msg.starts_with("I/O error: "));
319 assert!(msg.contains("nope"));
320 }
321
322 #[test]
323 fn question_mark_propagates_parse_int_into_error() -> Result<(), Error> {
324 let n: i32 = "42".parse()?;
325 assert_eq!(n, 42);
326 Ok(())
327 }
328
329 #[test]
330 fn width_round_trips_through_accessor() {
331 assert_eq!(Width::new(12).bits(), 12);
332 }
333
334 #[test]
335 fn cycle_round_trips_through_accessor() {
336 assert_eq!(Cycle::new(7).index(), 7);
337 }
338
339 #[test]
340 fn type_name_round_trips_through_accessor() {
341 assert_eq!(TypeName::new("Bool").as_str(), "Bool");
342 }
343
344 #[test]
345 fn signal_name_round_trips_through_accessor() {
346 assert_eq!(SignalName::new("rst").as_str(), "rst");
347 }
348}