seq_runtime/value.rs
1use crate::seqstring::SeqString;
2use std::collections::HashMap;
3use std::hash::{Hash, Hasher};
4use std::sync::Arc;
5
6// Note: Arc is used for both Closure.env and Variant to enable O(1) cloning.
7// This is essential for functional programming with recursive data structures.
8
9/// MapKey: Hashable subset of Value for use as map keys
10///
11/// Only types that can be meaningfully hashed are allowed as map keys:
12/// Int, String, Bool. Float is excluded due to NaN equality issues.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum MapKey {
15 Int(i64),
16 String(SeqString),
17 Bool(bool),
18}
19
20impl Hash for MapKey {
21 fn hash<H: Hasher>(&self, state: &mut H) {
22 // Discriminant for type safety
23 std::mem::discriminant(self).hash(state);
24 match self {
25 MapKey::Int(n) => n.hash(state),
26 MapKey::String(s) => s.as_str().hash(state),
27 MapKey::Bool(b) => b.hash(state),
28 }
29 }
30}
31
32impl MapKey {
33 /// Try to convert a Value to a MapKey
34 /// Returns None for non-hashable types (Float, Variant, Quotation, Closure, Map)
35 pub fn from_value(value: &Value) -> Option<MapKey> {
36 match value {
37 Value::Int(n) => Some(MapKey::Int(*n)),
38 Value::String(s) => Some(MapKey::String(s.clone())),
39 Value::Bool(b) => Some(MapKey::Bool(*b)),
40 _ => None,
41 }
42 }
43
44 /// Convert MapKey back to Value
45 pub fn to_value(&self) -> Value {
46 match self {
47 MapKey::Int(n) => Value::Int(*n),
48 MapKey::String(s) => Value::String(s.clone()),
49 MapKey::Bool(b) => Value::Bool(*b),
50 }
51 }
52}
53
54/// Value: What the language talks about
55///
56/// This is pure data with no pointers to other values.
57/// Values can be pushed on the stack, stored in variants, etc.
58/// The key insight: Value is independent of Stack structure.
59#[derive(Debug, Clone, PartialEq)]
60pub enum Value {
61 /// Integer value
62 Int(i64),
63
64 /// Floating-point value (IEEE 754 double precision)
65 Float(f64),
66
67 /// Boolean value
68 Bool(bool),
69
70 /// String (arena or globally allocated via SeqString)
71 String(SeqString),
72
73 /// Variant (sum type with tagged fields)
74 /// Uses Arc for O(1) cloning - essential for recursive data structures
75 Variant(Arc<VariantData>),
76
77 /// Map (key-value dictionary with O(1) lookup)
78 /// Keys must be hashable types (Int, String, Bool)
79 Map(Box<HashMap<MapKey, Value>>),
80
81 /// Quotation (stateless function with two entry points for calling convention compatibility)
82 /// - wrapper: C-convention entry point for calls from the runtime
83 /// - impl_: tailcc entry point for tail calls from compiled code (enables TCO)
84 Quotation {
85 /// C-convention wrapper function pointer (for runtime calls via patch_seq_call)
86 wrapper: usize,
87 /// tailcc implementation function pointer (for musttail from compiled code)
88 impl_: usize,
89 },
90
91 /// Closure (quotation with captured environment)
92 /// Contains function pointer and Arc-shared array of captured values.
93 /// Arc enables TCO: no cleanup needed after tail call, ref-count handles it.
94 Closure {
95 /// Function pointer (transmuted to function taking Stack + environment)
96 fn_ptr: usize,
97 /// Captured values from creation site (Arc for TCO support)
98 /// Ordered top-down: env[0] is top of stack at creation
99 env: Arc<[Value]>,
100 },
101}
102
103// Safety: Value can be sent and shared between strands (green threads)
104//
105// Send (safe to transfer ownership between threads):
106// - Int, Float, Bool are Copy types (trivially Send)
107// - String (SeqString) implements Send (clone to global on transfer)
108// - Variant contains Arc<VariantData> which is Send when VariantData is Send+Sync
109// - Quotation stores function pointer as usize (Send-safe, no owned data)
110// - Closure: fn_ptr is usize (Send), env is Arc<[Value]> (Send when Value is Send+Sync)
111// - Map contains Box<HashMap> which is Send because keys and values are Send
112//
113// Sync (safe to share references between threads):
114// - Value has no interior mutability (no Cell, RefCell, Mutex, etc.)
115// - All operations on Value are read-only or create new values (functional semantics)
116// - Arc requires T: Send + Sync for full thread-safety
117//
118// This is required for:
119// - Channel communication between strands
120// - Arc-based sharing of Variants and Closure environments
121unsafe impl Send for Value {}
122unsafe impl Sync for Value {}
123
124/// VariantData: Composite values (sum types)
125///
126/// Fields are stored in a heap-allocated array, NOT linked via next pointers.
127/// This is the key difference from cem2, which used StackCell.next for field linking.
128///
129/// # Arc and Reference Cycles
130///
131/// Variants use `Arc<VariantData>` for O(1) cloning, which could theoretically
132/// create reference cycles. However, cycles are prevented by design:
133/// - VariantData.fields is immutable (no mutation after creation)
134/// - All variant operations create new variants rather than modifying existing ones
135/// - The Seq language has no mutation primitives for variant fields
136///
137/// This functional/immutable design ensures Arc reference counts always reach zero.
138#[derive(Debug, Clone, PartialEq)]
139pub struct VariantData {
140 /// Tag identifies which variant constructor was used
141 pub tag: u32,
142
143 /// Fields stored as an owned array of values
144 /// This is independent of any stack structure
145 pub fields: Box<[Value]>,
146}
147
148impl VariantData {
149 /// Create a new variant with the given tag and fields
150 pub fn new(tag: u32, fields: Vec<Value>) -> Self {
151 Self {
152 tag,
153 fields: fields.into_boxed_slice(),
154 }
155 }
156}
157
158// We'll implement proper cleanup in Drop later
159// For now, Rust's ownership handles most of it