1use crate::span::Span;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum Expr<A> {
17 Atom(A),
19 Not(Box<Expr<A>>),
21 And(Box<Expr<A>>, Box<Expr<A>>),
23 Or(Box<Expr<A>>, Box<Expr<A>>),
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36pub struct RawAtom {
37 pub text: String,
39 pub span: Span,
41}
42
43impl<A> Expr<A> {
44 pub fn try_map<B, E, F>(self, mut f: F) -> Result<Expr<B>, E>
55 where
56 F: FnMut(A) -> Result<B, E>,
57 {
58 self.try_map_inner(&mut f)
59 }
60
61 fn try_map_inner<B, E, F>(self, f: &mut F) -> Result<Expr<B>, E>
62 where
63 F: FnMut(A) -> Result<B, E>,
64 {
65 match self {
66 Expr::Atom(a) => Ok(Expr::Atom(f(a)?)),
67 Expr::Not(inner) => {
68 let inner = (*inner).try_map_inner(f)?;
69 Ok(Expr::Not(Box::new(inner)))
70 }
71 Expr::And(left, right) => {
72 let left = (*left).try_map_inner(f)?;
73 let right = (*right).try_map_inner(f)?;
74 Ok(Expr::And(Box::new(left), Box::new(right)))
75 }
76 Expr::Or(left, right) => {
77 let left = (*left).try_map_inner(f)?;
78 let right = (*right).try_map_inner(f)?;
79 Ok(Expr::Or(Box::new(left), Box::new(right)))
80 }
81 }
82 }
83
84 pub fn eval<F>(&self, mut f: F) -> bool
91 where
92 F: FnMut(&A) -> bool,
93 {
94 self.eval_inner(&mut f)
95 }
96
97 fn eval_inner<F>(&self, f: &mut F) -> bool
98 where
99 F: FnMut(&A) -> bool,
100 {
101 match self {
102 Expr::Atom(a) => f(a),
103 Expr::Not(inner) => !inner.eval_inner(f),
104 Expr::And(left, right) => left.eval_inner(f) && right.eval_inner(f),
105 Expr::Or(left, right) => left.eval_inner(f) || right.eval_inner(f),
106 }
107 }
108
109 pub fn try_eval<F, E>(&self, mut f: F) -> Result<bool, E>
121 where
122 F: FnMut(&A) -> Result<bool, E>,
123 {
124 self.try_eval_inner(&mut f)
125 }
126
127 fn try_eval_inner<F, E>(&self, f: &mut F) -> Result<bool, E>
128 where
129 F: FnMut(&A) -> Result<bool, E>,
130 {
131 match self {
132 Expr::Atom(a) => f(a),
133 Expr::Not(inner) => Ok(!inner.try_eval_inner(f)?),
134 Expr::And(left, right) => Ok(left.try_eval_inner(f)? && right.try_eval_inner(f)?),
135 Expr::Or(left, right) => Ok(left.try_eval_inner(f)? || right.try_eval_inner(f)?),
136 }
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 fn atom(text: &str, start: usize, end: usize) -> Expr<RawAtom> {
145 Expr::Atom(RawAtom {
146 text: text.to_owned(),
147 span: Span { start, end },
148 })
149 }
150
151 #[test]
152 fn expr_is_structurally_equal_to_a_clone() {
153 let original = Expr::And(
154 Box::new(atom("a", 0, 1)),
155 Box::new(Expr::Or(
156 Box::new(atom("b", 4, 5)),
157 Box::new(Expr::Not(Box::new(atom("c", 9, 10)))),
158 )),
159 );
160 let cloned = original.clone();
161 assert_eq!(original, cloned);
162 }
163
164 #[test]
165 fn raw_atom_preserves_byte_text_and_span() {
166 let atom = RawAtom {
167 text: "name:foo".to_owned(),
168 span: Span { start: 2, end: 10 },
169 };
170 assert_eq!(atom.text, "name:foo");
171 assert_eq!(atom.span, Span { start: 2, end: 10 });
172 }
173
174 #[test]
175 fn try_map_transforms_a_single_atom() {
176 let expr: Expr<&str> = Expr::Atom("42");
177 let mapped: Expr<i32> = expr.try_map(str::parse::<i32>).unwrap();
178 assert_eq!(mapped, Expr::Atom(42));
179 }
180
181 #[test]
182 fn try_map_preserves_tree_structure() {
183 let expr: Expr<&str> = Expr::And(
184 Box::new(Expr::Or(
185 Box::new(Expr::Atom("1")),
186 Box::new(Expr::Not(Box::new(Expr::Atom("2")))),
187 )),
188 Box::new(Expr::Atom("3")),
189 );
190 let mapped: Expr<i32> = expr.try_map(str::parse::<i32>).unwrap();
191 let expected: Expr<i32> = Expr::And(
192 Box::new(Expr::Or(
193 Box::new(Expr::Atom(1)),
194 Box::new(Expr::Not(Box::new(Expr::Atom(2)))),
195 )),
196 Box::new(Expr::Atom(3)),
197 );
198 assert_eq!(mapped, expected);
199 }
200
201 #[test]
202 fn try_map_propagates_first_error_and_stops() {
203 let mut visited = Vec::new();
204 let expr: Expr<&str> = Expr::And(
205 Box::new(Expr::Atom("1")),
206 Box::new(Expr::Or(
207 Box::new(Expr::Atom("oops")),
208 Box::new(Expr::Atom("3")),
209 )),
210 );
211 let result: Result<Expr<i32>, _> = expr.try_map(|s| {
212 visited.push(s.to_owned());
213 s.parse::<i32>()
214 });
215 assert!(result.is_err());
216 assert_eq!(visited, vec!["1", "oops"]);
219 }
220
221 #[test]
222 fn try_map_visits_atoms_in_left_to_right_order() {
223 let mut visited = Vec::new();
224 let expr: Expr<&str> = Expr::Or(
225 Box::new(Expr::And(
226 Box::new(Expr::Atom("a")),
227 Box::new(Expr::Atom("b")),
228 )),
229 Box::new(Expr::Not(Box::new(Expr::Atom("c")))),
230 );
231 let _: Expr<usize> = expr
232 .try_map(|s: &str| {
233 visited.push(s.to_owned());
234 Ok::<usize, ()>(s.len())
235 })
236 .unwrap();
237 assert_eq!(visited, vec!["a", "b", "c"]);
238 }
239
240 #[test]
243 fn eval_returns_predicate_result_for_single_atom() {
244 let expr = Expr::Atom("yes");
245 assert!(expr.eval(|a| *a == "yes"));
246 assert!(!expr.eval(|a| *a == "no"));
247 }
248
249 #[test]
250 fn eval_applies_negation() {
251 let expr = Expr::Not(Box::new(Expr::Atom(true)));
252 assert!(!expr.eval(|a| *a));
253 }
254
255 #[test]
256 fn eval_folds_and_or_with_precedence() {
257 let expr = Expr::Or(
259 Box::new(Expr::And(
260 Box::new(Expr::Atom("a")),
261 Box::new(Expr::Not(Box::new(Expr::Atom("b")))),
262 )),
263 Box::new(Expr::Atom("c")),
264 );
265 let truthy = |s: &&str| *s == "a" || *s == "c";
266 assert!(expr.eval(truthy));
267 let only_b = |s: &&str| *s == "b";
268 assert!(!expr.eval(only_b));
269 }
270
271 #[test]
272 fn eval_short_circuits_and_on_first_false() {
273 let mut visited: Vec<&str> = Vec::new();
274 let expr = Expr::And(
275 Box::new(Expr::Atom("first")),
276 Box::new(Expr::Atom("second")),
277 );
278 let _ = expr.eval(|a| {
279 visited.push(*a);
280 *a != "first"
281 });
282 assert_eq!(visited, vec!["first"]);
283 }
284
285 #[test]
286 fn eval_short_circuits_or_on_first_true() {
287 let mut visited: Vec<&str> = Vec::new();
288 let expr = Expr::Or(
289 Box::new(Expr::Atom("first")),
290 Box::new(Expr::Atom("second")),
291 );
292 let _ = expr.eval(|a| {
293 visited.push(*a);
294 true
295 });
296 assert_eq!(visited, vec!["first"]);
297 }
298
299 #[test]
302 fn try_eval_returns_ok_for_infallible_predicate() {
303 let expr: Expr<i32> = Expr::And(Box::new(Expr::Atom(1)), Box::new(Expr::Atom(2)));
304 let result: Result<bool, ()> = expr.try_eval(|n| Ok(*n > 0));
305 assert_eq!(result, Ok(true));
306 }
307
308 #[test]
309 fn try_eval_propagates_error_from_first_failing_atom() {
310 let mut visited: Vec<i32> = Vec::new();
311 let expr: Expr<i32> = Expr::Or(Box::new(Expr::Atom(1)), Box::new(Expr::Atom(2)));
312 let result: Result<bool, &'static str> = expr.try_eval(|n| {
313 visited.push(*n);
314 if *n == 1 { Err("boom") } else { Ok(true) }
315 });
316 assert_eq!(result, Err("boom"));
317 assert_eq!(visited, vec![1]);
319 }
320
321 #[test]
322 fn try_eval_short_circuits_and_on_false_without_visiting_right() {
323 let mut visited: Vec<i32> = Vec::new();
324 let expr: Expr<i32> = Expr::And(Box::new(Expr::Atom(1)), Box::new(Expr::Atom(2)));
325 let result: Result<bool, ()> = expr.try_eval(|n| {
326 visited.push(*n);
327 Ok(*n != 1)
328 });
329 assert_eq!(result, Ok(false));
330 assert_eq!(visited, vec![1]);
331 }
332}