1use core_storage::Value;
2use std::cmp::Ordering;
3
4pub fn values_equal(a: &Value, b: &Value) -> bool {
5 match (a, b) {
6 (Value::Int(x), Value::Int(y)) => x == y,
7 (Value::Float(x), Value::Float(y)) => x == y,
8 (Value::Int(x), Value::Float(y)) => *x as f64 == *y,
9 (Value::Float(x), Value::Int(y)) => *x == *y as f64,
10 (Value::Str(x), Value::Str(y)) => x == y,
11 (Value::Bool(x), Value::Bool(y)) => x == y,
12 (Value::List(x), Value::List(y)) => {
13 x.len() == y.len() && x.iter().zip(y.iter()).all(|(l, r)| values_equal(l, r))
14 }
15 (Value::Map(x), Value::Map(y)) => {
16 x.len() == y.len()
17 && x.iter()
18 .zip(y.iter())
19 .all(|((k1, v1), (k2, v2))| k1 == k2 && values_equal(v1, v2))
20 }
21 _ => false,
22 }
23}
24
25fn class_rank(v: &Value) -> u8 {
26 match v {
27 Value::Int(_) | Value::Float(_) => 0,
28 Value::Str(_) => 1,
29 Value::Bool(_) => 2,
30 Value::List(_) => 3,
31 Value::Map(_) => 4,
33 }
34}
35
36fn as_f64(v: &Value) -> f64 {
37 match v {
38 Value::Int(i) => *i as f64,
39 Value::Float(f) => *f,
40 _ => unreachable!("as_f64 only for numeric class"),
41 }
42}
43
44pub fn cmp_values(a: &Value, b: &Value) -> Ordering {
45 let ra = class_rank(a);
46 let rb = class_rank(b);
47 if ra != rb {
48 return ra.cmp(&rb);
49 }
50 match (a, b) {
51 (Value::Int(_) | Value::Float(_), Value::Int(_) | Value::Float(_)) => {
52 as_f64(a).total_cmp(&as_f64(b))
53 }
54 (Value::Str(x), Value::Str(y)) => x.cmp(y),
55 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
56 (Value::List(x), Value::List(y)) => {
57 for (l, r) in x.iter().zip(y.iter()) {
58 let c = cmp_values(l, r);
59 if c != Ordering::Equal {
60 return c;
61 }
62 }
63 x.len().cmp(&y.len())
64 }
65 (Value::Map(x), Value::Map(y)) => {
66 for ((k1, v1), (k2, v2)) in x.iter().zip(y.iter()) {
68 let ck = k1.cmp(k2);
69 if ck != Ordering::Equal {
70 return ck;
71 }
72 let cv = cmp_values(v1, v2);
73 if cv != Ordering::Equal {
74 return cv;
75 }
76 }
77 x.len().cmp(&y.len())
78 }
79 _ => unreachable!("same class rank implies matching variants"),
80 }
81}
82
83pub fn cmp_optional(a: Option<&Value>, b: Option<&Value>, descending: bool) -> Ordering {
84 match (a, b) {
85 (None, None) => Ordering::Equal,
86 (None, Some(_)) => Ordering::Greater,
88 (Some(_), None) => Ordering::Less,
89 (Some(x), Some(y)) => {
90 let c = cmp_values(x, y);
91 if descending {
92 c.reverse()
93 } else {
94 c
95 }
96 }
97 }
98}
99
100pub fn index_list(base: Option<Value>, index: Option<Value>) -> Option<Value> {
106 let (Some(Value::List(items)), Some(idx)) = (base, index) else {
107 return None;
108 };
109 let i = match idx {
110 Value::Int(n) => n,
111 Value::Float(f) if f.fract() == 0.0 && f.is_finite() => f as i64,
112 _ => return None,
113 };
114 let len = i64::try_from(items.len()).ok()?;
115 let pos = if i < 0 { len.checked_add(i)? } else { i };
116 if pos < 0 || pos >= len {
117 return None;
118 }
119 items.into_iter().nth(pos as usize)
120}
121
122#[cfg(test)]
123mod tests {
124 use super::{cmp_optional, cmp_values, values_equal};
125 use core_storage::Value;
126 use std::cmp::Ordering::*;
127
128 #[test]
129 fn int_equals_float_numerically() {
130 assert!(values_equal(&Value::Int(2), &Value::Float(2.0)));
131 assert!(values_equal(&Value::Float(2.0), &Value::Int(2)));
132 assert!(!values_equal(&Value::Int(2), &Value::Float(2.1)));
133 }
134
135 #[test]
136 fn same_variant_equality() {
137 assert!(values_equal(&Value::Int(1), &Value::Int(1)));
138 assert!(!values_equal(&Value::Int(1), &Value::Int(2)));
139 assert!(values_equal(
140 &Value::Str("a".into()),
141 &Value::Str("a".into())
142 ));
143 assert!(!values_equal(
144 &Value::Str("a".into()),
145 &Value::Str("b".into())
146 ));
147 assert!(values_equal(&Value::Bool(true), &Value::Bool(true)));
148 assert!(!values_equal(&Value::Bool(true), &Value::Bool(false)));
149 assert!(values_equal(&Value::Float(1.5), &Value::Float(1.5)));
150 assert!(values_equal(
151 &Value::List(vec![Value::Int(1), Value::Float(2.0)]),
152 &Value::List(vec![Value::Float(1.0), Value::Int(2)]),
153 ));
154 assert!(!values_equal(
155 &Value::List(vec![Value::Int(1)]),
156 &Value::List(vec![Value::Int(1), Value::Int(2)]),
157 ));
158 }
159
160 #[test]
161 fn cross_variant_not_equal_except_int_float() {
162 assert!(!values_equal(&Value::Str("2".into()), &Value::Int(2)));
163 assert!(!values_equal(&Value::Bool(true), &Value::Int(1)));
164 assert!(!values_equal(&Value::List(vec![]), &Value::Str("".into())));
165 assert!(!values_equal(&Value::Bool(false), &Value::Float(0.0)));
166 }
167
168 #[test]
169 fn cmp_values_class_ranks() {
170 assert_eq!(cmp_values(&Value::Int(99), &Value::Str("a".into())), Less);
172 assert_eq!(cmp_values(&Value::Float(1.0), &Value::Bool(false)), Less);
173 assert_eq!(
174 cmp_values(&Value::Str("z".into()), &Value::Bool(false)),
175 Less
176 );
177 assert_eq!(cmp_values(&Value::Bool(true), &Value::List(vec![])), Less);
178 assert_eq!(cmp_values(&Value::List(vec![]), &Value::Int(0)), Greater);
179
180 assert_eq!(cmp_values(&Value::Int(1), &Value::Float(1.5)), Less);
182 assert_eq!(cmp_values(&Value::Int(2), &Value::Float(2.0)), Equal);
183 assert_eq!(cmp_values(&Value::Float(-0.0), &Value::Float(0.0)), Less);
185
186 assert_eq!(
188 cmp_values(&Value::Str("a".into()), &Value::Str("b".into())),
189 Less
190 );
191
192 assert_eq!(cmp_values(&Value::Bool(false), &Value::Bool(true)), Less);
194
195 assert_eq!(
197 cmp_values(
198 &Value::List(vec![Value::Int(1), Value::Int(2)]),
199 &Value::List(vec![Value::Int(1), Value::Int(3)]),
200 ),
201 Less
202 );
203 assert_eq!(
204 cmp_values(
205 &Value::List(vec![Value::Int(1)]),
206 &Value::List(vec![Value::Int(1), Value::Int(2)]),
207 ),
208 Less
209 );
210 assert_eq!(
212 cmp_values(
213 &Value::List(vec![Value::Int(1)]),
214 &Value::List(vec![Value::Str("a".into())]),
215 ),
216 Less
217 );
218 }
219
220 #[test]
221 fn cmp_optional_none_last_both_directions() {
222 let a = Value::Int(1);
223 let b = Value::Int(2);
224 assert_eq!(cmp_optional(None, Some(&a), false), Greater);
225 assert_eq!(cmp_optional(Some(&a), None, false), Less);
226 assert_eq!(cmp_optional(None, Some(&a), true), Greater);
227 assert_eq!(cmp_optional(Some(&a), None, true), Less);
228 assert_eq!(cmp_optional(None, None, false), Equal);
229 assert_eq!(cmp_optional(None, None, true), Equal);
230 assert_eq!(cmp_optional(Some(&a), Some(&b), false), Less);
231 assert_eq!(cmp_optional(Some(&a), Some(&b), true), Greater);
232 assert_eq!(cmp_optional(Some(&a), Some(&a), false), Equal);
233 assert_eq!(cmp_optional(Some(&a), Some(&a), true), Equal);
234 }
235}