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 pointer stored as usize for Send safety)
78 /// No captured environment - backward compatible
79 Quotation(usize),
80
81 /// Closure (quotation with captured environment)
82 /// Contains function pointer and Arc-shared array of captured values.
83 /// Arc enables TCO: no cleanup needed after tail call, ref-count handles it.
84 Closure {
85 /// Function pointer (transmuted to function taking Stack + environment)
86 fn_ptr: usize,
87 /// Captured values from creation site (Arc for TCO support)
88 /// Ordered top-down: env[0] is top of stack at creation
89 env: Arc<[Value]>,
90 },
91}
92
93// Safety: Value can be sent between strands (green threads)
94// - Int, Float, Bool are Copy types (trivially Send)
95// - String (SeqString) implements Send (clone to global on transfer)
96// - Variant contains Box<VariantData> which is Send because VariantData contains Send types
97// - Quotation stores function pointer as usize (Send-safe, no owned data)
98// - Closure: fn_ptr is usize (Send), env is Arc<[Value]>
99// Arc<T> is Send when T: Send + Sync. Since we manually impl Send for Value,
100// and Value contains no interior mutability, Arc<[Value]> is effectively Send.
101// Arc is used instead of Box to enable TCO: no cleanup needed after tail calls.
102// - Map contains Box<HashMap> which is Send because keys and values are Send
103// This is required for channel communication between strands
104unsafe impl Send for Value {}
105
106/// VariantData: Composite values (sum types)
107///
108/// Fields are stored in a heap-allocated array, NOT linked via next pointers.
109/// This is the key difference from cem2, which used StackCell.next for field linking.
110#[derive(Debug, Clone, PartialEq)]
111pub struct VariantData {
112 /// Tag identifies which variant constructor was used
113 pub tag: u32,
114
115 /// Fields stored as an owned array of values
116 /// This is independent of any stack structure
117 pub fields: Box<[Value]>,
118}
119
120impl VariantData {
121 /// Create a new variant with the given tag and fields
122 pub fn new(tag: u32, fields: Vec<Value>) -> Self {
123 Self {
124 tag,
125 fields: fields.into_boxed_slice(),
126 }
127 }
128}
129
130// We'll implement proper cleanup in Drop later
131// For now, Rust's ownership handles most of it