1use std::collections::{BTreeMap, HashMap};
4use std::fmt;
5
6pub(crate) use crate::error::ExprError;
7
8#[derive(Debug, Clone, PartialEq, Default)]
15pub enum Value {
16 #[default]
18 Null,
19 Bool(bool),
21 Num(f64),
23 Str(String),
25 List(Vec<Value>),
27 Obj(BTreeMap<String, Value>),
29}
30
31impl Value {
32 pub fn is_null(&self) -> bool {
34 matches!(self, Value::Null)
35 }
36
37 pub fn is_num(&self) -> bool {
39 matches!(self, Value::Num(_))
40 }
41
42 pub fn as_num(&self) -> Option<f64> {
44 match self {
45 Value::Num(n) => Some(*n),
46 _ => None,
47 }
48 }
49
50 pub fn as_bool(&self) -> Option<bool> {
52 match self {
53 Value::Bool(b) => Some(*b),
54 _ => None,
55 }
56 }
57
58 pub fn as_str(&self) -> Option<&str> {
60 match self {
61 Value::Str(s) => Some(s),
62 _ => None,
63 }
64 }
65
66 pub fn as_list(&self) -> Option<&[Value]> {
68 match self {
69 Value::List(l) => Some(l),
70 _ => None,
71 }
72 }
73
74 pub fn as_obj(&self) -> Option<&BTreeMap<String, Value>> {
76 match self {
77 Value::Obj(o) => Some(o),
78 _ => None,
79 }
80 }
81
82 pub fn type_name(&self) -> &'static str {
85 match self {
86 Value::Null => "null",
87 Value::Bool(_) => "boolean",
88 Value::Num(_) => "number",
89 Value::Str(_) => "string",
90 Value::List(_) => "list",
91 Value::Obj(_) => "object",
92 }
93 }
94
95 pub fn dialect_eq(&self, other: &Value) -> bool {
99 match (self, other) {
100 (Value::Null, Value::Null) => true,
101 (Value::Bool(x), Value::Bool(y)) => x == y,
102 (Value::Bool(_), _) | (_, Value::Bool(_)) => false,
103 (Value::Null, _) | (_, Value::Null) => false,
104 (Value::Num(x), Value::Num(y)) => x == y,
105 (Value::Str(x), Value::Str(y)) => x == y,
106 (Value::List(x), Value::List(y)) => x.len() == y.len() && x.iter().zip(y).all(|(p, q)| p.dialect_eq(q)),
107 (Value::Obj(x), Value::Obj(y)) => {
108 x.len() == y.len() && x.iter().all(|(k, v)| y.get(k).is_some_and(|w| v.dialect_eq(w)))
109 }
110 _ => false,
111 }
112 }
113}
114
115impl fmt::Display for Value {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 write!(f, "{}", crate::json::value_to_json(self))
119 }
120}
121
122impl From<bool> for Value {
123 fn from(b: bool) -> Self {
124 Value::Bool(b)
125 }
126}
127impl From<f64> for Value {
128 fn from(n: f64) -> Self {
129 Value::Num(n)
130 }
131}
132impl From<i64> for Value {
133 fn from(n: i64) -> Self {
134 Value::Num(n as f64)
135 }
136}
137impl From<i32> for Value {
138 fn from(n: i32) -> Self {
139 Value::Num(f64::from(n))
140 }
141}
142impl From<&str> for Value {
143 fn from(s: &str) -> Self {
144 Value::Str(s.to_string())
145 }
146}
147impl From<String> for Value {
148 fn from(s: String) -> Self {
149 Value::Str(s)
150 }
151}
152impl<T: Into<Value>> From<Vec<T>> for Value {
153 fn from(v: Vec<T>) -> Self {
154 Value::List(v.into_iter().map(Into::into).collect())
155 }
156}
157impl<T: Into<Value>> From<Option<T>> for Value {
158 fn from(v: Option<T>) -> Self {
159 v.map_or(Value::Null, Into::into)
160 }
161}
162impl From<BTreeMap<String, Value>> for Value {
163 fn from(o: BTreeMap<String, Value>) -> Self {
164 Value::Obj(o)
165 }
166}
167
168pub type Scope = BTreeMap<String, Value>;
171
172pub trait Lookup {
176 fn lookup(&self, name: &str) -> Value;
178}
179
180impl Lookup for Scope {
181 fn lookup(&self, name: &str) -> Value {
182 self.get(name).cloned().unwrap_or(Value::Null)
183 }
184}
185
186impl Lookup for HashMap<String, Value> {
187 fn lookup(&self, name: &str) -> Value {
188 self.get(name).cloned().unwrap_or(Value::Null)
189 }
190}
191
192impl Lookup for [(String, Value)] {
194 fn lookup(&self, name: &str) -> Value {
195 self.iter()
196 .find(|(k, _)| k == name)
197 .map(|(_, v)| v.clone())
198 .unwrap_or(Value::Null)
199 }
200}
201
202impl Lookup for Vec<(String, Value)> {
203 fn lookup(&self, name: &str) -> Value {
204 self.as_slice().lookup(name)
205 }
206}
207
208impl<T: Lookup + ?Sized> Lookup for &T {
209 fn lookup(&self, name: &str) -> Value {
210 (**self).lookup(name)
211 }
212}
213
214#[derive(Debug, Clone, Copy, Default)]
216pub struct Empty;
217
218impl Lookup for Empty {
219 fn lookup(&self, _name: &str) -> Value {
220 Value::Null
221 }
222}
223
224pub type EResult = Result<Value, ExprError>;
226
227pub(crate) fn err<T>(msg: impl Into<String>) -> Result<T, ExprError> {
229 Err(ExprError::call(msg))
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn conversions_and_accessors() {
238 assert_eq!(Value::from(3), Value::Num(3.0));
239 assert_eq!(Value::from(2.5_f64).as_num(), Some(2.5));
240 assert_eq!(Value::from("x").as_str(), Some("x"));
241 assert_eq!(Value::from(String::from("y")).type_name(), "string");
242 assert_eq!(
243 Value::from(vec![1, 2]),
244 Value::List(vec![Value::Num(1.0), Value::Num(2.0)])
245 );
246 assert_eq!(Value::from(None::<bool>), Value::Null);
247 assert_eq!(Value::from(Some(true)).as_bool(), Some(true));
248 assert!(Value::default().is_null() && !Value::Bool(true).is_num());
249 let o: BTreeMap<String, Value> = [("k".to_string(), Value::from(1))].into();
250 assert_eq!(Value::from(o.clone()).as_obj(), Some(&o));
251 assert_eq!(Value::from(vec![Value::Null]).as_list().map(<[Value]>::len), Some(1));
252 assert_eq!(Value::from("s").to_string(), "\"s\"");
253 assert_eq!(Value::Num(f64::NAN).to_string(), "null");
254 }
255
256 #[test]
257 fn dialect_equality_rules() {
258 assert!(Value::Null.dialect_eq(&Value::Null));
259 assert!(!Value::Bool(true).dialect_eq(&Value::Num(1.0)));
260 assert!(!Value::Null.dialect_eq(&Value::Num(0.0)));
261 assert!(!Value::Num(f64::NAN).dialect_eq(&Value::Num(f64::NAN)));
262 assert!(Value::from(vec![1]).dialect_eq(&Value::from(vec![1.0])));
263 assert!(!Value::from(vec![1]).dialect_eq(&Value::from(vec![1, 2])));
264 }
265
266 #[test]
267 fn lookups_agree() {
268 let row = vec![("a".to_string(), Value::from(1))];
269 let scope: Scope = row.iter().cloned().collect();
270 let hash: HashMap<String, Value> = row.iter().cloned().collect();
271 for l in [&row as &dyn Lookup, &scope, &hash, &row.as_slice()] {
272 assert_eq!(l.lookup("a"), Value::Num(1.0));
273 assert_eq!(l.lookup("missing"), Value::Null);
274 }
275 assert_eq!(Empty.lookup("a"), Value::Null);
276 assert_eq!(Lookup::lookup(&&scope, "a"), Value::Num(1.0));
277 }
278}