From 9db030eb1f05827ae1380e8073fd5567774446a1 Mon Sep 17 00:00:00 2001 From: NgoQuocViet2001 Date: Thu, 3 Sep 2026 15:32:56 +0700 Subject: [PATCH] fix(expr-eval): make ^ right-associative parse_power accepted a single exponent (`unary ('^' unary)?`), so a chained power stopped after the first `^`. The remaining `^2` was left unconsumed, and eval_to_string's embedded-expression scan then handed it back as literal text: typing `2^3^2` into a coordinate or distance field produced the string "8^2" rather than 512, and `2^3^2+1" produced "8^3". Recurse into parse_power for the exponent so `2^3^2` reads as `2^(3^2)", the standard right-associative meaning, and the whole expression is consumed. --- src/app/expr_eval.rs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/app/expr_eval.rs b/src/app/expr_eval.rs index 7e6163d52..be064efcd 100644 --- a/src/app/expr_eval.rs +++ b/src/app/expr_eval.rs @@ -207,7 +207,13 @@ impl Parser { Ok(left) } - // power → unary ('^' unary)? + // power → unary ('^' power)? + // + // The exponent recurses into `parse_power`, not `parse_unary`, so `^` is + // right-associative — `2^3^2` is `2^(3^2)`, the standard reading. Binding + // it to the left both gave the wrong number and left the trailing `^2` + // unconsumed, which the caller's scan then handed back as literal text: + // `2^3^2` evaluated to the string "8^2" instead of 512. fn parse_power(&mut self, depth: usize) -> Result { if depth > MAX_RECURSION { return Err(()); @@ -215,7 +221,7 @@ impl Parser { let base = self.parse_unary(depth + 1)?; if let Some(Token::Caret) = self.peek() { self.advance(); - let exp = self.parse_unary(depth + 1)?; + let exp = self.parse_power(depth + 1)?; return Ok(base.powf(exp)); } Ok(base) @@ -591,6 +597,21 @@ mod tests { assert_eq!(eval_to_string("(((10)))"), "10"); } + // `^` is right-associative, so a chained exponent evaluates as one number + // instead of stopping at the first `^` and leaving the rest as text. + #[test] + fn power_is_right_associative() { + assert_eq!(eval_to_string("2^3^2"), "512"); + assert_eq!(eval_to_string("2^3^2+1"), "513"); + assert_eq!(eval_to_string("4^0.5^2"), "1.4142135623730951"); // 4^(1/4) + assert_eq!(eval_to_string("2^2^2^2"), "65536"); + // A single exponent, and a parenthesised one, are unaffected. + assert_eq!(eval_to_string("2^3"), "8"); + assert_eq!(eval_to_string("2^(3^2)"), "512"); + assert_eq!(eval_to_string("(2^3)^2"), "64"); + assert_eq!(eval_to_string("2^-1"), "0.5"); + } + #[test] fn unary_ops() { assert_eq!(eval_to_string("--5"), "5");