usecop_derive/
lib.rs

1// -----------------------------------------------------------------------------
2// Rust SECoP playground
3//
4// This program is free software; you can redistribute it and/or modify it under
5// the terms of the GNU General Public License as published by the Free Software
6// Foundation; either version 2 of the License, or (at your option) any later
7// version.
8//
9// This program is distributed in the hope that it will be useful, but WITHOUT
10// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11// FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
12// details.
13//
14// You should have received a copy of the GNU General Public License along with
15// this program; if not, write to the Free Software Foundation, Inc.,
16// 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17//
18// Module authors:
19//   Georg Brandl <g.brandl@fz-juelich.de>
20//
21// -----------------------------------------------------------------------------
22
23use syn::*;
24use quote::{ToTokens, quote, format_ident};
25use proc_macro2::TokenStream;
26use darling::{FromMeta, FromDeriveInput};
27
28
29#[derive(FromDeriveInput, Debug)]
30#[darling(attributes(secop), supports(struct_named))]
31struct Module {
32    #[darling(multiple)]
33    interface: Vec<String>,
34    #[darling(multiple)]
35    feature: Vec<String>,
36    #[darling(multiple)]
37    param: Vec<Param>,
38    #[darling(multiple)]
39    command: Vec<Command>,
40}
41
42#[derive(FromMeta, Debug)]
43struct Param {
44    name: String,
45    doc: String,
46    datainfo: DataInfo,
47    readonly: bool,
48    group: Option<String>,
49    // internal
50    polling: Option<bool>,
51    generate_accessors: Option<bool>,
52}
53
54#[derive(FromMeta, Debug)]
55struct Command {
56    name: String,
57    doc: String,
58    argument: DataInfo,
59    result: DataInfo,
60    group: Option<String>,
61}
62
63#[derive(FromMeta, Debug)]
64enum DataInfo {
65    Null {},
66    Bool {},
67    Double { min: Option<f64>, max: Option<f64>,
68             unit: Option<String>, fmtstr: Option<String>,
69             abs_res: Option<f64>, rel_res: Option<f64> },
70    Scaled { scale: f64, min: i64, max: i64,
71             unit: Option<String>, fmtstr: Option<String>,
72             abs_res: Option<f64>, rel_res: Option<f64> },
73    Int { min: i64, max: i64 },
74    Str { minchars: Option<usize>, maxchars: usize, is_utf8: Option<bool> },
75    Blob { minbytes: Option<usize>, maxbytes: usize },
76    Enum_ { #[darling(multiple)] member: Vec<EnumMember> },
77    Array { minlen: usize, maxlen: usize, members: Box<DataInfo> },
78    Tuple { #[darling(multiple)] member: Vec<DataInfo> },
79    Struct { #[darling(multiple)] member: Vec<StructMember> },
80    Rust(String),
81}
82
83fn optionize<T: ToTokens>(val: &Option<T>) -> TokenStream {
84    match *val {
85        None => quote!(None),
86        Some(ref v) => quote!(Some(#v)),
87    }
88}
89
90impl ToTokens for DataInfo {
91    fn to_tokens(&self, tokens: &mut TokenStream) {
92        match self {
93            DataInfo::Null {} => quote!(usecop::DataInfo::Null),
94            DataInfo::Bool {} => quote!(usecop::DataInfo::Bool),
95            DataInfo::Double { min, max, unit, fmtstr, abs_res, rel_res } => {
96                let min = optionize(min);
97                let max = optionize(max);
98                let unit = optionize(unit);
99                let fmtstr = optionize(fmtstr);
100                let abs_res = optionize(abs_res);
101                let rel_res = optionize(rel_res);
102                quote!(usecop::DataInfo::Double {
103                    min: #min, max: #max, unit: #unit, fmtstr: #fmtstr,
104                    abs_res: #abs_res, rel_res: #rel_res
105                })
106            }
107            DataInfo::Scaled { scale, min, max, unit, fmtstr, abs_res, rel_res } => {
108                let unit = optionize(unit);
109                let fmtstr = optionize(fmtstr);
110                let abs_res = optionize(abs_res);
111                let rel_res = optionize(rel_res);
112                quote!(usecop::DataInfo::Scaled {
113                    scale: #scale, min: #min, max: #max, unit: #unit, fmtstr: #fmtstr,
114                    abs_res: #abs_res, rel_res: #rel_res
115                })
116            }
117            DataInfo::Int { min, max } => quote!(usecop::DataInfo::Int {
118                min: #min, max: #max
119            }),
120            DataInfo::Str { minchars, maxchars, is_utf8 } => {
121                let minchars = optionize(minchars);
122                let is_utf8 = is_utf8 == &Some(true);
123                quote!(usecop::DataInfo::Str {
124                    minchars: #minchars, maxchars: #maxchars, is_utf8: #is_utf8
125                })
126            }
127            DataInfo::Blob { minbytes, maxbytes } => {
128                let minbytes = optionize(minbytes);
129                quote!(usecop::DataInfo::Blob {
130                    minbytes: #minbytes, maxbytes: #maxbytes
131                })
132            }
133            DataInfo::Enum_ { member } => {
134                let mut members = vec![];
135                for m in member {
136                    let EnumMember { name, value } = m;
137                    members.push(quote!((#name, #value)));
138                }
139                quote!(usecop::DataInfo::Enum {
140                    members: &[#(#members),*],
141                })
142            }
143            DataInfo::Array { minlen, maxlen, members } => quote!(usecop::DataInfo::Array {
144                minlen: #minlen, maxlen: #maxlen, members: &#members
145            }),
146            DataInfo::Tuple { member } => {
147                let mut members = vec![];
148                for m in member {
149                    members.push(quote!(#m));
150                }
151                quote!(usecop::DataInfo::Tuple {
152                    members: &[#(#members),*],
153                })
154            }
155            DataInfo::Struct { member } => {
156                let mut members = vec![];
157                for m in member {
158                    let StructMember { name, datainfo } = m;
159                    members.push(quote!((#name, #datainfo)));
160                }
161                quote!(usecop::DataInfo::Struct {
162                    members: &[#(#members),*],
163                })
164            }
165            DataInfo::Rust(name) => {
166                let ident = format_ident!("{}", name);
167                quote!(<#ident as usecop::datamodel::CustomDataInfo>::DATAINFO)
168            }
169        }.to_tokens(tokens)
170    }
171}
172
173impl DataInfo {
174    fn rust_type(&self) -> TokenStream {
175        match self {
176            DataInfo::Null {} => quote!(()),
177            DataInfo::Bool {} => quote!(bool),
178            DataInfo::Double { .. } => quote!(f64),
179            DataInfo::Scaled { .. } => quote!(i64),
180            DataInfo::Int { .. } => quote!(i64),
181            DataInfo::Enum_ { .. } => quote!(i32),
182            DataInfo::Tuple { member } => {
183                let mut members = vec![];
184                for m in member {
185                    members.push(m.rust_type());
186                }
187                quote!((#(#members),*))
188            }
189            DataInfo::Rust(name) => {
190                let ident = format_ident!("{}", name);
191                quote!(#ident)
192            }
193            DataInfo::Str { .. } => panic!("can't represent Str"),
194            DataInfo::Blob { .. } => panic!("can't represent Blob"),
195            DataInfo::Array { .. } => panic!("can't represent Array"),
196            DataInfo::Struct { .. } => panic!("can't represent Struct"),
197        }
198    }
199}
200
201#[derive(FromMeta, Debug)]
202struct EnumMember { name: String, value: i32 }
203
204#[derive(FromMeta, Debug)]
205struct StructMember { name: String, datainfo: DataInfo }
206
207#[proc_macro_derive(Module, attributes(secop))]
208pub fn derive_module(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
209    let input = parse_macro_input!(input as DeriveInput);
210    let mut attrs = Module::from_derive_input(&input).unwrap();
211
212    attrs.param.push(Param {
213        name: "pollinterval".to_string(),
214        doc: "polling interval in seconds".to_string(),
215        datainfo: DataInfo::Double {
216            min: Some(0.1), max: Some(3600.0), unit: Some("s".to_string()),
217            fmtstr: None, abs_res: None, rel_res: None,
218        },
219        readonly: false,
220        group: None,
221        polling: Some(false),
222        generate_accessors: None,
223    });
224
225    let Module { interface, feature, param, command } = &attrs;
226
227    let mut datainfos = vec![];
228    let mut accessibles = vec![];
229    for param in param {
230        let Param { name, doc, readonly, datainfo, group, .. } = param;
231        let group = optionize(group);
232        let di_name = format_ident!("DATAINFO_P_{}", name.to_uppercase());
233        accessibles.push(quote! {
234            (#name, usecop::AccessibleDescription {
235                description: #doc,
236                readonly: #readonly,
237                datainfo: &#di_name,
238                group: #group,
239            })
240        });
241        datainfos.push(quote! {
242            const #di_name: usecop::DataInfo = #datainfo;
243        });
244    }
245    for command in command {
246        let Command { name, doc, argument, result, group } = command;
247        let group = optionize(group);
248        let di_arg_name = format_ident!("DATAINFO_CA_{}", name.to_uppercase());
249        let di_res_name = format_ident!("DATAINFO_CR_{}", name.to_uppercase());
250        accessibles.push(quote! {
251            (#name, usecop::AccessibleDescription {
252                description: #doc,
253                readonly: false,
254                datainfo: &usecop::DataInfo::Command {
255                    argument: &#di_arg_name,
256                    result: &#di_res_name,
257                },
258                group: #group,
259            })
260        });
261        datainfos.push(quote! {
262            const #di_arg_name: usecop::DataInfo = #argument;
263            const #di_res_name: usecop::DataInfo = #result;
264        });
265    }
266
267    let mut read_impl = vec![];
268    let mut change_impl = vec![];
269    let mut poll_impl = vec![];
270    let mut accessors = vec![];
271    for param in param {
272        let Param { name, datainfo, readonly, generate_accessors, polling, .. } = param;
273        let ident = format_ident!("{}", name);
274        let read_method = format_ident!("read_{}", name);
275        let write_method = format_ident!("write_{}", name);
276        let di_name = format_ident!("DATAINFO_P_{}", name.to_uppercase());
277
278        read_impl.push(quote! {
279            Some(#name) => {
280                match self.#read_method() {
281                    // TODO: check return val?
282                    Ok(value) => reply.send(
283                        usecop::proto::OutMsg::Reply { spec, value, time }),
284                    Err(e) => reply.send(e.spec_msg(usecop::wire::READ, spec)),
285                }
286            }
287        });
288
289        let regular_poll = polling != &Some(false) && generate_accessors != &Some(true);
290        poll_impl.push(quote! {
291            if #regular_poll || all {
292                match self.#read_method() {
293                    Ok(value) => reply.distribute(usecop::proto::OutMsg::Update {
294                        spec: usecop::proto::Specifier::new(name, #name), value, time
295                    }),
296                    Err(e) => reply.distribute(e.spec_msg(usecop::wire::UPDATE,
297                                                          usecop::proto::Specifier::new(name, #name))),
298                }
299            }
300        });
301
302        if *readonly {
303            change_impl.push(quote! {
304                Some(#name) => {
305                    return reply.send(usecop::Error::read_only()
306                                      .spec_msg(usecop::wire::CHANGE, spec))
307                }
308            });
309        } else {
310            change_impl.push(quote! {
311                Some(#name) => {
312                    let val = match usecop::FromJson::from_json(value, &#di_name) {
313                        Ok(value) => value,
314                        Err(e) => return reply.send(e.spec_msg(usecop::wire::CHANGE, spec)),
315                    };
316                    match self.#write_method(val) {
317                        Ok(value) => {
318                            // TODO: check return val?
319                            reply.distribute(usecop::proto::OutMsg::Update {
320                                spec: spec.clone(), value, time });
321                            reply.send(usecop::proto::OutMsg::Changed { spec, value, time })
322                        }
323                        Err(e) => reply.send(e.spec_msg(usecop::wire::CHANGE, spec)),
324                    }
325                }
326            });
327        }
328
329        if generate_accessors == &Some(true) {
330            let ptype = datainfo.rust_type();
331            accessors.push(quote! {
332                fn #read_method(&mut self) -> usecop::Result<#ptype> {
333                    Ok(self.#ident)
334                }
335                fn #write_method(&mut self, val: #ptype) -> usecop::Result<#ptype> {
336                    self.#ident = val;
337                    Ok(val)
338                }
339            });
340        }
341    }
342
343    let mut do_impl = vec![];
344    for command in command {
345        let Command { name, .. } = command;
346        let do_method = format_ident!("do_{}", name);
347        let di_arg_name = format_ident!("DATAINFO_CA_{}", name.to_uppercase());
348        let di_res_name = format_ident!("DATAINFO_CR_{}", name.to_uppercase());
349        do_impl.push(quote! {
350            Some(#name) => {
351                let val = match usecop::FromJson::from_json(arg, &#di_arg_name) {
352                    Ok(value) => value,
353                    Err(e) => return reply.send(e.spec_msg(usecop::wire::DO, spec)),
354                };
355                let res = match self.#do_method(val) {
356                    Ok(value) => value,
357                    Err(e) => return reply.send(e.spec_msg(usecop::wire::DO, spec)),
358                };
359                match usecop::ToJson::check(&res, &#di_res_name) {
360                    Ok(_) => reply.send(usecop::proto::OutMsg::Done { spec, value: res, time }),
361                    Err(e) => reply.send(e.spec_msg(usecop::wire::DO, spec)),
362                }
363            }
364        });
365    }
366
367    let name = &input.ident;
368    quote! {
369        impl #name {
370            fn read_pollinterval(&mut self) -> usecop::Result<f64> {
371                Ok(self.internals.pollinterval)
372            }
373            fn write_pollinterval(&mut self, val: f64) -> usecop::Result<f64> {
374                self.internals.pollinterval = val;
375                Ok(self.internals.pollinterval)
376            }
377
378            #(#accessors)*
379        }
380
381        const _: () = {
382
383            #(#datainfos)*
384
385            impl usecop::Module for #name {
386                fn describe(&self) -> usecop::ModuleDescription {
387                    usecop::ModuleDescription {
388                        description: self.internals.description,
389                        implementation: "microSECoP",
390                        interface_classes: &[#(#interface),*],
391                        features: &[#(#feature),*],
392                        accessibles: &[#(#accessibles),*],
393                    }
394                }
395
396                fn read(&mut self, time: usecop::proto::Timestamp, spec: usecop::proto::Specifier,
397                        mut reply: usecop::node::Sender) {
398                    match &spec.param {
399                        #(#read_impl)*
400                        _ => reply.send(usecop::Error::no_param().spec_msg(usecop::wire::READ, spec))
401                    }
402                }
403
404                fn change(&mut self, time: usecop::proto::Timestamp, spec: usecop::proto::Specifier,
405                          value: &mut str, mut reply: usecop::node::Sender) {
406                    match &spec.param {
407                        #(#change_impl)*
408                        _ => reply.send(usecop::Error::no_param().spec_msg(usecop::wire::CHANGE, spec))
409                    }
410                }
411
412                fn do_(&mut self, time: usecop::proto::Timestamp, spec: usecop::proto::Specifier,
413                       arg: &mut str, mut reply: usecop::node::Sender) {
414                    match &spec.param {
415                        #(#do_impl)*
416                        _ => reply.send(usecop::Error::no_command().spec_msg(usecop::wire::DO, spec))
417                    }
418                }
419
420                fn poll(&mut self, time: usecop::proto::Timestamp, name: &str, all: bool,
421                        reply: &mut usecop::node::Sender) {
422                    if all || time.any() >= self.internals.lastpoll + self.internals.pollinterval {
423                        self.internals.lastpoll = time.any();
424                        #(#poll_impl)*
425                    }
426                }
427            }
428        };
429    }.into()
430}
431
432
433#[proc_macro_derive(Modules)]
434pub fn derive_modules(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
435    let input = parse_macro_input!(input as DeriveInput);
436    let name = &input.ident;
437
438    let fields = match &input.data {
439        Data::Struct(DataStruct { fields: Fields::Named(n), .. }) => &n.named,
440        _ => panic!("Modules can only be derived for structs with named fields")
441    };
442
443    let count = fields.len();
444    let by_name = fields.iter().map(|field| {
445        let ident = field.ident.as_ref().unwrap();
446        let name = ident.to_string();
447        quote! { if name == #name { return Some(&mut self.#ident); } }
448    }).collect::<Vec<_>>();
449    let for_each = fields.iter().map(|field| {
450        let ident = field.ident.as_ref().unwrap();
451        let name = ident.to_string();
452        quote! { f(#name, &mut self.#ident); }
453    }).collect::<Vec<_>>();
454    let describe = fields.iter().map(|field| {
455        let ident = field.ident.as_ref().unwrap();
456        let name = ident.to_string();
457        quote! { (#name, usecop::Module::describe(&self.#ident)) }
458    }).collect::<Vec<_>>();
459
460    quote! {
461        impl usecop::Modules for #name {
462            fn count(&self) -> usize {
463                #count
464            }
465
466            fn by_name(&mut self, name: &str) -> Option<&mut dyn usecop::Module> {
467                #(#by_name)*
468                None
469            }
470
471            fn for_each(&mut self, mut f: impl FnMut(&str, &mut dyn usecop::Module)) {
472                #(#for_each)*
473            }
474
475            fn describe(&self, eq_id: &str, desc: &str, mut sender: usecop::node::Sender) {
476                let msg: usecop::proto::OutMsg<()> = usecop::proto::OutMsg::Describing {
477                    id: ".",
478                    structure: usecop::NodeDescription {
479                        equipment_id: eq_id,
480                        description: desc,
481                        firmware: "microSECoP",
482                        version: "2021.02",
483                        interface: "tcp://10767",
484                        modules: &[#(#describe),*],
485                    }
486                };
487                sender.send(msg);
488            }
489        }
490    }.into()
491}
492
493#[proc_macro_derive(DataInfo, attributes(secop))]
494pub fn derive_datainfo(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
495    let input = parse_macro_input!(input as DeriveInput);
496    let name = &input.ident;
497
498    match &input.data {
499        // Data::Struct(DataStruct { fields: Fields::Named(n), .. }) =>
500        //     derive_datainfo_struct(name, n.named.iter()),
501        Data::Enum(DataEnum { variants: v, .. }) if is_plain_enum(v.iter()) =>
502            derive_datainfo_enum(name, v.iter()),
503        _ => panic!("DataInfo can only be derived for structs with named fields \
504                     or plain enums")
505    }
506}
507
508fn is_plain_enum<'a>(variants: impl Iterator<Item=&'a Variant>) -> bool {
509    for v in variants {
510        if !matches!(v.fields, Fields::Unit) || v.discriminant.is_none() {
511            return false;
512        }
513    }
514    true
515}
516
517fn derive_datainfo_enum<'a>(name: &Ident, variants: impl Iterator<Item=&'a Variant>) -> proc_macro::TokenStream {
518    let mut members = vec![];
519    let mut matches = vec![];
520    for v in variants {
521        let ident = &v.ident;
522        let string = v.ident.to_string();
523        let value = match &v.discriminant {
524            Some((_, Expr::Lit(ExprLit { lit: Lit::Int(i), .. }))) => i.base10_parse::<i32>().unwrap(),
525            _ => panic!("Enum variants must have integer discriminants"),
526        };
527        members.push(quote!((#string, #value)));
528        matches.push(quote!(#value => Ok(#name :: #ident),));
529    }
530    quote! {
531        impl usecop::datamodel::CustomDataInfo for #name {
532            const DATAINFO: usecop::DataInfo<'static> = usecop::DataInfo::Enum {
533                members: &[#(#members),*],
534            };
535        }
536
537        impl usecop::FromJson<'_> for #name {
538            fn from_json<'a>(json: &mut str, _: &usecop::DataInfo<'a>) -> usecop::Result<'a, Self> {
539                let val = json.parse::<i32>().map_err(|_| usecop::Error::bad_value("expected int"))?;
540                match val {
541                    #(#matches)*
542                    _ => Err(usecop::Error::bad_value("invalid enum value")),
543                }
544            }
545        }
546
547        impl usecop::ToJson for #name {
548            fn to_json(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
549                write!(f, "{}", *self as i32)
550            }
551            fn check<'a>(&self, _: &usecop::DataInfo<'a>) -> usecop::Result<'a, ()> {
552                Ok(())  // Can't ever be a wrong value.
553            }
554        }
555    }.into()
556}