Skip to main content

seq_core/
value.rs

1//! The `Value` type — the datum a Seq program talks about — plus the
2//! supporting types it embeds or composes with:
3//!
4//! - [`Value`]: the 11-variant enum (Int, Float, Bool, String, Symbol,
5//!   Variant, Map, Quotation, Closure, Channel, WeaveCtx).
6//! - [`VariantData`]: the heap-allocated payload behind `Value::Variant`.
7//! - [`MapKey`]: the hashable subset of `Value` allowed as map keys.
8//! - [`ChannelData`] / [`WeaveChannelData`] / [`WeaveMessage`]: the channel
9//!   handles that back `Value::Channel` and `Value::WeaveCtx`.
10//!
11//! `Value` has `#[repr(C)]` so compiled code can write into it directly
12//! without going through FFI, and implements `Send + Sync` via an `unsafe
13//! impl` (see the comment block on that impl for the safety argument).
14
15use crate::seqstring::SeqString;
16use may::sync::mpmc;
17use std::collections::HashMap;
18use std::hash::{Hash, Hasher};
19use std::sync::Arc;
20use std::sync::atomic::AtomicBool;
21
22/// Message type for plain channels.
23///
24/// Mirrors `WeaveMessage`: wrapping the underlying `may::mpmc` queue
25/// in a typed enum lets lifecycle signals travel through the same
26/// channel as user data without any value collision. `chan.close`
27/// sends one `Closed` sentinel; receivers re-broadcast it so all
28/// blocked consumers in an MPMC fan-out wake up. See issue #499 and
29/// `docs/design/CHAN_CLOSE_SEMANTICS.md`.
30#[derive(Debug, Clone, PartialEq)]
31pub enum ChannelMsg {
32    /// Normal value being sent through the channel.
33    Value(Value),
34    /// Channel-closed sentinel — `chan.close` sends one of these on
35    /// the first close; `chan.receive` re-sends it before returning
36    /// failure so the next blocked receiver also wakes.
37    Closed,
38}
39
40/// Channel data: holds sender, receiver, and a closed flag.
41///
42/// Both sender and receiver are Clone (MPMC), so duplicating a
43/// Channel value just clones the Arc. Send/receive operations use
44/// the handles directly with zero mutex overhead — the `closed`
45/// flag is a single atomic load on the send hot path, no locking.
46#[derive(Debug, Clone)]
47pub struct ChannelData {
48    pub sender: mpmc::Sender<ChannelMsg>,
49    pub receiver: mpmc::Receiver<ChannelMsg>,
50    /// Set by `chan.close`. Reads gate `chan.send`; the close itself
51    /// also enqueues one `ChannelMsg::Closed` sentinel to wake any
52    /// already-blocked receivers.
53    pub closed: Arc<AtomicBool>,
54}
55
56// PartialEq by identity (Arc pointer comparison)
57impl PartialEq for ChannelData {
58    fn eq(&self, other: &Self) -> bool {
59        std::ptr::eq(self, other)
60    }
61}
62
63/// Message type for weave channels.
64///
65/// Using an enum instead of sentinel values ensures no collision with user data.
66/// Any `Value` can be safely yielded/resumed, including `i64::MIN`.
67#[derive(Debug, Clone, PartialEq)]
68pub enum WeaveMessage {
69    /// Normal value being yielded or resumed
70    Value(Value),
71    /// Weave completed naturally (sent on yield_chan)
72    Done,
73    /// Cancellation requested (sent on resume_chan)
74    Cancel,
75}
76
77/// Channel data specifically for weave communication.
78///
79/// Uses `WeaveMessage` instead of raw `Value` to support typed control flow.
80#[derive(Debug, Clone)]
81pub struct WeaveChannelData {
82    pub sender: mpmc::Sender<WeaveMessage>,
83    pub receiver: mpmc::Receiver<WeaveMessage>,
84}
85
86// PartialEq by identity (Arc pointer comparison)
87impl PartialEq for WeaveChannelData {
88    fn eq(&self, other: &Self) -> bool {
89        std::ptr::eq(self, other)
90    }
91}
92
93// Note: Arc is used for both Closure.env and Variant to enable O(1) cloning.
94// This is essential for functional programming with recursive data structures.
95
96/// MapKey: Hashable subset of Value for use as map keys
97///
98/// Only types that can be meaningfully hashed are allowed as map keys:
99/// Int, String, Bool. Float is excluded due to NaN equality issues.
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub enum MapKey {
102    Int(i64),
103    String(SeqString),
104    Bool(bool),
105}
106
107impl Hash for MapKey {
108    fn hash<H: Hasher>(&self, state: &mut H) {
109        // Discriminant for type safety
110        std::mem::discriminant(self).hash(state);
111        match self {
112            MapKey::Int(n) => n.hash(state),
113            MapKey::String(s) => s.as_str().hash(state),
114            MapKey::Bool(b) => b.hash(state),
115        }
116    }
117}
118
119impl MapKey {
120    /// Try to convert a Value to a MapKey
121    /// Returns None for non-hashable types (Float, Variant, Quotation, Closure, Map)
122    pub fn from_value(value: &Value) -> Option<MapKey> {
123        match value {
124            Value::Int(n) => Some(MapKey::Int(*n)),
125            Value::String(s) => Some(MapKey::String(s.clone())),
126            Value::Bool(b) => Some(MapKey::Bool(*b)),
127            _ => None,
128        }
129    }
130
131    /// Convert MapKey back to Value
132    pub fn to_value(&self) -> Value {
133        match self {
134            MapKey::Int(n) => Value::Int(*n),
135            MapKey::String(s) => Value::String(s.clone()),
136            MapKey::Bool(b) => Value::Bool(*b),
137        }
138    }
139}
140
141/// VariantData: Composite values (sum types)
142///
143/// Fields are stored in a heap-allocated array, NOT linked via next pointers.
144/// This is the key difference from cem2, which used StackCell.next for field linking.
145///
146/// # Arc and Reference Cycles
147///
148/// Variants use `Arc<VariantData>` for O(1) cloning, which could theoretically
149/// create reference cycles. However, cycles are prevented by design:
150/// - VariantData.fields is immutable (no mutation after creation)
151/// - All variant operations create new variants rather than modifying existing ones
152/// - The Seq language has no mutation primitives for variant fields
153///
154/// This functional/immutable design ensures Arc reference counts always reach zero.
155#[derive(Debug, Clone, PartialEq)]
156pub struct VariantData {
157    /// Tag identifies which variant constructor was used (symbol name)
158    /// Stored as SeqString for dynamic variant construction via `wrap-N`
159    pub tag: SeqString,
160
161    /// Fields stored as a Vec for COW (copy-on-write) optimization.
162    /// When Arc refcount == 1, list.push can append in place (amortized O(1)).
163    /// When shared, a clone is made before mutation.
164    pub fields: Vec<Value>,
165}
166
167impl VariantData {
168    /// Create a new variant with the given tag and fields
169    pub fn new(tag: SeqString, fields: Vec<Value>) -> Self {
170        Self { tag, fields }
171    }
172}
173
174/// Value: What the language talks about
175///
176/// This is pure data with no pointers to other values.
177/// Values can be pushed on the stack, stored in variants, etc.
178/// The key insight: Value is independent of Stack structure.
179///
180/// # Memory Layout
181///
182/// Using `#[repr(C)]` ensures a predictable C-compatible layout:
183/// - Discriminant (tag) at offset 0
184/// - Payload data follows at a fixed offset
185///
186/// This allows compiled code to write Values directly without FFI calls,
187/// enabling inline integer/boolean operations for better performance.
188#[repr(C)]
189#[derive(Debug, Clone, PartialEq)]
190pub enum Value {
191    /// Integer value
192    Int(i64),
193
194    /// Floating-point value (IEEE 754 double precision)
195    Float(f64),
196
197    /// Boolean value
198    Bool(bool),
199
200    /// String (arena or globally allocated via SeqString)
201    String(SeqString),
202
203    /// Symbol (identifier for dynamic variant construction)
204    /// Like Ruby/Clojure symbols - lightweight identifiers used for tags.
205    /// Note: Currently NOT interned (each symbol allocates). Interning may be
206    /// added in the future for O(1) equality comparison.
207    Symbol(SeqString),
208
209    /// Variant (sum type with tagged fields)
210    /// Uses Arc for O(1) cloning - essential for recursive data structures
211    Variant(Arc<VariantData>),
212
213    /// Map (key-value dictionary with O(1) lookup)
214    /// Keys must be hashable types (Int, String, Bool)
215    Map(Box<HashMap<MapKey, Value>>),
216
217    /// Quotation (stateless function with two entry points for calling convention compatibility)
218    /// - wrapper: C-convention entry point for calls from the runtime
219    /// - impl_: tailcc entry point for tail calls from compiled code (enables TCO)
220    Quotation {
221        /// C-convention wrapper function pointer (for runtime calls via patch_seq_call)
222        wrapper: usize,
223        /// tailcc implementation function pointer (for musttail from compiled code)
224        impl_: usize,
225    },
226
227    /// Closure (quotation with captured environment)
228    /// Contains function pointer and Arc-shared array of captured values.
229    /// Arc enables TCO: no cleanup needed after tail call, ref-count handles it.
230    Closure {
231        /// Function pointer (transmuted to function taking Stack + environment)
232        fn_ptr: usize,
233        /// Captured values from creation site (Arc for TCO support)
234        /// Ordered top-down: `env[0]` is top of stack at creation
235        env: Arc<[Value]>,
236    },
237
238    /// Channel (MPMC sender/receiver pair for CSP-style concurrency)
239    /// Uses Arc for O(1) cloning - duplicating a channel shares the underlying handles.
240    /// Send/receive operations use the handles directly with zero mutex overhead.
241    Channel(Arc<ChannelData>),
242
243    /// Weave context (generator/coroutine communication channels)
244    /// Contains both yield and resume channels for bidirectional communication.
245    /// Travels on the stack - no global registry needed.
246    /// Uses WeaveChannelData with WeaveMessage for type-safe control flow.
247    WeaveCtx {
248        yield_chan: Arc<WeaveChannelData>,
249        resume_chan: Arc<WeaveChannelData>,
250    },
251}
252
253// Safety: Value can be sent and shared between strands (green threads)
254//
255// Send (safe to transfer ownership between threads):
256// - Int, Float, Bool are Copy types (trivially Send)
257// - String (SeqString) implements Send (clone to global on transfer)
258// - Variant contains Arc<VariantData> which is Send when VariantData is Send+Sync
259// - Quotation stores function pointer as usize (Send-safe, no owned data)
260// - Closure: fn_ptr is usize (Send), env is Arc<[Value]> (Send when Value is Send+Sync)
261// - Map contains Box<HashMap> which is Send because keys and values are Send
262// - Channel contains Arc<ChannelData> which is Send (May's Sender/Receiver are Send)
263//
264// Sync (safe to share references between threads):
265// - Value has no interior mutability (no Cell, RefCell, Mutex, etc.)
266// - All operations on Value are read-only or create new values (functional semantics)
267// - Arc requires T: Send + Sync for full thread-safety
268//
269// This is required for:
270// - Channel communication between strands
271// - Arc-based sharing of Variants, Closure environments, and Channels
272unsafe impl Send for Value {}
273unsafe impl Sync for Value {}
274
275impl std::fmt::Display for Value {
276    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
277        match self {
278            Value::Int(n) => write!(f, "{}", n),
279            Value::Float(n) => write!(f, "{}", n),
280            Value::Bool(b) => write!(f, "{}", b),
281            // Display is human-facing; lossy-display non-UTF-8 strings.
282            // Round-trip data uses `as_bytes()` directly via the
283            // appropriate runtime op, not Display.
284            Value::String(s) => write!(f, "{:?}", s.as_str_lossy()),
285            Value::Symbol(s) => write!(f, ":{}", s.as_str_lossy()),
286            Value::Variant(v) => {
287                write!(f, ":{}", v.tag.as_str_lossy())?;
288                if !v.fields.is_empty() {
289                    write!(f, "(")?;
290                }
291                for (i, field) in v.fields.iter().enumerate() {
292                    if i > 0 {
293                        write!(f, ", ")?;
294                    }
295                    write!(f, "{}", field)?;
296                }
297                if !v.fields.is_empty() {
298                    write!(f, ")")?;
299                }
300                Ok(())
301            }
302            Value::Map(m) => {
303                write!(f, "{{")?;
304                for (i, (k, v)) in m.iter().enumerate() {
305                    if i > 0 {
306                        write!(f, ", ")?;
307                    }
308                    write!(f, "{}: {}", k.to_value(), v)?;
309                }
310                write!(f, "}}")
311            }
312            Value::Quotation { .. } => write!(f, "<quotation>"),
313            Value::Closure { .. } => write!(f, "<closure>"),
314            Value::Channel(_) => write!(f, "<channel>"),
315            Value::WeaveCtx { .. } => write!(f, "<weave-ctx>"),
316        }
317    }
318}
319
320#[cfg(test)]
321mod tests {
322    use super::*;
323    use std::mem::{align_of, size_of};
324
325    #[test]
326    fn test_value_layout() {
327        println!("size_of::<Value>() = {}", size_of::<Value>());
328        println!("align_of::<Value>() = {}", align_of::<Value>());
329
330        // Value (Rust enum) is always 40 bytes with #[repr(C)]
331        assert_eq!(
332            size_of::<Value>(),
333            40,
334            "Value must be exactly 40 bytes, got {}",
335            size_of::<Value>()
336        );
337
338        // StackValue is 8 bytes (tagged pointer / u64)
339        use crate::tagged_stack::StackValue;
340        assert_eq!(
341            size_of::<StackValue>(),
342            8,
343            "StackValue must be 8 bytes, got {}",
344            size_of::<StackValue>()
345        );
346
347        assert_eq!(align_of::<Value>(), 8);
348    }
349
350    #[test]
351    fn test_value_int_layout() {
352        let val = Value::Int(42);
353        let ptr = &val as *const Value as *const u8;
354
355        unsafe {
356            // With #[repr(C)], the discriminant is at offset 0
357            // For 9 variants, discriminant fits in 1 byte but is padded
358            let discriminant_byte = *ptr;
359            assert_eq!(
360                discriminant_byte, 0,
361                "Int discriminant should be 0, got {}",
362                discriminant_byte
363            );
364
365            // The i64 value should be at a fixed offset after the discriminant
366            // With C repr, it's typically at offset 8 (discriminant + padding)
367            let value_ptr = ptr.add(8) as *const i64;
368            let stored_value = *value_ptr;
369            assert_eq!(
370                stored_value, 42,
371                "Int value should be 42 at offset 8, got {}",
372                stored_value
373            );
374        }
375    }
376
377    #[test]
378    fn test_value_bool_layout() {
379        let val_true = Value::Bool(true);
380        let val_false = Value::Bool(false);
381        let ptr_true = &val_true as *const Value as *const u8;
382        let ptr_false = &val_false as *const Value as *const u8;
383
384        unsafe {
385            // Bool is variant index 2 (after Int=0, Float=1)
386            let discriminant = *ptr_true;
387            assert_eq!(
388                discriminant, 2,
389                "Bool discriminant should be 2, got {}",
390                discriminant
391            );
392
393            // The bool value should be at offset 8
394            let value_ptr_true = ptr_true.add(8);
395            let value_ptr_false = ptr_false.add(8);
396            assert_eq!(*value_ptr_true, 1, "true should be 1");
397            assert_eq!(*value_ptr_false, 0, "false should be 0");
398        }
399    }
400
401    #[test]
402    fn test_value_display() {
403        // Test Display impl formats values correctly
404        assert_eq!(format!("{}", Value::Int(42)), "42");
405        assert_eq!(format!("{}", Value::Float(2.5)), "2.5");
406        assert_eq!(format!("{}", Value::Bool(true)), "true");
407        assert_eq!(format!("{}", Value::Bool(false)), "false");
408
409        // String shows with quotes (Debug-style)
410        let s = Value::String(SeqString::from("hello"));
411        assert_eq!(format!("{}", s), "\"hello\"");
412
413        // Symbol shows with : prefix
414        let sym = Value::Symbol(SeqString::from("my-symbol"));
415        assert_eq!(format!("{}", sym), ":my-symbol");
416    }
417}