Merge pull request #2069 from Silvertree2010/fix/h839-field-arithmetic-rejects-followe

Evaluate * and / left to right in field arithmetic
This commit is contained in:
BFlat
2026-10-16 02:33:15 +07:00
committed by GitHub
2 changed files with 17 additions and 3 deletions
+8 -3
View File
@@ -270,17 +270,22 @@ impl Unit {
fn parse_measure(s: &str, default: Unit) -> Option<f64> {
let s = s.trim();
// Simple arithmetic on the right (Illustrator fields accept "10+5", "100/2", "3in*2").
for op in ['+', '-', '*', '/'] {
// `*` and `/` share one precedence level, so they split at the last of either (left to right).
for ops in [&['+'][..], &['-'][..], &['*', '/'][..]] {
// Past the first character, so a leading sign isn't an operator.
let skip = s.chars().next().map_or(0, char::len_utf8);
// A `+` or `-` right after another operator is the sign of its operand (`10*-2`, `5--3`).
let is_sign = |i: usize| matches!(op, '+' | '-') && s.get(..i).and_then(|l| l.trim_end().chars().last()).is_some_and(|c| "+-*/".contains(c));
let found = s.char_indices().rev().find(|&(i, c)| i >= skip && c == op && !is_sign(i));
let is_sign = |i: usize| {
s.get(i..).and_then(|r| r.chars().next()).is_some_and(|c| matches!(c, '+' | '-'))
&& s.get(..i).and_then(|l| l.trim_end().chars().last()).is_some_and(|c| "+-*/".contains(c))
};
let found = s.char_indices().rev().find(|&(i, c)| i >= skip && ops.contains(&c) && !is_sign(i));
if let Some(i) = found.map(|(i, _)| i) {
let (l, r) = (&s[..i], &s[i + 1..]);
if l.trim().is_empty() {
continue;
}
let Some(op) = s[i..].chars().next() else { continue };
return match op {
'+' => Some(parse_measure(l, default)? + parse_measure(r, default)?),
'-' => Some(parse_measure(l, default)? - parse_measure(r, default)?),
+9
View File
@@ -77,3 +77,12 @@ fn a_negative_operand_after_an_operator_is_a_sign() {
assert_eq!(Unit::Points.parse("10-2"), Some(8.0));
assert_eq!(super::parse_number("90*-1"), Some(-90.0));
}
#[test]
fn mixed_mul_div() {
// `*` and `/` share one precedence level and run left to right.
assert_eq!(Unit::Points.parse("10/2*5"), Some(25.0));
assert_eq!(crate::parse_number("100*3/4"), Some(75.0));
assert_eq!(Unit::Points.parse("10*2/5"), Some(4.0));
assert_eq!(Unit::Points.parse("10*2/5 px"), Some(Unit::Pixels.to_pt(4.0)));
}