Skip to main content

m2_syn_macros/
lib.rs

1//! Procedural macros backing the `m2-syn` typed syntax API.
2#![warn(missing_docs)]
3
4extern crate proc_macro;
5
6mod quote_m2;
7mod roles;
8mod syntax;
9mod tokens;
10
11use proc_macro::TokenStream;
12use std::collections::BTreeSet;
13
14use proc_macro2::{TokenStream as TokenStream2, TokenTree};
15use quote::{ToTokens, format_ident, quote};
16use syn::{Data, DeriveInput, Error, Fields, Member, Result, Type, parse_macro_input, parse_quote};
17
18fn base_crate() -> TokenStream2 {
19    quote!(::m2_syn)
20}
21
22/// Declares typed syntax nodes, token atoms, and traversal implementations.
23#[proc_macro]
24pub fn syntax(input: TokenStream) -> TokenStream {
25    let expansion = match syntax::generate(input.into()) {
26        Ok(expansion) => expansion,
27        Err(error) => return error.into_compile_error().into(),
28    };
29    expansion.into()
30}
31
32/// Resolves a token spelling to the type `syntax!` generates for it.
33///
34/// This is what `m2_syn::Token![…]` forwards to. The mapping is pure
35/// computation over the spelling, so it needs no access to the schema and
36/// nothing has to be generated for it.
37#[proc_macro]
38pub fn token(input: TokenStream) -> TokenStream {
39    let base = base_crate();
40    match tokens::token_type_name(&input.into()) {
41        Ok(name) => quote!(#base::#name).into(),
42        Err(error) => error.into_compile_error().into(),
43    }
44}
45/// Constructs an M2 token stream with recursive Rust interpolation.
46///
47/// `$(expression)` emits any value implementing `m2_syn::Lex`.
48/// `$[pattern in iterator] { template }` repeats a recursively quoted template
49/// with an explicit Rust `for` loop.
50#[proc_macro]
51pub fn quote_m2(input: TokenStream) -> TokenStream {
52    quote_m2::expand(input)
53}
54
55/// Quotes M2 syntax and parses it as the type inferred at the call site.
56#[proc_macro]
57pub fn parse_quote_m2(input: TokenStream) -> TokenStream {
58    let input = TokenStream2::from(input);
59    let base = base_crate();
60    quote!({
61        let tokens = #base::quote_m2!(#input);
62        #base::parse2(tokens).expect("parse_quote_m2! input should parse")
63    })
64    .into()
65}
66
67/// Derives `m2_syn::Spanned` by joining the spans of a value's fields.
68#[proc_macro_derive(Spanned)]
69pub fn derive_spanned(input: TokenStream) -> TokenStream {
70    let input = parse_macro_input!(input as DeriveInput);
71    match expand_spanned(&input) {
72        Ok(expansion) => expansion.into(),
73        Err(error) => error.to_compile_error().into(),
74    }
75}
76
77fn expand_spanned_members(
78    members: impl IntoIterator<Item = (Member, TokenStream2)>,
79) -> TokenStream2 {
80    let members = members.into_iter().collect::<Vec<_>>();
81    let base = base_crate();
82    let span = quote! {#base::Spanned::span};
83    if let Some((_, value)) = members
84        .iter()
85        .find(|(member, _)| matches!(member, Member::Named(ident) if ident == "span"))
86    {
87        quote!(#span(#value))
88    } else {
89        let values = members.iter().map(|(_, value)| value);
90        quote! {
91            #base::Span::join_all(
92                [#(#base::Spanned::span(#values)),*]
93            )
94        }
95    }
96}
97
98fn expand_spanned(input: &DeriveInput) -> Result<TokenStream2> {
99    let name = &input.ident;
100    let base = base_crate();
101    let span_ty = quote! {#base::Span};
102    let mut generics = input.generics.clone();
103    let type_parameters = input
104        .generics
105        .type_params()
106        .map(|parameter| parameter.ident.to_string())
107        .collect::<BTreeSet<_>>();
108    for ty in spanned_field_types(input)
109        .into_iter()
110        .filter(|ty| type_mentions_parameter(ty, &type_parameters))
111    {
112        generics
113            .make_where_clause()
114            .predicates
115            .push(parse_quote!(#ty: #base::Spanned));
116    }
117    let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
118    let body = match &input.data {
119        Data::Struct(data) => {
120            let members = data.fields.members().map(|member| {
121                let value = quote!(&self.#member);
122                (member, value)
123            });
124            expand_spanned_members(members)
125        }
126        Data::Enum(data) => {
127            let arms = data
128                .variants
129                .iter()
130                .map(|variant| {
131                    let variant_name = &variant.ident;
132                    let members = variant.fields.members().collect::<Vec<_>>();
133                    let bindings = members
134                        .iter()
135                        .enumerate()
136                        .map(|(index, _)| format_ident!("__m2_syn_field_{index}"))
137                        .collect::<Vec<_>>();
138                    let values = bindings.iter().map(|binding| quote!(#binding));
139                    let span = expand_spanned_members(members.iter().cloned().zip(values));
140
141                    match &variant.fields {
142                        Fields::Named(_) => {
143                            let fields = members
144                                .iter()
145                                .zip(&bindings)
146                                .map(|(member, binding)| quote!(#member: #binding));
147                            quote! {
148                                Self::#variant_name { #(#fields),* } => #span
149                            }
150                        }
151                        Fields::Unnamed(_) => quote! {
152                            Self::#variant_name(#(#bindings),*) => #span
153                        },
154                        Fields::Unit => quote! {
155                            Self::#variant_name => #span
156                        },
157                    }
158                })
159                .collect::<Vec<_>>();
160            let empty_fallback = data
161                .variants
162                .is_empty()
163                .then(|| quote!(_ => #span_ty::detached()));
164            quote! {
165                match self {
166                    #(#arms,)*
167                    #empty_fallback
168                }
169            }
170        }
171        Data::Union(_) => {
172            return Err(Error::new_spanned(
173                name,
174                "Spanned cannot be derived for unions",
175            ));
176        }
177    };
178
179    Ok(quote! {
180        impl #impl_generics
181            #base::Spanned for #name #type_generics #where_clause
182        {
183            fn span(&self) -> #span_ty {
184                #body
185            }
186        }
187    })
188}
189
190fn type_mentions_parameter(ty: &impl ToTokens, parameters: &BTreeSet<String>) -> bool {
191    fn stream_mentions_parameter(stream: TokenStream2, parameters: &BTreeSet<String>) -> bool {
192        stream.into_iter().any(|token| match token {
193            TokenTree::Ident(ident) => parameters.contains(&ident.to_string()),
194            TokenTree::Group(group) => stream_mentions_parameter(group.stream(), parameters),
195            _ => false,
196        })
197    }
198    stream_mentions_parameter(ty.to_token_stream(), parameters)
199}
200
201fn spanned_field_types(input: &DeriveInput) -> Vec<&Type> {
202    fn selected(fields: &Fields) -> Vec<&Type> {
203        fields
204            .iter()
205            .find(|field| field.ident.as_ref().is_some_and(|ident| ident == "span"))
206            .map_or_else(
207                || fields.iter().map(|field| &field.ty).collect(),
208                |field| vec![&field.ty],
209            )
210    }
211
212    match &input.data {
213        Data::Struct(data) => selected(&data.fields),
214        Data::Enum(data) => data
215            .variants
216            .iter()
217            .flat_map(|variant| selected(&variant.fields))
218            .collect(),
219        Data::Union(_) => Vec::new(),
220    }
221}