fairyvoid_with/
lib.rs

1#![feature(proc_macro_diagnostic)]
2
3use proc_macro::TokenStream;
4use quote::{quote, ToTokens};
5use syn::parse::{Parse, ParseStream, Result};
6use syn::{parse_macro_input, Ident, Token, Expr};
7
8struct With {
9    fields: Vec<WithField>,
10    over: Option<Expr>,
11}
12
13struct WithField {
14    name: Ident,
15    init: Option<Expr>,
16}
17
18impl Parse for With {
19    fn parse(input: ParseStream) -> Result<Self> {
20        let mut fields = vec![];
21        let mut over: Option<Expr> = None;
22        let mut found_default = false;
23
24        while !input.is_empty() {
25            if input.peek(Token![..]) {
26                input.parse::<Token![..]>()?;
27                if !input.is_empty() {
28                    over = Some(input.parse::<Expr>()?);
29                }
30                found_default = true;
31                break;
32            }
33            let name = input.parse::<Ident>()?;
34            let init: Option<Expr>;
35            if input.peek(Token![:]) {
36                input.parse::<Token![:]>()?;
37                init = Some(input.parse::<Expr>()?);
38            } else {
39                init = None;
40            }
41            fields.push(WithField { name, init });
42
43            if input.peek(Token![,]) {
44                input.parse::<Token![,]>()?;
45            } else {
46                break;
47            }
48        }
49        if !found_default {
50            if !fields.is_empty() {
51                input.parse::<Token![,]>()?;
52            }
53            input.parse::<Token![..]>()?;
54        }
55        Ok(With { fields, over })
56    }
57}
58
59/// Initializes a structure without specifying its name.
60/// 
61/// # Syntax
62/// 
63/// ```
64/// with! {
65///     field_1: value,
66///     field_2,
67///     .. // ..Default::default()
68/// }
69///
70///
71/// with! {
72///     field_1: value,
73///     field_2,
74///     ..base
75/// }
76/// ```
77#[proc_macro]
78pub fn with(input: TokenStream) -> TokenStream {
79    let With { fields, over } = parse_macro_input!(input as With);
80
81    let mut expanded_fields = proc_macro2::TokenStream::new();
82
83    for WithField { name, init } in fields {
84        if let Some(init) = init {
85            expanded_fields.extend::<proc_macro2::TokenStream>(quote! {
86                _with_obj_1.#name = #init;
87            }.try_into().unwrap());
88        } else {
89            expanded_fields.extend::<proc_macro2::TokenStream>(quote! {
90                _with_obj_1.#name = #name;
91            }.try_into().unwrap());
92        }
93    }
94
95    let expanded_over = over.map(|over| over.into_token_stream()).unwrap_or(quote! {::std::default::Default::default()});
96
97    let mut expanded = TokenStream::new();
98
99    expanded.extend::<TokenStream>(quote! {
100        {
101            let mut _with_obj_1 = #expanded_over;
102            if false {
103                _with_obj_1
104            } else {
105                #expanded_fields
106                _with_obj_1
107            }
108        }
109    }.try_into().unwrap());
110
111    expanded
112}