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 with two entry points for calling convention compatibility)
78    /// - wrapper: C-convention entry point for calls from the runtime
79    /// - impl_: tailcc entry point for tail calls from compiled code (enables TCO)
80    Quotation {
81        /// C-convention wrapper function pointer (for runtime calls via patch_seq_call)
82        wrapper: usize,
83        /// tailcc implementation function pointer (for musttail from compiled code)
84        impl_: usize,
85    },
86
87    /// Closure (quotation with captured environment)
88    /// Contains function pointer and Arc-shared array of captured values.
89    /// Arc enables TCO: no cleanup needed after tail call, ref-count handles it.
90    Closure {
91        /// Function pointer (transmuted to function taking Stack + environment)
92        fn_ptr: usize,
93        /// Captured values from creation site (Arc for TCO support)
94        /// Ordered top-down: env[0] is top of stack at creation
95        env: Arc<[Value]>,
96    },
97}
98
99// Safety: Value can be sent between strands (green threads)
100// - Int, Float, Bool are Copy types (trivially Send)
101// - String (SeqString) implements Send (clone to global on transfer)
102// - Variant contains Box<VariantData> which is Send because VariantData contains Send types
103// - Quotation stores function pointer as usize (Send-safe, no owned data)
104// - Closure: fn_ptr is usize (Send), env is Arc<[Value]>
105//   Arc<T> is Send when T: Send + Sync. Since we manually impl Send for Value,
106//   and Value contains no interior mutability, Arc<[Value]> is effectively Send.
107//   Arc is used instead of Box to enable TCO: no cleanup needed after tail calls.
108// - Map contains Box<HashMap> which is Send because keys and values are Send
109// This is required for channel communication between strands
110unsafe impl Send for Value {}
111
112/// VariantData: Composite values (sum types)
113///
114/// Fields are stored in a heap-allocated array, NOT linked via next pointers.
115/// This is the key difference from cem2, which used StackCell.next for field linking.
116#[derive(Debug, Clone, PartialEq)]
117pub struct VariantData {
118    /// Tag identifies which variant constructor was used
119    pub tag: u32,
120
121    /// Fields stored as an owned array of values
122    /// This is independent of any stack structure
123    pub fields: Box<[Value]>,
124}
125
126impl VariantData {
127    /// Create a new variant with the given tag and fields
128    pub fn new(tag: u32, fields: Vec<Value>) -> Self {
129        Self {
130            tag,
131            fields: fields.into_boxed_slice(),
132        }
133    }
134}
135
136// We'll implement proper cleanup in Drop later
137// For now, Rust's ownership handles most of it