Skip to main content

edikt_core/
value.rs

1//! The value calculus domain.
2//!
3//! `Value` is the in-memory model the expression evaluator and format conversion
4//! operate over. It is *not* how format-preserving edits round-trip (those stay
5//! in the CST); it is the domain for computed values (`.count + 1`), query
6//! output, and cross-format conversion.
7//!
8//! Numbers keep an `Int`/`Float` distinction so integer output stays clean.
9//! Objects are insertion-ordered (`Vec` of pairs) because key order is
10//! user-visible.
11
12use std::cmp::Ordering;
13
14#[derive(Debug, Clone)]
15pub enum Value {
16    Null,
17    Bool(bool),
18    Int(i64),
19    Float(f64),
20    Str(String),
21    Array(Vec<Value>),
22    Object(Vec<(String, Value)>),
23}
24
25// Equality follows the total order, so `Int(1) == Float(1.0)`. This lets `Expr`
26// (which embeds `Value` in literals) derive `PartialEq` for tests.
27impl PartialEq for Value {
28    fn eq(&self, other: &Value) -> bool {
29        self.value_eq(other)
30    }
31}
32
33impl Value {
34    /// jq-style truthiness: only `false` and `null` are falsy.
35    pub fn is_truthy(&self) -> bool {
36        !matches!(self, Value::Null | Value::Bool(false))
37    }
38
39    /// The type name, as reported by the `type` builtin.
40    pub fn type_name(&self) -> &'static str {
41        match self {
42            Value::Null => "null",
43            Value::Bool(_) => "boolean",
44            Value::Int(_) | Value::Float(_) => "number",
45            Value::Str(_) => "string",
46            Value::Array(_) => "array",
47            Value::Object(_) => "object",
48        }
49    }
50
51    /// Numeric coercion to f64 for arithmetic and comparison.
52    pub fn as_f64(&self) -> Option<f64> {
53        match self {
54            Value::Int(i) => Some(*i as f64),
55            Value::Float(f) => Some(*f),
56            _ => None,
57        }
58    }
59
60    /// Total order across types (jq's ordering:
61    /// null < bool < number < string < array < object).
62    pub fn order(&self, other: &Value) -> Ordering {
63        fn rank(v: &Value) -> u8 {
64            match v {
65                Value::Null => 0,
66                Value::Bool(_) => 1,
67                Value::Int(_) | Value::Float(_) => 2,
68                Value::Str(_) => 3,
69                Value::Array(_) => 4,
70                Value::Object(_) => 5,
71            }
72        }
73        match (self, other) {
74            (Value::Null, Value::Null) => Ordering::Equal,
75            (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
76            (Value::Str(a), Value::Str(b)) => a.cmp(b),
77            (Value::Array(a), Value::Array(b)) => {
78                for (x, y) in a.iter().zip(b.iter()) {
79                    let c = x.order(y);
80                    if c != Ordering::Equal {
81                        return c;
82                    }
83                }
84                a.len().cmp(&b.len())
85            }
86            (Value::Object(a), Value::Object(b)) => {
87                // Compare by sorted keys, then by the values at those keys.
88                let mut ka: Vec<&String> = a.iter().map(|(k, _)| k).collect();
89                let mut kb: Vec<&String> = b.iter().map(|(k, _)| k).collect();
90                ka.sort();
91                kb.sort();
92                match ka.cmp(&kb) {
93                    Ordering::Equal => {}
94                    ne => return ne,
95                }
96                for k in ka {
97                    let va = a.iter().find(|(kk, _)| kk == k).map(|(_, v)| v);
98                    let vb = b.iter().find(|(kk, _)| kk == k).map(|(_, v)| v);
99                    if let (Some(va), Some(vb)) = (va, vb) {
100                        let c = va.order(vb);
101                        if c != Ordering::Equal {
102                            return c;
103                        }
104                    }
105                }
106                Ordering::Equal
107            }
108            _ => {
109                // Numbers compare numerically; otherwise fall back to type rank.
110                if let (Some(a), Some(b)) = (self.as_f64(), other.as_f64()) {
111                    a.partial_cmp(&b).unwrap_or(Ordering::Equal)
112                } else {
113                    rank(self).cmp(&rank(other))
114                }
115            }
116        }
117    }
118
119    /// Structural equality consistent with [`Value::order`].
120    pub fn value_eq(&self, other: &Value) -> bool {
121        self.order(other) == Ordering::Equal
122    }
123
124    /// Render as a raw scalar for query output (no surrounding quotes on
125    /// strings). Composite values are rendered as JSON.
126    pub fn to_raw_string(&self) -> String {
127        match self {
128            Value::Str(s) => s.clone(),
129            Value::Null => "null".to_string(),
130            Value::Bool(b) => b.to_string(),
131            Value::Int(i) => i.to_string(),
132            Value::Float(f) => format_f64(*f),
133            Value::Array(_) | Value::Object(_) => self.to_json(),
134        }
135    }
136
137    /// Render as compact JSON.
138    pub fn to_json(&self) -> String {
139        let mut out = String::new();
140        self.write_json(&mut out);
141        out
142    }
143
144    fn write_json(&self, out: &mut String) {
145        match self {
146            Value::Null => out.push_str("null"),
147            Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
148            Value::Int(i) => out.push_str(&i.to_string()),
149            Value::Float(f) => out.push_str(&format_f64(*f)),
150            Value::Str(s) => write_json_string(s, out),
151            Value::Array(a) => {
152                out.push('[');
153                for (i, v) in a.iter().enumerate() {
154                    if i > 0 {
155                        out.push(',');
156                    }
157                    v.write_json(out);
158                }
159                out.push(']');
160            }
161            Value::Object(m) => {
162                out.push('{');
163                for (i, (k, v)) in m.iter().enumerate() {
164                    if i > 0 {
165                        out.push(',');
166                    }
167                    write_json_string(k, out);
168                    out.push(':');
169                    v.write_json(out);
170                }
171                out.push('}');
172            }
173        }
174    }
175}
176
177/// Format an f64 the way jq does: integral values print without a decimal point.
178fn format_f64(f: f64) -> String {
179    if f.is_finite() && f.fract() == 0.0 && f.abs() < 1e15 {
180        format!("{}", f as i64)
181    } else {
182        format!("{f}")
183    }
184}
185
186fn write_json_string(s: &str, out: &mut String) {
187    out.push('"');
188    for c in s.chars() {
189        match c {
190            '"' => out.push_str("\\\""),
191            '\\' => out.push_str("\\\\"),
192            '\n' => out.push_str("\\n"),
193            '\r' => out.push_str("\\r"),
194            '\t' => out.push_str("\\t"),
195            c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
196            c => out.push(c),
197        }
198    }
199    out.push('"');
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    fn arr(v: &[Value]) -> Value {
207        Value::Array(v.to_vec())
208    }
209    fn obj(pairs: &[(&str, Value)]) -> Value {
210        Value::Object(
211            pairs
212                .iter()
213                .map(|(k, v)| (k.to_string(), v.clone()))
214                .collect(),
215        )
216    }
217
218    #[test]
219    fn total_order_across_types() {
220        // jq's ordering: null < bool < number < string < array < object.
221        let ladder = [
222            Value::Null,
223            Value::Bool(false),
224            Value::Bool(true),
225            Value::Int(-1),
226            Value::Float(2.5),
227            Value::Str("a".into()),
228            arr(&[Value::Int(1)]),
229            obj(&[("k", Value::Int(1))]),
230        ];
231        for i in 0..ladder.len() {
232            for j in 0..ladder.len() {
233                let want = i.cmp(&j);
234                // Equal ranks (the two bools, two numbers) compare by value, so
235                // only assert the strict cross-rank orderings here.
236                if want != Ordering::Equal {
237                    assert_eq!(
238                        ladder[i].order(&ladder[j]),
239                        want,
240                        "{:?} vs {:?}",
241                        ladder[i],
242                        ladder[j]
243                    );
244                }
245            }
246        }
247    }
248
249    #[test]
250    fn numbers_compare_across_int_and_float() {
251        assert!(Value::Int(1).value_eq(&Value::Float(1.0)));
252        assert_eq!(Value::Int(2).order(&Value::Float(2.5)), Ordering::Less);
253        assert_eq!(Value::Float(3.0).order(&Value::Int(3)), Ordering::Equal);
254        assert_eq!(Value::Int(1).as_f64(), Some(1.0));
255        assert_eq!(Value::Str("x".into()).as_f64(), None);
256    }
257
258    #[test]
259    fn arrays_and_objects_order_structurally() {
260        // Arrays compare element-wise, then by length.
261        assert_eq!(
262            arr(&[Value::Int(1), Value::Int(2)]).order(&arr(&[Value::Int(1), Value::Int(3)])),
263            Ordering::Less
264        );
265        assert_eq!(
266            arr(&[Value::Int(1)]).order(&arr(&[Value::Int(1), Value::Int(0)])),
267            Ordering::Less
268        );
269        // Objects compare by sorted keys, then values at those keys.
270        assert_eq!(
271            obj(&[("a", Value::Int(1))]).order(&obj(&[("b", Value::Int(1))])),
272            Ordering::Less
273        );
274        assert_eq!(
275            obj(&[("a", Value::Int(1))]).order(&obj(&[("a", Value::Int(2))])),
276            Ordering::Less
277        );
278        // Key order doesn't affect equality.
279        assert!(
280            obj(&[("a", Value::Int(1)), ("b", Value::Int(2))])
281                .value_eq(&obj(&[("b", Value::Int(2)), ("a", Value::Int(1))]))
282        );
283    }
284
285    #[test]
286    fn raw_string_and_truthiness() {
287        assert_eq!(Value::Float(1.0).to_raw_string(), "1");
288        assert_eq!(Value::Float(1.5).to_raw_string(), "1.5");
289        assert_eq!(Value::Null.to_raw_string(), "null");
290        assert_eq!(Value::Bool(true).to_raw_string(), "true");
291        assert_eq!(arr(&[Value::Int(1)]).to_raw_string(), "[1]");
292        assert_eq!(obj(&[("a", Value::Int(1))]).to_raw_string(), "{\"a\":1}");
293        // Only false and null are falsy (jq).
294        assert!(Value::Int(0).is_truthy());
295        assert!(Value::Str("".into()).is_truthy());
296        assert!(!Value::Bool(false).is_truthy());
297        assert!(!Value::Null.is_truthy());
298    }
299
300    #[test]
301    fn json_encoding_escapes_and_floats() {
302        assert_eq!(
303            Value::Str("a\"b\\c\n\t\r".into()).to_json(),
304            "\"a\\\"b\\\\c\\n\\t\\r\""
305        );
306        // A control char below 0x20 becomes a \u escape.
307        assert_eq!(Value::Str("\u{0001}".into()).to_json(), "\"\\u0001\"");
308        // Integral floats print without a decimal point; fractional keep it.
309        assert_eq!(Value::Float(42.0).to_json(), "42");
310        assert_eq!(Value::Float(2.5).to_json(), "2.5");
311        assert_eq!(
312            obj(&[("n", Value::Null), ("xs", arr(&[Value::Bool(true)]))]).to_json(),
313            "{\"n\":null,\"xs\":[true]}"
314        );
315        assert_eq!(arr(&[]).to_json(), "[]");
316    }
317
318    #[test]
319    fn type_names() {
320        assert_eq!(Value::Null.type_name(), "null");
321        assert_eq!(Value::Bool(true).type_name(), "boolean");
322        assert_eq!(Value::Int(1).type_name(), "number");
323        assert_eq!(Value::Float(1.0).type_name(), "number");
324        assert_eq!(Value::Str("x".into()).type_name(), "string");
325        assert_eq!(arr(&[]).type_name(), "array");
326        assert_eq!(obj(&[]).type_name(), "object");
327    }
328}