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