use smol_str::SmolStr;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct BusBitChars {
pub open: char,
pub close: char,
}
impl Default for BusBitChars {
fn default() -> Self {
Self {
open: '[',
close: ']',
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BusName {
pub base: SmolStr,
pub lo: i32,
pub hi: i32,
pub ascending: bool,
}
impl BusName {
pub fn expand(&self, chars: BusBitChars) -> impl Iterator<Item = String> + '_ {
let (start, step): (i32, i32) = if self.ascending {
(self.lo, 1)
} else {
(self.hi, -1)
};
let count = (self.hi - self.lo).unsigned_abs() as usize + 1;
(0..count).map(move |k| {
let i = start + (k as i32) * step;
format!("{}{}{i}{}", self.base, chars.open, chars.close)
})
}
pub fn width(&self) -> u32 {
(self.hi - self.lo).unsigned_abs() + 1
}
}
pub fn parse_bus(s: &str, chars: BusBitChars) -> Option<BusName> {
let open = s.find(chars.open)?;
let close = s.find(chars.close)?;
if open >= close {
return None;
}
let base = SmolStr::from(s[..open].trim());
let inside = &s[open + 1..close];
let parts: Vec<&str> = inside.split(':').collect();
let (a, b) = match parts.as_slice() {
[single] => {
let n: i32 = single.trim().parse().ok()?;
(n, n)
}
[a, b] => {
let a: i32 = a.trim().parse().ok()?;
let b: i32 = b.trim().parse().ok()?;
(a, b)
}
_ => return None,
};
let lo = a.min(b);
let hi = a.max(b);
let ascending = a <= b;
Some(BusName {
base,
lo,
hi,
ascending,
})
}
pub fn format_bus_pin(base: &str, index: i32, chars: BusBitChars) -> String {
format!("{base}{}{index}{}", chars.open, chars.close)
}
#[derive(Clone, Debug)]
pub struct Bus {
pub name: BusName,
pub member_pins: Vec<SmolStr>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_descending_range() {
let b = parse_bus("DATA[3:0]", BusBitChars::default()).unwrap();
assert_eq!(b.base.as_str(), "DATA");
assert_eq!(b.lo, 0);
assert_eq!(b.hi, 3);
assert!(!b.ascending);
assert_eq!(b.width(), 4);
}
#[test]
fn parse_ascending_range() {
let b = parse_bus("Q[0:7]", BusBitChars::default()).unwrap();
assert!(b.ascending);
assert_eq!(b.width(), 8);
}
#[test]
fn parse_single_bit() {
let b = parse_bus("A[5]", BusBitChars::default()).unwrap();
assert_eq!(b.lo, 5);
assert_eq!(b.hi, 5);
assert_eq!(b.width(), 1);
}
#[test]
fn parse_returns_none_for_scalar() {
assert!(parse_bus("scalar_pin", BusBitChars::default()).is_none());
}
#[test]
fn alternate_bit_chars() {
let chars = BusBitChars {
open: '<',
close: '>',
};
let b = parse_bus("D<7:0>", chars).unwrap();
assert_eq!(b.width(), 8);
assert_eq!(b.base.as_str(), "D");
}
#[test]
fn expand_yields_scalar_names() {
let b = parse_bus("A[3:0]", BusBitChars::default()).unwrap();
let names: Vec<String> = b.expand(BusBitChars::default()).collect();
assert_eq!(names, vec!["A[3]", "A[2]", "A[1]", "A[0]"]);
}
#[test]
fn ascending_expand_preserves_direction() {
let b = parse_bus("A[0:3]", BusBitChars::default()).unwrap();
let names: Vec<String> = b.expand(BusBitChars::default()).collect();
assert_eq!(names, vec!["A[0]", "A[1]", "A[2]", "A[3]"]);
}
#[test]
fn format_bus_pin_uses_chosen_chars() {
let s = format_bus_pin("VAL", 42, BusBitChars { open: '<', close: '>' });
assert_eq!(s, "VAL<42>");
}
}