1extern crate proc_macro; use 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), }
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 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 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 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}