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