use crate::ast::compiler::bound_nodes::{ Address, AnalyzedNode, BoundKind, GlobalIdx, NodeMetrics, TypedNode, }; use crate::ast::types::Purity; use std::collections::{HashMap, HashSet}; use std::rc::Rc; pub struct Analyzer<'a> { global_purity: &'a HashMap, /// Stack of currently visiting lambdas to detect direct recursion. lambda_stack: Vec, /// Map of global index to its Lambda identity if known. globals_to_lambdas: HashMap, /// Set of identities that were found to be recursive. recursive_identities: HashSet, } impl<'a> Analyzer<'a> { pub fn analyze( node: &TypedNode, global_purity: &'a HashMap, ) -> AnalyzedNode { let mut analyzer = Self { global_purity, lambda_stack: Vec::new(), globals_to_lambdas: HashMap::new(), recursive_identities: HashSet::new(), }; // First pass: map globals to their lambda identities analyzer.collect_globals(node); // Second pass: full analysis (decorating TypedNode into AnalyzedNode) analyzer.visit(Rc::new(node.clone())) } fn collect_globals(&mut self, node: &TypedNode) { match &node.kind { BoundKind::Define { addr: Address::Global(global_index), value, .. } => { if let BoundKind::Lambda { .. } = &value.kind { self.globals_to_lambdas .insert(*global_index, value.identity.clone()); } self.collect_globals(value); } BoundKind::Block { exprs } => { for e in exprs { self.collect_globals(e); } } _ => { node.kind .for_each_child(|child| self.collect_globals(child)); } } } fn visit(&mut self, node_rc: Rc) -> AnalyzedNode { let node = &*node_rc; let mut is_recursive = false; let (new_kind, purity) = match &node.kind { BoundKind::Constant(v) => (BoundKind::Constant(v.clone()), Purity::Pure), BoundKind::Nop => (BoundKind::Nop, Purity::Pure), BoundKind::Get { addr, name } => { let p = match addr { Address::Global(idx) => { self.global_purity.get(idx).cloned().unwrap_or(Purity::Pure) } _ => Purity::Pure, }; ( BoundKind::Get { addr: *addr, name: name.clone(), }, p, ) } BoundKind::FieldAccessor(k) => (BoundKind::FieldAccessor(*k), Purity::Pure), BoundKind::GetField { rec, field } => { let rec_m = self.visit(Rc::new((**rec).clone())); let p = rec_m.ty.purity; ( BoundKind::GetField { rec: Box::new(rec_m), field: *field, }, p, ) } BoundKind::Set { addr, value } => { let val_m = self.visit(Rc::new((**value).clone())); ( BoundKind::Set { addr: *addr, value: Box::new(val_m), }, Purity::Impure, ) } BoundKind::Define { name, addr, kind, value, captured_by, } => { let val_m = self.visit(Rc::new((**value).clone())); let p = val_m.ty.purity; ( BoundKind::Define { name: name.clone(), addr: *addr, kind: *kind, value: Box::new(val_m), captured_by: captured_by.clone(), }, p, ) } BoundKind::If { cond, then_br, else_br, } => { let cond_m = self.visit(Rc::new((**cond).clone())); let then_m = self.visit(Rc::new((**then_br).clone())); let else_m = else_br.as_ref().map(|e| self.visit(Rc::new((**e).clone()))); let mut p = cond_m.ty.purity.min(then_m.ty.purity); if let Some(ref em) = else_m { p = p.min(em.ty.purity); } ( BoundKind::If { cond: Box::new(cond_m), then_br: Box::new(then_m), else_br: else_m.map(Box::new), }, p, ) } BoundKind::Lambda { params, upvalues, body, positional_count, } => { self.lambda_stack.push(node.identity.clone()); let params_m = self.visit(params.clone()); let body_m = self.visit(body.clone()); self.lambda_stack.pop(); is_recursive = self.recursive_identities.contains(&node.identity); ( BoundKind::Lambda { params: Rc::new(params_m), upvalues: upvalues.clone(), body: Rc::new(body_m), positional_count: *positional_count, }, Purity::Pure, ) } BoundKind::Destructure { pattern, value } => { let pat_m = self.visit(Rc::new((**pattern).clone())); let val_m = self.visit(Rc::new((**value).clone())); ( BoundKind::Destructure { pattern: Box::new(pat_m), value: Box::new(val_m), }, Purity::Impure, ) } BoundKind::Call { callee, args } => { let callee_m = self.visit(Rc::new((**callee).clone())); let args_m = self.visit(Rc::new((**args).clone())); if let BoundKind::Get { addr: Address::Global(idx), .. } = &callee.kind && let Some(lambda_id) = self.globals_to_lambdas.get(idx) && self.lambda_stack.contains(lambda_id) { self.recursive_identities.insert(lambda_id.clone()); is_recursive = true; } let p_func = if let BoundKind::Get { addr: Address::Global(idx), .. } = &callee.kind { self.global_purity .get(idx) .cloned() .unwrap_or(Purity::Impure) } else { Purity::Impure }; let p = callee_m.ty.purity.min(args_m.ty.purity).min(p_func); ( BoundKind::Call { callee: Box::new(callee_m), args: Box::new(args_m), }, p, ) } BoundKind::Again { args } => { let args_m = self.visit(Rc::new((**args).clone())); if let Some(lambda_id) = self.lambda_stack.last() { self.recursive_identities.insert(lambda_id.clone()); is_recursive = true; } ( BoundKind::Again { args: Box::new(args_m), }, Purity::Impure, ) } BoundKind::Pipe { inputs, lambda, out_type, } => { let mut analyzed_inputs = Vec::with_capacity(inputs.len()); for input in inputs { analyzed_inputs.push(self.visit(Rc::new(input.clone()))); } let a_lambda = Box::new(self.visit(Rc::new((**lambda).clone()))); ( BoundKind::Pipe { inputs: analyzed_inputs, lambda: a_lambda, out_type: out_type.clone(), }, Purity::Impure, ) } BoundKind::Block { exprs } => { let mut new_exprs = Vec::with_capacity(exprs.len()); let mut p = Purity::Pure; for e in exprs { let em = self.visit(Rc::new(e.clone())); p = p.min(em.ty.purity); new_exprs.push(em); } (BoundKind::Block { exprs: new_exprs }, p) } BoundKind::Tuple { elements } => { let mut new_elements = Vec::with_capacity(elements.len()); let mut p = Purity::Pure; for e in elements { let em = self.visit(Rc::new(e.clone())); p = p.min(em.ty.purity); new_elements.push(em); } ( BoundKind::Tuple { elements: new_elements, }, p, ) } BoundKind::Record { layout, values } => { let mut new_values = Vec::with_capacity(values.len()); let mut p = Purity::Pure; for v in values { let vm = self.visit(Rc::new(v.clone())); p = p.min(vm.ty.purity); new_values.push(vm); } ( BoundKind::Record { layout: layout.clone(), values: new_values, }, p, ) } BoundKind::Expansion { original_call, bound_expanded, } => { let expanded_m = self.visit(Rc::new((**bound_expanded).clone())); ( BoundKind::Expansion { original_call: original_call.clone(), bound_expanded: Box::new(expanded_m.clone()), }, expanded_m.ty.purity, ) } BoundKind::Extension(_) => (BoundKind::Nop, Purity::Impure), BoundKind::Error => (BoundKind::Error, Purity::Impure), }; crate::ast::nodes::Node { identity: node.identity.clone(), kind: new_kind, ty: NodeMetrics { original: node_rc, purity, is_recursive, }, } } } trait NodeExt { fn for_each_child(&self, f: F); } impl NodeExt for BoundKind { fn for_each_child(&self, mut f: F) { match self { BoundKind::If { cond, then_br, else_br, } => { f(cond); f(then_br); if let Some(e) = else_br { f(e); } } BoundKind::Define { value, .. } | BoundKind::Set { value, .. } => { f(value); } BoundKind::GetField { rec, .. } => { f(rec); } BoundKind::Lambda { params, body, .. } => { f(params); f(body); } BoundKind::Call { callee, args } => { f(callee); f(args); } BoundKind::Block { exprs } => { for e in exprs { f(e); } } BoundKind::Tuple { elements } => { for e in elements { f(e); } } BoundKind::Record { values, .. } => { for v in values { f(v); } } BoundKind::Expansion { bound_expanded, .. } => { f(bound_expanded); } _ => {} } } }