matcher_proc_macro/
lib.rs

1#![warn(clippy::pedantic, clippy::nursery, clippy::cargo)]
2#![deny(
3    clippy::use_self,
4    rust_2018_idioms,
5    missing_debug_implementations,
6    clippy::missing_panics_doc
7)]
8#![doc = include_str!("./../README.md")]
9use core::fmt;
10use std::collections::HashSet;
11
12use crate::custom::DotDotPlus;
13use custom::id;
14use proc_macro::TokenStream;
15use quote::{ToTokens, TokenStreamExt, quote};
16use rand::random;
17use syn::{
18    Ident, Token, ext::IdentExt, parenthesized, parse::Parse, parse_macro_input, spanned::Spanned,
19};
20
21#[derive(Clone, Debug, PartialEq)]
22struct MatchStruct {
23    binders: HashSet<Ident>,
24}
25mod custom {
26    use syn::{custom_keyword, custom_punctuation};
27
28    custom_punctuation!(DotDotPlus, ..+);
29    custom_keyword!(id);
30}
31struct NameAsSExpr(Ident, SExpr);
32impl Parse for NameAsSExpr {
33    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
34        let ident = Ident::parse_any(input)?;
35        input.parse::<Token![as]>()?;
36        let sexpr = input.parse()?;
37        Ok(Self(ident, sexpr))
38    }
39}
40#[derive(Debug, PartialEq, Clone)]
41enum SExpr {
42    Many(Box<Self>, MatchStruct),
43    ManyOne(Box<Self>, MatchStruct),
44    Symbol(Ident),
45    // only matches identifiers
46    Identifier(Ident),
47    Empty,
48    Pair {
49        car: Box<Self>,
50        cdr: Box<Self>,
51        binders: MatchStruct,
52    },
53}
54impl fmt::Display for SExpr {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        match self {
57            Self::Many(sexpr, _) => write!(f, "{sexpr} ..."),
58            Self::ManyOne(sexpr, _) => write!(f, "{sexpr} ..+"),
59            Self::Symbol(ident) => write!(f, "{ident}"),
60            Self::Identifier(ident) => write!(f, "{ident}:id"),
61            Self::Empty => write!(f, "()"),
62            Self::Pair {
63                car,
64                cdr,
65                binders: _,
66            } => {
67                write!(f, "({car}")?;
68                let mut second: Self = *(cdr.clone());
69                while let Self::Pair {
70                    car,
71                    cdr,
72                    binders: _,
73                } = second
74                {
75                    write!(f, " {car}")?;
76                    second = *cdr;
77                }
78                if second != Self::Empty {
79                    write!(f, " . {second}")?;
80                }
81                write!(f, ")")
82            }
83        }
84    }
85}
86impl SExpr {
87    fn binders(&self) -> MatchStruct {
88        match self {
89            Self::Many(_, match_struct) | Self::ManyOne(_, match_struct) => match_struct.clone(),
90            Self::Symbol(ident) | Self::Identifier(ident) => MatchStruct {
91                binders: HashSet::from([ident.clone()]),
92            },
93            Self::Empty => MatchStruct {
94                binders: HashSet::new(),
95            },
96            Self::Pair {
97                car: _,
98                cdr: _,
99                binders,
100            } => binders.clone(),
101        }
102    }
103}
104impl Parse for SExpr {
105    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
106        let sexpr = if input.peek(Ident::peek_any) {
107            let ident = Ident::parse_any(input)?;
108            if input.peek(Token![:]) {
109                input.parse::<Token![:]>()?;
110                if input.peek(id) {
111                    input.parse::<id>()?;
112                    let ident = Ident::new(&(ident.to_string() + "_id"), ident.span());
113                    Self::Identifier(ident)
114                } else {
115                    return Err(input.error("unkown syntax expected `id` after `:`"));
116                }
117            } else if ident == "id" {
118                Self::Identifier(ident)
119            } else {
120                Self::Symbol(ident)
121            }
122        } else {
123            let paren_input;
124            parenthesized!(paren_input in input);
125            parse_paren(&paren_input)?
126        };
127        if input.peek(Token![...]) {
128            input.parse::<Token![...]>()?;
129
130            let binders = sexpr.binders();
131            Ok(Self::Many(Box::new(sexpr), binders))
132        } else if input.peek(DotDotPlus) {
133            input.parse::<DotDotPlus>()?;
134            let binders = sexpr.binders();
135            Ok(Self::ManyOne(Box::new(sexpr), binders))
136        } else {
137            Ok(sexpr)
138        }
139    }
140}
141
142fn parse_paren(input: &syn::parse::ParseBuffer<'_>) -> syn::Result<SExpr> {
143    if input.is_empty() {
144        Ok(SExpr::Empty)
145    } else {
146        let current = input
147            .parse::<SExpr>()
148            .map_err(|_| input.error("unterminated sexpr pair"))?;
149        let mut current_binders = current.binders();
150        if input.peek(Token![.]) && !(input.peek(Token![...]) || input.peek(DotDotPlus)) {
151            input.parse::<Token![.]>()?;
152            let end = input.parse::<SExpr>().map_err(|_| {
153                input.error("expected expression after improper list dots".to_string())
154            })?;
155            if input.is_empty() {
156                check_duplicates(input, &mut current_binders, &end)?;
157                Ok(SExpr::Pair {
158                    car: Box::new(current),
159                    cdr: Box::new(end),
160                    binders: current_binders,
161                })
162            } else {
163                Err(input.error("expected nothing after last expression in improper list"))
164            }
165        } else if input.is_empty() && matches!(current, |SExpr::Many(_, _)| SExpr::ManyOne(_, _)) {
166            Ok(current)
167        } else {
168            let next = parse_paren(input)?;
169            check_duplicates(input, &mut current_binders, &next)?;
170            Ok(SExpr::Pair {
171                car: Box::new(current),
172                cdr: Box::new(next),
173                binders: current_binders,
174            })
175        }
176    }
177}
178
179fn check_duplicates(
180    input: &syn::parse::ParseBuffer<'_>,
181    current_binders: &mut MatchStruct,
182    next: &SExpr,
183) -> Result<(), syn::Error> {
184    next.binders().binders.into_iter().try_for_each(|binder| {
185        let message = format!("duplicate binder {binder}");
186        if current_binders.binders.insert(binder) {
187            Ok(())
188        } else {
189            Err(input.error(message))
190        }
191    })
192}
193
194impl ToTokens for SExpr {
195    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
196        match self {
197            Self::Many(sexpr, match_struct) => {
198                let binders = match_struct.binders.clone().into_iter();
199                let binders1 = match_struct.binders.clone().into_iter();
200                let token = quote! {
201                    let res = s.fold_to_syntax_list::<Self, String>(
202                        &mut |s, mut current| {
203                            let mut this = Self::default();
204                            #sexpr;
205                            #(  current.#binders = crate::ast::Ast::Pair(Box::new(crate::ast::Pair(this.#binders, current.#binders))); )*
206                            Ok(current)
207                        },
208                        Self::default()
209                    )?;
210
211                    #(  this.#binders1 = res.#binders1; )*
212                };
213                tokens.append_all(token);
214            }
215            Self::ManyOne(sexpr, match_struct) => {
216                let sexpr_string = format!("{sexpr}");
217                let binders = match_struct.binders.clone().into_iter();
218                let binders1 = match_struct.binders.clone().into_iter();
219                let token = quote! {
220                    let sexpr = #sexpr_string;
221                    let error = format!("expected at least one of {sexpr} {s}");
222                    let res = s.fold_to_syntax_list::<(usize, Self), String>(
223                        &mut |s, (i, mut current)| {
224                            let next_i = if s == crate::ast::Ast::TheEmptyList { 0 } else  { 1 } + i;
225                            let mut this = Self::default();
226                            #sexpr;
227                            #(  current.#binders = crate::ast::Ast::Pair(Box::new(Pair(this.#binders, current.#binders))); )*
228                            Ok((next_i, current))
229                        },
230                        (0, Self::default())
231                    )?;
232
233                    if res.0 == 0 {
234                        return Err(error)
235                    }
236                    #(  this.#binders1 = res.1.#binders1; )*
237                };
238                tokens.append_all(token);
239            }
240            Self::Symbol(ident) => {
241                let token = quote! {
242                    this.#ident = s;
243                };
244                tokens.append_all(token);
245            }
246            Self::Identifier(ident) => {
247                let ident_string = ident.to_string();
248                let token = quote! {
249                    let ident = #ident_string;
250                    if !s.identifier() {
251                        return Err(format!("not an identifier {s} when matching identifier: {ident}"))
252                    }
253                    this.#ident = s;
254                };
255                tokens.append_all(token);
256            }
257            Self::Empty => {
258                let token = quote! {
259                    let s = if let Ast::Syntax(s) = s { s.0 } else { s };
260                    if s != crate::ast::Ast::TheEmptyList {
261                        return Err(format!("bad syntax expected expected null {s}"))
262                    }
263                };
264                tokens.append_all(token);
265            }
266            Self::Pair {
267                car,
268                cdr,
269                binders: _,
270            } => {
271                let this_string = self.to_string();
272                let token = quote! {
273                    let s = if let Ast::Syntax(s) = s { s.0 } else { s };
274                    let this_string = #this_string;
275                    if let crate::ast::Ast::Pair(p) = s {
276                        let crate::ast::Pair(car, cdr) = *p;
277                        {
278                            let s = car;
279                            #car
280                        }
281                        {
282                            let s = cdr;
283                            #cdr
284                        }
285                    } else {
286                        let this_string = #this_string;
287                        return Err(format!("not a pair {s} when matching pair: {this_string}"))
288                    }
289                };
290                tokens.append_all(token);
291            }
292        }
293    }
294}
295#[proc_macro]
296pub fn match_syntax_as(input: TokenStream) -> TokenStream {
297    let input = parse_macro_input!(input as NameAsSExpr);
298    let name = input.0;
299    let input = input.1;
300    let binders = input.binders().binders.into_iter();
301    let binders1 = input.binders().binders.into_iter();
302    quote! {
303        #[derive(Clone)]
304        struct #name {
305            #(  #binders: crate::ast::Ast, )*
306        }
307        impl Default for #name {
308            fn default() -> Self {
309                Self {
310                    #(  #binders1: crate::ast::Ast::TheEmptyList, )*
311                }
312            }
313        }
314        impl #name {
315            fn matches(s: Ast) -> Result<Self, String> {
316                let mut this = Self::default();
317                #input
318                Ok(this)
319            }
320        }
321        // TODO: somehow just return the type (#name), but doesn't seem to be usable in a type context
322
323    }
324    .into()
325}
326#[proc_macro]
327pub fn match_syntax(input: TokenStream) -> TokenStream {
328    let input = parse_macro_input!(input as SExpr);
329    let binders = input.binders().binders.into_iter();
330    let binders1 = input.binders().binders.into_iter();
331    let name = syn::Ident::new(&format!("Matcher{}", random::<u64>()), input.span());
332    quote! {
333        {
334            #[derive(Clone)]
335            struct #name {
336                #(  #binders: crate::ast::Ast, )*
337            }
338            impl Default for #name {
339                fn default() -> Self {
340                    Self {
341                        #(  #binders1: crate::ast::Ast::TheEmptyList, )*
342                    }
343                }
344            }
345            impl #name {
346                fn matches(s: Ast) -> Result<Self, String> {
347                    let mut this = Self::default();
348                    #input
349                    Ok(this)
350                }
351            }
352            // TODO: somehow just return the type (#name), but doesn't seem to be usable in a type context
353            (#name::matches)
354        }
355    }
356    .into()
357}