seqc/
types.rs

1//! Type system for Seq
2//!
3//! Based on cem2's row polymorphism design with improvements.
4//! Supports stack effect declarations like: ( ..a Int -- ..a Bool )
5
6/// Base types in the language
7#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8pub enum Type {
9    /// Integer type
10    Int,
11    /// Floating-point type (IEEE 754 double precision)
12    Float,
13    /// Boolean type
14    Bool,
15    /// String type
16    String,
17    /// Quotation type (stateless code block with stack effect)
18    /// Example: [ Int -- Int ] is a quotation that takes Int and produces Int
19    /// No captured values - backward compatible with existing quotations
20    Quotation(Box<Effect>),
21    /// Closure type (quotation with captured environment)
22    /// Example: Closure { effect: [Int -- Int], captures: [Int] }
23    /// A closure that captures one Int and takes another Int to produce Int
24    Closure {
25        /// Stack effect when the closure is called
26        effect: Box<Effect>,
27        /// Types of values captured from the creation site
28        /// Ordered top-down: captures[0] is top of stack at creation
29        captures: Vec<Type>,
30    },
31    /// Type variable (for polymorphism)
32    /// Example: T in ( ..a T -- ..a T T )
33    Var(String),
34}
35
36/// Stack types with row polymorphism
37#[derive(Debug, Clone, PartialEq, Eq, Hash)]
38pub enum StackType {
39    /// Empty stack
40    Empty,
41
42    /// Stack with a value on top of rest
43    /// Example: Int on top of ..a
44    Cons {
45        /// The rest of the stack (may be Empty, another Cons, or RowVar)
46        rest: Box<StackType>,
47        /// The type on top of the stack
48        top: Type,
49    },
50
51    /// Row variable representing "rest of stack"
52    /// Example: ..a in ( ..a Int -- ..a Bool )
53    RowVar(String),
54}
55
56/// Stack effect: transformation from input stack to output stack
57/// Example: ( ..a Int -- ..a Bool ) means:
58///   - Consumes an Int from stack with ..a underneath
59///   - Produces a Bool on stack with ..a underneath
60#[derive(Debug, Clone, PartialEq, Eq, Hash)]
61pub struct Effect {
62    /// Input stack type (before word executes)
63    pub inputs: StackType,
64    /// Output stack type (after word executes)
65    pub outputs: StackType,
66}
67
68impl StackType {
69    /// Create an empty stack type
70    pub fn empty() -> Self {
71        StackType::Empty
72    }
73
74    /// Create a stack type with a single value
75    pub fn singleton(ty: Type) -> Self {
76        StackType::Cons {
77            rest: Box::new(StackType::Empty),
78            top: ty,
79        }
80    }
81
82    /// Push a type onto a stack type
83    pub fn push(self, ty: Type) -> Self {
84        StackType::Cons {
85            rest: Box::new(self),
86            top: ty,
87        }
88    }
89
90    /// Create a stack type from a vector of types (bottom to top)
91    pub fn from_vec(types: Vec<Type>) -> Self {
92        types
93            .into_iter()
94            .fold(StackType::Empty, |stack, ty| stack.push(ty))
95    }
96
97    /// Pop a type from a stack type, returning (rest, top) if successful
98    pub fn pop(self) -> Option<(StackType, Type)> {
99        match self {
100            StackType::Cons { rest, top } => Some((*rest, top)),
101            _ => None,
102        }
103    }
104}
105
106impl Effect {
107    /// Create a new stack effect
108    pub fn new(inputs: StackType, outputs: StackType) -> Self {
109        Effect { inputs, outputs }
110    }
111}
112
113impl std::fmt::Display for Type {
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        match self {
116            Type::Int => write!(f, "Int"),
117            Type::Float => write!(f, "Float"),
118            Type::Bool => write!(f, "Bool"),
119            Type::String => write!(f, "String"),
120            Type::Quotation(effect) => write!(f, "[{}]", effect),
121            Type::Closure { effect, captures } => {
122                let cap_str: Vec<_> = captures.iter().map(|t| format!("{}", t)).collect();
123                write!(f, "Closure[{}, captures=({})]", effect, cap_str.join(", "))
124            }
125            Type::Var(name) => write!(f, "{}", name),
126        }
127    }
128}
129
130impl std::fmt::Display for StackType {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        match self {
133            StackType::Empty => write!(f, "()"),
134            StackType::RowVar(name) => write!(f, "..{}", name),
135            StackType::Cons { rest, top } => {
136                // Collect all types from top to bottom
137                let mut types = vec![format!("{}", top)];
138                let mut current = rest.as_ref();
139                loop {
140                    match current {
141                        StackType::Empty => break,
142                        StackType::RowVar(name) => {
143                            types.push(format!("..{}", name));
144                            break;
145                        }
146                        StackType::Cons { rest, top } => {
147                            types.push(format!("{}", top));
148                            current = rest;
149                        }
150                    }
151                }
152                types.reverse();
153                write!(f, "({})", types.join(" "))
154            }
155        }
156    }
157}
158
159impl std::fmt::Display for Effect {
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        write!(f, "{} -- {}", self.inputs, self.outputs)
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn test_empty_stack() {
171        let stack = StackType::empty();
172        assert_eq!(stack, StackType::Empty);
173    }
174
175    #[test]
176    fn test_singleton_stack() {
177        let stack = StackType::singleton(Type::Int);
178        assert_eq!(
179            stack,
180            StackType::Cons {
181                rest: Box::new(StackType::Empty),
182                top: Type::Int
183            }
184        );
185    }
186
187    #[test]
188    fn test_push_pop() {
189        let stack = StackType::empty().push(Type::Int).push(Type::Bool);
190
191        let (rest, top) = stack.pop().unwrap();
192        assert_eq!(top, Type::Bool);
193
194        let (rest2, top2) = rest.pop().unwrap();
195        assert_eq!(top2, Type::Int);
196        assert_eq!(rest2, StackType::Empty);
197    }
198
199    #[test]
200    fn test_from_vec() {
201        let stack = StackType::from_vec(vec![Type::Int, Type::Bool, Type::String]);
202
203        // Stack should be: String on top of Bool on top of Int on top of Empty
204        let (rest, top) = stack.pop().unwrap();
205        assert_eq!(top, Type::String);
206
207        let (rest2, top2) = rest.pop().unwrap();
208        assert_eq!(top2, Type::Bool);
209
210        let (rest3, top3) = rest2.pop().unwrap();
211        assert_eq!(top3, Type::Int);
212        assert_eq!(rest3, StackType::Empty);
213    }
214
215    #[test]
216    fn test_row_variable() {
217        let stack = StackType::Cons {
218            rest: Box::new(StackType::RowVar("a".to_string())),
219            top: Type::Int,
220        };
221
222        // This represents: Int on top of ..a
223        let (rest, top) = stack.pop().unwrap();
224        assert_eq!(top, Type::Int);
225        assert_eq!(rest, StackType::RowVar("a".to_string()));
226    }
227
228    #[test]
229    fn test_effect() {
230        // Effect: ( Int -- Bool )
231        let effect = Effect::new(
232            StackType::singleton(Type::Int),
233            StackType::singleton(Type::Bool),
234        );
235
236        assert_eq!(effect.inputs, StackType::singleton(Type::Int));
237        assert_eq!(effect.outputs, StackType::singleton(Type::Bool));
238    }
239
240    #[test]
241    fn test_polymorphic_effect() {
242        // Effect: ( ..a Int -- ..a Bool )
243        let inputs = StackType::Cons {
244            rest: Box::new(StackType::RowVar("a".to_string())),
245            top: Type::Int,
246        };
247
248        let outputs = StackType::Cons {
249            rest: Box::new(StackType::RowVar("a".to_string())),
250            top: Type::Bool,
251        };
252
253        let effect = Effect::new(inputs, outputs);
254
255        // Verify structure
256        assert!(matches!(effect.inputs, StackType::Cons { .. }));
257        assert!(matches!(effect.outputs, StackType::Cons { .. }));
258    }
259}