lib-ruby-parser 4.0.0+ruby-3.1.0

Ruby parser
Documentation
use lib_ruby_parser_nodes::{template::*, NodeField};

const TEMPLATE: &str = "// This file is auto-generated by {{ helper generated-by }}

use crate::nodes::*;
use crate::Node;

/// Common trait for all visitors
///
/// ```rust
/// use lib_ruby_parser::{
///     nodes::{Class, Const},
///     traverse::visitor::{visit_class, Visitor},
///     Node,
/// };
///
/// struct ClassesCollector {
///     classes: Vec<String>,
/// }
///
/// impl Visitor for ClassesCollector {
///     fn on_class(&mut self, node: &Class) {
///         self.classes.push(fetch_const_name(&node.name));
///         visit_class(self, node);
///     }
/// }
///
/// fn fetch_const_name(name: &Node) -> String {
///     match name {
///         Node::Const(Const { name, .. }) => name.to_owned(),
///         other => panic!(\"Don't know how to fetch const name from {:?}\", other),
///     }
/// }
///
/// use lib_ruby_parser::{Parser, ParserOptions, ParserResult};
/// let parser = Parser::new(
///     b\"class A; class B; end; end\".to_vec(),
///     ParserOptions::default(),
/// );
/// let ParserResult { ast, .. } = parser.do_parse();
/// let ast = ast.unwrap();
///
/// let mut collector = ClassesCollector { classes: vec![] };
/// collector.visit(&ast);
/// assert_eq!(
///     collector.classes,
///     vec![String::from(\"A\"), String::from(\"B\")]
/// );
/// ```
pub trait Visitor: Sized {
{{ each node }}<dnl>
    /// Invoked by a `Visitor` on entering into `{{ helper node-camelcase-name }}` node.
    ///
    /// Has a default implementation, but you can override it and (optionally) call
    /// `visit_{{ helper node-lower-name }}(node)` to continue traversing.
    fn on_{{ helper node-lower-name }}(&mut self, node: &{{ helper node-camelcase-name }}) {
        visit_{{ helper node-lower-name }}(self, node);
    }
{{ end }}

    /// Generic `visit` router that calls `on_<type>` under the hood
    fn visit(&mut self, node: &Node) {
        match node {
{{ each node }}<dnl>
            Node::{{ helper node-camelcase-name }}(inner) => {
                self.on_{{ helper node-lower-name }}(inner);
            }
{{ end }}
        }
    }
}

{{ each node }}<dnl>
/// Visits all children of {{ helper node-camelcase-name }} node
#[allow(unused_variables)]
pub fn visit_{{ helper node-lower-name }}<V: Visitor>(visitor: &mut V, node: &{{ helper node-camelcase-name }}) {
{{ each node-field }}<dnl>
    {{ helper visit-child }}
{{ end }}<dnl>
}
{{ end }}
";

pub(crate) fn codegen() {
    let template = TemplateRoot::new(TEMPLATE).unwrap();
    let mut fns = crate::codegen::fns::default_fns!();

    fns.register::<NodeField, F::Helper>("visit-child", local_helpers::visit_child);

    let contents = template.render(ALL_DATA, &fns);
    std::fs::write("src/traverse/visitor/visit_gen.rs", contents).unwrap();
}

mod local_helpers {
    use lib_ruby_parser_nodes::NodeField;

    pub(crate) fn visit_child(node_field: &NodeField) -> String {
        let field_name = crate::codegen::fns::rust::node_fields::rust_field_name(node_field);

        use lib_ruby_parser_nodes::NodeFieldType::*;
        match node_field.field_type {
            Node => {
                format!("visitor.visit(&node.{});", field_name)
            }
            Nodes => {
                format!(
                    "for item in &node.{} {{ visitor.visit(item); }}",
                    field_name
                )
            }
            MaybeNode { .. } => {
                format!(
                    "if let Some(inner) = node.{}.as_ref() {{ visitor.visit(inner); }}",
                    field_name
                )
            }

            Loc | MaybeLoc | Str { .. } | MaybeStr { .. } | StringValue | U8 => {
                return format!("// skip {}", field_name)
            }
        }
    }
}