Skip to main content

palladium/effects/
mod.rs

1// Effect system for Palladium
2// "Tracking the ripples of computation"
3
4use crate::ast::{Expr, Function, Stmt};
5use crate::errors::Result;
6use std::collections::HashSet;
7
8/// Represents different kinds of effects a function can have
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub enum Effect {
11    /// IO operations (file, network, console)
12    IO,
13    /// Memory allocation/deallocation
14    Memory,
15    /// Can panic or throw exceptions
16    Panic,
17    /// Asynchronous operations
18    Async,
19    /// Unsafe operations
20    Unsafe,
21    /// Pure - no side effects
22    Pure,
23}
24
25/// Effect set for a function or expression
26#[derive(Debug, Clone, PartialEq, Eq, Default)]
27pub struct EffectSet {
28    effects: HashSet<Effect>,
29}
30
31impl EffectSet {
32    /// Create an empty effect set (pure)
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    /// Create an effect set with a single effect
38    pub fn singleton(effect: Effect) -> Self {
39        let mut effects = HashSet::new();
40        effects.insert(effect);
41        Self { effects }
42    }
43
44    /// Add an effect to the set
45    pub fn add(&mut self, effect: Effect) {
46        // Pure effect is removed when any other effect is added
47        if effect != Effect::Pure {
48            self.effects.remove(&Effect::Pure);
49        }
50        self.effects.insert(effect);
51    }
52
53    /// Union two effect sets
54    pub fn union(&mut self, other: &EffectSet) {
55        for effect in &other.effects {
56            self.add(effect.clone());
57        }
58    }
59
60    /// Check if the effect set is pure
61    pub fn is_pure(&self) -> bool {
62        self.effects.is_empty() || self.effects.contains(&Effect::Pure)
63    }
64
65    /// Check if the effect set contains a specific effect
66    pub fn contains(&self, effect: &Effect) -> bool {
67        self.effects.contains(effect)
68    }
69
70    /// Get all effects
71    pub fn effects(&self) -> &HashSet<Effect> {
72        &self.effects
73    }
74}
75
76/// Effect analyzer for tracking effects in code
77pub struct EffectAnalyzer {
78    /// Map of function names to their effect sets
79    function_effects: std::collections::HashMap<String, EffectSet>,
80    /// Built-in functions and their effects
81    builtin_effects: std::collections::HashMap<String, EffectSet>,
82}
83
84impl Default for EffectAnalyzer {
85    fn default() -> Self {
86        let mut builtin_effects = std::collections::HashMap::new();
87
88        // IO functions
89        builtin_effects.insert("print".to_string(), EffectSet::singleton(Effect::IO));
90        builtin_effects.insert("print_int".to_string(), EffectSet::singleton(Effect::IO));
91        builtin_effects.insert("file_open".to_string(), EffectSet::singleton(Effect::IO));
92        builtin_effects.insert(
93            "file_read_all".to_string(),
94            EffectSet::singleton(Effect::IO),
95        );
96        builtin_effects.insert(
97            "file_read_line".to_string(),
98            EffectSet::singleton(Effect::IO),
99        );
100        builtin_effects.insert("file_write".to_string(), EffectSet::singleton(Effect::IO));
101        builtin_effects.insert("file_close".to_string(), EffectSet::singleton(Effect::IO));
102        builtin_effects.insert("file_exists".to_string(), EffectSet::singleton(Effect::IO));
103
104        // Memory functions
105        // For now, we don't have explicit allocation functions
106
107        // Pure functions
108        builtin_effects.insert("string_len".to_string(), EffectSet::new());
109        builtin_effects.insert("string_concat".to_string(), EffectSet::new());
110        builtin_effects.insert("string_eq".to_string(), EffectSet::new());
111        builtin_effects.insert("string_char_at".to_string(), EffectSet::new());
112        builtin_effects.insert("string_substring".to_string(), EffectSet::new());
113        builtin_effects.insert("string_from_char".to_string(), EffectSet::new());
114        builtin_effects.insert("char_is_digit".to_string(), EffectSet::new());
115        builtin_effects.insert("char_is_alpha".to_string(), EffectSet::new());
116        builtin_effects.insert("char_is_whitespace".to_string(), EffectSet::new());
117        builtin_effects.insert("string_to_int".to_string(), EffectSet::new());
118        builtin_effects.insert("int_to_string".to_string(), EffectSet::new());
119
120        Self {
121            function_effects: std::collections::HashMap::new(),
122            builtin_effects,
123        }
124    }
125}
126
127impl EffectAnalyzer {
128    pub fn new() -> Self {
129        Self::default()
130    }
131
132    /// Analyze effects for a function
133    pub fn analyze_function(&mut self, func: &Function) -> Result<EffectSet> {
134        let mut effects = EffectSet::new();
135
136        // Async functions have async effect
137        if func.is_async {
138            effects.add(Effect::Async);
139        }
140
141        // Analyze function body
142        for stmt in &func.body {
143            let stmt_effects = self.analyze_statement(stmt)?;
144            effects.union(&stmt_effects);
145        }
146
147        // Store the function's effects
148        self.function_effects
149            .insert(func.name.clone(), effects.clone());
150
151        Ok(effects)
152    }
153
154    /// Analyze effects for a statement
155    fn analyze_statement(&mut self, stmt: &Stmt) -> Result<EffectSet> {
156        match stmt {
157            Stmt::Expr(expr) => self.analyze_expression(expr),
158            Stmt::Let { value, .. } => self.analyze_expression(value),
159            Stmt::Return(Some(expr)) => self.analyze_expression(expr),
160            Stmt::Return(None) => Ok(EffectSet::new()),
161            Stmt::If {
162                condition,
163                then_branch,
164                else_branch,
165                ..
166            } => {
167                let mut effects = self.analyze_expression(condition)?;
168
169                for stmt in then_branch {
170                    let stmt_effects = self.analyze_statement(stmt)?;
171                    effects.union(&stmt_effects);
172                }
173
174                if let Some(else_stmts) = else_branch {
175                    for stmt in else_stmts {
176                        let stmt_effects = self.analyze_statement(stmt)?;
177                        effects.union(&stmt_effects);
178                    }
179                }
180
181                Ok(effects)
182            }
183            Stmt::While {
184                condition, body, ..
185            } => {
186                let mut effects = self.analyze_expression(condition)?;
187
188                for stmt in body {
189                    let stmt_effects = self.analyze_statement(stmt)?;
190                    effects.union(&stmt_effects);
191                }
192
193                Ok(effects)
194            }
195            Stmt::For {
196                var: _, iter, body, ..
197            } => {
198                let mut effects = self.analyze_expression(iter)?;
199
200                for stmt in body {
201                    let stmt_effects = self.analyze_statement(stmt)?;
202                    effects.union(&stmt_effects);
203                }
204
205                Ok(effects)
206            }
207            Stmt::Match { expr, arms, .. } => {
208                let mut effects = self.analyze_expression(expr)?;
209
210                for arm in arms {
211                    // Pattern matching is pure
212
213                    for stmt in &arm.body {
214                        let stmt_effects = self.analyze_statement(stmt)?;
215                        effects.union(&stmt_effects);
216                    }
217                }
218
219                Ok(effects)
220            }
221            Stmt::Break { .. } | Stmt::Continue { .. } => Ok(EffectSet::new()),
222            Stmt::Unsafe { body, .. } => {
223                let mut effects = EffectSet::singleton(Effect::Unsafe);
224
225                for stmt in body {
226                    let stmt_effects = self.analyze_statement(stmt)?;
227                    effects.union(&stmt_effects);
228                }
229
230                Ok(effects)
231            }
232            Stmt::Assign { value, .. } => self.analyze_expression(value),
233        }
234    }
235
236    /// Analyze effects for an expression
237    fn analyze_expression(&mut self, expr: &Expr) -> Result<EffectSet> {
238        match expr {
239            // Literals are pure
240            Expr::Integer(_) | Expr::String(_) | Expr::Bool(_) | Expr::Ident(_) => {
241                Ok(EffectSet::new())
242            }
243
244            // Function calls may have effects
245            Expr::Call { func, args, .. } => {
246                let mut effects = EffectSet::new();
247
248                // Analyze function expression (in case it's not just an identifier)
249                let func_effects = self.analyze_expression(func)?;
250                effects.union(&func_effects);
251
252                // Analyze arguments
253                for arg in args {
254                    let arg_effects = self.analyze_expression(arg)?;
255                    effects.union(&arg_effects);
256                }
257
258                // Add function's own effects
259                if let Expr::Ident(func_name) = func.as_ref() {
260                    if let Some(builtin_effects) = self.builtin_effects.get(func_name) {
261                        effects.union(builtin_effects);
262                    } else if let Some(func_effects) = self.function_effects.get(func_name) {
263                        effects.union(func_effects);
264                    }
265                    // If function is unknown, we conservatively assume it's pure
266                }
267
268                Ok(effects)
269            }
270
271            // Binary operations are usually pure
272            Expr::Binary { left, right, .. } => {
273                let mut effects = self.analyze_expression(left)?;
274                let right_effects = self.analyze_expression(right)?;
275                effects.union(&right_effects);
276                Ok(effects)
277            }
278
279            // Unary operations are pure
280            Expr::Unary { operand, .. } => self.analyze_expression(operand),
281
282            // Array operations
283            Expr::ArrayLiteral { elements, .. } => {
284                let mut effects = EffectSet::new();
285                for elem in elements {
286                    let elem_effects = self.analyze_expression(elem)?;
287                    effects.union(&elem_effects);
288                }
289                Ok(effects)
290            }
291
292            Expr::ArrayRepeat { value, count, .. } => {
293                let mut effects = self.analyze_expression(value)?;
294                let count_effects = self.analyze_expression(count)?;
295                effects.union(&count_effects);
296                Ok(effects)
297            }
298
299            Expr::Index { array, index, .. } => {
300                let mut effects = self.analyze_expression(array)?;
301                let index_effects = self.analyze_expression(index)?;
302                effects.union(&index_effects);
303                Ok(effects)
304            }
305
306            // Struct operations are pure
307            Expr::StructLiteral { fields, .. } => {
308                let mut effects = EffectSet::new();
309                for (_, field_expr) in fields {
310                    let field_effects = self.analyze_expression(field_expr)?;
311                    effects.union(&field_effects);
312                }
313                Ok(effects)
314            }
315
316            Expr::FieldAccess { object, .. } => self.analyze_expression(object),
317
318            // Enum operations are pure
319            Expr::EnumConstructor { data, .. } => {
320                let mut effects = EffectSet::new();
321                if let Some(constructor_data) = data {
322                    match constructor_data {
323                        crate::ast::EnumConstructorData::Tuple(exprs) => {
324                            for expr in exprs {
325                                let expr_effects = self.analyze_expression(expr)?;
326                                effects.union(&expr_effects);
327                            }
328                        }
329                        crate::ast::EnumConstructorData::Struct(fields) => {
330                            for (_, expr) in fields {
331                                let expr_effects = self.analyze_expression(expr)?;
332                                effects.union(&expr_effects);
333                            }
334                        }
335                    }
336                }
337                Ok(effects)
338            }
339
340            // Range is pure
341            Expr::Range { start, end, .. } => {
342                let mut effects = EffectSet::new();
343                let start_effects = self.analyze_expression(start)?;
344                effects.union(&start_effects);
345                let end_effects = self.analyze_expression(end)?;
346                effects.union(&end_effects);
347                Ok(effects)
348            }
349
350            // References are pure
351            Expr::Reference { expr, .. } => self.analyze_expression(expr),
352            Expr::Deref { expr, .. } => self.analyze_expression(expr),
353
354            // Question mark operator can panic
355            Expr::Question { expr, .. } => {
356                let mut effects = self.analyze_expression(expr)?;
357                effects.add(Effect::Panic);
358                Ok(effects)
359            }
360
361            // Await has async effect
362            Expr::Await { expr, .. } => {
363                let mut effects = self.analyze_expression(expr)?;
364                effects.add(Effect::Async);
365                Ok(effects)
366            }
367
368            // Macros are analyzed based on their expansion
369            Expr::MacroInvocation { .. } => {
370                // For now, assume macros are pure
371                // In a real implementation, we'd analyze the expanded code
372                Ok(EffectSet::new())
373            }
374        }
375    }
376
377    /// Get the effects for a function
378    pub fn get_function_effects(&self, func_name: &str) -> Option<&EffectSet> {
379        self.function_effects.get(func_name)
380    }
381
382    /// Check if a function is pure
383    pub fn is_function_pure(&self, func_name: &str) -> bool {
384        self.function_effects
385            .get(func_name)
386            .map(|effects| effects.is_pure())
387            .unwrap_or(true) // Unknown functions assumed pure
388    }
389}
390
391#[cfg(test)]
392mod tests {
393    use super::*;
394
395    #[test]
396    fn test_effect_set() {
397        let mut effects = EffectSet::new();
398        assert!(effects.is_pure());
399
400        effects.add(Effect::IO);
401        assert!(!effects.is_pure());
402        assert!(effects.contains(&Effect::IO));
403
404        let other = EffectSet::singleton(Effect::Async);
405        effects.union(&other);
406        assert!(effects.contains(&Effect::IO));
407        assert!(effects.contains(&Effect::Async));
408    }
409}