Skip to main content

harn_kernel/
value.rs

1use std::collections::BTreeMap;
2use std::fmt;
3use std::sync::Arc;
4
5pub type DictMap = BTreeMap<String, VmValue>;
6pub type HarnStr = arcstr::ArcStr;
7
8/// Borrowed projection used by the compiler and portable executor's shared
9/// authority-free value semantics.
10///
11/// Keeping equality and ordering generic over this small view avoids a second
12/// recursive implementation for runtime values while still allowing the
13/// executor to retain closures and host handles outside the data vocabulary.
14pub(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/// Authority-free value vocabulary used by artifacts, snapshots, and hosts.
32#[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
130/// Structural equality for any authority-free value projection.
131///
132/// The explicit work stack makes compiler constant folding safe for deeply
133/// nested source values and lets the executor charge the whole value graph
134/// once before evaluating it without consuming the Rust call stack.
135pub(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
170/// Canonical ordering for the pure value vocabulary.
171///
172/// Lists compare lexicographically. NaN is unordered and propagates through a
173/// containing list. Other non-orderable values retain Harn's established
174/// comparison behavior and compare equal for relational purposes; equality
175/// itself remains structural through [`semantic_values_equal`].
176pub(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}