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