use anyhow::Result; use crate::walk::Node; /// Walk-specific trait adding the ability to traverse the KCL AST. /// /// This trait is implemented on [Node] to handle the fairly tricky bit of /// recursing into the AST in a single place, as well as helpers for traversing /// the tree. for callers to use. pub trait Visitable<'tree> { /// Return a `Vec` for all *direct* children of this AST node. This /// should only contain direct descendants. fn children(&self) -> Vec>; /// Return `self` as a [Node]. Generally speaking, the [Visitable] trait /// is only going to be implemented on [Node], so this is purely used by /// helpers that are generic over a [Visitable] and want to deref back /// into a [Node]. fn node(&self) -> Node<'tree>; /// Call the provided [Visitor] in order to Visit `self`. This will /// only be called on `self` -- the [Visitor] is responsible for /// recursing into any children, if desired. fn visit(&self, visitor: VisitorT) -> Result where VisitorT: Visitor<'tree>, { visitor.visit_node(self.node()) } } /// Trait used to enable visiting members of KCL AST. /// /// Implementing this trait enables the implementer to be invoked over /// members of KCL AST by using the [Visitable::visit] function on /// a [Node]. pub trait Visitor<'tree> { /// Error type returned by the [Self::visit] function. type Error; /// Visit a KCL AST [Node]. /// /// In general, implementers likely wish to check to see if a Node is what /// they're looking for, and either descend into that [Node]'s children (by /// calling [Visitable::children] on [Node] to get children nodes, /// calling [Visitable::visit] on each node of interest), or perform /// some action. fn visit_node(&self, node: Node<'tree>) -> Result; } impl<'a, FnT, ErrorT> Visitor<'a> for FnT where FnT: Fn(Node<'a>) -> Result, { type Error = ErrorT; fn visit_node(&self, n: Node<'a>) -> Result { self(n) } } impl<'tree> Visitable<'tree> for Node<'tree> { fn node(&self) -> Node<'tree> { *self } fn children(&self) -> Vec> { match self { Node::Program(n) => n.body.iter().map(|node| node.into()).collect(), Node::ExpressionStatement(n) => { vec![(&n.expression).into()] } Node::BinaryExpression(n) => { vec![(&n.left).into(), (&n.right).into()] } Node::FunctionExpression(n) => { let mut children = n.params.iter().map(|v| v.into()).collect::>(); children.push((&n.body).into()); children } Node::CallExpressionKw(n) => { let mut children: Vec> = Vec::with_capacity(1 + if n.unlabeled.is_some() { 1 } else { 0 } + n.arguments.len()); children.push((&n.callee).into()); children.extend(n.unlabeled.iter().map(Node::from)); // TODO: this is wrong but it's what the old walk code was doing. // We likely need a real LabeledArg AST node, but I don't // want to tango with it since it's a lot deeper than // adding it to the enum. children.extend(n.arguments.iter().map(|v| Node::from(&v.arg))); children } Node::PipeExpression(n) => n.body.iter().map(|v| v.into()).collect(), Node::ArrayExpression(n) => n.elements.iter().map(|v| v.into()).collect(), Node::ArrayRangeExpression(n) => { vec![(&n.start_element).into(), (&n.end_element).into()] } Node::ObjectExpression(n) => n.properties.iter().map(|v| v.into()).collect(), Node::MemberExpression(n) => { vec![(&n.object).into(), (&n.property).into()] } Node::IfExpression(n) => { let mut children = n.else_ifs.iter().map(|v| v.into()).collect::>(); children.insert(0, n.cond.as_ref().into()); children.push(n.final_else.as_ref().into()); children } Node::VariableDeclaration(n) => vec![(&n.declaration).into()], Node::TypeDeclaration(n) => vec![(&n.name).into()], Node::ReturnStatement(n) => { vec![(&n.argument).into()] } Node::VariableDeclarator(n) => { vec![(&n.id).into(), (&n.init).into()] } Node::UnaryExpression(n) => { vec![(&n.argument).into()] } Node::Parameter(n) => { vec![(&n.identifier).into()] } Node::ObjectProperty(n) => { vec![(&n.value).into()] } Node::ElseIf(n) => { vec![(&n.cond).into(), n.then_val.as_ref().into()] } Node::LabelledExpression(e) => { vec![(&e.expr).into(), (&e.label).into()] } Node::AscribedExpression(e) => { vec![(&e.expr).into()] } Node::Name(n) => Some((&n.name).into()) .into_iter() .chain(n.path.iter().map(|n| n.into())) .collect(), Node::PipeSubstitution(_) | Node::TagDeclarator(_) | Node::Identifier(_) | Node::ImportStatement(_) | Node::KclNone(_) | Node::Literal(_) => vec![], } } } #[cfg(test)] mod tests { use std::sync::Mutex; use super::*; macro_rules! kcl { ( $kcl:expr ) => {{ $crate::parsing::top_level_parse($kcl).unwrap() }}; } #[test] fn count_crows() { let program = kcl!( "\ const crow1 = 1 const crow2 = 2 fn crow3() { const crow4 = 3 crow5() } " ); #[derive(Debug, Default)] struct CountCrows { n: Box>, } impl<'tree> Visitor<'tree> for &CountCrows { type Error = (); fn visit_node(&self, node: Node<'tree>) -> Result { if let Node::VariableDeclarator(vd) = node { if vd.id.name.starts_with("crow") { *self.n.lock().unwrap() += 1; } } for child in node.children().iter() { if !child.visit(*self)? { return Ok(false); } } Ok(true) } } let prog: Node = (&program).into(); let count_crows: CountCrows = Default::default(); Visitable::visit(&prog, &count_crows).unwrap(); assert_eq!(*count_crows.n.lock().unwrap(), 4); } }