1use std::collections::BTreeMap;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct DimExpr {
17 text: String,
18 terms: Vec<Vec<Factor>>,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
22enum Factor {
23 Name(String),
24 Lit(u64),
25 CeilDiv(String, u64),
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
31pub enum DimError {
32 #[error("bad dimension expression `{0}`")]
34 Syntax(String),
35 #[error("dimension `{name}` in `{expr}` is not defined by the arch shape")]
37 Unknown {
38 name: String,
40 expr: String,
42 },
43 #[error("dimension expression `{0}` overflows")]
45 Overflow(String),
46}
47
48impl DimExpr {
49 pub fn parse(text: &str) -> Result<Self, DimError> {
51 let syntax = || DimError::Syntax(text.to_string());
52 let compact: String = text.chars().filter(|c| !c.is_whitespace()).collect();
53 if compact.is_empty() {
54 return Err(syntax());
55 }
56 let mut terms = Vec::new();
57 for term in compact.split('+') {
58 let mut factors = Vec::new();
59 for f in term.split('*') {
60 factors.push(parse_factor(f).ok_or_else(syntax)?);
61 }
62 terms.push(factors);
63 }
64 Ok(DimExpr {
65 text: compact,
66 terms,
67 })
68 }
69
70 pub fn text(&self) -> &str {
72 &self.text
73 }
74
75 pub fn names(&self) -> impl Iterator<Item = &str> {
77 self.terms.iter().flatten().filter_map(|f| match f {
78 Factor::Name(n) | Factor::CeilDiv(n, _) => Some(n.as_str()),
79 Factor::Lit(_) => None,
80 })
81 }
82
83 pub fn eval(&self, dims: &BTreeMap<String, u64>) -> Result<u64, DimError> {
85 let overflow = || DimError::Overflow(self.text.clone());
86 let mut sum: u64 = 0;
87 for term in &self.terms {
88 let mut prod: u64 = 1;
89 for f in term {
90 let dim = |n: &String| {
91 dims.get(n).copied().ok_or_else(|| DimError::Unknown {
92 name: n.clone(),
93 expr: self.text.clone(),
94 })
95 };
96 let v = match f {
97 Factor::Lit(v) => *v,
98 Factor::Name(n) => dim(n)?,
99 Factor::CeilDiv(n, d) => dim(n)?.div_ceil(*d),
100 };
101 prod = prod.checked_mul(v).ok_or_else(overflow)?;
102 }
103 sum = sum.checked_add(prod).ok_or_else(overflow)?;
104 }
105 Ok(sum)
106 }
107}
108
109fn parse_factor(f: &str) -> Option<Factor> {
110 if let Some((name, lit)) = f.split_once('/') {
111 let d = lit.parse::<u64>().ok().filter(|&v| v > 0)?;
112 return match parse_factor(name)? {
113 Factor::Name(n) => Some(Factor::CeilDiv(n, d)),
114 Factor::Lit(_) | Factor::CeilDiv(..) => None,
115 };
116 }
117 let first = f.chars().next()?;
118 if first.is_ascii_digit() {
119 return f.parse::<u64>().ok().filter(|&v| v > 0).map(Factor::Lit);
120 }
121 let ident = (first.is_ascii_lowercase() || first == '_')
122 && f.chars()
123 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_');
124 ident.then(|| Factor::Name(f.to_string()))
125}
126
127#[cfg(test)]
128#[path = "dims_tests.rs"]
129mod dims_tests;