galvan-ast-macro 0.0.0-dev10

AST macros for the Galvan programming language
Documentation
extern crate proc_macro;

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields};

#[proc_macro_derive(AstNode)]
pub fn ast_node_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    let struct_name = input.ident;
    match &input.data {
        Data::Struct(data) => {
            let fields = match &data.fields {
                Fields::Named(fields) => &fields.named,
                _ => unimplemented!(),
            };
            let field_names: Vec<_> = fields
                .iter()
                .filter_map(|f| {
                    if f.ident.as_ref().unwrap() != "span" {
                        Some(f.ident.as_ref().unwrap())
                    } else {
                        None
                    }
                })
                .collect();

            let gen = quote! {
                impl AstNode for #struct_name {
                    fn span(&self) -> Span {
                        self.span
                    }

                    fn print(&self, indent: usize) -> String {
                        let indent_str = " ".repeat(indent);
                        let mut result = format!("{}{}\n", indent_str, stringify!(#struct_name));
                        #(
                            let field_name = stringify!(#field_names);
                            let field_value = self.#field_names.print_ast(indent + 2);
                            result.push_str(&format!("{}  {}{}\n", indent_str, field_name, field_value));
                        )*
                        result
                    }
                }
            };

            gen.into()
        }
        Data::Enum(e) => {
            let cases: Vec<_> = e.variants.iter().map(|v| v.ident.clone()).collect();
            let gen = quote! {
                impl AstNode for #struct_name {
                    fn print(&self, indent: usize) -> String {
                        use #struct_name::*;
                        match self {
                            #(
                                #cases(c) => c.print(indent),
                            )*
                        }
                    }

                    fn span(&self) -> Span {
                        use #struct_name::*;
                        match self {
                            #(
                                #cases(c) => c.span(),
                            )*
                        }
                    }
                }
            };

            gen.into()
        }
        Data::Union(_) => panic!("Not allowed on unions!"),
    }
}

#[proc_macro_derive(PrintAst)]
pub fn print_ast_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    let struct_name = input.ident;
    match &input.data {
        Data::Struct(data) => {
            let fields = match &data.fields {
                Fields::Named(fields) => &fields.named,
                _ => unimplemented!(),
            };
            let field_names: Vec<_> = fields
                .iter()
                .filter_map(|f| {
                    if f.ident.as_ref().unwrap() != "span" {
                        Some(f.ident.as_ref().unwrap())
                    } else {
                        None
                    }
                })
                .collect();

            let gen = quote! {
                impl PrintAst for #struct_name {
                    fn print_ast(&self, indent: usize) -> String {
                        let indent_str = " ".repeat(indent);
                        let mut result = format!("{}{}\n", indent_str, stringify!(#struct_name));
                        #(
                            let field_name = stringify!(#field_names);
                            let field_value = self.#field_names.print_ast(indent + 2);
                            result.push_str(&format!("{}  {}{}\n", indent_str, field_name, field_value));
                        )*
                        result
                    }
                }
            };

            gen.into()
        }
        Data::Enum(e) => {
            let cases: Vec<_> = e.variants.iter().map(|v| v.ident.clone()).collect();

            let gen = quote! {
                impl PrintAst for #struct_name {
                    fn print_ast(&self, indent: usize) -> String {
                        use #struct_name::*;
                        match self {
                            #(
                                #cases(c) => c.print_ast(indent),
                            )*
                        }
                    }
                }
            };

            gen.into()
        }
        Data::Union(_) => panic!("Not allowed on unions!"),
    }
}