seq-core 7.7.2

Core runtime library for stack-based languages (Value, Stack, Channels)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
//! The `Value` type — the datum a Seq program talks about — plus the
//! supporting types it embeds or composes with:
//!
//! - [`Value`]: the 11-variant enum (Int, Float, Bool, String, Symbol,
//!   Variant, Map, Quotation, Closure, Channel, WeaveCtx).
//! - [`VariantData`]: the heap-allocated payload behind `Value::Variant`.
//! - [`MapKey`]: the hashable subset of `Value` allowed as map keys.
//! - [`ChannelData`] / [`WeaveChannelData`] / [`WeaveMessage`]: the channel
//!   handles that back `Value::Channel` and `Value::WeaveCtx`.
//!
//! `Value` has `#[repr(C)]` so compiled code can write into it directly
//! without going through FFI, and implements `Send + Sync` via an `unsafe
//! impl` (see the comment block on that impl for the safety argument).

use crate::seqstring::SeqString;
use may::sync::mpmc;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use std::sync::atomic::AtomicBool;

/// Message type for plain channels.
///
/// Mirrors `WeaveMessage`: wrapping the underlying `may::mpmc` queue
/// in a typed enum lets lifecycle signals travel through the same
/// channel as user data without any value collision. `chan.close`
/// sends one `Closed` sentinel; receivers re-broadcast it so all
/// blocked consumers in an MPMC fan-out wake up. See issue #499 and
/// `docs/design/CHAN_CLOSE_SEMANTICS.md`.
#[derive(Debug, Clone, PartialEq)]
pub enum ChannelMsg {
    /// Normal value being sent through the channel.
    Value(Value),
    /// Channel-closed sentinel — `chan.close` sends one of these on
    /// the first close; `chan.receive` re-sends it before returning
    /// failure so the next blocked receiver also wakes.
    Closed,
}

/// Channel data: holds sender, receiver, and a closed flag.
///
/// Both sender and receiver are Clone (MPMC), so duplicating a
/// Channel value just clones the Arc. Send/receive operations use
/// the handles directly with zero mutex overhead — the `closed`
/// flag is a single atomic load on the send hot path, no locking.
#[derive(Debug, Clone)]
pub struct ChannelData {
    pub sender: mpmc::Sender<ChannelMsg>,
    pub receiver: mpmc::Receiver<ChannelMsg>,
    /// Set by `chan.close`. Reads gate `chan.send`; the close itself
    /// also enqueues one `ChannelMsg::Closed` sentinel to wake any
    /// already-blocked receivers.
    pub closed: Arc<AtomicBool>,
}

// PartialEq by identity (Arc pointer comparison)
impl PartialEq for ChannelData {
    fn eq(&self, other: &Self) -> bool {
        std::ptr::eq(self, other)
    }
}

/// Message type for weave channels.
///
/// Using an enum instead of sentinel values ensures no collision with user data.
/// Any `Value` can be safely yielded/resumed, including `i64::MIN`.
#[derive(Debug, Clone, PartialEq)]
pub enum WeaveMessage {
    /// Normal value being yielded or resumed
    Value(Value),
    /// Weave completed naturally (sent on yield_chan)
    Done,
    /// Cancellation requested (sent on resume_chan)
    Cancel,
}

/// Channel data specifically for weave communication.
///
/// Uses `WeaveMessage` instead of raw `Value` to support typed control flow.
#[derive(Debug, Clone)]
pub struct WeaveChannelData {
    pub sender: mpmc::Sender<WeaveMessage>,
    pub receiver: mpmc::Receiver<WeaveMessage>,
}

// PartialEq by identity (Arc pointer comparison)
impl PartialEq for WeaveChannelData {
    fn eq(&self, other: &Self) -> bool {
        std::ptr::eq(self, other)
    }
}

// Note: Arc is used for both Closure.env and Variant to enable O(1) cloning.
// This is essential for functional programming with recursive data structures.

/// MapKey: Hashable subset of Value for use as map keys
///
/// Only types that can be meaningfully hashed are allowed as map keys:
/// Int, String, Bool. Float is excluded due to NaN equality issues.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MapKey {
    Int(i64),
    String(SeqString),
    Bool(bool),
}

impl Hash for MapKey {
    fn hash<H: Hasher>(&self, state: &mut H) {
        // Discriminant for type safety
        std::mem::discriminant(self).hash(state);
        match self {
            MapKey::Int(n) => n.hash(state),
            MapKey::String(s) => s.as_str().hash(state),
            MapKey::Bool(b) => b.hash(state),
        }
    }
}

impl MapKey {
    /// Try to convert a Value to a MapKey
    /// Returns None for non-hashable types (Float, Variant, Quotation, Closure, Map)
    pub fn from_value(value: &Value) -> Option<MapKey> {
        match value {
            Value::Int(n) => Some(MapKey::Int(*n)),
            Value::String(s) => Some(MapKey::String(s.clone())),
            Value::Bool(b) => Some(MapKey::Bool(*b)),
            _ => None,
        }
    }

    /// Convert MapKey back to Value
    pub fn to_value(&self) -> Value {
        match self {
            MapKey::Int(n) => Value::Int(*n),
            MapKey::String(s) => Value::String(s.clone()),
            MapKey::Bool(b) => Value::Bool(*b),
        }
    }
}

/// VariantData: Composite values (sum types)
///
/// Fields are stored in a heap-allocated array, NOT linked via next pointers.
/// This is the key difference from cem2, which used StackCell.next for field linking.
///
/// # Arc and Reference Cycles
///
/// Variants use `Arc<VariantData>` for O(1) cloning, which could theoretically
/// create reference cycles. However, cycles are prevented by design:
/// - VariantData.fields is immutable (no mutation after creation)
/// - All variant operations create new variants rather than modifying existing ones
/// - The Seq language has no mutation primitives for variant fields
///
/// This functional/immutable design ensures Arc reference counts always reach zero.
#[derive(Debug, Clone, PartialEq)]
pub struct VariantData {
    /// Tag identifies which variant constructor was used (symbol name)
    /// Stored as SeqString for dynamic variant construction via `wrap-N`
    pub tag: SeqString,

    /// Fields stored as a Vec for COW (copy-on-write) optimization.
    /// When Arc refcount == 1, list.push can append in place (amortized O(1)).
    /// When shared, a clone is made before mutation.
    pub fields: Vec<Value>,
}

impl VariantData {
    /// Create a new variant with the given tag and fields
    pub fn new(tag: SeqString, fields: Vec<Value>) -> Self {
        Self { tag, fields }
    }
}

/// Value: What the language talks about
///
/// This is pure data with no pointers to other values.
/// Values can be pushed on the stack, stored in variants, etc.
/// The key insight: Value is independent of Stack structure.
///
/// # Memory Layout
///
/// Using `#[repr(C)]` ensures a predictable C-compatible layout:
/// - Discriminant (tag) at offset 0
/// - Payload data follows at a fixed offset
///
/// This allows compiled code to write Values directly without FFI calls,
/// enabling inline integer/boolean operations for better performance.
#[repr(C)]
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
    /// Integer value
    Int(i64),

    /// Floating-point value (IEEE 754 double precision)
    Float(f64),

    /// Boolean value
    Bool(bool),

    /// String (arena or globally allocated via SeqString)
    String(SeqString),

    /// Symbol (identifier for dynamic variant construction)
    /// Like Ruby/Clojure symbols - lightweight identifiers used for tags.
    /// Note: Currently NOT interned (each symbol allocates). Interning may be
    /// added in the future for O(1) equality comparison.
    Symbol(SeqString),

    /// Variant (sum type with tagged fields)
    /// Uses Arc for O(1) cloning - essential for recursive data structures
    Variant(Arc<VariantData>),

    /// Map (key-value dictionary with O(1) lookup)
    /// Keys must be hashable types (Int, String, Bool)
    Map(Box<HashMap<MapKey, Value>>),

    /// Quotation (stateless function with two entry points for calling convention compatibility)
    /// - wrapper: C-convention entry point for calls from the runtime
    /// - impl_: tailcc entry point for tail calls from compiled code (enables TCO)
    Quotation {
        /// C-convention wrapper function pointer (for runtime calls via patch_seq_call)
        wrapper: usize,
        /// tailcc implementation function pointer (for musttail from compiled code)
        impl_: usize,
    },

    /// Closure (quotation with captured environment)
    /// Contains function pointer and Arc-shared array of captured values.
    /// Arc enables TCO: no cleanup needed after tail call, ref-count handles it.
    Closure {
        /// Function pointer (transmuted to function taking Stack + environment)
        fn_ptr: usize,
        /// Captured values from creation site (Arc for TCO support)
        /// Ordered top-down: `env[0]` is top of stack at creation
        env: Arc<[Value]>,
    },

    /// Channel (MPMC sender/receiver pair for CSP-style concurrency)
    /// Uses Arc for O(1) cloning - duplicating a channel shares the underlying handles.
    /// Send/receive operations use the handles directly with zero mutex overhead.
    Channel(Arc<ChannelData>),

    /// Weave context (generator/coroutine communication channels)
    /// Contains both yield and resume channels for bidirectional communication.
    /// Travels on the stack - no global registry needed.
    /// Uses WeaveChannelData with WeaveMessage for type-safe control flow.
    WeaveCtx {
        yield_chan: Arc<WeaveChannelData>,
        resume_chan: Arc<WeaveChannelData>,
    },
}

// Safety: Value can be sent and shared between strands (green threads)
//
// Send (safe to transfer ownership between threads):
// - Int, Float, Bool are Copy types (trivially Send)
// - String (SeqString) implements Send (clone to global on transfer)
// - Variant contains Arc<VariantData> which is Send when VariantData is Send+Sync
// - Quotation stores function pointer as usize (Send-safe, no owned data)
// - Closure: fn_ptr is usize (Send), env is Arc<[Value]> (Send when Value is Send+Sync)
// - Map contains Box<HashMap> which is Send because keys and values are Send
// - Channel contains Arc<ChannelData> which is Send (May's Sender/Receiver are Send)
//
// Sync (safe to share references between threads):
// - Value has no interior mutability (no Cell, RefCell, Mutex, etc.)
// - All operations on Value are read-only or create new values (functional semantics)
// - Arc requires T: Send + Sync for full thread-safety
//
// This is required for:
// - Channel communication between strands
// - Arc-based sharing of Variants, Closure environments, and Channels
unsafe impl Send for Value {}
unsafe impl Sync for Value {}

impl std::fmt::Display for Value {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Value::Int(n) => write!(f, "{}", n),
            Value::Float(n) => write!(f, "{}", n),
            Value::Bool(b) => write!(f, "{}", b),
            // Display is human-facing; lossy-display non-UTF-8 strings.
            // Round-trip data uses `as_bytes()` directly via the
            // appropriate runtime op, not Display.
            Value::String(s) => write!(f, "{:?}", s.as_str_lossy()),
            Value::Symbol(s) => write!(f, ":{}", s.as_str_lossy()),
            Value::Variant(v) => fmt_variant(f, v),
            Value::Map(m) => fmt_map(f, m),
            Value::Quotation { .. } => write!(f, "<quotation>"),
            Value::Closure { .. } => write!(f, "<closure>"),
            Value::Channel(_) => write!(f, "<channel>"),
            Value::WeaveCtx { .. } => write!(f, "<weave-ctx>"),
        }
    }
}

/// Format a variant as `:tag` or `:tag(f0, f1, …)`.
fn fmt_variant(f: &mut std::fmt::Formatter<'_>, v: &VariantData) -> std::fmt::Result {
    write!(f, ":{}", v.tag.as_str_lossy())?;
    if v.fields.is_empty() {
        return Ok(());
    }
    write!(f, "(")?;
    for (i, field) in v.fields.iter().enumerate() {
        if i > 0 {
            write!(f, ", ")?;
        }
        write!(f, "{}", field)?;
    }
    write!(f, ")")
}

/// Format a map as `{k0: v0, k1: v1, …}`.
fn fmt_map(f: &mut std::fmt::Formatter<'_>, m: &HashMap<MapKey, Value>) -> std::fmt::Result {
    write!(f, "{{")?;
    for (i, (k, v)) in m.iter().enumerate() {
        if i > 0 {
            write!(f, ", ")?;
        }
        write!(f, "{}: {}", k.to_value(), v)?;
    }
    write!(f, "}}")
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::mem::{align_of, size_of};

    #[test]
    fn test_value_layout() {
        println!("size_of::<Value>() = {}", size_of::<Value>());
        println!("align_of::<Value>() = {}", align_of::<Value>());

        // Value (Rust enum) is always 40 bytes with #[repr(C)]
        assert_eq!(
            size_of::<Value>(),
            40,
            "Value must be exactly 40 bytes, got {}",
            size_of::<Value>()
        );

        // StackValue is 8 bytes (tagged pointer / u64)
        use crate::tagged_stack::StackValue;
        assert_eq!(
            size_of::<StackValue>(),
            8,
            "StackValue must be 8 bytes, got {}",
            size_of::<StackValue>()
        );

        assert_eq!(align_of::<Value>(), 8);
    }

    #[test]
    fn test_value_int_layout() {
        let val = Value::Int(42);
        let ptr = &val as *const Value as *const u8;

        unsafe {
            // With #[repr(C)], the discriminant is at offset 0
            // For 9 variants, discriminant fits in 1 byte but is padded
            let discriminant_byte = *ptr;
            assert_eq!(
                discriminant_byte, 0,
                "Int discriminant should be 0, got {}",
                discriminant_byte
            );

            // The i64 value should be at a fixed offset after the discriminant
            // With C repr, it's typically at offset 8 (discriminant + padding)
            let value_ptr = ptr.add(8) as *const i64;
            let stored_value = *value_ptr;
            assert_eq!(
                stored_value, 42,
                "Int value should be 42 at offset 8, got {}",
                stored_value
            );
        }
    }

    #[test]
    fn test_value_bool_layout() {
        let val_true = Value::Bool(true);
        let val_false = Value::Bool(false);
        let ptr_true = &val_true as *const Value as *const u8;
        let ptr_false = &val_false as *const Value as *const u8;

        unsafe {
            // Bool is variant index 2 (after Int=0, Float=1)
            let discriminant = *ptr_true;
            assert_eq!(
                discriminant, 2,
                "Bool discriminant should be 2, got {}",
                discriminant
            );

            // The bool value should be at offset 8
            let value_ptr_true = ptr_true.add(8);
            let value_ptr_false = ptr_false.add(8);
            assert_eq!(*value_ptr_true, 1, "true should be 1");
            assert_eq!(*value_ptr_false, 0, "false should be 0");
        }
    }

    #[test]
    fn test_value_display() {
        // Test Display impl formats values correctly
        assert_eq!(format!("{}", Value::Int(42)), "42");
        assert_eq!(format!("{}", Value::Float(2.5)), "2.5");
        assert_eq!(format!("{}", Value::Bool(true)), "true");
        assert_eq!(format!("{}", Value::Bool(false)), "false");

        // String shows with quotes (Debug-style)
        let s = Value::String(SeqString::from("hello"));
        assert_eq!(format!("{}", s), "\"hello\"");

        // Symbol shows with : prefix
        let sym = Value::Symbol(SeqString::from("my-symbol"));
        assert_eq!(format!("{}", sym), ":my-symbol");
    }
}