1use crate::interpreter::Scope;
16use crate::ast::{Value, VarPath, VarSegment};
17use anyhow::{bail, Context, Result};
18
19pub fn eval_arithmetic(expr: &str, scope: &Scope) -> Result<i64> {
31 let mut parser = ArithParser::new(expr, scope);
32 let result = parser.parse_comparison()?;
33 parser.expect_end()?;
34 Ok(result)
35}
36
37struct ArithParser<'a> {
39 input: &'a str,
40 pos: usize,
41 scope: &'a Scope,
42}
43
44impl<'a> ArithParser<'a> {
45 fn new(input: &'a str, scope: &'a Scope) -> Self {
46 Self { input, pos: 0, scope }
47 }
48
49 fn skip_whitespace(&mut self) {
50 while self.pos < self.input.len() {
51 let ch = self.input.as_bytes()[self.pos];
52 if ch == b' ' || ch == b'\t' {
53 self.pos += 1;
54 } else {
55 break;
56 }
57 }
58 }
59
60 fn peek(&mut self) -> Option<char> {
61 self.skip_whitespace();
62 self.input[self.pos..].chars().next()
63 }
64
65 fn advance(&mut self) -> Option<char> {
66 self.skip_whitespace();
67 let ch = self.input[self.pos..].chars().next()?;
68 self.pos += ch.len_utf8();
69 Some(ch)
70 }
71
72 fn peek_ahead(&mut self, n: usize) -> Option<char> {
74 self.skip_whitespace();
75 self.input[self.pos..].chars().nth(n)
76 }
77
78 fn expect_end(&mut self) -> Result<()> {
79 self.skip_whitespace();
80 if self.pos < self.input.len() {
81 bail!("unexpected characters at end of arithmetic expression: {:?}",
82 &self.input[self.pos..]);
83 }
84 Ok(())
85 }
86
87 fn parse_comparison(&mut self) -> Result<i64> {
90 let mut left = self.parse_expr()?;
91
92 loop {
93 self.skip_whitespace();
94 match (self.peek_ahead(0), self.peek_ahead(1)) {
95 (Some('>'), Some('=')) => {
97 self.advance(); self.advance(); let right = self.parse_expr()?;
100 left = if left >= right { 1 } else { 0 };
101 }
102 (Some('<'), Some('=')) => {
103 self.advance(); self.advance(); let right = self.parse_expr()?;
106 left = if left <= right { 1 } else { 0 };
107 }
108 (Some('='), Some('=')) => {
109 self.advance(); self.advance(); let right = self.parse_expr()?;
112 left = if left == right { 1 } else { 0 };
113 }
114 (Some('!'), Some('=')) => {
115 self.advance(); self.advance(); let right = self.parse_expr()?;
118 left = if left != right { 1 } else { 0 };
119 }
120 (Some('>'), _) => {
122 self.advance(); let right = self.parse_expr()?;
124 left = if left > right { 1 } else { 0 };
125 }
126 (Some('<'), _) => {
127 self.advance(); let right = self.parse_expr()?;
129 left = if left < right { 1 } else { 0 };
130 }
131 _ => break,
132 }
133 }
134
135 Ok(left)
136 }
137
138 fn parse_expr(&mut self) -> Result<i64> {
140 let mut left = self.parse_term()?;
141
142 loop {
143 match self.peek() {
144 Some('+') => {
145 self.advance();
146 let right = self.parse_term()?;
147 left = left.checked_add(right)
148 .context("arithmetic overflow in addition")?;
149 }
150 Some('-') => {
151 self.advance();
152 let right = self.parse_term()?;
153 left = left.checked_sub(right)
154 .context("arithmetic overflow in subtraction")?;
155 }
156 _ => break,
157 }
158 }
159
160 Ok(left)
161 }
162
163 fn parse_term(&mut self) -> Result<i64> {
165 let mut left = self.parse_unary()?;
166
167 loop {
168 match self.peek() {
169 Some('*') => {
170 self.advance();
171 let right = self.parse_unary()?;
172 left = left.checked_mul(right)
173 .context("arithmetic overflow in multiplication")?;
174 }
175 Some('/') => {
176 self.advance();
177 let right = self.parse_unary()?;
178 if right == 0 {
179 bail!("division by zero");
180 }
181 left = left.checked_div(right)
182 .context("arithmetic overflow in division")?;
183 }
184 Some('%') => {
185 self.advance();
186 let right = self.parse_unary()?;
187 if right == 0 {
188 bail!("modulo by zero");
189 }
190 left = left.checked_rem(right)
191 .context("arithmetic overflow in modulo")?;
192 }
193 _ => break,
194 }
195 }
196
197 Ok(left)
198 }
199
200 fn parse_unary(&mut self) -> Result<i64> {
202 match self.peek() {
203 Some('+') => {
204 self.advance();
205 self.parse_unary()
206 }
207 Some('-') => {
208 self.advance();
209 let val = self.parse_unary()?;
210 val.checked_neg().context("arithmetic overflow in negation")
211 }
212 _ => self.parse_primary(),
213 }
214 }
215
216 fn parse_primary(&mut self) -> Result<i64> {
218 self.skip_whitespace();
219
220 match self.peek() {
221 Some('(') => {
222 self.advance(); let val = self.parse_expr()?;
224 match self.peek() {
225 Some(')') => {
226 self.advance();
227 Ok(val)
228 }
229 _ => bail!("expected ')' in arithmetic expression"),
230 }
231 }
232 Some('$') => {
233 self.advance(); if self.peek() == Some('?') {
238 self.advance(); return Ok(self.scope.last_result().code);
240 }
241
242 if self.peek() == Some('$') {
244 self.advance(); return Ok(self.scope.pid() as i64);
246 }
247
248 let var_name = if self.peek() == Some('{') {
249 self.advance(); if self.peek() == Some('?') {
253 self.advance(); if self.peek() != Some('}') {
255 bail!("expected '}}' after ${{?}} in arithmetic");
256 }
257 self.advance(); return Ok(self.scope.last_result().code);
259 }
260
261 if self.peek() == Some('$') {
263 self.advance(); if self.peek() != Some('}') {
265 bail!("expected '}}' after ${{$}} in arithmetic");
266 }
267 self.advance(); return Ok(self.scope.pid() as i64);
269 }
270
271 let name = self.parse_identifier()?;
272 if self.peek() == Some('[') {
274 return self.eval_braced_path(&name);
275 }
276 if self.peek() != Some('}') {
277 bail!("expected '}}' after variable name in arithmetic");
278 }
279 self.advance(); name
281 } else {
282 self.parse_identifier()?
283 };
284 self.get_var_value(&var_name)
285 }
286 Some(c) if c.is_ascii_digit() => {
287 self.parse_number()
288 }
289 Some(c) if c.is_ascii_alphabetic() || c == '_' => {
290 let var_name = self.parse_identifier()?;
292 if self.peek() == Some('[') {
293 return self.eval_bare_subscript_path(&var_name);
295 }
296 self.get_var_value(&var_name)
297 }
298 Some(c) => bail!("unexpected character in arithmetic expression: {:?}", c),
299 None => bail!("unexpected end of arithmetic expression"),
300 }
301 }
302
303 fn parse_number(&mut self) -> Result<i64> {
304 let start = self.pos;
305 while self.pos < self.input.len() {
306 let ch = self.input.as_bytes()[self.pos];
307 if ch.is_ascii_digit() {
308 self.pos += 1;
309 } else {
310 break;
311 }
312 }
313 let num_str = &self.input[start..self.pos];
314 num_str.parse().context("invalid number in arithmetic expression")
315 }
316
317 fn parse_identifier(&mut self) -> Result<String> {
318 let start = self.pos;
319 while self.pos < self.input.len() {
320 let ch = self.input.as_bytes()[self.pos];
321 if ch.is_ascii_alphanumeric() || ch == b'_' {
322 self.pos += 1;
323 } else {
324 break;
325 }
326 }
327 if start == self.pos {
328 bail!("expected identifier in arithmetic expression");
329 }
330 Ok(self.input[start..self.pos].to_string())
331 }
332
333 fn get_var_value(&self, name: &str) -> Result<i64> {
334 if let Ok(index) = name.parse::<usize>() {
337 if let Some(pos_val) = self.scope.get_positional(index) {
338 return pos_val.parse().with_context(|| {
339 format!("${} has non-numeric value: {:?}", index, pos_val)
340 });
341 }
342 return Ok(0); }
344
345 match self.scope.get(name).cloned() {
347 Some(value) => self.value_to_arith(&value, name),
348 None => Ok(0), }
350 }
351
352 fn eval_braced_path(&mut self, root: &str) -> Result<i64> {
356 let mut brackets = String::new();
357 while self.peek() == Some('[') {
358 brackets.push('[');
359 self.advance(); let mut depth = 1;
361 while depth > 0 {
362 match self.advance() {
363 Some('[') => {
364 depth += 1;
365 brackets.push('[');
366 }
367 Some(']') => {
368 depth -= 1;
369 brackets.push(']');
370 }
371 Some(c) => brackets.push(c),
372 None => bail!("unterminated subscript in arithmetic"),
373 }
374 }
375 }
376 if self.peek() != Some('}') {
377 bail!("expected '}}' after subscripted variable in arithmetic");
378 }
379 self.advance(); let raw = format!("${{{root}{brackets}}}");
382 let path = crate::parser::parse_varpath(&raw);
383 let value = self.scope.resolve_path(&path).map_err(|e| match e {
384 crate::interpreter::PathError::UndefinedRoot(_) => {
385 anyhow::anyhow!("undefined variable in arithmetic: {root}")
386 }
387 crate::interpreter::PathError::Absence(msg)
388 | crate::interpreter::PathError::Shape(msg) => anyhow::anyhow!(msg),
389 })?;
390 self.value_to_arith(&value, root)
391 }
392
393 fn eval_bare_subscript_path(&mut self, root: &str) -> Result<i64> {
403 let mut segments = vec![VarSegment::Field(root.to_string())];
404 while self.peek() == Some('[') {
405 self.advance(); let index = self.parse_comparison()?; self.skip_whitespace();
408 if self.peek() != Some(']') {
409 bail!("expected ']' to close subscript in arithmetic");
410 }
411 self.advance(); segments.push(VarSegment::Index(index));
413 }
414 let path = VarPath { segments };
415 let value = self.scope.resolve_path(&path).map_err(|e| match e {
416 crate::interpreter::PathError::UndefinedRoot(_) => {
417 anyhow::anyhow!("undefined variable in arithmetic: {root}")
418 }
419 crate::interpreter::PathError::Absence(msg)
420 | crate::interpreter::PathError::Shape(msg) => anyhow::anyhow!(msg),
421 })?;
422 self.value_to_arith(&value, root)
423 }
424
425 fn value_to_arith(&self, value: &Value, name: &str) -> Result<i64> {
427 match value {
428 Value::Int(n) => Ok(*n),
429 Value::String(s) => {
430 s.parse().with_context(|| format!(
432 "variable '{}' has non-numeric value: {:?}", name, s
433 ))
434 }
435 Value::Float(f) => Ok(*f as i64),
436 Value::Bool(b) => Ok(if *b { 1 } else { 0 }),
437 Value::Null => Ok(0), Value::Json(_) => anyhow::bail!("variable '{}' is JSON, not a number", name),
439 Value::Bytes(_) => anyhow::bail!("variable '{}' is binary data, not a number", name),
440 }
441 }
442}
443
444#[cfg(test)]
445mod tests {
446 use super::*;
447
448 fn eval(expr: &str) -> i64 {
449 let scope = Scope::new();
450 eval_arithmetic(expr, &scope).expect("eval should succeed")
451 }
452
453 fn eval_with_var(expr: &str, name: &str, value: i64) -> i64 {
454 let mut scope = Scope::new();
455 scope.set(name, Value::Int(value));
456 eval_arithmetic(expr, &scope).expect("eval should succeed")
457 }
458
459 #[test]
460 fn test_simple_integers() {
461 assert_eq!(eval("42"), 42);
462 assert_eq!(eval("0"), 0);
463 assert_eq!(eval("12345"), 12345);
464 }
465
466 fn eval_with_scope(expr: &str, setup: impl FnOnce(&mut Scope)) -> Result<i64> {
471 let mut scope = Scope::new();
472 setup(&mut scope);
473 eval_arithmetic(expr, &scope)
474 }
475
476 #[test]
477 fn bare_subscript_index_is_a_variable() {
478 let r = eval_with_scope("xs[i]", |s| {
480 s.set("xs", Value::Json(serde_json::json!([10, 20, 30])));
481 s.set("i", Value::Int(1));
482 })
483 .expect("bare xs[i] should resolve via variable i");
484 assert_eq!(r, 20);
485 }
486
487 #[test]
488 fn bare_subscript_literal_index() {
489 let r = eval_with_scope("xs[0] + 1", |s| {
490 s.set("xs", Value::Json(serde_json::json!([10, 20, 30])));
491 })
492 .expect("xs[0] + 1");
493 assert_eq!(r, 11);
494 }
495
496 #[test]
497 fn bare_subscript_inner_is_an_expression() {
498 let r = eval_with_scope("xs[i + 1]", |s| {
500 s.set("xs", Value::Json(serde_json::json!([10, 20, 30])));
501 s.set("i", Value::Int(0));
502 })
503 .expect("xs[i + 1]");
504 assert_eq!(r, 20);
505 }
506
507 #[test]
508 fn bare_subscript_negative_index() {
509 let r = eval_with_scope("xs[-1]", |s| {
510 s.set("xs", Value::Json(serde_json::json!([10, 20, 30])));
511 })
512 .expect("xs[-1]");
513 assert_eq!(r, 30);
514 }
515
516 #[test]
517 fn bare_subscript_out_of_bounds_is_loud() {
518 let r = eval_with_scope("xs[9]", |s| {
519 s.set("xs", Value::Json(serde_json::json!([10, 20])));
520 });
521 assert!(r.is_err(), "out-of-bounds index must be a loud error");
522 }
523
524 #[test]
525 fn test_addition() {
526 assert_eq!(eval("1 + 2"), 3);
527 assert_eq!(eval("10 + 20 + 30"), 60);
528 }
529
530 #[test]
531 fn test_subtraction() {
532 assert_eq!(eval("10 - 3"), 7);
533 assert_eq!(eval("100 - 50 - 25"), 25);
534 }
535
536 #[test]
537 fn test_multiplication() {
538 assert_eq!(eval("3 * 4"), 12);
539 assert_eq!(eval("2 * 3 * 4"), 24);
540 }
541
542 #[test]
543 fn test_division() {
544 assert_eq!(eval("10 / 2"), 5);
545 assert_eq!(eval("100 / 10 / 2"), 5);
546 }
547
548 #[test]
549 fn test_modulo() {
550 assert_eq!(eval("10 % 3"), 1);
551 assert_eq!(eval("17 % 5"), 2);
552 }
553
554 #[test]
555 fn test_precedence() {
556 assert_eq!(eval("2 + 3 * 4"), 14); assert_eq!(eval("10 - 6 / 2"), 7); }
559
560 #[test]
561 fn test_parentheses() {
562 assert_eq!(eval("(2 + 3) * 4"), 20);
563 assert_eq!(eval("((1 + 2) * (3 + 4))"), 21);
564 }
565
566 #[test]
567 fn test_unary_minus() {
568 assert_eq!(eval("-5"), -5);
569 assert_eq!(eval("10 + -3"), 7);
570 assert_eq!(eval("--5"), 5);
571 }
572
573 #[test]
574 fn test_unary_plus() {
575 assert_eq!(eval("+5"), 5);
576 assert_eq!(eval("++5"), 5);
577 }
578
579 #[test]
580 fn test_whitespace() {
581 assert_eq!(eval(" 1 + 2 "), 3);
582 assert_eq!(eval("1+2"), 3);
583 }
584
585 #[test]
586 fn test_variable_dollar() {
587 assert_eq!(eval_with_var("$X", "X", 10), 10);
588 assert_eq!(eval_with_var("$X + 5", "X", 10), 15);
589 }
590
591 #[test]
592 fn test_variable_dollar_braces() {
593 assert_eq!(eval_with_var("${X}", "X", 10), 10);
594 assert_eq!(eval_with_var("${X} * 2", "X", 10), 20);
595 }
596
597 #[test]
598 fn test_variable_bare() {
599 assert_eq!(eval_with_var("X", "X", 10), 10);
600 assert_eq!(eval_with_var("X + Y", "X", 10), 10); }
602
603 #[test]
604 fn test_unset_variable() {
605 let scope = Scope::new();
606 let result = eval_arithmetic("UNDEFINED", &scope).expect("should succeed");
607 assert_eq!(result, 0); }
609
610 #[test]
611 fn test_division_by_zero() {
612 let scope = Scope::new();
613 let result = eval_arithmetic("10 / 0", &scope);
614 assert!(result.is_err());
615 }
616
617 #[test]
618 fn test_modulo_by_zero() {
619 let scope = Scope::new();
620 let result = eval_arithmetic("10 % 0", &scope);
621 assert!(result.is_err());
622 }
623
624 #[test]
625 fn test_complex_expression() {
626 assert_eq!(eval("(1 + 2) * (3 + 4) - 5"), 16);
627 }
628
629 #[test]
631 fn test_greater_than() {
632 assert_eq!(eval("5 > 3"), 1);
633 assert_eq!(eval("3 > 5"), 0);
634 assert_eq!(eval("5 > 5"), 0);
635 }
636
637 #[test]
638 fn test_less_than() {
639 assert_eq!(eval("3 < 5"), 1);
640 assert_eq!(eval("5 < 3"), 0);
641 assert_eq!(eval("5 < 5"), 0);
642 }
643
644 #[test]
645 fn test_greater_or_equal() {
646 assert_eq!(eval("5 >= 3"), 1);
647 assert_eq!(eval("5 >= 5"), 1);
648 assert_eq!(eval("3 >= 5"), 0);
649 }
650
651 #[test]
652 fn test_less_or_equal() {
653 assert_eq!(eval("3 <= 5"), 1);
654 assert_eq!(eval("5 <= 5"), 1);
655 assert_eq!(eval("5 <= 3"), 0);
656 }
657
658 #[test]
659 fn test_equal() {
660 assert_eq!(eval("5 == 5"), 1);
661 assert_eq!(eval("5 == 3"), 0);
662 }
663
664 #[test]
665 fn test_not_equal() {
666 assert_eq!(eval("5 != 3"), 1);
667 assert_eq!(eval("5 != 5"), 0);
668 }
669
670 #[test]
671 fn test_comparison_with_arithmetic() {
672 assert_eq!(eval("(2 + 3) > 4"), 1);
673 assert_eq!(eval("10 / 2 == 5"), 1);
674 assert_eq!(eval("3 * 4 >= 12"), 1);
675 assert_eq!(eval("10 - 5 < 6"), 1);
676 }
677
678 #[test]
679 fn test_comparison_with_variables() {
680 assert_eq!(eval_with_var("X > 5", "X", 10), 1);
681 assert_eq!(eval_with_var("X == 10", "X", 10), 1);
682 assert_eq!(eval_with_var("X <= 10", "X", 10), 1);
683 }
684
685 #[test]
686 fn test_chained_comparison() {
687 assert_eq!(eval("5 > 3 > 2"), 0);
690 assert_eq!(eval("5 > 3 == 1"), 1);
692 }
693}