1use std::collections::BTreeMap;
2use std::fmt;
3use std::sync::Arc;
4
5pub type DictMap = BTreeMap<String, VmValue>;
6pub type HarnStr = arcstr::ArcStr;
7
8pub(crate) enum ValueView<'a, T> {
15 Nil,
16 Bool(bool),
17 Int(i64),
18 Float(f64),
19 String(&'a str),
20 Bytes(&'a [u8]),
21 Duration(i64),
22 List(&'a [T]),
23 Record(&'a BTreeMap<String, T>),
24 Enum {
25 enum_name: &'a str,
26 variant: &'a str,
27 fields: &'a [T],
28 },
29 Opaque,
30}
31
32pub(crate) trait SemanticValue: Sized {
33 fn semantic_view(&self) -> ValueView<'_, Self>;
34}
35
36#[derive(Debug, Clone)]
38pub enum VmValue {
39 Int(i64),
40 Float(f64),
41 String(HarnStr),
42 Bool(bool),
43 Nil,
44 Duration(i64),
45 List(Arc<Vec<VmValue>>),
46 Dict(Arc<DictMap>),
47}
48
49impl VmValue {
50 pub fn dict<K>(entries: impl IntoIterator<Item = (K, VmValue)>) -> Self
51 where
52 K: Into<String>,
53 {
54 Self::Dict(Arc::new(
55 entries
56 .into_iter()
57 .map(|(key, value)| (key.into(), value))
58 .collect(),
59 ))
60 }
61
62 pub fn is_truthy(&self) -> bool {
63 match self {
64 Self::Nil | Self::Bool(false) => false,
65 Self::Int(value) => *value != 0,
66 Self::Float(value) => *value != 0.0 && !value.is_nan(),
67 Self::String(value) => !value.is_empty(),
68 Self::List(value) => !value.is_empty(),
69 Self::Dict(value) => !value.is_empty(),
70 Self::Bool(true) | Self::Duration(_) => true,
71 }
72 }
73
74 pub fn display(&self) -> String {
75 self.to_string()
76 }
77}
78
79impl SemanticValue for VmValue {
80 fn semantic_view(&self) -> ValueView<'_, Self> {
81 match self {
82 Self::Nil => ValueView::Nil,
83 Self::Bool(value) => ValueView::Bool(*value),
84 Self::Int(value) => ValueView::Int(*value),
85 Self::Float(value) => ValueView::Float(*value),
86 Self::String(value) => ValueView::String(value),
87 Self::Duration(value) => ValueView::Duration(*value),
88 Self::List(values) => ValueView::List(values),
89 Self::Dict(values) => ValueView::Record(values),
90 }
91 }
92}
93
94impl fmt::Display for VmValue {
95 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
96 match self {
97 Self::Int(value) => write!(formatter, "{value}"),
98 Self::Float(value) => write!(formatter, "{value}"),
99 Self::String(value) => formatter.write_str(value),
100 Self::Bool(value) => write!(formatter, "{value}"),
101 Self::Nil => formatter.write_str("nil"),
102 Self::Duration(value) => write!(formatter, "{value}ms"),
103 Self::List(values) => {
104 formatter.write_str("[")?;
105 for (index, value) in values.iter().enumerate() {
106 if index > 0 {
107 formatter.write_str(", ")?;
108 }
109 write!(formatter, "{value}")?;
110 }
111 formatter.write_str("]")
112 }
113 Self::Dict(entries) => {
114 formatter.write_str("{")?;
115 for (index, (key, value)) in entries.iter().enumerate() {
116 if index > 0 {
117 formatter.write_str(", ")?;
118 }
119 write!(formatter, "{key}: {value}")?;
120 }
121 formatter.write_str("}")
122 }
123 }
124 }
125}
126
127pub fn values_equal(left: &VmValue, right: &VmValue) -> bool {
128 semantic_values_equal(left, right)
129}
130
131pub fn try_compare_values(left: &VmValue, right: &VmValue) -> Option<i8> {
132 semantic_try_compare(left, right)
133}
134
135pub(crate) fn semantic_values_equal<T: SemanticValue>(left: &T, right: &T) -> bool {
141 let mut pending = vec![(left, right)];
142 while let Some((left, right)) = pending.pop() {
143 match (left.semantic_view(), right.semantic_view()) {
144 (ValueView::Nil, ValueView::Nil) => {}
145 (ValueView::Bool(left), ValueView::Bool(right)) if left == right => {}
146 (ValueView::Int(left), ValueView::Int(right)) if left == right => {}
147 (ValueView::Float(left), ValueView::Float(right)) if left == right => {}
148 (ValueView::Int(left), ValueView::Float(right)) if left as f64 == right => {}
149 (ValueView::Float(left), ValueView::Int(right)) if left == right as f64 => {}
150 (ValueView::String(left), ValueView::String(right)) if left == right => {}
151 (ValueView::Bytes(left), ValueView::Bytes(right)) if left == right => {}
152 (ValueView::Duration(left), ValueView::Duration(right)) if left == right => {}
153 (ValueView::List(left), ValueView::List(right)) if left.len() == right.len() => {
154 pending.extend(left.iter().zip(right.iter()));
155 }
156 (ValueView::Record(left), ValueView::Record(right)) if left.len() == right.len() => {
157 for (key, left_value) in left {
158 let Some(right_value) = right.get(key) else {
159 return false;
160 };
161 pending.push((left_value, right_value));
162 }
163 }
164 (
165 ValueView::Enum {
166 enum_name: left_enum,
167 variant: left_variant,
168 fields: left_fields,
169 },
170 ValueView::Enum {
171 enum_name: right_enum,
172 variant: right_variant,
173 fields: right_fields,
174 },
175 ) if left_enum == right_enum
176 && left_variant == right_variant
177 && left_fields.len() == right_fields.len() =>
178 {
179 pending.extend(left_fields.iter().zip(right_fields.iter()));
180 }
181 _ => return false,
182 }
183 }
184 true
185}
186
187enum CompareTask<'a, T> {
188 Values(&'a T, &'a T),
189 ListLength(usize, usize),
190}
191
192pub(crate) fn semantic_try_compare<T: SemanticValue>(left: &T, right: &T) -> Option<i8> {
199 use std::cmp::Ordering;
200
201 let mut pending = vec![CompareTask::Values(left, right)];
202 while let Some(task) = pending.pop() {
203 let ordering = match task {
204 CompareTask::ListLength(left, right) => left.cmp(&right),
205 CompareTask::Values(left, right) => match (left.semantic_view(), right.semantic_view())
206 {
207 (ValueView::Int(left), ValueView::Int(right)) => left.cmp(&right),
208 (ValueView::Float(left), ValueView::Float(right)) => left.partial_cmp(&right)?,
209 (ValueView::Int(left), ValueView::Float(right)) => {
210 (left as f64).partial_cmp(&right)?
211 }
212 (ValueView::Float(left), ValueView::Int(right)) => {
213 left.partial_cmp(&(right as f64))?
214 }
215 (ValueView::String(left), ValueView::String(right)) => left.cmp(right),
216 (ValueView::List(left), ValueView::List(right)) => {
217 pending.push(CompareTask::ListLength(left.len(), right.len()));
218 for (left, right) in left.iter().zip(right.iter()).rev() {
219 pending.push(CompareTask::Values(left, right));
220 }
221 continue;
222 }
223 _ => Ordering::Equal,
224 },
225 };
226 match ordering {
227 Ordering::Less => return Some(-1),
228 Ordering::Greater => return Some(1),
229 Ordering::Equal => {}
230 }
231 }
232 Some(0)
233}
234
235pub fn intern_key(key: &str) -> String {
236 key.to_owned()
237}
238
239pub trait VmDictExt {
240 fn put_str(&mut self, key: &str, value: &str);
241}
242
243impl VmDictExt for DictMap {
244 fn put_str(&mut self, key: &str, value: &str) {
245 self.insert(key.to_string(), VmValue::String(value.into()));
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252
253 fn list(values: Vec<VmValue>) -> VmValue {
254 VmValue::List(Arc::new(values))
255 }
256
257 #[test]
258 fn shared_ordering_is_iterative_lexicographic_and_nan_aware() {
259 assert_eq!(
260 try_compare_values(
261 &list(vec![VmValue::Int(1), VmValue::Int(2)]),
262 &list(vec![VmValue::Int(1), VmValue::Int(3)]),
263 ),
264 Some(-1)
265 );
266 assert_eq!(
267 try_compare_values(
268 &list(vec![VmValue::Int(1)]),
269 &list(vec![VmValue::Int(1), VmValue::Int(0)])
270 ),
271 Some(-1)
272 );
273 assert_eq!(
274 try_compare_values(
275 &list(vec![VmValue::Float(f64::NAN)]),
276 &list(vec![VmValue::Int(1)]),
277 ),
278 None
279 );
280 }
281
282 #[test]
283 fn non_orderable_values_retain_native_relational_fallback() {
284 assert_eq!(
285 try_compare_values(&VmValue::Bool(false), &VmValue::Bool(true)),
286 Some(0)
287 );
288 assert_eq!(
289 try_compare_values(&VmValue::Nil, &VmValue::Dict(Arc::new(BTreeMap::new()))),
290 Some(0)
291 );
292 }
293}