use crate::ast::{self, ParsedFile}; #[derive(Debug)] pub(crate) struct ScopeGraph { nodes: Vec, edges: Vec, lets: Vec, } impl ScopeGraph { pub(crate) fn from_file(file: &ParsedFile) -> Self { let mut graph = Self { nodes: Vec::new(), edges: Vec::new(), lets: Vec::new(), }; let root = graph.add_scope(None, ScopeKind::Root); graph.build_expr(&file.root, root); graph } pub(crate) fn let_scopes(&self) -> &[LetScope] { &self.lets } fn build_expr(&mut self, expr: &ast::Expr, scope: NodeId) -> Vec { match expr { ast::Expr::Ident(ident) => vec![self.add_reference(&ident.name, scope)], ast::Expr::Let { range, bindings, body, } => self.build_let(*range, bindings, body, scope), ast::Expr::AttrSet(bindings) => bindings .iter() .flat_map(|binding| self.build_expr(&binding.value, scope)) .collect(), ast::Expr::Lambda { parameters, body } => { let scope = self.add_scope(Some(scope), ScopeKind::Lambda); for parameter in parameters { self.add_declaration(¶meter.name, scope); } self.build_expr(body, scope) } ast::Expr::Apply(application) => [ self.build_expr(&application.lambda, scope), self.build_expr(&application.argument, scope), ] .concat(), ast::Expr::Other(expressions) => expressions .iter() .flat_map(|expression| self.build_expr(expression, scope)) .collect(), ast::Expr::Select(select) => vec![self.add_reference(&select.receiver, scope)], } } fn build_let( &mut self, range: ast::SourceRange, bindings: &[ast::Binding], body: &ast::Expr, parent: NodeId, ) -> Vec { let scope = self.add_scope(Some(parent), ScopeKind::Let); let mut graph_bindings = bindings .iter() .map(|binding| Binding { declaration: self.add_declaration(&binding.key.name, scope), name: binding.key.name.clone(), references: Vec::new(), is_import: is_import(&binding.value), is_lambda: matches!(binding.value, ast::Expr::Lambda { .. }), range: binding.range, }) .collect::>(); for (binding, graph_binding) in bindings.iter().zip(&mut graph_bindings) { graph_binding.references = self.build_expr(&binding.value, scope); } let entry_references = self.build_expr(body, scope); let references = graph_bindings .iter() .flat_map(|binding| binding.references.iter().copied()) .chain(entry_references.iter().copied()) .collect(); self.lets.push(LetScope { range, bindings: graph_bindings, entry_references, }); references } pub(crate) fn crosses_lambda_scope(&self, reference: NodeId, declaration: NodeId) -> bool { let Some(mut scope) = self.edge_target(reference, Node::is_scope) else { return false; }; let Some(declaration_scope) = self .edges .iter() .find(|edge| edge.to == declaration && self.nodes[edge.from.0].is_scope()) .map(|edge| edge.from) else { return false; }; loop { if scope == declaration_scope { return false; } if matches!(self.nodes[scope.0], Node::Scope(ScopeKind::Lambda)) { return true; } let Some(parent) = self.edge_target(scope, Node::is_scope) else { return false; }; scope = parent; } } pub(crate) fn resolve(&self, reference: NodeId) -> Vec { let Node::Reference(name) = &self.nodes[reference.0] else { return Vec::new(); }; let Some(mut scope) = self.edge_target(reference, Node::is_scope) else { return Vec::new(); }; loop { let declarations = self .edge_targets(scope, |node| matches!(node, Node::Declaration(declaration) if declaration.name == *name)) .collect::>(); if !declarations.is_empty() { return declarations; } let Some(parent) = self.edge_target(scope, Node::is_scope) else { return Vec::new(); }; scope = parent; } } fn add_scope(&mut self, parent: Option, kind: ScopeKind) -> NodeId { let scope = self.add_node(Node::Scope(kind)); if let Some(parent) = parent { self.add_edge(scope, parent); } scope } fn add_declaration(&mut self, name: &str, scope: NodeId) -> NodeId { let declaration = self.add_node(Node::Declaration(Declaration { name: name.to_owned(), })); self.add_edge(scope, declaration); declaration } fn add_reference(&mut self, name: &str, scope: NodeId) -> NodeId { let reference = self.add_node(Node::Reference(name.to_owned())); self.add_edge(reference, scope); reference } fn add_node(&mut self, node: Node) -> NodeId { let id = NodeId(self.nodes.len()); self.nodes.push(node); id } fn add_edge(&mut self, from: NodeId, to: NodeId) { self.edges.push(Edge { from, to }); } fn edge_target(&self, from: NodeId, predicate: fn(&Node) -> bool) -> Option { self.edge_targets(from, predicate).next() } fn edge_targets<'graph>( &'graph self, from: NodeId, predicate: impl Fn(&Node) -> bool + 'graph, ) -> impl Iterator + 'graph { self.edges .iter() .filter(move |edge| edge.from == from && predicate(&self.nodes[edge.to.0])) .map(|edge| edge.to) } } fn is_import(expr: &ast::Expr) -> bool { match unwrap_expr(expr) { ast::Expr::Apply(application) => is_import_callee(&application.lambda), _ => false, } } fn is_import_callee(expr: &ast::Expr) -> bool { match unwrap_expr(expr) { ast::Expr::Apply(application) => is_import_callee(&application.lambda), ast::Expr::Ident(ident) => ident.name == "import", _ => false, } } fn unwrap_expr(expr: &ast::Expr) -> &ast::Expr { match expr { ast::Expr::Other(expressions) if expressions.len() == 1 => unwrap_expr(&expressions[0]), _ => expr, } } #[derive(Debug)] enum Node { Scope(ScopeKind), Declaration(Declaration), Reference(String), } impl Node { fn is_scope(&self) -> bool { matches!(self, Self::Scope(_)) } } #[derive(Debug)] enum ScopeKind { Root, Let, Lambda, } #[derive(Debug)] struct Declaration { name: String, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) struct NodeId(usize); #[derive(Debug)] struct Edge { from: NodeId, to: NodeId, } #[derive(Debug)] pub(crate) struct LetScope { pub(crate) range: ast::SourceRange, pub(crate) bindings: Vec, pub(crate) entry_references: Vec, } #[derive(Debug)] pub(crate) struct Binding { pub(crate) declaration: NodeId, pub(crate) name: String, pub(crate) references: Vec, pub(crate) is_import: bool, pub(crate) is_lambda: bool, pub(crate) range: ast::SourceRange, }