extern crate proc_macro;
use self::proc_macro::TokenStream;
use syn::parse_macro_input;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use quote::ToTokens;
#[proc_macro_derive(SlaveProcessImage, attributes(pdos, watchdog, dc, entry, no_arrays))]
pub fn derive_single_process_image(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput);
let ident = input.ident;
let id_str = ident.to_string();
let slave_id = if id_str.starts_with("EK") {
let nr = id_str[2..6].parse::<u32>().unwrap();
quote!(ethercat::SlaveId { vendor_id: 2, product_code: (#nr << 16) | 0x2c52 })
} else if id_str.starts_with("EL") {
let nr = id_str[2..6].parse::<u32>().unwrap();
quote!(ethercat::SlaveId { vendor_id: 2, product_code: (#nr << 16) | 0x3052 })
} else {
panic!("cannot interpret struct name '{}' into a slave ID", id_str);
};
let mut sync_infos = vec![];
let mut wd_config = quote!(None);
let mut dc_config = quote!(None);
let mut pdo_regs = vec![];
let mut running_size = 0usize;
let mut pdo_mapping = std::collections::HashMap::new();
if let syn::Data::Struct(syn::DataStruct {
fields: syn::Fields::Named(flds), ..
}) = input.data {
for field in flds.named {
let ty = field.ty.into_token_stream().to_string();
let bitlen = match &*ty {
"u8" | "i8" => 8,
"u16" | "i16" => 16,
"u32" | "i32" | "f32" => 32,
"u64" | "i64" | "f64" => 64,
_ => panic!("cannot handle type '{}' in image", ty)
};
for attr in &field.attrs {
if attr.path.is_ident("entry") {
if let syn::Meta::List(syn::MetaList { nested, .. }) =
attr.parse_meta().unwrap()
{
let (pdo_str, ix, subix) = if nested.len() == 2 {
("".into(), &nested[0], &nested[1])
} else {
let pdo = &nested[0];
(quote!(#pdo).to_string(), &nested[1], &nested[2])
};
pdo_regs.push(quote! {
(ethercat::PdoEntryIdx { idx: ethercat::Idx::from(#ix),
sub_idx: ethercat::SubIdx::from(#subix) },
ethercat::Offset { byte: #running_size, bit: 0 })
});
pdo_mapping.entry(pdo_str).or_insert_with(Vec::new).push(quote! {
ethercat::PdoEntryInfo {
entry_idx: ethercat::PdoEntryIdx {
idx: ethercat::Idx::from(#ix),
sub_idx: ethercat::SubIdx::from(#subix)
},
bit_len: #bitlen as u8,
name: String::new(),
pos: ethercat::PdoEntryPos::from(0), }
});
}
}
}
running_size += bitlen / 8;
}
} else {
panic!("SlaveProcessImage must be a struct with named fields");
}
let mut no_arrays = false;
for attr in &input.attrs {
if attr.path.is_ident("no_arrays") {
no_arrays = true;
} else if attr.path.is_ident("pdos") {
if let syn::Meta::List(syn::MetaList { nested, .. }) =
attr.parse_meta().unwrap()
{
let sm = &nested[0];
let sd = &nested[1];
let mut pdos = vec![];
for pdo_index in nested.iter().skip(2) {
let pdo_str = quote!(#pdo_index).to_string();
let entries = &pdo_mapping.get(&pdo_str).map_or(&[][..], |v| v);
pdos.push(quote! {
ethercat::PdoCfg {
idx: ethercat::PdoIdx::from(#pdo_index),
entries: vec![#( #entries ),*]
}
})
}
sync_infos.push(quote! {
(
ethercat::SmCfg {
idx: ethercat::SmIdx::from(#sm),
direction: ethercat::SyncDirection::#sd,
watchdog_mode: ethercat::WatchdogMode::Default,
},
vec![#( #pdos ),*]
)
});
}
} else if attr.path.is_ident("watchdog") {
if let syn::Meta::List(syn::MetaList { nested, .. }) =
attr.parse_meta().unwrap()
{
let a = &nested[0];
let b = &nested[1];
wd_config = quote!( Some((#a, #b)) );
}
} else if attr.path.is_ident("dc") {
if let syn::Meta::List(syn::MetaList { nested, .. }) =
attr.parse_meta().unwrap()
{
let a = &nested[0];
let b = &nested[1];
let c = &nested[2];
let d = &nested[3];
let e = &nested[4];
dc_config = quote!( Some((#a, #b, #c, #d, #e)) );
}
}
}
let sync_infos = if sync_infos.is_empty() {
quote!(None)
} else {
quote!(Some(vec![#( #sync_infos ),*]))
};
let mut generated = quote! {
#[automatically_derived]
impl ProcessImage for #ident {
const SLAVE_COUNT: usize = 1;
fn get_slave_ids() -> Vec<ethercat::SlaveId> { vec![#slave_id] }
fn get_slave_pdos() -> Vec<Option<Vec<(ethercat::SmCfg, Vec<ethercat::PdoCfg>)>>> {
vec![#sync_infos]
}
fn get_slave_regs() -> Vec<Vec<(ethercat::PdoEntryIdx, ethercat::Offset)>> {
vec![vec![ #( #pdo_regs ),* ]]
}
fn get_slave_wd_dc() -> Vec<(Option<(u16, u16)>, Option<(u16, u32, i32, u32, i32)>)> {
vec![(#wd_config, #dc_config)]
}
}
};
if !no_arrays {
generated = quote! {
#generated
macro_rules! impl_it {
($n:literal) => {
impl ProcessImage for [#ident; $n] {
const SLAVE_COUNT: usize = $n;
fn get_slave_ids() -> Vec<ethercat::SlaveId> { vec![#slave_id; $n] }
fn get_slave_pdos() -> Vec<Option<Vec<(ethercat::SmCfg, Vec<ethercat::PdoCfg>)>>> {
vec![#sync_infos; $n]
}
fn get_slave_regs() -> Vec<Vec<(ethercat::PdoEntryIdx, ethercat::Offset)>> {
vec![vec![ #( #pdo_regs ),* ]; $n]
}
fn get_slave_wd_dc() -> Vec<(Option<(u16, u16)>, Option<(u16, u32, i32, u32, i32)>)> {
vec![(#wd_config, #dc_config); $n]
}
}
};
}
impl_it!(2);
impl_it!(3);
impl_it!(4);
impl_it!(5);
impl_it!(6);
impl_it!(7);
impl_it!(8);
};
}
generated.into()
}
fn sdo_extract(ix: &syn::NestedMeta, subix: &syn::NestedMeta, val: &syn::NestedMeta,
sdos: &mut Vec<TokenStream2>) {
match val {
syn::NestedMeta::Lit(syn::Lit::Str(s)) => {
let data_str = syn::parse_str::<syn::Expr>(&s.value()).unwrap();
sdos.push(quote! {
(ethercat::SdoIdx { idx: ethercat::Idx::from(#ix),
sub_idx: ethercat::SubIdx::from(#subix) },
&#data_str)
});
}
syn::NestedMeta::Meta(syn::Meta::Path(p)) => {
sdos.push(quote! {
(ethercat::SdoIdx { idx: ethercat::Idx::from(#ix),
sub_idx: ethercat::SubIdx::from(#subix) },
{
match cfg.get_sdo_var(stringify!(#p)) {
None => panic!(concat!("required config value ",
stringify!(#p), " not given")),
Some(x) => x
}
})
});
}
_ => panic!("invalid SDO value, must be a string or identifier"),
};
}
#[proc_macro_derive(ProcessImage, attributes(slave_id, sdo, array_sdo))]
pub fn derive_process_image(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput);
let ident = input.ident;
let mut slave_sdos = vec![];
let mut slave_tys = vec![];
let mut slave_ids = vec![];
if let syn::Data::Struct(syn::DataStruct {
fields: syn::Fields::Named(flds), ..
}) = input.data {
for field in flds.named {
let mut single_sdos = vec![];
let mut array_sdos = vec![];
let mut id = None;
for attr in &field.attrs {
if attr.path.is_ident("sdo") {
if let syn::Meta::List(syn::MetaList { nested, .. }) =
attr.parse_meta().unwrap()
{
sdo_extract(&nested[0], &nested[1], &nested[2], &mut single_sdos);
}
} else if attr.path.is_ident("array_sdo") {
if let syn::Meta::List(syn::MetaList { nested, .. }) =
attr.parse_meta().unwrap()
{
let ix: usize = match &nested[0] {
syn::NestedMeta::Lit(syn::Lit::Int(lit)) => lit.base10_parse().unwrap(),
_ => panic!("invalid sdo_array index")
};
if array_sdos.len() < ix + 1 {
array_sdos.resize(ix + 1, vec![]);
}
sdo_extract(&nested[1], &nested[2], &nested[3], &mut array_sdos[ix]);
}
} else if attr.path.is_ident("slave_id") {
if let syn::Meta::List(syn::MetaList { nested, .. }) =
attr.parse_meta().unwrap()
{
if let syn::NestedMeta::Lit(syn::Lit::Int(p)) = &nested[0] {
id = Some(quote!(
vec![ethercat::SlaveId { vendor_id: 2, product_code: (#p << 16) | 0x3052 }]
));
}
}
}
}
let ty = field.ty;
if !array_sdos.is_empty() {
for single in array_sdos {
slave_sdos.push(quote!( res.push(vec![#( #single ),*]); ));
}
} else if !single_sdos.is_empty() {
slave_sdos.push(quote!( res.push(vec![#( #single_sdos ),*]); ));
} else {
slave_sdos.push(quote!( res.extend(#ty::get_slave_sdos(&())); ));
}
let id = id.unwrap_or(quote!( <#ty>::get_slave_ids() ));
slave_tys.push(ty);
slave_ids.push(id);
}
} else {
return compile_error("only structs with named fields can be a process image");
}
let generated = quote! {
#[automatically_derived]
impl ProcessImage for #ident {
const SLAVE_COUNT: usize = #( <#slave_tys>::SLAVE_COUNT )+*;
fn get_slave_ids() -> Vec<ethercat::SlaveId> {
let mut res = vec![]; #( res.extend(#slave_ids); )* res
}
fn get_slave_pdos() -> Vec<Option<Vec<(ethercat::SmCfg, Vec<ethercat::PdoCfg>)>>> {
let mut res = vec![]; #( res.extend(<#slave_tys>::get_slave_pdos()); )* res
}
fn get_slave_regs() -> Vec<Vec<(ethercat::PdoEntryIdx, ethercat::Offset)>> {
let mut res = vec![]; #( res.extend(<#slave_tys>::get_slave_regs()); )* res
}
fn get_slave_wd_dc() -> Vec<(Option<(u16, u16)>, Option<(u16, u32, i32, u32, i32)>)> {
let mut res = vec![]; #( res.extend(<#slave_tys>::get_slave_wd_dc()); )* res
}
fn get_slave_sdos<C: ethercat_plc::ProcessConfig>(cfg: &C) ->
Vec<Vec<(ethercat::SdoIdx, &dyn ethercat::SdoData)>>
{
let mut res = vec![]; #(#slave_sdos)* res
}
}
};
generated.into()
}
#[proc_macro_derive(ExternImage, attributes(plc))]
pub fn derive_extern_image(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput);
let ident = input.ident;
let generated = quote! {
impl ExternImage for #ident {}
};
generated.into()
}
fn compile_error(message: impl Into<String>) -> TokenStream {
let message = message.into();
quote!(compile_error! { #message }).into()
}