1use std::rc::Rc;
6
7use proc_macro::TokenStream;
8use proc_macro_error2::{abort, proc_macro_error};
9use quote::{format_ident, quote};
10use syn::punctuated::Punctuated;
11use syn::spanned::Spanned;
12use syn::token::Comma;
13use syn::{Attribute, Expr, Ident, ImplItemFn, ItemImpl, Lit, Path, Type, parse_macro_input};
14
15mod tree;
16
17use microscpi_common::Command;
18use tree::Tree;
19
20#[derive(Clone)]
26enum CommandHandler {
27 UserFunction(Ident),
29 StandardFunction(&'static str),
31}
32
33impl CommandHandler {
34 fn span(&self) -> proc_macro2::Span {
36 match self {
37 CommandHandler::UserFunction(ident) => ident.span(),
38 CommandHandler::StandardFunction(_) => proc_macro2::Span::call_site(),
39 }
40 }
41}
42
43#[derive(Default)]
45struct Config {
46 pub error_commands: bool,
48 pub standard_commands: bool,
50 pub status_commands: bool,
52}
53
54#[derive(Clone)]
56struct CommandDefinition {
57 pub id: Option<usize>,
59 pub command: Command,
61 pub handler: CommandHandler,
63 pub args: Vec<Type>,
65 pub future: bool,
67}
68
69impl CommandDefinition {
70 fn args(&self) -> Punctuated<Expr, Comma> {
75 self.args
76 .iter()
77 .enumerate()
78 .map(|(id, _arg)| -> Expr {
79 syn::parse_quote! {
80 args.get(#id).unwrap().try_into()?
81 }
82 })
83 .collect()
84 }
85
86 fn call(&self) -> proc_macro2::TokenStream {
87 let command_id = self.id;
88 let arg_count = self.args.len();
89 let args = self.args();
90
91 let fn_call = match &self.handler {
92 CommandHandler::UserFunction(ident) => {
93 let func = ident.clone();
94 quote! { self.#func(#args) }
95 }
96 CommandHandler::StandardFunction(path) => {
97 let path: Path = syn::parse(path.parse().unwrap()).unwrap();
98 quote! { ::microscpi::#path(self, #args) }
99 }
100 };
101
102 let fn_call = if self.future {
103 quote! { #fn_call.await? }
104 } else {
105 quote! { #fn_call? }
106 };
107
108 quote! {
109 #command_id => {
110 if args.len() != #arg_count {
111 Err(::microscpi::Error::UnexpectedNumberOfParameters)
112 }
113 else {
114 let result = #fn_call;
115 result.write_response(response)
116 }
117 }
118 }
119 }
120}
121
122impl CommandDefinition {
123 fn parse(func: &ImplItemFn, attr: &Attribute) -> syn::Result<CommandDefinition> {
135 let mut cmd: Option<String> = None;
136
137 attr.parse_nested_meta(|meta| {
138 if meta.path.is_ident("cmd") {
139 if let Lit::Str(name) = meta.value()?.parse()? {
140 cmd = Some(name.value());
141 Ok(())
142 } else {
143 abort!(
144 meta.path.span(),
145 "SCPI command name must be a string literal"
146 )
147 }
148 } else {
149 Ok(())
150 }
151 })?;
152
153 let args = func
155 .sig
156 .inputs
157 .iter()
158 .filter_map(|arg| match arg {
159 syn::FnArg::Typed(arg_type) => Some(*arg_type.ty.clone()),
160 syn::FnArg::Receiver(_) => None,
161 })
162 .collect();
163
164 if let Some(cmd) = cmd {
165 Ok(CommandDefinition {
166 id: None,
167 command: Command::try_from(cmd.as_str())
168 .map_err(|_| syn::Error::new(attr.span(), "Invalid SCPI command syntax"))?,
169 handler: CommandHandler::UserFunction(func.sig.ident.to_owned()),
170 args,
171 future: func.sig.asyncness.is_some(),
172 })
173 } else {
174 abort!(
175 attr.span(),
176 "Missing `cmd` attribute in SCPI command. Expected: #[scpi(cmd = \"COMMAND:NAME\")]"
177 )
178 }
179 }
180}
181
182struct CommandSet {
183 commands: Vec<Rc<CommandDefinition>>,
184}
185
186impl CommandSet {
187 pub fn new() -> Self {
188 Self {
189 commands: Vec::new(),
190 }
191 }
192
193 pub fn push(&mut self, mut command: CommandDefinition) {
194 command.id = Some(self.commands.len());
195 self.commands.push(Rc::new(command));
196 }
197
198 fn extract_commands(&mut self, input: &mut ItemImpl) -> Result<(), syn::Error> {
214 for item in input.items.iter_mut() {
215 if let syn::ImplItem::Fn(item_fn) = item {
216 let mut idx = 0;
220 while idx < item_fn.attrs.len() {
221 if item_fn.attrs[idx].path().is_ident("scpi") {
222 let attr = item_fn.attrs.remove(idx);
223 self.push(CommandDefinition::parse(item_fn, &attr)?);
224 } else {
225 idx += 1;
226 }
227 }
228 }
229 }
230 Ok(())
231 }
232}
233
234impl AsRef<[Rc<CommandDefinition>]> for CommandSet {
235 fn as_ref(&self) -> &[Rc<CommandDefinition>] {
236 self.commands.as_ref()
237 }
238}
239
240#[proc_macro_error]
266#[proc_macro_attribute]
267pub fn interface(attr: TokenStream, item: TokenStream) -> TokenStream {
268 let attrs: Punctuated<Path, Comma> = parse_macro_input!(attr with Punctuated::parse_terminated);
269 let mut input_impl = parse_macro_input!(item as ItemImpl);
270
271 let mut config = Config::default();
272
273 for attr in attrs {
275 if attr.is_ident("ErrorCommands") {
276 config.error_commands = true;
277 } else if attr.is_ident("StandardCommands") {
278 config.standard_commands = true;
279 } else if attr.is_ident("StatusCommands") {
280 config.status_commands = true;
281 } else {
282 abort!(attr.span(), "Unknown SCPI interface option.");
283 }
284 }
285
286 let mut command_set = CommandSet::new();
287
288 if config.standard_commands {
289 command_set.push(CommandDefinition {
290 id: None,
291 args: Vec::new(),
292 command: Command::try_from("SYSTem:VERSion?").unwrap(),
293 handler: CommandHandler::StandardFunction("StandardCommands::system_version"),
294 future: false,
295 });
296 }
297
298 if config.error_commands {
299 command_set.push(CommandDefinition {
300 id: None,
301 args: Vec::new(),
302 command: Command::try_from("SYSTem:ERRor:[NEXT]?").unwrap(),
303 handler: CommandHandler::StandardFunction("ErrorCommands::system_error_next"),
304 future: false,
305 });
306
307 command_set.push(CommandDefinition {
308 id: None,
309 args: Vec::new(),
310 command: Command::try_from("SYSTem:ERRor:COUNt?").unwrap(),
311 handler: CommandHandler::StandardFunction("ErrorCommands::system_error_count"),
312 future: false,
313 });
314 }
315
316 if config.status_commands {
317 command_set.push(CommandDefinition {
318 id: None,
319 args: Vec::new(),
320 command: Command::try_from("*OPC").unwrap(),
321 handler: CommandHandler::StandardFunction("StatusCommands::set_operation_complete"),
322 future: false,
323 });
324
325 command_set.push(CommandDefinition {
326 id: None,
327 args: Vec::new(),
328 command: Command::try_from("*OPC?").unwrap(),
329 handler: CommandHandler::StandardFunction("StatusCommands::operation_complete"),
330 future: false,
331 });
332
333 command_set.push(CommandDefinition {
334 id: None,
335 args: Vec::new(),
336 command: Command::try_from("*CLS").unwrap(),
337 handler: CommandHandler::StandardFunction("StatusCommands::clear_event_status"),
338 future: false,
339 });
340
341 command_set.push(CommandDefinition {
342 id: None,
343 args: Vec::new(),
344 command: Command::try_from("*ESE?").unwrap(),
345 handler: CommandHandler::StandardFunction("StatusCommands::event_status_enable"),
346 future: false,
347 });
348
349 command_set.push(CommandDefinition {
350 id: None,
351 args: vec![Type::Verbatim(quote! { u8 })],
352 command: Command::try_from("*ESE").unwrap(),
353 handler: CommandHandler::StandardFunction("StatusCommands::set_event_status_enable"),
354 future: false,
355 });
356
357 command_set.push(CommandDefinition {
358 id: None,
359 args: Vec::new(),
360 command: Command::try_from("*ESR?").unwrap(),
361 handler: CommandHandler::StandardFunction("StatusCommands::event_status_register"),
362 future: false,
363 });
364
365 command_set.push(CommandDefinition {
366 id: None,
367 args: Vec::new(),
368 command: Command::try_from("*STB?").unwrap(),
369 handler: CommandHandler::StandardFunction("StatusCommands::status_byte"),
370 future: false,
371 });
372
373 command_set.push(CommandDefinition {
374 id: None,
375 args: Vec::new(),
376 command: Command::try_from("*SRE?").unwrap(),
377 handler: CommandHandler::StandardFunction("StatusCommands::status_byte_enable"),
378 future: false,
379 });
380
381 command_set.push(CommandDefinition {
382 id: None,
383 args: vec![Type::Verbatim(quote! { u8 })],
384 command: Command::try_from("*SRE").unwrap(),
385 handler: CommandHandler::StandardFunction("StatusCommands::set_status_byte_enable"),
386 future: false,
387 });
388 }
389
390 if let Err(error) = command_set.extract_commands(&mut input_impl) {
392 return error.into_compile_error().into();
393 }
394
395 let mut tree = Tree::new();
396
397 for cmd in command_set.as_ref().iter() {
399 if let Err(error) = tree.insert(cmd.clone()) {
400 abort!(
401 cmd.handler.span(),
402 "Failed to register SCPI command '{}': {}",
403 cmd.command.canonical_path(),
404 error
405 )
406 }
407 }
408
409 let command_items: Vec<proc_macro2::TokenStream> =
410 command_set.as_ref().iter().map(|cmd| cmd.call()).collect();
411
412 let mut nodes: Vec<proc_macro2::TokenStream> = Vec::new();
413
414 for (node_id, cmd_node) in tree.items {
415 let node_name = format_ident!("SCPI_NODE_{}", node_id);
416
417 let entries = cmd_node.children.iter().map(|(name, node_id)| {
418 let reference = format_ident!("SCPI_NODE_{}", node_id);
419 quote!((#name, &#reference))
420 });
421
422 let command = if let Some(command_id) = cmd_node.command.map(|cmd_def| cmd_def.id) {
423 quote! { Some(#command_id) }
424 } else {
425 quote! { None }
426 };
427 let query = if let Some(command_id) = cmd_node.query.map(|cmd_def| cmd_def.id) {
428 quote! { Some(#command_id) }
429 } else {
430 quote! { None }
431 };
432
433 let node_item = quote! {
434 static #node_name: ::microscpi::Node = ::microscpi::Node {
435 children: &[
436 #(#entries),*
437 ],
438 command: #command,
439 query: #query
440 };
441 };
442
443 nodes.push(node_item);
444 }
445
446 let impl_ty = input_impl.self_ty.clone();
447
448 let mut interface_impl: ItemImpl = syn::parse_quote! {
449 impl ::microscpi::Interface for #impl_ty {
450 fn root_node(&self) -> &'static ::microscpi::Node {
451 &SCPI_NODE_0
452 }
453 async fn execute_command<'a>(
454 &'a mut self,
455 command_id: ::microscpi::CommandId,
456 args: &[::microscpi::Value<'a>],
457 response: &mut impl ::microscpi::Write
458 ) -> Result<(), ::microscpi::Error> {
459 use ::microscpi::Response;
460 match command_id {
461 #(#command_items),*,
462 _ => Err(::microscpi::Error::UndefinedHeader)
463 }
464 }
465 }
466 };
467
468 interface_impl.generics = input_impl.generics.clone();
470
471 quote! {
472 #(#nodes)*
473 #input_impl
474 #interface_impl
475 }
476 .into()
477}