use comnum_core::{Error, Result};
use std::fmt;
#[derive(Clone, Debug, PartialEq)]
pub enum Value {
F64(f64),
I64(i64),
Str(String),
Bool(bool),
}
impl Value {
pub fn as_f64(&self) -> Option<f64> {
match self {
Value::F64(v) => Some(*v),
Value::I64(v) => Some(*v as f64),
_ => None,
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::F64(v) => write!(f, "{v}"),
Value::I64(v) => write!(f, "{v}"),
Value::Str(v) => write!(f, "{v}"),
Value::Bool(v) => write!(f, "{v}"),
}
}
}
impl From<f64> for Value {
fn from(v: f64) -> Self {
Value::F64(v)
}
}
impl From<f32> for Value {
fn from(v: f32) -> Self {
Value::F64(v as f64)
}
}
impl From<i64> for Value {
fn from(v: i64) -> Self {
Value::I64(v)
}
}
impl From<i32> for Value {
fn from(v: i32) -> Self {
Value::I64(v as i64)
}
}
impl From<usize> for Value {
fn from(v: usize) -> Self {
Value::I64(v as i64)
}
}
impl From<u32> for Value {
fn from(v: u32) -> Self {
Value::I64(v as i64)
}
}
impl From<&str> for Value {
fn from(v: &str) -> Self {
Value::Str(v.to_string())
}
}
impl From<String> for Value {
fn from(v: String) -> Self {
Value::Str(v)
}
}
impl From<bool> for Value {
fn from(v: bool) -> Self {
Value::Bool(v)
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Condition {
entries: Vec<(String, Value)>,
}
impl Condition {
pub fn new() -> Condition {
Condition::default()
}
pub fn with(mut self, key: &str, value: impl Into<Value>) -> Condition {
self.set(key, value);
self
}
pub fn set(&mut self, key: &str, value: impl Into<Value>) {
let v = value.into();
match self.entries.iter_mut().find(|(k, _)| k == key) {
Some(e) => e.1 = v,
None => self.entries.push((key.to_string(), v)),
}
}
pub fn entries(&self) -> &[(String, Value)] {
&self.entries
}
pub fn keys(&self) -> impl Iterator<Item = &str> {
self.entries.iter().map(|(k, _)| k.as_str())
}
pub fn get(&self, key: &str) -> Option<&Value> {
self.entries.iter().find(|(k, _)| k == key).map(|(_, v)| v)
}
fn need(&self, key: &str) -> Result<&Value> {
self.get(key).ok_or_else(|| Error::Condition(format!("no key {key:?} in ({})", self.label())))
}
pub fn f64(&self, key: &str) -> Result<f64> {
self.need(key)?.as_f64().ok_or_else(|| Error::Condition(format!("{key:?} is not a number")))
}
pub fn i64(&self, key: &str) -> Result<i64> {
match self.need(key)? {
Value::I64(v) => Ok(*v),
Value::F64(v) if v.fract() == 0.0 => Ok(*v as i64),
_ => Err(Error::Condition(format!("{key:?} is not an integer"))),
}
}
pub fn usize(&self, key: &str) -> Result<usize> {
let v = self.i64(key)?;
usize::try_from(v).map_err(|_| Error::Condition(format!("{key:?} = {v} is negative")))
}
pub fn str(&self, key: &str) -> Result<&str> {
match self.need(key)? {
Value::Str(s) => Ok(s),
_ => Err(Error::Condition(format!("{key:?} is not a string"))),
}
}
pub fn bool(&self, key: &str) -> Result<bool> {
match self.need(key)? {
Value::Bool(b) => Ok(*b),
_ => Err(Error::Condition(format!("{key:?} is not a boolean"))),
}
}
pub fn label(&self) -> String {
self.entries.iter().map(|(k, v)| format!("{k}={v}")).collect::<Vec<_>>().join(", ")
}
}
#[derive(Clone, Debug, PartialEq)]
enum Part {
Axis(String, Vec<Value>),
Zip(Vec<String>, Vec<Vec<Value>>),
Fixed(String, Value),
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Grid {
parts: Vec<Part>,
explicit: Option<Vec<Condition>>,
}
impl Grid {
pub fn new() -> Grid {
Grid::default()
}
pub fn from_conditions(conditions: Vec<Condition>) -> Grid {
Grid { parts: Vec::new(), explicit: Some(conditions) }
}
pub fn axis<V: Into<Value>>(mut self, name: &str, values: impl IntoIterator<Item = V>) -> Grid {
self.parts.push(Part::Axis(name.to_string(), values.into_iter().map(Into::into).collect()));
self
}
pub fn zip(mut self, names: &[&str], rows: Vec<Vec<Value>>) -> Result<Grid> {
if let Some((i, r)) = rows.iter().enumerate().find(|(_, r)| r.len() != names.len()) {
return Err(Error::Shape(format!(
"grid: row {i} of the zip of {names:?} has {} values for {} names",
r.len(),
names.len()
)));
}
self.parts.push(Part::Zip(names.iter().map(std::string::ToString::to_string).collect(), rows));
Ok(self)
}
pub fn fixed(mut self, name: &str, value: impl Into<Value>) -> Grid {
self.parts.push(Part::Fixed(name.to_string(), value.into()));
self
}
pub fn check(&self) -> Result<()> {
let mut seen: Vec<&str> = Vec::new();
for p in &self.parts {
let names: Vec<&str> = match p {
Part::Axis(n, _) | Part::Fixed(n, _) => vec![n.as_str()],
Part::Zip(names, _) => names.iter().map(String::as_str).collect(),
};
for n in names {
let explicit = self.explicit.iter().flatten().any(|c| c.get(n).is_some());
if seen.contains(&n) || explicit {
return Err(Error::Config(format!(
"grid: the key {n:?} twice{}: the second value would replace the first and repeat conditions",
if explicit { " (in the explicit conditions and an axis)" } else { "" }
)));
}
seen.push(n);
}
}
Ok(())
}
pub fn conditions(&self) -> Vec<Condition> {
let mut out = vec![Condition::new()];
for p in &self.parts {
let mut next = Vec::new();
for c in &out {
match p {
Part::Axis(name, values) => {
for v in values {
next.push(c.clone().with(name, v.clone()));
}
}
Part::Zip(names, rows) => {
for row in rows {
let mut c2 = c.clone();
for (n, v) in names.iter().zip(row) {
c2.set(n, v.clone());
}
next.push(c2);
}
}
Part::Fixed(name, v) => next.push(c.clone().with(name, v.clone())),
}
}
out = next;
}
match &self.explicit {
Some(list) => list
.iter()
.flat_map(|e| {
out.iter().map(move |c| {
let mut m = e.clone();
for (k, v) in c.entries() {
m.set(k, v.clone());
}
m
})
})
.collect(),
None => out,
}
}
pub fn len(&self) -> usize {
self.conditions().len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}