1use comnum_core::{Error, Result};
36use std::fmt;
37
38#[derive(Clone, Debug, PartialEq)]
40pub enum Value {
41 F64(f64),
43 I64(i64),
45 Str(String),
47 Bool(bool),
49}
50
51impl Value {
52 pub fn as_f64(&self) -> Option<f64> {
54 match self {
55 Value::F64(v) => Some(*v),
56 Value::I64(v) => Some(*v as f64),
57 _ => None,
58 }
59 }
60}
61
62impl fmt::Display for Value {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 match self {
65 Value::F64(v) => write!(f, "{v}"),
66 Value::I64(v) => write!(f, "{v}"),
67 Value::Str(v) => write!(f, "{v}"),
68 Value::Bool(v) => write!(f, "{v}"),
69 }
70 }
71}
72
73impl From<f64> for Value {
74 fn from(v: f64) -> Self {
75 Value::F64(v)
76 }
77}
78impl From<f32> for Value {
79 fn from(v: f32) -> Self {
80 Value::F64(v as f64)
81 }
82}
83impl From<i64> for Value {
84 fn from(v: i64) -> Self {
85 Value::I64(v)
86 }
87}
88impl From<i32> for Value {
89 fn from(v: i32) -> Self {
90 Value::I64(v as i64)
91 }
92}
93impl From<usize> for Value {
94 fn from(v: usize) -> Self {
95 Value::I64(v as i64)
96 }
97}
98impl From<u32> for Value {
99 fn from(v: u32) -> Self {
100 Value::I64(v as i64)
101 }
102}
103impl From<&str> for Value {
104 fn from(v: &str) -> Self {
105 Value::Str(v.to_string())
106 }
107}
108impl From<String> for Value {
109 fn from(v: String) -> Self {
110 Value::Str(v)
111 }
112}
113impl From<bool> for Value {
114 fn from(v: bool) -> Self {
115 Value::Bool(v)
116 }
117}
118
119#[derive(Clone, Debug, Default, PartialEq)]
121pub struct Condition {
122 entries: Vec<(String, Value)>,
123}
124
125impl Condition {
126 pub fn new() -> Condition {
128 Condition::default()
129 }
130
131 pub fn with(mut self, key: &str, value: impl Into<Value>) -> Condition {
133 self.set(key, value);
134 self
135 }
136
137 pub fn set(&mut self, key: &str, value: impl Into<Value>) {
139 let v = value.into();
140 match self.entries.iter_mut().find(|(k, _)| k == key) {
141 Some(e) => e.1 = v,
142 None => self.entries.push((key.to_string(), v)),
143 }
144 }
145
146 pub fn entries(&self) -> &[(String, Value)] {
148 &self.entries
149 }
150
151 pub fn keys(&self) -> impl Iterator<Item = &str> {
153 self.entries.iter().map(|(k, _)| k.as_str())
154 }
155
156 pub fn get(&self, key: &str) -> Option<&Value> {
158 self.entries.iter().find(|(k, _)| k == key).map(|(_, v)| v)
159 }
160
161 fn need(&self, key: &str) -> Result<&Value> {
162 self.get(key).ok_or_else(|| Error::Condition(format!("no key {key:?} in ({})", self.label())))
163 }
164
165 pub fn f64(&self, key: &str) -> Result<f64> {
170 self.need(key)?.as_f64().ok_or_else(|| Error::Condition(format!("{key:?} is not a number")))
171 }
172
173 pub fn i64(&self, key: &str) -> Result<i64> {
178 match self.need(key)? {
179 Value::I64(v) => Ok(*v),
180 Value::F64(v) if v.fract() == 0.0 => Ok(*v as i64),
181 _ => Err(Error::Condition(format!("{key:?} is not an integer"))),
182 }
183 }
184
185 pub fn usize(&self, key: &str) -> Result<usize> {
190 let v = self.i64(key)?;
191 usize::try_from(v).map_err(|_| Error::Condition(format!("{key:?} = {v} is negative")))
192 }
193
194 pub fn str(&self, key: &str) -> Result<&str> {
199 match self.need(key)? {
200 Value::Str(s) => Ok(s),
201 _ => Err(Error::Condition(format!("{key:?} is not a string"))),
202 }
203 }
204
205 pub fn bool(&self, key: &str) -> Result<bool> {
210 match self.need(key)? {
211 Value::Bool(b) => Ok(*b),
212 _ => Err(Error::Condition(format!("{key:?} is not a boolean"))),
213 }
214 }
215
216 pub fn label(&self) -> String {
218 self.entries.iter().map(|(k, v)| format!("{k}={v}")).collect::<Vec<_>>().join(", ")
219 }
220}
221
222#[derive(Clone, Debug, PartialEq)]
223enum Part {
224 Axis(String, Vec<Value>),
225 Zip(Vec<String>, Vec<Vec<Value>>),
226 Fixed(String, Value),
227}
228
229#[derive(Clone, Debug, Default, PartialEq)]
231pub struct Grid {
232 parts: Vec<Part>,
233 explicit: Option<Vec<Condition>>,
234}
235
236impl Grid {
237 pub fn new() -> Grid {
239 Grid::default()
240 }
241
242 pub fn from_conditions(conditions: Vec<Condition>) -> Grid {
244 Grid { parts: Vec::new(), explicit: Some(conditions) }
245 }
246
247 pub fn axis<V: Into<Value>>(mut self, name: &str, values: impl IntoIterator<Item = V>) -> Grid {
249 self.parts.push(Part::Axis(name.to_string(), values.into_iter().map(Into::into).collect()));
250 self
251 }
252
253 pub fn zip(mut self, names: &[&str], rows: Vec<Vec<Value>>) -> Result<Grid> {
267 if let Some((i, r)) = rows.iter().enumerate().find(|(_, r)| r.len() != names.len()) {
268 return Err(Error::Shape(format!(
269 "grid: row {i} of the zip of {names:?} has {} values for {} names",
270 r.len(),
271 names.len()
272 )));
273 }
274 self.parts.push(Part::Zip(names.iter().map(std::string::ToString::to_string).collect(), rows));
275 Ok(self)
276 }
277
278 pub fn fixed(mut self, name: &str, value: impl Into<Value>) -> Grid {
280 self.parts.push(Part::Fixed(name.to_string(), value.into()));
281 self
282 }
283
284 pub fn check(&self) -> Result<()> {
300 let mut seen: Vec<&str> = Vec::new();
301 for p in &self.parts {
302 let names: Vec<&str> = match p {
303 Part::Axis(n, _) | Part::Fixed(n, _) => vec![n.as_str()],
304 Part::Zip(names, _) => names.iter().map(String::as_str).collect(),
305 };
306 for n in names {
307 let explicit = self.explicit.iter().flatten().any(|c| c.get(n).is_some());
308 if seen.contains(&n) || explicit {
309 return Err(Error::Config(format!(
310 "grid: the key {n:?} twice{}: the second value would replace the first and repeat conditions",
311 if explicit { " (in the explicit conditions and an axis)" } else { "" }
312 )));
313 }
314 seen.push(n);
315 }
316 }
317 Ok(())
318 }
319
320 pub fn conditions(&self) -> Vec<Condition> {
322 let mut out = vec![Condition::new()];
323 for p in &self.parts {
324 let mut next = Vec::new();
325 for c in &out {
326 match p {
327 Part::Axis(name, values) => {
328 for v in values {
329 next.push(c.clone().with(name, v.clone()));
330 }
331 }
332 Part::Zip(names, rows) => {
333 for row in rows {
334 let mut c2 = c.clone();
335 for (n, v) in names.iter().zip(row) {
336 c2.set(n, v.clone());
337 }
338 next.push(c2);
339 }
340 }
341 Part::Fixed(name, v) => next.push(c.clone().with(name, v.clone())),
342 }
343 }
344 out = next;
345 }
346 match &self.explicit {
347 Some(list) => list
348 .iter()
349 .flat_map(|e| {
350 out.iter().map(move |c| {
351 let mut m = e.clone();
352 for (k, v) in c.entries() {
353 m.set(k, v.clone());
354 }
355 m
356 })
357 })
358 .collect(),
359 None => out,
360 }
361 }
362
363 pub fn len(&self) -> usize {
365 self.conditions().len()
366 }
367
368 pub fn is_empty(&self) -> bool {
370 self.len() == 0
371 }
372}