seq_runtime/value.rs
1use crate::seqstring::SeqString;
2use std::collections::HashMap;
3use std::hash::{Hash, Hasher};
4use std::sync::Arc;
5
6/// MapKey: Hashable subset of Value for use as map keys
7///
8/// Only types that can be meaningfully hashed are allowed as map keys:
9/// Int, String, Bool. Float is excluded due to NaN equality issues.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum MapKey {
12 Int(i64),
13 String(SeqString),
14 Bool(bool),
15}
16
17impl Hash for MapKey {
18 fn hash<H: Hasher>(&self, state: &mut H) {
19 // Discriminant for type safety
20 std::mem::discriminant(self).hash(state);
21 match self {
22 MapKey::Int(n) => n.hash(state),
23 MapKey::String(s) => s.as_str().hash(state),
24 MapKey::Bool(b) => b.hash(state),
25 }
26 }
27}
28
29impl MapKey {
30 /// Try to convert a Value to a MapKey
31 /// Returns None for non-hashable types (Float, Variant, Quotation, Closure, Map)
32 pub fn from_value(value: &Value) -> Option<MapKey> {
33 match value {
34 Value::Int(n) => Some(MapKey::Int(*n)),
35 Value::String(s) => Some(MapKey::String(s.clone())),
36 Value::Bool(b) => Some(MapKey::Bool(*b)),
37 _ => None,
38 }
39 }
40
41 /// Convert MapKey back to Value
42 pub fn to_value(&self) -> Value {
43 match self {
44 MapKey::Int(n) => Value::Int(*n),
45 MapKey::String(s) => Value::String(s.clone()),
46 MapKey::Bool(b) => Value::Bool(*b),
47 }
48 }
49}
50
51/// Value: What the language talks about
52///
53/// This is pure data with no pointers to other values.
54/// Values can be pushed on the stack, stored in variants, etc.
55/// The key insight: Value is independent of Stack structure.
56#[derive(Debug, Clone, PartialEq)]
57pub enum Value {
58 /// Integer value
59 Int(i64),
60
61 /// Floating-point value (IEEE 754 double precision)
62 Float(f64),
63
64 /// Boolean value
65 Bool(bool),
66
67 /// String (arena or globally allocated via SeqString)
68 String(SeqString),
69
70 /// Variant (sum type with tagged fields)
71 Variant(Box<VariantData>),
72
73 /// Map (key-value dictionary with O(1) lookup)
74 /// Keys must be hashable types (Int, String, Bool)
75 Map(Box<HashMap<MapKey, Value>>),
76
77 /// Quotation (stateless function with two entry points for calling convention compatibility)
78 /// - wrapper: C-convention entry point for calls from the runtime
79 /// - impl_: tailcc entry point for tail calls from compiled code (enables TCO)
80 Quotation {
81 /// C-convention wrapper function pointer (for runtime calls via patch_seq_call)
82 wrapper: usize,
83 /// tailcc implementation function pointer (for musttail from compiled code)
84 impl_: usize,
85 },
86
87 /// Closure (quotation with captured environment)
88 /// Contains function pointer and Arc-shared array of captured values.
89 /// Arc enables TCO: no cleanup needed after tail call, ref-count handles it.
90 Closure {
91 /// Function pointer (transmuted to function taking Stack + environment)
92 fn_ptr: usize,
93 /// Captured values from creation site (Arc for TCO support)
94 /// Ordered top-down: env[0] is top of stack at creation
95 env: Arc<[Value]>,
96 },
97}
98
99// Safety: Value can be sent between strands (green threads)
100// - Int, Float, Bool are Copy types (trivially Send)
101// - String (SeqString) implements Send (clone to global on transfer)
102// - Variant contains Box<VariantData> which is Send because VariantData contains Send types
103// - Quotation stores function pointer as usize (Send-safe, no owned data)
104// - Closure: fn_ptr is usize (Send), env is Arc<[Value]>
105// Arc<T> is Send when T: Send + Sync. Since we manually impl Send for Value,
106// and Value contains no interior mutability, Arc<[Value]> is effectively Send.
107// Arc is used instead of Box to enable TCO: no cleanup needed after tail calls.
108// - Map contains Box<HashMap> which is Send because keys and values are Send
109// This is required for channel communication between strands
110unsafe impl Send for Value {}
111
112/// VariantData: Composite values (sum types)
113///
114/// Fields are stored in a heap-allocated array, NOT linked via next pointers.
115/// This is the key difference from cem2, which used StackCell.next for field linking.
116#[derive(Debug, Clone, PartialEq)]
117pub struct VariantData {
118 /// Tag identifies which variant constructor was used
119 pub tag: u32,
120
121 /// Fields stored as an owned array of values
122 /// This is independent of any stack structure
123 pub fields: Box<[Value]>,
124}
125
126impl VariantData {
127 /// Create a new variant with the given tag and fields
128 pub fn new(tag: u32, fields: Vec<Value>) -> Self {
129 Self {
130 tag,
131 fields: fields.into_boxed_slice(),
132 }
133 }
134}
135
136// We'll implement proper cleanup in Drop later
137// For now, Rust's ownership handles most of it