seq_runtime/
value.rs

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