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::traverse::finder::{Finder, PatternItem};
use crate::traverse::visitor::Visitor;
use crate::nodes::*;
use crate::Node;

impl Visitor for Finder {
{{ each node }}<dnl>
    fn on_{{ helper node-lower-name }}(&mut self, node: &{{ helper node-camelcase-name }}) {
        match self.pattern.unshift() {
{{ each node-field }}<dnl>
            {{ helper visit-child-branch }}
{{ end }}<dnl>
            None => {
                // end of the search chain, match
                self.result = Some(Node::{{ helper node-camelcase-name }}(node.clone()));
            }
            Some(_) => {
                // end of the search chain, no match
            }
        }
    }
{{ end }}

    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 }}
        }
    }
}

fn visit_node_list(finder: &mut Finder, nodes: &[Node]) {
    if let Some(PatternItem::Idx(idx)) = finder.pattern.unshift() {
        if let Some(item) = nodes.get(idx) {
            finder.visit(item)
        }
    } else {
        // end of the search chain, no match
    }
}
";

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-branch", local_helpers::visit_child);

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

mod local_helpers {
    use lib_ruby_parser_nodes::NodeField;

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

        let variant = {
            fn capitalize_field_name(s: &str) -> String {
                s.split("_").map(|word| capitalize_word(word)).collect()
            }

            fn capitalize_word(s: &str) -> String {
                let mut c = s.chars();
                match c.next() {
                    None => String::new(),
                    Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
                }
            }

            match (&node.wqp_name[..], &field_name[..]) {
                (_, "statements") => "Stmts".to_string(),
                (_, "call") => "MethodCall".to_string(),
                (_, "default") => "DefaultValue".to_string(),
                (_, "items") => "MlhsItems".to_string(),
                ("when", "patterns") => "Args".to_string(),
                ("undef", "names") => "Args".to_string(),
                ("args", "args") => "Arglist".to_string(),
                ("procarg0", "args") => "Arglist".to_string(),
                ("rescue", "else_") => "ElseBody".to_string(),
                _ => capitalize_field_name(&field_name),
            }
        };

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

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

        format!(
            "Some(PatternItem::{variant}) => {{ {code} }}",
            variant = variant,
            code = code
        )
    }
}