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/// Allocation-free representation facts for Harn truth testing.
9/// Hosts project their values here; this policy also governs constant folding.
10pub 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
34/// Borrowed projection used by the compiler and portable executor's shared
35/// authority-free value semantics.
36///
37/// Keeping equality and ordering generic over this small view avoids a second
38/// recursive implementation for runtime values while still allowing the
39/// executor to retain closures and host handles outside the data vocabulary.
40pub(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/// Authority-free value vocabulary used by artifacts, snapshots, and hosts.
63#[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
163/// Structural equality for any authority-free value projection.
164///
165/// The explicit work stack makes compiler constant folding safe for deeply
166/// nested source values and lets the executor charge the whole value graph
167/// once before evaluating it without consuming the Rust call stack.
168pub(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
220/// Canonical ordering for the pure value vocabulary.
221///
222/// Lists compare lexicographically. NaN is unordered and propagates through a
223/// containing list. Other non-orderable values retain Harn's established
224/// comparison behavior and compare equal for relational purposes; equality
225/// itself remains structural through [`semantic_values_equal`].
226pub(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}