use std::sync::LazyLock;
use evalexpr::{ContextWithMutableVariables, DefaultNumericTypes, HashMapContext};
use itertools::Itertools;
use regex::Regex;
use serde::Deserialize;
use custom_error::custom_error;
use self::VarOrConst::Var;
#[derive(Clone, Debug, Deserialize)]
pub struct Variable {
name: String,
from: i64,
to: i64,
#[serde(default = "default_step")]
step: i64,
}
fn default_step() -> i64 {
1
}
impl Variable {
fn check(&self) -> Result<(), BadVariableError> {
static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\w+$").unwrap());
if !RE.is_match(&self.name) {
return Err(BadVariableError::BadName {
name: self.name.clone(),
});
}
let steps = (self.to - self.from) / self.step;
if steps < 0 {
return Err(BadVariableError::Infinite {
name: self.name.clone(),
});
} else if steps > i64::from(u32::MAX) {
return Err(BadVariableError::TooManyValues {
name: self.name.clone(),
steps,
});
}
Ok(())
}
pub fn name(&self) -> &str {
&self.name
}
}
#[derive(Clone)]
pub struct VariableIterator {
from: i64,
to: i64,
step: i64,
current: i64,
}
impl<'a> VariableIterator {
fn in_range(&'a self) -> bool {
let i = self.current;
(self.from <= i && i <= self.to) || (self.to <= i && i <= self.from)
}
}
impl Iterator for VariableIterator {
type Item = i64;
fn next(&mut self) -> Option<Self::Item> {
if self.in_range() {
let current = self.current;
self.current += self.step;
Some(current)
} else {
None
}
}
}
impl IntoIterator for &Variable {
type Item = i64;
type IntoIter = VariableIterator;
fn into_iter(self) -> Self::IntoIter {
VariableIterator {
from: self.from,
to: self.to,
step: self.step,
current: self.from,
}
}
}
#[derive(Deserialize, Clone, Debug)]
pub struct Constant {
name: String,
value: i64,
}
#[derive(Deserialize, Clone, Debug)]
#[serde(untagged)]
pub enum VarOrConst {
Var(Variable),
Const(Constant),
}
impl VarOrConst {
pub fn var(name: &str, from: i64, to: i64, step: i64) -> Result<VarOrConst, BadVariableError> {
let var = Variable {
name: name.to_string(),
from,
to,
step,
};
var.check().and(Ok(Var(var)))
}
pub fn name(&self) -> &str {
match self {
VarOrConst::Var(v) => v.name(),
VarOrConst::Const(c) => &c.name,
}
}
}
impl IntoIterator for &VarOrConst {
type Item = i64;
type IntoIter = VariableIterator;
fn into_iter(self) -> Self::IntoIter {
match self {
VarOrConst::Var(v) => v.into_iter(),
VarOrConst::Const(c) => VariableIterator {
from: c.value,
to: c.value,
current: c.value,
step: 1,
},
}
}
}
#[derive(Deserialize, Debug)]
pub struct Variables(Vec<VarOrConst>);
impl Variables {
#[cfg(test)]
pub fn new(vars: Vec<VarOrConst>) -> Variables {
Variables(vars)
}
pub fn iter_contexts(
&self,
) -> impl Iterator<Item = Result<HashMapContext<DefaultNumericTypes>, BadVariableError>> + '_
{
self.0
.iter()
.map(|variable| variable.into_iter().map(move |val| (variable.name(), val)))
.multi_cartesian_product()
.map(|var_values| {
let mut ctx = build_context();
for (var_name, var_value) in var_values {
ctx.set_value(var_name.into(), evalexpr::Value::Int(var_value))?;
}
Ok(ctx)
})
}
}
fn build_context() -> HashMapContext<DefaultNumericTypes> {
HashMapContext::new()
}
custom_error! {pub BadVariableError
BadName{name: String} = "invalid variable name: '{name}'",
TooManyValues{name:String, steps:i64}= "the range of values for {name} is too wide: {steps} steps",
Infinite{name:String}= "the range of values for {name} is incorrect",
EvalError{source:evalexpr::EvalexprError} = "{source}",
}
#[cfg(test)]
mod tests {
use evalexpr::Context;
use super::super::variable::VarOrConst;
use super::{Variable, Variables};
#[test]
fn variable_iteration() {
let var = Variable {
name: "hello".to_string(),
from: 3,
to: -3,
step: -3,
};
assert_eq!(var.into_iter().collect::<Vec<i64>>(), vec![3, 0, -3]);
}
#[test]
fn variable_validity_check_name() {
let check = Variable {
name: "hello world".to_string(),
from: 0,
to: 1,
step: 1,
}
.check();
assert!(
check
.unwrap_err()
.to_string()
.contains("invalid variable name")
);
}
#[test]
fn iter_contexts() {
let vars = Variables(vec![
VarOrConst::var("x", 0, 1, 1).unwrap(),
VarOrConst::var("y", 8, 9, 1).unwrap(),
]);
let ctxs: Vec<_> = vars.iter_contexts().collect::<Result<_, _>>().unwrap();
assert_eq!(4, ctxs.len());
assert_eq!(Some(&evalexpr::Value::Int(0)), ctxs[0].get_value("x"));
assert_eq!(Some(&evalexpr::Value::Int(8)), ctxs[0].get_value("y"));
assert_eq!(Some(&evalexpr::Value::Int(0)), ctxs[1].get_value("x"));
assert_eq!(Some(&evalexpr::Value::Int(9)), ctxs[1].get_value("y"));
assert_eq!(Some(&evalexpr::Value::Int(1)), ctxs[2].get_value("x"));
assert_eq!(Some(&evalexpr::Value::Int(8)), ctxs[2].get_value("y"));
assert_eq!(Some(&evalexpr::Value::Int(1)), ctxs[3].get_value("x"));
assert_eq!(Some(&evalexpr::Value::Int(9)), ctxs[3].get_value("y"));
}
}