seq_runtime/
value.rs

1use crate::seqstring::SeqString;
2use std::collections::HashMap;
3use std::hash::{Hash, Hasher};
4use std::sync::Arc;
5
6/// MapKey: Hashable subset of Value for use as map keys
7///
8/// Only types that can be meaningfully hashed are allowed as map keys:
9/// Int, String, Bool. Float is excluded due to NaN equality issues.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum MapKey {
12    Int(i64),
13    String(SeqString),
14    Bool(bool),
15}
16
17impl Hash for MapKey {
18    fn hash<H: Hasher>(&self, state: &mut H) {
19        // Discriminant for type safety
20        std::mem::discriminant(self).hash(state);
21        match self {
22            MapKey::Int(n) => n.hash(state),
23            MapKey::String(s) => s.as_str().hash(state),
24            MapKey::Bool(b) => b.hash(state),
25        }
26    }
27}
28
29impl MapKey {
30    /// Try to convert a Value to a MapKey
31    /// Returns None for non-hashable types (Float, Variant, Quotation, Closure, Map)
32    pub fn from_value(value: &Value) -> Option<MapKey> {
33        match value {
34            Value::Int(n) => Some(MapKey::Int(*n)),
35            Value::String(s) => Some(MapKey::String(s.clone())),
36            Value::Bool(b) => Some(MapKey::Bool(*b)),
37            _ => None,
38        }
39    }
40
41    /// Convert MapKey back to Value
42    pub fn to_value(&self) -> Value {
43        match self {
44            MapKey::Int(n) => Value::Int(*n),
45            MapKey::String(s) => Value::String(s.clone()),
46            MapKey::Bool(b) => Value::Bool(*b),
47        }
48    }
49}
50
51/// Value: What the language talks about
52///
53/// This is pure data with no pointers to other values.
54/// Values can be pushed on the stack, stored in variants, etc.
55/// The key insight: Value is independent of Stack structure.
56#[derive(Debug, Clone, PartialEq)]
57pub enum Value {
58    /// Integer value
59    Int(i64),
60
61    /// Floating-point value (IEEE 754 double precision)
62    Float(f64),
63
64    /// Boolean value
65    Bool(bool),
66
67    /// String (arena or globally allocated via SeqString)
68    String(SeqString),
69
70    /// Variant (sum type with tagged fields)
71    Variant(Box<VariantData>),
72
73    /// Map (key-value dictionary with O(1) lookup)
74    /// Keys must be hashable types (Int, String, Bool)
75    Map(Box<HashMap<MapKey, Value>>),
76
77    /// Quotation (stateless function pointer stored as usize for Send safety)
78    /// No captured environment - backward compatible
79    Quotation(usize),
80
81    /// Closure (quotation with captured environment)
82    /// Contains function pointer and Arc-shared array of captured values.
83    /// Arc enables TCO: no cleanup needed after tail call, ref-count handles it.
84    Closure {
85        /// Function pointer (transmuted to function taking Stack + environment)
86        fn_ptr: usize,
87        /// Captured values from creation site (Arc for TCO support)
88        /// Ordered top-down: env[0] is top of stack at creation
89        env: Arc<[Value]>,
90    },
91}
92
93// Safety: Value can be sent between strands (green threads)
94// - Int, Float, Bool are Copy types (trivially Send)
95// - String (SeqString) implements Send (clone to global on transfer)
96// - Variant contains Box<VariantData> which is Send because VariantData contains Send types
97// - Quotation stores function pointer as usize (Send-safe, no owned data)
98// - Closure: fn_ptr is usize (Send), env is Arc<[Value]>
99//   Arc<T> is Send when T: Send + Sync. Since we manually impl Send for Value,
100//   and Value contains no interior mutability, Arc<[Value]> is effectively Send.
101//   Arc is used instead of Box to enable TCO: no cleanup needed after tail calls.
102// - Map contains Box<HashMap> which is Send because keys and values are Send
103// This is required for channel communication between strands
104unsafe impl Send for Value {}
105
106/// VariantData: Composite values (sum types)
107///
108/// Fields are stored in a heap-allocated array, NOT linked via next pointers.
109/// This is the key difference from cem2, which used StackCell.next for field linking.
110#[derive(Debug, Clone, PartialEq)]
111pub struct VariantData {
112    /// Tag identifies which variant constructor was used
113    pub tag: u32,
114
115    /// Fields stored as an owned array of values
116    /// This is independent of any stack structure
117    pub fields: Box<[Value]>,
118}
119
120impl VariantData {
121    /// Create a new variant with the given tag and fields
122    pub fn new(tag: u32, fields: Vec<Value>) -> Self {
123        Self {
124            tag,
125            fields: fields.into_boxed_slice(),
126        }
127    }
128}
129
130// We'll implement proper cleanup in Drop later
131// For now, Rust's ownership handles most of it