1use 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 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 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 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::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(()) }
554 }
555 }.into()
556}