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