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) => fmt_variant(f, v),
287            Value::Map(m) => fmt_map(f, m),
288            Value::Quotation { .. } => write!(f, "<quotation>"),
289            Value::Closure { .. } => write!(f, "<closure>"),
290            Value::Channel(_) => write!(f, "<channel>"),
291            Value::WeaveCtx { .. } => write!(f, "<weave-ctx>"),
292        }
293    }
294}
295
296/// Format a variant as `:tag` or `:tag(f0, f1, …)`.
297fn fmt_variant(f: &mut std::fmt::Formatter<'_>, v: &VariantData) -> std::fmt::Result {
298    write!(f, ":{}", v.tag.as_str_lossy())?;
299    if v.fields.is_empty() {
300        return Ok(());
301    }
302    write!(f, "(")?;
303    for (i, field) in v.fields.iter().enumerate() {
304        if i > 0 {
305            write!(f, ", ")?;
306        }
307        write!(f, "{}", field)?;
308    }
309    write!(f, ")")
310}
311
312/// Format a map as `{k0: v0, k1: v1, …}`.
313fn fmt_map(f: &mut std::fmt::Formatter<'_>, m: &HashMap<MapKey, Value>) -> std::fmt::Result {
314    write!(f, "{{")?;
315    for (i, (k, v)) in m.iter().enumerate() {
316        if i > 0 {
317            write!(f, ", ")?;
318        }
319        write!(f, "{}: {}", k.to_value(), v)?;
320    }
321    write!(f, "}}")
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327    use std::mem::{align_of, size_of};
328
329    #[test]
330    fn test_value_layout() {
331        println!("size_of::<Value>() = {}", size_of::<Value>());
332        println!("align_of::<Value>() = {}", align_of::<Value>());
333
334        // Value (Rust enum) is always 40 bytes with #[repr(C)]
335        assert_eq!(
336            size_of::<Value>(),
337            40,
338            "Value must be exactly 40 bytes, got {}",
339            size_of::<Value>()
340        );
341
342        // StackValue is 8 bytes (tagged pointer / u64)
343        use crate::tagged_stack::StackValue;
344        assert_eq!(
345            size_of::<StackValue>(),
346            8,
347            "StackValue must be 8 bytes, got {}",
348            size_of::<StackValue>()
349        );
350
351        assert_eq!(align_of::<Value>(), 8);
352    }
353
354    #[test]
355    fn test_value_int_layout() {
356        let val = Value::Int(42);
357        let ptr = &val as *const Value as *const u8;
358
359        unsafe {
360            // With #[repr(C)], the discriminant is at offset 0
361            // For 9 variants, discriminant fits in 1 byte but is padded
362            let discriminant_byte = *ptr;
363            assert_eq!(
364                discriminant_byte, 0,
365                "Int discriminant should be 0, got {}",
366                discriminant_byte
367            );
368
369            // The i64 value should be at a fixed offset after the discriminant
370            // With C repr, it's typically at offset 8 (discriminant + padding)
371            let value_ptr = ptr.add(8) as *const i64;
372            let stored_value = *value_ptr;
373            assert_eq!(
374                stored_value, 42,
375                "Int value should be 42 at offset 8, got {}",
376                stored_value
377            );
378        }
379    }
380
381    #[test]
382    fn test_value_bool_layout() {
383        let val_true = Value::Bool(true);
384        let val_false = Value::Bool(false);
385        let ptr_true = &val_true as *const Value as *const u8;
386        let ptr_false = &val_false as *const Value as *const u8;
387
388        unsafe {
389            // Bool is variant index 2 (after Int=0, Float=1)
390            let discriminant = *ptr_true;
391            assert_eq!(
392                discriminant, 2,
393                "Bool discriminant should be 2, got {}",
394                discriminant
395            );
396
397            // The bool value should be at offset 8
398            let value_ptr_true = ptr_true.add(8);
399            let value_ptr_false = ptr_false.add(8);
400            assert_eq!(*value_ptr_true, 1, "true should be 1");
401            assert_eq!(*value_ptr_false, 0, "false should be 0");
402        }
403    }
404
405    #[test]
406    fn test_value_display() {
407        // Test Display impl formats values correctly
408        assert_eq!(format!("{}", Value::Int(42)), "42");
409        assert_eq!(format!("{}", Value::Float(2.5)), "2.5");
410        assert_eq!(format!("{}", Value::Bool(true)), "true");
411        assert_eq!(format!("{}", Value::Bool(false)), "false");
412
413        // String shows with quotes (Debug-style)
414        let s = Value::String(SeqString::from("hello"));
415        assert_eq!(format!("{}", s), "\"hello\"");
416
417        // Symbol shows with : prefix
418        let sym = Value::Symbol(SeqString::from("my-symbol"));
419        assert_eq!(format!("{}", sym), ":my-symbol");
420    }
421}