Skip to main content

derive_attr_parser/internals/
parse.rs

1use std::collections::HashMap;
2
3use proc_macro2::Span;
4use syn::meta::ParseNestedMeta;
5use syn::punctuated::Punctuated;
6use syn::{token, Attribute, DeriveInput, Error, Token};
7
8use crate::internals::ast::{Container, Data, Field, Style, Symbol, Val, Variant};
9use crate::internals::ctxt::Ctxt;
10
11// The attr should keep simple as following supported literal
12// you can process string val as you want after extract the meta from attr,
13// NOTE: everything in val should treated as string.
14// supported:
15// #[sim(ode_solver = "eula")] V
16// #[sim(ode_solver(algo = "eula", steps = "10"))] V
17// #[sim(ode_solver(algorithms = r#"["eula", "newton", "test"]"#))] V
18// #[sim(ode_solver(algo = r#"{algo:"eula", steps:10}"#))] V
19// unsupported:
20// #[sim(ode_solver{algo:"eula", steps:"10"})] X
21// #[sim(ode_solver={algo:"eula", steps:"10"})] X
22// #[sim(ode_solver(ty=String, steps = 10))] X
23// #[sim(ode_solver("eula", "10"))] X
24// #[sim(ode_solver["eula", "modified_newton"))] X
25// #[sim(ode_solver=["eula", "modified_newton"))] X
26pub fn from_ast<'a>(
27    cx: &Ctxt,
28    input: &'a DeriveInput,
29    root: Symbol,
30) -> Result<Container<'a>, Error> {
31    container_from_ast(cx, input, root)
32}
33
34fn container_from_ast<'a>(
35    cx: &Ctxt,
36    input: &'a DeriveInput,
37    root: Symbol,
38) -> Result<Container<'a>, Error> {
39    let attrs = parse_attrs(cx, &input.attrs, root)?;
40    let res = data_from_ast(cx, &input, root);
41    if let Some(data) = res {
42        //eprintln!("{root} {attrs:#?}");
43        let item = Container {
44            ident: input.ident.clone(),
45            attrs,
46            data,
47            generics: &input.generics,
48            original: &input,
49        };
50        Ok(item)
51    } else {
52        Err(Error::new(
53            Span::call_site(),
54            "Data is none#container_from_ast",
55        ))
56    }
57}
58
59fn parse_sub_attrs(cx: &Ctxt, meta: &ParseNestedMeta) -> syn::Result<HashMap<String, Val>> {
60    let lookahead = meta.input.lookahead1();
61    let mut attrs = HashMap::new();
62    if let Some(ident) = meta.path.get_ident() {
63        let key = ident.to_string();
64        // #[sim(ode_solver = "eula")]
65        if lookahead.peek(Token![=]) {
66            attrs.insert(key, get_val_str(&meta)?);
67        } else if lookahead.peek(token::Paren) {
68            // #[sim(ode_solver(algo = "eula", steps = "10"))]
69            let mut all_sub_attrs = HashMap::new();
70            if let Err(err) = meta.parse_nested_meta(|m| {
71                merge_map(cx, parse_sub_attrs(cx, &m)?, &mut all_sub_attrs);
72                Ok(())
73            }) {
74                cx.syn_error(err);
75            }
76            attrs.insert(key, Val::Map(all_sub_attrs));
77        } else if lookahead.peek(Token![:]) {
78            attrs.insert(key, get_val_str(&meta)?);
79        } else {
80            attrs.insert(key, Val::Empty);
81        }
82    } else {
83        let msg = format!("no ident found #parse_sub_attrs");
84        let err = Error::new(Span::call_site(), msg);
85        cx.syn_error(err);
86    }
87
88    Ok(attrs)
89}
90
91fn get_val_str(meta: &ParseNestedMeta) -> syn::Result<Val> {
92    if let Err(eq) = meta.input.parse::<Token![=]>() {
93        if let Err(_ec) = meta.input.parse::<Token![:]>() {
94            let ident = meta.path.get_ident();
95            let msg = format!("expect either '=' or ':' after ident {ident:?} #get_val_str");
96            let err = Error::new(eq.span(), msg);
97            return Err(err);
98        }
99    }
100    let expr: syn::Expr = meta.input.parse()?;
101    let mut value = &expr;
102    while let syn::Expr::Group(e) = value {
103        value = &e.expr;
104    }
105    if let syn::Expr::Lit(syn::ExprLit {
106        lit: syn::Lit::Str(lit),
107        ..
108    }) = value
109    {
110        let suffix = lit.suffix();
111        if !suffix.is_empty() {}
112        Ok(Val::Str(lit.clone().value()))
113    } else {
114        Ok(Val::Str("".to_string()))
115    }
116}
117
118fn data_from_ast<'a>(cx: &Ctxt, input: &'a DeriveInput, root: Symbol) -> Option<Data<'a>> {
119    let data = match &input.data {
120        syn::Data::Enum(data) => Data::Enum(enum_from_ast(cx, &data.variants, root)),
121        syn::Data::Struct(data) => {
122            let (style, fields) = struct_from_ast(cx, &data.fields, root);
123            Data::Struct(style, fields)
124        }
125        syn::Data::Union(_) => {
126            let msg = format!("Does not support derive for unions#data_from_ast");
127            cx.error_spanned_by(input, &msg);
128            return None;
129        }
130    };
131
132    Some(data)
133}
134
135fn struct_from_ast<'a>(
136    cx: &Ctxt,
137    fields: &'a syn::Fields,
138    root: Symbol,
139) -> (Style, Vec<Field<'a>>) {
140    match fields {
141        syn::Fields::Named(fields) => (Style::Struct, fields_from_ast(cx, &fields.named, root)),
142        syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
143            (Style::Newtype, fields_from_ast(cx, &fields.unnamed, root))
144        }
145        syn::Fields::Unnamed(fields) => (Style::Tuple, fields_from_ast(cx, &fields.unnamed, root)),
146        syn::Fields::Unit => (Style::Unit, Vec::new()),
147    }
148}
149
150fn fields_from_ast<'a>(
151    cx: &Ctxt,
152    fields: &'a Punctuated<syn::Field, Token![,]>,
153    root: Symbol,
154) -> Vec<Field<'a>> {
155    fields
156        .iter()
157        .enumerate()
158        .map(|(i, field)| Field {
159            member: match &field.ident {
160                Some(ident) => syn::Member::Named(ident.clone()),
161                None => syn::Member::Unnamed(i.into()),
162            },
163            attrs: filed_from_ast(cx, i, field, root),
164            ty: &field.ty,
165            original: field,
166        })
167        .collect()
168}
169
170fn parse_attrs(
171    cx: &Ctxt,
172    attrs: &Vec<Attribute>,
173    root: Symbol,
174) -> syn::Result<HashMap<String, Val>> {
175    let mut all = HashMap::new();
176    for attr in attrs {
177        if attr.path() != root {
178            continue;
179        }
180        if let syn::Meta::List(meta) = &attr.meta {
181            if meta.tokens.is_empty() {
182                continue;
183            }
184        }
185        let mut attrs = HashMap::new();
186        if let Err(err) = attr.parse_nested_meta(|meta| {
187            // 解析子 attr
188            let sub_attrs = parse_sub_attrs(cx, &meta);
189            merge_map(cx, sub_attrs?, &mut attrs);
190            Ok(())
191        }) {
192            cx.syn_error(err);
193        }
194        merge_map(cx, attrs, &mut all)
195    }
196
197    // eprintln!("{root} {all:#?}");
198    Ok(all)
199}
200
201fn filed_from_ast(
202    cx: &Ctxt,
203    _index: usize,
204    field: &syn::Field,
205    root: Symbol,
206) -> HashMap<String, Val> {
207    match parse_attrs(cx, &field.attrs, root) {
208        Ok(m) => m,
209        Err(e) => {
210            cx.error_spanned_by(field, e);
211            HashMap::new()
212        }
213    }
214}
215
216fn variant_from_ast(cx: &Ctxt, variant: &syn::Variant, root: Symbol) -> HashMap<String, Val> {
217    match parse_attrs(cx, &variant.attrs, root) {
218        Ok(map) => map,
219        Err(e) => {
220            cx.syn_error(e);
221            HashMap::new()
222        }
223    }
224}
225
226fn enum_from_ast<'a>(
227    cx: &Ctxt,
228    variants: &'a Punctuated<syn::Variant, Token![,]>,
229    root: Symbol,
230) -> Vec<Variant<'a>> {
231    let variants: Vec<Variant> = variants
232        .iter()
233        .map(|variant| {
234            let attrs = variant_from_ast(cx, variant, root);
235            let (style, fields) = struct_from_ast(cx, &variant.fields, root);
236            Variant {
237                ident: variant.ident.clone(),
238                attrs,
239                style,
240                fields,
241                original: variant,
242            }
243        })
244        .collect();
245    variants
246}
247
248fn merge_map(cx: &Ctxt, from: HashMap<String, Val>, to: &mut HashMap<String, Val>) {
249    for (k, v) in from {
250        if to.get(&k).is_some() {
251            let msg = format!("duplicated key {{{k}}}#merge_map");
252            cx.syn_error(Error::new(Span::call_site(), msg));
253        } else {
254            to.insert(k, v);
255        }
256    }
257}