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