1mod analyse;
2mod codegen;
3mod lower;
4mod parse;
5
6use proc_macro2::TokenStream;
7use syn::{DeriveInput, Error, Ident, Meta, Token, Type, parenthesized, punctuated::Punctuated};
8
9use self::{analyse::*, codegen::*, lower::*, parse::*};
10
11#[proc_macro_derive(Cuisiner, attributes(cuisiner))]
12pub fn derive_cuisiner(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
13 let derive_input = syn::parse_macro_input!(ts as DeriveInput);
15
16 match derive_cuisiner_inner(derive_input) {
18 Ok(ts) => ts,
19 Result::Err(e) => e.into_compile_error(),
20 }
21 .into()
22}
23
24fn derive_cuisiner_inner(derive_input: DeriveInput) -> Result<TokenStream, Error> {
27 let ast = parse(derive_input)?;
28 let model = analyse(ast)?;
29 let ir = lower(model)?;
30 codegen(ir)
31}
32
33#[derive(Clone)]
35enum Fields {
36 Named(Vec<(Ident, Type, Option<Vec<Meta>>)>),
38 Unnamed(Vec<(Type, Option<Vec<Meta>>)>),
40 Unit,
42}
43
44impl TryFrom<&syn::Fields> for Fields {
45 type Error = Error;
46
47 fn try_from(fields: &syn::Fields) -> Result<Self, Self::Error> {
48 Ok(match fields {
49 syn::Fields::Named(fields_named) => Fields::Named(
50 fields_named
51 .named
52 .iter()
53 .map(|field| {
54 let ident = field
55 .ident
56 .clone()
57 .expect("named struct field must have ident");
58 let ty = field.ty.clone();
59
60 let mut assert_layout = None;
61 for attr in &field.attrs {
62 if !attr.path().is_ident("cuisiner") {
63 continue;
64 }
65
66 attr.parse_nested_meta(|meta| {
67 if meta.path.is_ident("assert") {
68 let args;
70 parenthesized!(args in meta.input);
71
72 assert_layout = Some(
74 Punctuated::<Meta, Token![,]>::parse_terminated(&args)?
75 .into_iter()
76 .collect(),
77 );
78
79 return Ok(());
80 }
81
82 Err(Error::new_spanned(&meta.path, "unknown attribute"))
83 })?;
84 }
85
86 Ok((ident, ty, assert_layout))
87 })
88 .collect::<Result<_, Error>>()?,
89 ),
90 syn::Fields::Unnamed(fields_unnamed) => Fields::Unnamed(
91 fields_unnamed
92 .unnamed
93 .iter()
94 .map(|field| {
95 Ok((field.ty.clone(), {
96 let mut assert_layout = None;
97 for attr in &field.attrs {
98 if !attr.path().is_ident("cuisiner") {
99 continue;
100 }
101
102 attr.parse_nested_meta(|meta| {
103 if meta.path.is_ident("assert") {
104 let args;
106 parenthesized!(args in meta.input);
107
108 assert_layout = Some(
110 Punctuated::<Meta, Token![,]>::parse_terminated(&args)?
111 .into_iter()
112 .collect(),
113 );
114
115 return Ok(());
116 }
117
118 Err(Error::new_spanned(&meta.path, "unknown attribute"))
119 })?;
120 }
121 assert_layout
122 }))
123 })
124 .collect::<Result<_, Error>>()?,
125 ),
126 syn::Fields::Unit => Fields::Unit,
127 })
128 }
129}