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    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/// Authority-free value vocabulary used by artifacts, snapshots, and hosts.
37#[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
135/// Structural equality for any authority-free value projection.
136///
137/// The explicit work stack makes compiler constant folding safe for deeply
138/// nested source values and lets the executor charge the whole value graph
139/// once before evaluating it without consuming the Rust call stack.
140pub(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
192/// Canonical ordering for the pure value vocabulary.
193///
194/// Lists compare lexicographically. NaN is unordered and propagates through a
195/// containing list. Other non-orderable values retain Harn's established
196/// comparison behavior and compare equal for relational purposes; equality
197/// itself remains structural through [`semantic_values_equal`].
198pub(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}