lib-ruby-parser 4.0.0+ruby-3.1.0

Ruby parser
Documentation
use lib_ruby_parser_nodes::Node;

fn contents() -> String {
    format!(
        "// This file is autogenerated by {generator}

use super::LocName;
use crate::{{Node, Loc}};
use crate::nodes::*;

impl LocName {{
    {loc_getters}

    pub(crate) fn get(&self, node: &Node) -> Option<Loc> {{
        match self {{
            {loc_branches}
        }}
    }}
}}
",
        generator = file!(),
        loc_getters = map_loc(&loc_getter).join("\n\n    "),
        loc_branches = map_loc(&loc_branch).join("\n            ")
    )
}

pub(crate) fn codegen() {
    std::fs::write("src/test_helpers/loc_matcher/loc_name_gen.rs", contents()).unwrap();
}

#[derive(Debug)]
pub enum LocName {
    Begin,
    End,
    Expression,
    Keyword,
    Name,
    Assignment,
    Colon,
    DoubleColon,
    Else,
    HeredocBody,
    Operator,
    Selector,
    Assoc,
    Question,
    HeredocEnd,
}

impl LocName {
    fn to_str(&self) -> &'static str {
        match self {
            LocName::Begin => "begin_l",
            LocName::End => "end_l",
            LocName::Expression => "expression_l",
            LocName::Keyword => "keyword_l",
            LocName::Name => "name_l",
            LocName::Assignment => "assignment_l",
            LocName::Colon => "colon_l",
            LocName::DoubleColon => "double_colon_l",
            LocName::Else => "else_l",
            LocName::HeredocBody => "heredoc_body_l",
            LocName::Operator => "operator_l",
            LocName::Selector => "selector_l",
            LocName::Assoc => "assoc_l",
            LocName::Question => "question_l",
            LocName::HeredocEnd => "heredoc_end_l",
        }
    }
}

const LOC_NAMES: &[&'static LocName] = &[
    &LocName::Begin,
    &LocName::End,
    &LocName::Expression,
    &LocName::Keyword,
    &LocName::Name,
    &LocName::Assignment,
    &LocName::Colon,
    &LocName::DoubleColon,
    &LocName::Else,
    &LocName::HeredocBody,
    &LocName::Operator,
    &LocName::Selector,
    &LocName::Assoc,
    &LocName::Question,
    &LocName::HeredocEnd,
];

fn map_loc(f: &dyn Fn(&LocName) -> String) -> Vec<String> {
    LOC_NAMES.iter().map(|l| f(*l)).collect()
}

fn loc_getter(loc_name: &LocName) -> String {
    let mut nullable_variants = vec![];
    let mut non_nullable_variants = vec![];

    for node in lib_ruby_parser_nodes::nodes() {
        for field in node.fields {
            if field.snakecase_name == loc_name.to_str() {
                match field.field_type {
                    lib_ruby_parser_nodes::NodeFieldType::Loc => {
                        non_nullable_variants.push((*node).clone())
                    }
                    lib_ruby_parser_nodes::NodeFieldType::MaybeLoc => {
                        nullable_variants.push((*node).clone())
                    }
                    _ => {}
                }
            }
        }
    }

    let branches = |variants: &[Node], loc_name: &LocName| {
        variants
            .into_iter()
            .map(|node| {
                format!(
                    "Node::{node_name}({node_name} {{ {loc_name}, .. }})",
                    node_name = node.camelcase_name,
                    loc_name = loc_name.to_str()
                )
            })
            .collect::<Vec<_>>()
    };

    let branches = {
        let nullable_branches = if nullable_variants.is_empty() {
            format!("")
        } else {
            format!(
                "{nullable_branches} => {loc_name}.clone(),",
                nullable_branches = branches(&nullable_variants, loc_name).join("\n            | "),
                loc_name = loc_name.to_str(),
            )
        };

        let non_nullable_branches = if non_nullable_variants.is_empty() {
            format!("")
        } else {
            format!(
                "{non_nullable_branches} => Some({loc_name}.clone()),",
                non_nullable_branches =
                    branches(&non_nullable_variants, loc_name).join("\n            | "),
                loc_name = loc_name.to_str(),
            )
        };

        format!(
            "{}\n\n            {}",
            nullable_branches, non_nullable_branches
        )
    };

    format!(
        "fn get_{loc_name}(node: &Node) -> Option<Loc> {{
        match node {{
            {branches}

            #[allow(unreachable_patterns)]
            _ => {{
                panic!(\"node {{}} doesn't support {loc_name} loc\", node.str_type())
            }}
        }}
    }}",
        loc_name = loc_name.to_str(),
        branches = branches,
    )
}

fn loc_branch(loc_name: &LocName) -> String {
    let camelcase_loc_name = format!("{:?}", loc_name);
    format!(
        "LocName::{camelcase_loc_name} => Self::get_{loc_name}(node),",
        camelcase_loc_name = camelcase_loc_name,
        loc_name = loc_name.to_str()
    )
}