dezoomify_rs/custom_yaml/
variable.rs1use 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#[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 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}