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}