dezoomify_rs/custom_yaml/
variable.rs

1use evalexpr::{HashMapContext, ContextWithMutableVariables};
2use itertools::Itertools;
3use regex::Regex;
4use serde::Deserialize;
5
6use custom_error::custom_error;
7use lazy_static::lazy_static;
8
9use self::VarOrConst::Var;
10
11#[derive(Clone, Debug, Deserialize)]
12pub struct Variable {
13    name: String,
14    from: i64,
15    to: i64,
16    #[serde(default = "default_step")]
17    step: i64,
18}
19
20fn default_step() -> i64 {
21    1
22}
23
24impl Variable {
25    fn check(&self) -> Result<(), BadVariableError> {
26        lazy_static! {
27            static ref RE: Regex = Regex::new(r"^\w+$").unwrap();
28        }
29        if !RE.is_match(&self.name) {
30            return Err(BadVariableError::BadName {
31                name: self.name.clone(),
32            });
33        }
34        let steps = (self.to - self.from) / self.step;
35        if steps < 0 {
36            return Err(BadVariableError::Infinite {
37                name: self.name.clone(),
38            });
39        } else if steps > i64::from(std::u32::MAX) {
40            return Err(BadVariableError::TooManyValues {
41                name: self.name.clone(),
42                steps,
43            });
44        }
45        Ok(())
46    }
47
48    pub fn name(&self) -> &str {
49        &self.name
50    }
51}
52
53#[derive(Clone)]
54pub struct VariableIterator {
55    from: i64,
56    to: i64,
57    step: i64,
58    current: i64,
59}
60
61impl<'a> VariableIterator {
62    fn in_range(&'a self) -> bool {
63        let i = self.current;
64        (self.from <= i && i <= self.to) || (self.to <= i && i <= self.from)
65    }
66}
67
68impl Iterator for VariableIterator {
69    type Item = i64;
70
71    fn next(&mut self) -> Option<Self::Item> {
72        if self.in_range() {
73            let current = self.current;
74            self.current += self.step;
75            Some(current)
76        } else {
77            None
78        }
79    }
80}
81
82impl<'a> IntoIterator for &'a Variable {
83    type Item = i64;
84    type IntoIter = VariableIterator;
85
86    fn into_iter(self) -> Self::IntoIter {
87        VariableIterator {
88            from: self.from,
89            to: self.to,
90            step: self.step,
91            current: self.from,
92        }
93    }
94}
95
96/// Represents a Variable that can have only a single value
97#[derive(Deserialize, Clone, Debug)]
98pub struct Constant {
99    name: String,
100    value: i64,
101}
102
103#[derive(Deserialize, Clone, Debug)]
104#[serde(untagged)]
105pub enum VarOrConst {
106    Var(Variable),
107    Const(Constant),
108}
109
110impl VarOrConst {
111    pub fn var(name: &str, from: i64, to: i64, step: i64) -> Result<VarOrConst, BadVariableError> {
112        let var = Variable {
113            name: name.to_string(),
114            from,
115            to,
116            step,
117        };
118        var.check().and(Ok(Var(var)))
119    }
120    pub fn name(&self) -> &str {
121        match self {
122            VarOrConst::Var(v) => v.name(),
123            VarOrConst::Const(c) => &c.name,
124        }
125    }
126}
127
128impl<'a> IntoIterator for &'a VarOrConst {
129    type Item = i64;
130    type IntoIter = VariableIterator;
131
132    fn into_iter(self) -> Self::IntoIter {
133        match self {
134            VarOrConst::Var(v) => v.into_iter(),
135            VarOrConst::Const(c) => VariableIterator {
136                from: c.value,
137                to: c.value,
138                current: c.value,
139                step: 1,
140            },
141        }
142    }
143}
144
145#[derive(Deserialize, Debug)]
146pub struct Variables(Vec<VarOrConst>);
147
148impl Variables {
149    #[cfg(test)]
150    pub fn new(vars: Vec<VarOrConst>) -> Variables {
151        Variables(vars)
152    }
153    pub fn iter_contexts(
154        &self,
155    ) -> impl Iterator<Item = Result<HashMapContext, BadVariableError>> + '_ {
156        self.0
157            .iter()
158            .map(|variable| variable.into_iter().map(move |val| (variable.name(), val)))
159            .multi_cartesian_product()
160            .map(|var_values| {
161                // Iterator on all the combination of values for the variables
162                let mut ctx = HashMapContext::new();
163                for (var_name, var_value) in var_values {
164                    ctx.set_value(var_name.into(), var_value.into())?;
165                }
166                Ok(ctx)
167            })
168    }
169}
170
171custom_error! {pub BadVariableError
172    BadName{name: String} = "invalid variable name: '{name}'",
173    TooManyValues{name:String, steps:i64}= "the range of values for {name} is too wide: {steps} steps",
174    Infinite{name:String}= "the range of values for {name} is incorrect",
175    EvalError{source:evalexpr::EvalexprError} = "{source}",
176}
177
178#[cfg(test)]
179mod tests {
180    use evalexpr::Context;
181
182    use super::super::variable::VarOrConst;
183    use super::{Variable, Variables};
184
185    #[test]
186    fn variable_iteration() {
187        let var = Variable {
188            name: "hello".to_string(),
189            from: 3,
190            to: -3,
191            step: -3,
192        };
193        assert_eq!(var.into_iter().collect::<Vec<i64>>(), vec![3, 0, -3]);
194    }
195
196    #[test]
197    fn variable_validity_check_name() {
198        let check = Variable {
199            name: "hello world".to_string(),
200            from: 0,
201            to: 1,
202            step: 1,
203        }
204        .check();
205        assert!(check
206            .unwrap_err()
207            .to_string()
208            .contains("invalid variable name"))
209    }
210
211    #[test]
212    fn iter_contexts() {
213        let vars = Variables(vec![
214            VarOrConst::var("x", 0, 1, 1).unwrap(),
215            VarOrConst::var("y", 8, 9, 1).unwrap(),
216        ]);
217        let ctxs: Vec<_> = vars.iter_contexts().collect::<Result<_, _>>().unwrap();
218        assert_eq!(4, ctxs.len());
219        assert_eq!(Some(&0.into()), ctxs[0].get_value("x"));
220        assert_eq!(Some(&8.into()), ctxs[0].get_value("y"));
221
222        assert_eq!(Some(&0.into()), ctxs[1].get_value("x"));
223        assert_eq!(Some(&9.into()), ctxs[1].get_value("y"));
224
225        assert_eq!(Some(&1.into()), ctxs[2].get_value("x"));
226        assert_eq!(Some(&8.into()), ctxs[2].get_value("y"));
227
228        assert_eq!(Some(&1.into()), ctxs[3].get_value("x"));
229        assert_eq!(Some(&9.into()), ctxs[3].get_value("y"));
230    }
231}