From 0ebce56b36ea2a875609949f4da81c9ca6a4a081 Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Tue, 5 Dec 2023 15:15:03 -0300 Subject: [PATCH] Implement lexicographic array comparison (#2827) --- crates/typst/src/eval/ops.rs | 21 +++++++++++++++++++++ tests/typ/compiler/ops-invalid.typ | 8 ++++++++ tests/typ/compiler/ops.typ | 12 ++++++++++++ 3 files changed, 41 insertions(+) diff --git a/crates/typst/src/eval/ops.rs b/crates/typst/src/eval/ops.rs index cb830614e..8e8b6d293 100644 --- a/crates/typst/src/eval/ops.rs +++ b/crates/typst/src/eval/ops.rs @@ -551,6 +551,7 @@ pub fn compare(lhs: &Value, rhs: &Value) -> StrResult { (Duration(a), Duration(b)) => a.cmp(b), (Datetime(a), Datetime(b)) => try_cmp_datetimes(a, b)?, + (Array(a), Array(b)) => try_cmp_arrays(a.as_slice(), b.as_slice())?, _ => mismatch!("cannot compare {} and {}", lhs, rhs), }) @@ -568,6 +569,26 @@ fn try_cmp_datetimes(a: &Datetime, b: &Datetime) -> StrResult { .ok_or_else(|| eco_format!("cannot compare {} and {}", a.kind(), b.kind())) } +/// Try to compare arrays of values lexicographically. +fn try_cmp_arrays(a: &[Value], b: &[Value]) -> StrResult { + a.iter() + .zip(b.iter()) + .find_map(|(first, second)| { + match compare(first, second) { + // Keep searching for a pair of elements that isn't equal. + Ok(Ordering::Equal) => None, + // Found a pair which either is not equal or not comparable, so + // we stop searching. + result => Some(result), + } + }) + .unwrap_or_else(|| { + // The two arrays are equal up to the shortest array's extent, + // so compare their lengths instead. + Ok(a.len().cmp(&b.len())) + }) +} + /// Test whether one value is "in" another one. pub fn in_(lhs: Value, rhs: Value) -> StrResult { if let Some(b) = contains(&lhs, &rhs) { diff --git a/tests/typ/compiler/ops-invalid.typ b/tests/typ/compiler/ops-invalid.typ index de8e62346..64e3a8786 100644 --- a/tests/typ/compiler/ops-invalid.typ +++ b/tests/typ/compiler/ops-invalid.typ @@ -37,6 +37,14 @@ // Error: 3-22 cannot compare 2.2 with NaN #(2.2 <= float("nan")) +--- +// Error: 3-26 cannot compare integer and string +#((0, 1, 3) > (0, 1, "a")) + +--- +// Error: 3-42 cannot compare 3.5 with NaN +#((0, "a", 3.5) <= (0, "a", float("nan"))) + --- // Error: 3-12 cannot divide by zero #(1.2 / 0.0) diff --git a/tests/typ/compiler/ops.typ b/tests/typ/compiler/ops.typ index 89ed3e5f7..64736228f 100644 --- a/tests/typ/compiler/ops.typ +++ b/tests/typ/compiler/ops.typ @@ -206,6 +206,18 @@ #test(50% < 40% + 0pt, false) #test(40% + 0pt < 50% + 0pt, true) #test(1em < 2em, true) +#test((0, 1, 2, 4) < (0, 1, 2, 5), true) +#test((0, 1, 2, 4) < (0, 1, 2, 3), false) +#test((0, 1, 2, 3.3) > (0, 1, 2, 4), false) +#test((0, 1, 2) < (0, 1, 2, 3), true) +#test((0, 1, "b") > (0, 1, "a", 3), true) +#test((0, 1.1, 3) >= (0, 1.1, 3), true) +#test((0, 1, datetime(day: 1, month: 12, year: 2023)) <= (0, 1, datetime(day: 1, month: 12, year: 2023), 3), true) +#test(("a", 23, 40, "b") > ("a", 23, 40), true) +#test(() <= (), true) +#test(() >= (), true) +#test(() <= (1,), true) +#test((1,) <= (), false) --- // Test assignment operators.