ethercat_derive/
lib.rs

1// Part of ethercat-rs. Copyright 2018-2023 by the authors.
2// This work is dual-licensed under Apache 2.0 and MIT terms.
3
4//! Support for deriving ethercat-plc traits for a struct.
5
6extern crate proc_macro;  // needed even in 2018
7
8use self::proc_macro::TokenStream;
9use syn::parse_macro_input;
10use proc_macro2::TokenStream as TokenStream2;
11use quote::quote;
12use quote::ToTokens;
13
14
15#[proc_macro_derive(SlaveProcessImage, attributes(pdos, watchdog, dc, entry, no_arrays))]
16pub fn derive_single_process_image(input: TokenStream) -> TokenStream {
17    let input = parse_macro_input!(input as syn::DeriveInput);
18    let ident = input.ident;
19
20    let id_str = ident.to_string();
21    let slave_id = if id_str.starts_with("EK") {
22        let nr = id_str[2..6].parse::<u32>().unwrap();
23        quote!(ethercat::SlaveId { vendor_id: 2, product_code: (#nr << 16) | 0x2c52 })
24    } else if id_str.starts_with("EL") {
25        let nr = id_str[2..6].parse::<u32>().unwrap();
26        quote!(ethercat::SlaveId { vendor_id: 2, product_code: (#nr << 16) | 0x3052 })
27    } else {
28        panic!("cannot interpret struct name '{}' into a slave ID", id_str);
29    };
30
31    let mut sync_infos = vec![];
32    let mut wd_config = quote!(None);
33    let mut dc_config = quote!(None);
34    let mut pdo_regs = vec![];
35    let mut running_size = 0usize;
36    let mut pdo_mapping = std::collections::HashMap::new();
37
38    if let syn::Data::Struct(syn::DataStruct {
39        fields: syn::Fields::Named(flds), ..
40    }) = input.data {
41        for field in flds.named {
42            let ty = field.ty.into_token_stream().to_string();
43            let bitlen = match &*ty {
44                "u8"  | "i8"  => 8,
45                "u16" | "i16" => 16,
46                "u32" | "i32" | "f32" => 32,
47                "u64" | "i64" | "f64" => 64,
48                _ => panic!("cannot handle type '{}' in image", ty)
49            };
50            for attr in &field.attrs {
51                if attr.path.is_ident("entry") {
52                    if let syn::Meta::List(syn::MetaList { nested, .. }) =
53                        attr.parse_meta().unwrap()
54                    {
55                        let (pdo_str, ix, subix) = if nested.len() == 2 {
56                            ("".into(), &nested[0], &nested[1])
57                        } else {
58                            let pdo = &nested[0];
59                            (quote!(#pdo).to_string(), &nested[1], &nested[2])
60                        };
61                        pdo_regs.push(quote! {
62                            (ethercat::PdoEntryIdx { idx: ethercat::Idx::from(#ix),
63                                                     sub_idx: ethercat::SubIdx::from(#subix) },
64                             ethercat::Offset { byte: #running_size, bit: 0 })
65                        });
66                        pdo_mapping.entry(pdo_str).or_insert_with(Vec::new).push(quote! {
67                            ethercat::PdoEntryInfo {
68                                entry_idx: ethercat::PdoEntryIdx {
69                                    idx: ethercat::Idx::from(#ix),
70                                    sub_idx: ethercat::SubIdx::from(#subix)
71                                },
72                                bit_len: #bitlen as u8,
73                                name: String::new(),
74                                pos: ethercat::PdoEntryPos::from(0),  // unused
75                            }
76                        });
77                    }
78                }
79            }
80            running_size += bitlen / 8;
81        }
82    } else {
83        panic!("SlaveProcessImage must be a struct with named fields");
84    }
85
86    let mut no_arrays = false;
87    for attr in &input.attrs {
88        if attr.path.is_ident("no_arrays") {
89            no_arrays = true;
90        } else if attr.path.is_ident("pdos") {
91            if let syn::Meta::List(syn::MetaList { nested, .. }) =
92                attr.parse_meta().unwrap()
93            {
94                let sm = &nested[0];
95                let sd = &nested[1];
96                let mut pdos = vec![];
97                for pdo_index in nested.iter().skip(2) {
98                    let pdo_str = quote!(#pdo_index).to_string();
99                    let entries = &pdo_mapping.get(&pdo_str).map_or(&[][..], |v| v);
100                    pdos.push(quote! {
101                        ethercat::PdoCfg {
102                            idx: ethercat::PdoIdx::from(#pdo_index),
103                            entries: vec![#( #entries ),*]
104                        }
105                    })
106                }
107                sync_infos.push(quote! {
108                    (
109                        ethercat::SmCfg {
110                            idx: ethercat::SmIdx::from(#sm),
111                            direction: ethercat::SyncDirection::#sd,
112                            watchdog_mode: ethercat::WatchdogMode::Default,
113                        },
114                        vec![#( #pdos ),*]
115                    )
116                });
117            }
118        } else if attr.path.is_ident("watchdog") {
119            if let syn::Meta::List(syn::MetaList { nested, .. }) =
120                attr.parse_meta().unwrap()
121            {
122                let a = &nested[0];
123                let b = &nested[1];
124                wd_config = quote!( Some((#a, #b)) );
125            }
126        } else if attr.path.is_ident("dc") {
127            if let syn::Meta::List(syn::MetaList { nested, .. }) =
128                attr.parse_meta().unwrap()
129            {
130                let a = &nested[0];
131                let b = &nested[1];
132                let c = &nested[2];
133                let d = &nested[3];
134                let e = &nested[4];
135                dc_config = quote!( Some((#a, #b, #c, #d, #e)) );
136            }
137        }
138    }
139
140    let sync_infos = if sync_infos.is_empty() {
141        quote!(None)
142    } else {
143        quote!(Some(vec![#( #sync_infos ),*]))
144    };
145
146    let mut generated = quote! {
147        #[automatically_derived]
148        impl ProcessImage for #ident {
149            const SLAVE_COUNT: usize = 1;
150            fn get_slave_ids() -> Vec<ethercat::SlaveId> { vec![#slave_id] }
151            fn get_slave_pdos() -> Vec<Option<Vec<(ethercat::SmCfg, Vec<ethercat::PdoCfg>)>>> {
152                vec![#sync_infos]
153            }
154            fn get_slave_regs() -> Vec<Vec<(ethercat::PdoEntryIdx, ethercat::Offset)>> {
155                vec![vec![ #( #pdo_regs ),* ]]
156            }
157            fn get_slave_wd_dc() -> Vec<(Option<(u16, u16)>, Option<(u16, u32, i32, u32, i32)>)> {
158                vec![(#wd_config, #dc_config)]
159            }
160        }
161    };
162
163    if !no_arrays {
164        generated = quote! {
165            #generated
166
167            macro_rules! impl_it {
168                ($n:literal) => {
169                    impl ProcessImage for [#ident; $n] {
170                        const SLAVE_COUNT: usize = $n;
171                        fn get_slave_ids() -> Vec<ethercat::SlaveId> { vec![#slave_id; $n] }
172                        fn get_slave_pdos() -> Vec<Option<Vec<(ethercat::SmCfg, Vec<ethercat::PdoCfg>)>>> {
173                            vec![#sync_infos; $n]
174                        }
175                        fn get_slave_regs() -> Vec<Vec<(ethercat::PdoEntryIdx, ethercat::Offset)>> {
176                            vec![vec![ #( #pdo_regs ),* ]; $n]
177                        }
178                        fn get_slave_wd_dc() -> Vec<(Option<(u16, u16)>, Option<(u16, u32, i32, u32, i32)>)> {
179                            vec![(#wd_config, #dc_config); $n]
180                        }
181                    }
182                };
183            }
184
185            impl_it!(2);
186            impl_it!(3);
187            impl_it!(4);
188            impl_it!(5);
189            impl_it!(6);
190            impl_it!(7);
191            impl_it!(8);
192        };
193    }
194
195    // println!("{}", generated);
196    generated.into()
197}
198
199
200fn sdo_extract(ix: &syn::NestedMeta, subix: &syn::NestedMeta, val: &syn::NestedMeta,
201               sdos: &mut Vec<TokenStream2>) {
202    match val {
203        syn::NestedMeta::Lit(syn::Lit::Str(s)) => {
204            let data_str = syn::parse_str::<syn::Expr>(&s.value()).unwrap();
205            sdos.push(quote! {
206                (ethercat::SdoIdx { idx: ethercat::Idx::from(#ix),
207                                    sub_idx: ethercat::SubIdx::from(#subix) },
208                 &#data_str)
209            });
210        }
211        syn::NestedMeta::Meta(syn::Meta::Path(p)) => {
212            sdos.push(quote! {
213                (ethercat::SdoIdx { idx: ethercat::Idx::from(#ix),
214                                    sub_idx: ethercat::SubIdx::from(#subix) },
215                 {
216                     match cfg.get_sdo_var(stringify!(#p)) {
217                         None => panic!(concat!("required config value ",
218                                                stringify!(#p), " not given")),
219                         Some(x) => x
220                     }
221                 })
222            });
223        }
224        _ => panic!("invalid SDO value, must be a string or identifier"),
225    };
226}
227
228
229#[proc_macro_derive(ProcessImage, attributes(slave_id, sdo, array_sdo))]
230pub fn derive_process_image(input: TokenStream) -> TokenStream {
231    let input = parse_macro_input!(input as syn::DeriveInput);
232    let ident = input.ident;
233
234    let mut slave_sdos = vec![];
235    let mut slave_tys = vec![];
236    let mut slave_ids = vec![];
237
238    if let syn::Data::Struct(syn::DataStruct {
239        fields: syn::Fields::Named(flds), ..
240    }) = input.data {
241        for field in flds.named {
242            let mut single_sdos = vec![];
243            let mut array_sdos = vec![];
244            let mut id = None;
245            for attr in &field.attrs {
246                if attr.path.is_ident("sdo") {
247                    if let syn::Meta::List(syn::MetaList { nested, .. }) =
248                        attr.parse_meta().unwrap()
249                    {
250                        sdo_extract(&nested[0], &nested[1], &nested[2], &mut single_sdos);
251                    }
252                } else if attr.path.is_ident("array_sdo") {
253                    if let syn::Meta::List(syn::MetaList { nested, .. }) =
254                        attr.parse_meta().unwrap()
255                    {
256                        let ix: usize = match &nested[0] {
257                            syn::NestedMeta::Lit(syn::Lit::Int(lit)) => lit.base10_parse().unwrap(),
258                            _ => panic!("invalid sdo_array index")
259                        };
260                        if array_sdos.len() < ix + 1 {
261                            array_sdos.resize(ix + 1, vec![]);
262                        }
263                        sdo_extract(&nested[1], &nested[2], &nested[3], &mut array_sdos[ix]);
264                    }
265                } else if attr.path.is_ident("slave_id") {
266                    if let syn::Meta::List(syn::MetaList { nested, .. }) =
267                        attr.parse_meta().unwrap()
268                    {
269                        if let syn::NestedMeta::Lit(syn::Lit::Int(p)) = &nested[0] {
270                            id = Some(quote!(
271                                vec![ethercat::SlaveId { vendor_id: 2, product_code: (#p << 16) | 0x3052 }]
272                            ));
273                        }
274                    }
275                }
276            }
277            let ty = field.ty;
278            if !array_sdos.is_empty() {
279                for single in array_sdos {
280                    slave_sdos.push(quote!( res.push(vec![#( #single ),*]); ));
281                }
282            } else if !single_sdos.is_empty() {
283                slave_sdos.push(quote!( res.push(vec![#( #single_sdos ),*]); ));
284            } else {
285                slave_sdos.push(quote!( res.extend(#ty::get_slave_sdos(&())); ));
286            }
287            let id = id.unwrap_or(quote!( <#ty>::get_slave_ids() ));
288            slave_tys.push(ty);
289            slave_ids.push(id);
290        }
291    } else {
292        return compile_error("only structs with named fields can be a process image");
293    }
294
295    let generated = quote! {
296        #[automatically_derived]
297        impl ProcessImage for #ident {
298            const SLAVE_COUNT: usize = #( <#slave_tys>::SLAVE_COUNT )+*;
299            fn get_slave_ids() -> Vec<ethercat::SlaveId> {
300                let mut res = vec![]; #( res.extend(#slave_ids); )* res
301            }
302            fn get_slave_pdos() -> Vec<Option<Vec<(ethercat::SmCfg, Vec<ethercat::PdoCfg>)>>> {
303                let mut res = vec![]; #( res.extend(<#slave_tys>::get_slave_pdos()); )* res
304            }
305            fn get_slave_regs() -> Vec<Vec<(ethercat::PdoEntryIdx, ethercat::Offset)>> {
306                let mut res = vec![]; #( res.extend(<#slave_tys>::get_slave_regs()); )* res
307            }
308            fn get_slave_wd_dc() -> Vec<(Option<(u16, u16)>, Option<(u16, u32, i32, u32, i32)>)> {
309                let mut res = vec![]; #( res.extend(<#slave_tys>::get_slave_wd_dc()); )* res
310            }
311            fn get_slave_sdos<C: ethercat_plc::ProcessConfig>(cfg: &C) ->
312                Vec<Vec<(ethercat::SdoIdx, &dyn ethercat::SdoData)>>
313            {
314                let mut res = vec![]; #(#slave_sdos)* res
315            }
316        }
317    };
318
319    // println!("{}", generated);
320    generated.into()
321}
322
323#[proc_macro_derive(ExternImage, attributes(plc))]
324pub fn derive_extern_image(input: TokenStream) -> TokenStream {
325    let input = parse_macro_input!(input as syn::DeriveInput);
326    let ident = input.ident;
327
328    // currently a no-op, later: auto-generate Default from #[plc] attributes
329    let generated = quote! {
330        impl ExternImage for #ident {}
331    };
332    generated.into()
333}
334
335fn compile_error(message: impl Into<String>) -> TokenStream {
336    let message = message.into();
337    quote!(compile_error! { #message }).into()
338}