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}