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