Skip to main content

tidepool_eval/
value.rs

1use crate::env::Env;
2use std::sync::{Arc, Mutex};
3use tidepool_repr::{CoreExpr, DataConId, Literal, VarId};
4
5/// Shared mutable byte array — `Arc<Mutex>` for in-place mutation semantics
6/// and Send requirement (tidepool-mcp spawns threads).
7/// IMPORTANT: Never hold two locks simultaneously on different SharedByteArrays
8/// within a single primop — always clone data out first to avoid deadlock.
9pub type SharedByteArray = Arc<Mutex<Vec<u8>>>;
10
11/// Runtime value for the tree-walking interpreter.
12#[derive(Debug, Clone)]
13pub enum Value {
14    /// Literal value (Int, Word, Char, String, Float, Double)
15    Lit(Literal),
16    /// Fully-applied data constructor
17    Con(DataConId, Vec<Value>),
18    /// Closure: captured env + binder + body
19    Closure(Env, VarId, CoreExpr),
20    /// Reference to a heap-allocated thunk
21    ThunkRef(ThunkId),
22    /// Join point continuation (lives in Env only, never heap-allocated)
23    JoinCont(Vec<VarId>, CoreExpr, Env),
24    /// Partially-applied data constructor: (tag, arity, accumulated args)
25    /// When all args are supplied, collapses to Con.
26    ConFun(DataConId, usize, Vec<Value>),
27    /// Mutable/immutable byte array (ByteArray# / MutableByteArray#)
28    ByteArray(SharedByteArray),
29}
30
31/// Thunk identifier — index into the thunk store.
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33pub struct ThunkId(pub u32);
34
35impl std::fmt::Display for ThunkId {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        write!(f, "<thunk#{}>", self.0)
38    }
39}
40
41impl std::fmt::Display for Value {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        match self {
44            Value::Lit(lit) => match lit {
45                Literal::LitInt(n) => write!(f, "{}", n),
46                Literal::LitWord(n) => write!(f, "{}", n),
47                Literal::LitChar(c) => write!(f, "'{}'", c.escape_default()),
48                Literal::LitString(bs) => match std::str::from_utf8(bs) {
49                    Ok(s) => write!(f, "{:?}", s),
50                    Err(_) => write!(f, "<bytes len={}>", bs.len()),
51                },
52                Literal::LitFloat(bits) => match u32::try_from(*bits) {
53                    Ok(bits32) => write!(f, "{}", f32::from_bits(bits32)),
54                    Err(_) => write!(f, "<invalid f32 bits=0x{:016x}>", *bits),
55                },
56                Literal::LitDouble(bits) => write!(f, "{}", f64::from_bits(*bits)),
57            },
58            Value::Con(id, fields) => {
59                write!(f, "<Con#{}>", id.0)?;
60                for field in fields {
61                    write!(f, " {}", field)?;
62                }
63                Ok(())
64            }
65            Value::Closure(..) => write!(f, "<closure>"),
66            Value::ThunkRef(id) => write!(f, "{}", id),
67            Value::JoinCont(..) => write!(f, "<join>"),
68            Value::ConFun(id, arity, args) => {
69                write!(f, "<partial Con#{} {}/{}>", id.0, args.len(), arity)
70            }
71            Value::ByteArray(ba) => match ba.lock() {
72                Ok(bytes) => write!(f, "<ByteArray# len={}>", bytes.len()),
73                Err(_) => write!(f, "<ByteArray# poisoned>"),
74            },
75        }
76    }
77}
78
79impl Value {
80    /// Count total nodes in a Value tree. O(n) walk used for size checks.
81    pub fn node_count(&self) -> usize {
82        match self {
83            Value::Con(_, fields) => 1 + fields.iter().map(|f| f.node_count()).sum::<usize>(),
84            _ => 1,
85        }
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use tidepool_repr::{CoreFrame, RecursiveTree};
93
94    #[test]
95    fn test_value_display() {
96        let env = Env::new();
97        let expr = RecursiveTree {
98            nodes: vec![CoreFrame::Var(VarId(0))],
99        };
100
101        assert_eq!(Value::Lit(Literal::LitInt(42)).to_string(), "42");
102        assert_eq!(Value::Lit(Literal::LitChar('x')).to_string(), "'x'");
103        assert_eq!(Value::Lit(Literal::LitChar('\n')).to_string(), r"'\n'");
104        assert_eq!(
105            Value::Lit(Literal::LitString(b"hello".to_vec())).to_string(),
106            "\"hello\""
107        );
108        assert_eq!(
109            Value::Lit(Literal::LitString(b"with \"quotes\"".to_vec())).to_string(),
110            "\"with \\\"quotes\\\"\""
111        );
112        assert_eq!(Value::Lit(Literal::from(3.14f64)).to_string(), "3.14");
113        assert_eq!(
114            Value::Lit(Literal::LitFloat(0xFFFF_FFFF_FFFF_FFFF)).to_string(),
115            "<invalid f32 bits=0xffffffffffffffff>"
116        );
117
118        assert_eq!(Value::Con(DataConId(1), vec![]).to_string(), "<Con#1>");
119        assert_eq!(
120            Value::Con(DataConId(1), vec![Value::Lit(Literal::LitInt(42))]).to_string(),
121            "<Con#1> 42"
122        );
123        assert_eq!(
124            Value::Con(
125                DataConId(1),
126                vec![
127                    Value::Lit(Literal::LitInt(42)),
128                    Value::Lit(Literal::LitString(b"hi".to_vec()))
129                ]
130            )
131            .to_string(),
132            "<Con#1> 42 \"hi\""
133        );
134
135        assert_eq!(
136            Value::Closure(env.clone(), VarId(0), expr.clone()).to_string(),
137            "<closure>"
138        );
139        assert_eq!(Value::ThunkRef(ThunkId(123)).to_string(), "<thunk#123>");
140        assert_eq!(
141            Value::JoinCont(vec![VarId(1)], expr, env).to_string(),
142            "<join>"
143        );
144
145        assert_eq!(
146            Value::ConFun(DataConId(1), 2, vec![Value::Lit(Literal::LitInt(42))]).to_string(),
147            "<partial Con#1 1/2>"
148        );
149    }
150
151    #[test]
152    fn test_value_construction() {
153        let env = Env::new();
154        let lit = Value::Lit(Literal::LitInt(42));
155        let con = Value::Con(DataConId(1), vec![lit.clone()]);
156
157        let expr = RecursiveTree {
158            nodes: vec![CoreFrame::Var(VarId(0))],
159        };
160        let closure = Value::Closure(env.clone(), VarId(0), expr.clone());
161        let thunk = Value::ThunkRef(ThunkId(0));
162        let join = Value::JoinCont(vec![VarId(1)], expr, env);
163
164        match lit {
165            Value::Lit(_) => (),
166            _ => panic!("Expected Lit"),
167        }
168        match con {
169            Value::Con(_, _) => (),
170            _ => panic!("Expected Con"),
171        }
172        match closure {
173            Value::Closure(_, _, _) => (),
174            _ => panic!("Expected Closure"),
175        }
176        match thunk {
177            Value::ThunkRef(_) => (),
178            _ => panic!("Expected ThunkRef"),
179        }
180        match join {
181            Value::JoinCont(_, _, _) => (),
182            _ => panic!("Expected JoinCont"),
183        }
184    }
185
186    #[test]
187    fn test_closure_clone() {
188        let env = Env::new();
189        let expr = RecursiveTree {
190            nodes: vec![CoreFrame::Var(VarId(0))],
191        };
192        let closure = Value::Closure(env, VarId(0), expr);
193        let _cloned = closure.clone();
194    }
195}