1use proc_macro::TokenStream;
21use quote::{quote, ToTokens};
22use syn::{parse_macro_input, parse_quote, Attribute, Error, Item, ItemImpl, LitStr, TraitItem};
23use zenoh_keyexpr::{
24 format::{
25 macro_support::{self, SegmentBuilder},
26 KeFormat,
27 },
28 key_expr::keyexpr,
29};
30
31const RUSTC_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/version.rs"));
32
33#[proc_macro]
34pub fn rustc_version_release(_tokens: TokenStream) -> TokenStream {
35 let release = RUSTC_VERSION
36 .split('\n')
37 .filter_map(|l| {
38 let line = l.trim();
39 if line.is_empty() {
40 None
41 } else {
42 Some(line)
43 }
44 })
45 .find_map(|l| l.strip_prefix("release: "))
46 .unwrap();
47 let commit = RUSTC_VERSION
48 .split('\n')
49 .filter_map(|l| {
50 let line = l.trim();
51 if line.is_empty() {
52 None
53 } else {
54 Some(line)
55 }
56 })
57 .find_map(|l| l.strip_prefix("commit-hash: "))
58 .unwrap();
59 (quote! {(#release, #commit)}).into()
60}
61
62#[allow(clippy::large_enum_variant)]
64enum AnnotableItem {
65 Item(Item),
67 TraitItem(TraitItem),
69}
70
71macro_rules! parse_annotable_item {
72 ($tokens:ident) => {{
73 let item: Item = parse_macro_input!($tokens as Item);
74
75 if matches!(item, Item::Verbatim(_)) {
76 let tokens = TokenStream::from(item.to_token_stream());
77 let trait_item: TraitItem = parse_macro_input!(tokens as TraitItem);
78
79 if matches!(trait_item, TraitItem::Verbatim(_)) {
80 Err(Error::new_spanned(
81 trait_item,
82 "the `unstable` proc-macro attribute only supports items and trait items",
83 ))
84 } else {
85 Ok(AnnotableItem::TraitItem(trait_item))
86 }
87 } else {
88 Ok(AnnotableItem::Item(item))
89 }
90 }};
91}
92
93impl AnnotableItem {
94 fn attributes_mut(&mut self) -> Result<&mut Vec<Attribute>, Error> {
96 match self {
97 AnnotableItem::Item(item) => match item {
98 Item::Const(item) => Ok(&mut item.attrs),
99 Item::Enum(item) => Ok(&mut item.attrs),
100 Item::ExternCrate(item) => Ok(&mut item.attrs),
101 Item::Fn(item) => Ok(&mut item.attrs),
102 Item::ForeignMod(item) => Ok(&mut item.attrs),
103 Item::Impl(item) => Ok(&mut item.attrs),
104 Item::Macro(item) => Ok(&mut item.attrs),
105 Item::Mod(item) => Ok(&mut item.attrs),
106 Item::Static(item) => Ok(&mut item.attrs),
107 Item::Struct(item) => Ok(&mut item.attrs),
108 Item::Trait(item) => Ok(&mut item.attrs),
109 Item::TraitAlias(item) => Ok(&mut item.attrs),
110 Item::Type(item) => Ok(&mut item.attrs),
111 Item::Union(item) => Ok(&mut item.attrs),
112 Item::Use(item) => Ok(&mut item.attrs),
113 other => Err(Error::new_spanned(
114 other,
115 "item is not supported by the `unstable` or `internal` proc-macro attribute",
116 )),
117 },
118 AnnotableItem::TraitItem(trait_item) => match trait_item {
119 TraitItem::Const(trait_item) => Ok(&mut trait_item.attrs),
120 TraitItem::Fn(trait_item) => Ok(&mut trait_item.attrs),
121 TraitItem::Type(trait_item) => Ok(&mut trait_item.attrs),
122 TraitItem::Macro(trait_item) => Ok(&mut trait_item.attrs),
123 other => Err(Error::new_spanned(
124 other,
125 "item is not supported by the `unstable` or `internal` proc-macro attribute",
126 )),
127 },
128 }
129 }
130
131 fn to_token_stream(&self) -> proc_macro2::TokenStream {
133 match self {
134 AnnotableItem::Item(item) => item.to_token_stream(),
135 AnnotableItem::TraitItem(trait_item) => trait_item.to_token_stream(),
136 }
137 }
138}
139
140#[proc_macro_attribute]
141pub fn unstable_doc(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
145 let mut item = match parse_annotable_item!(tokens) {
146 Ok(item) => item,
147 Err(err) => return err.into_compile_error().into(),
148 };
149
150 let attrs = match item.attributes_mut() {
151 Ok(attrs) => attrs,
152 Err(err) => return err.into_compile_error().into(),
153 };
154
155 if attrs.iter().any(is_doc_attribute) {
156 let mut pushed = false;
157 let oldattrs = std::mem::take(attrs);
158 for attr in oldattrs {
159 if is_doc_attribute(&attr) && !pushed {
160 attrs.push(attr);
161 let message = "<div class=\"warning\">This API has been marked as <strong>unstable</strong>: it works as advertised, but it may be changed in a future release.</div>";
163 let note: Attribute = parse_quote!(#[doc = #message]);
164 attrs.push(note);
165 pushed = true;
166 } else {
167 attrs.push(attr);
168 }
169 }
170 }
171
172 TokenStream::from(item.to_token_stream())
173}
174
175#[proc_macro_attribute]
176pub fn unstable(attr: TokenStream, tokens: TokenStream) -> TokenStream {
178 let tokens = unstable_doc(attr, tokens);
179 let mut item = match parse_annotable_item!(tokens) {
180 Ok(item) => item,
181 Err(err) => return err.into_compile_error().into(),
182 };
183
184 let attrs = match item.attributes_mut() {
185 Ok(attrs) => attrs,
186 Err(err) => return err.into_compile_error().into(),
187 };
188
189 let feature_gate: Attribute = parse_quote!(#[cfg(feature = "unstable")]);
190 attrs.push(feature_gate);
191
192 TokenStream::from(item.to_token_stream())
193}
194
195#[proc_macro_attribute]
197pub fn internal_config(args: TokenStream, tokens: TokenStream) -> TokenStream {
198 let tokens = unstable_doc(args, tokens);
199 let mut item = match parse_annotable_item!(tokens) {
200 Ok(item) => item,
201 Err(err) => return err.into_compile_error().into(),
202 };
203
204 let attrs = match item.attributes_mut() {
205 Ok(attrs) => attrs,
206 Err(err) => return err.into_compile_error().into(),
207 };
208
209 let feature_gate: Attribute = parse_quote!(#[cfg(feature = "internal_config")]);
210 let hide_doc: Attribute = parse_quote!(#[doc(hidden)]);
211 attrs.push(feature_gate);
212 attrs.push(hide_doc);
213
214 TokenStream::from(item.to_token_stream())
215}
216
217#[proc_macro_attribute]
218pub fn internal(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
220 let mut item = match parse_annotable_item!(tokens) {
221 Ok(item) => item,
222 Err(err) => return err.into_compile_error().into(),
223 };
224
225 let attrs = match item.attributes_mut() {
226 Ok(attrs) => attrs,
227 Err(err) => return err.into_compile_error().into(),
228 };
229
230 let feature_gate: Attribute = parse_quote!(#[cfg(feature = "internal")]);
231 let hide_doc: Attribute = parse_quote!(#[doc(hidden)]);
232 attrs.push(feature_gate);
233 attrs.push(hide_doc);
234
235 TokenStream::from(item.to_token_stream())
236}
237
238fn is_doc_attribute(attr: &Attribute) -> bool {
240 attr.path()
241 .get_ident()
242 .is_some_and(|ident| &ident.to_string() == "doc")
243}
244
245fn keformat_support(source: &str) -> proc_macro2::TokenStream {
246 let format = match KeFormat::new(&source) {
247 Ok(format) => format,
248 Err(e) => panic!("{}", e),
249 };
250 let specs = unsafe { macro_support::specs(&format) };
251 let len = specs.len();
252 let setters = specs.iter().map(|spec| {
253 let id = &source[spec.spec_start..(spec.spec_start + spec.id_end as usize)];
254 let set_id = quote::format_ident!("{}", id);
255 quote! {
256 pub fn #set_id <S: ::core::fmt::Display>(&mut self, value: S) -> Result<&mut Self, ::zenoh::key_expr::format::FormatSetError> {
257 match self.0.set(#id, value) {
258 Ok(_) => Ok(self),
259 Err(e) => Err(e)
260 }
261 }
262 }
263 });
264 let getters = specs.iter().map(|spec| {
265 let source = &source[spec.spec_start..spec.spec_end];
266 let id = &source[..(spec.id_end as usize)];
267 let get_id = quote::format_ident!("{}", id);
268 let pattern = unsafe {
269 keyexpr::from_str_unchecked(if spec.pattern_end != u16::MAX {
270 &source[(spec.id_end as usize + 1)..(spec.spec_start + spec.pattern_end as usize)]
271 } else {
272 &source[(spec.id_end as usize + 1)..]
273 })
274 };
275 let doc = format!("Get the parsed value for `{id}`.\n\nThis value is guaranteed to be a valid key expression intersecting with `{pattern}`");
276 if pattern.as_bytes() == b"**" {
277 quote! {
278 #[doc = #doc]
279 pub fn #get_id (&self) -> Option<& ::zenoh::key_expr::keyexpr> {
281 unsafe {
282 let s =self._0.get(#id).unwrap_unchecked();
283 (!s.is_empty()).then(|| ::zenoh::key_expr::keyexpr::from_str_unchecked(s))
284 }
285 }
286 }
287 } else {
288 quote! {
289 #[doc = #doc]
290 pub fn #get_id (&self) -> &::zenoh::key_expr::keyexpr {
291 unsafe {::zenoh::key_expr::keyexpr::from_str_unchecked(self._0.get(#id).unwrap_unchecked())}
292 }
293 }
294 }
295 });
296 let segments = specs.iter().map(|spec| {
297 let SegmentBuilder {
298 segment_start,
299 prefix_end,
300 spec_start,
301 id_end,
302 pattern_end,
303 spec_end,
304 segment_end,
305 } = spec;
306 quote! {
307 ::zenoh::key_expr::format::macro_support::SegmentBuilder {
308 segment_start: #segment_start,
309 prefix_end: #prefix_end,
310 spec_start: #spec_start,
311 id_end: #id_end,
312 pattern_end: #pattern_end,
313 spec_end: #spec_end,
314 segment_end: #segment_end,
315 },
316 }
317 });
318
319 let format_doc = format!("The `{source}` format, as a zero-sized-type.");
320 let formatter_doc = format!("And instance of a formatter for `{source}`.");
321
322 quote! {
323 use ::zenoh::Result as ZResult;
324 const FORMAT_INNER: ::zenoh::key_expr::format::KeFormat<'static, [::zenoh::key_expr::format::Segment<'static>; #len]> = unsafe {
325 ::zenoh::key_expr::format::macro_support::const_new(#source, [#(#segments)*])
326 };
327 #[doc = #format_doc]
328 #[derive(Copy, Clone, Hash)]
329 pub struct Format;
330
331 #[doc = #formatter_doc]
332 #[derive(Clone)]
333 pub struct Formatter(::zenoh::key_expr::format::KeFormatter<'static, [::zenoh::key_expr::format::Segment<'static>; #len]>);
334 impl ::core::fmt::Debug for Format {
335 fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
336 ::core::fmt::Debug::fmt(&FORMAT_INNER, f)
337 }
338 }
339 impl ::core::fmt::Debug for Formatter {
340 fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
341 ::core::fmt::Debug::fmt(&self.0, f)
342 }
343 }
344 impl ::core::fmt::Display for Format {
345 fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
346 ::core::fmt::Display::fmt(&FORMAT_INNER, f)
347 }
348 }
349 impl ::core::ops::Deref for Format {
350 type Target = ::zenoh::key_expr::format::KeFormat<'static, [::zenoh::key_expr::format::Segment<'static>; #len]>;
351 fn deref(&self) -> &Self::Target {&FORMAT_INNER}
352 }
353 impl ::core::ops::Deref for Formatter {
354 type Target = ::zenoh::key_expr::format::KeFormatter<'static, [::zenoh::key_expr::format::Segment<'static>; #len]>;
355 fn deref(&self) -> &Self::Target {&self.0}
356 }
357 impl ::core::ops::DerefMut for Formatter {
358 fn deref_mut(&mut self) -> &mut Self::Target {&mut self.0}
359 }
360 impl Formatter {
361 #(#setters)*
362 }
363 pub struct Parsed<'s>{_0: ::zenoh::key_expr::format::Parsed<'s, [::zenoh::key_expr::format::Segment<'s>; #len]>}
364 impl<'s> ::core::ops::Deref for Parsed<'s> {
365 type Target = ::zenoh::key_expr::format::Parsed<'s, [::zenoh::key_expr::format::Segment<'s>; #len]>;
366 fn deref(&self) -> &Self::Target {&self._0}
367 }
368 impl Parsed<'_> {
369 #(#getters)*
370 }
371 impl Format {
372 pub fn formatter() -> Formatter {
373 Formatter(Format.formatter())
374 }
375 pub fn parse<'s>(target: &'s ::zenoh::key_expr::keyexpr) -> ZResult<Parsed<'s>> {
376 Ok(Parsed{_0: Format.parse(target)?})
377 }
378 pub fn into_inner(self) -> ::zenoh::key_expr::format::KeFormat<'static, [::zenoh::key_expr::format::Segment<'static>; #len]> {
379 FORMAT_INNER
380 }
381 }
382 pub fn formatter() -> Formatter {
383 Format::formatter()
384 }
385 pub fn parse<'s>(target: &'s ::zenoh::key_expr::keyexpr) -> ZResult<Parsed<'s>> {
386 Format::parse(target)
387 }
388 }
389}
390
391struct FormatDeclaration {
392 vis: syn::Visibility,
393 name: syn::Ident,
394 lit: syn::LitStr,
395}
396impl syn::parse::Parse for FormatDeclaration {
397 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
398 let vis = input.parse()?;
399 let name = input.parse()?;
400 let _: syn::Token!(:) = input.parse()?;
401 let lit = input.parse()?;
402 Ok(FormatDeclaration { vis, name, lit })
403 }
404}
405struct FormatDeclarations(syn::punctuated::Punctuated<FormatDeclaration, syn::Token!(,)>);
406impl syn::parse::Parse for FormatDeclarations {
407 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
408 Ok(Self(input.parse_terminated(
409 FormatDeclaration::parse,
410 syn::Token![,],
411 )?))
412 }
413}
414
415#[proc_macro]
425pub fn kedefine(tokens: TokenStream) -> TokenStream {
426 let declarations: FormatDeclarations = syn::parse(tokens).unwrap();
427 let content = declarations.0.into_iter().map(|FormatDeclaration { vis, name, lit }|
428 {
429 let source = lit.value();
430 let docstring = format!(
431 r"The module associated with the `{source}` format, it contains:
432- `Format`, a zero-sized type that represents your format.
433- `formatter()`, a function that constructs a `Formatter` specialized for your format:
434 - for every spec in your format, `Formatter` will have a method named after the spec's `id` that lets you set a value for that field of your format. These methods will return `Result<&mut Formatter, FormatError>`.
435- `parse(target: &keyexpr) -> ZResult<Parsed<'_>>` will parse the provided key expression according to your format. Just like `KeFormat::parse`, parsing is lazy: each field will match the smallest subsection of your `target` that is included in its pattern.
436 - like `Formatter`, `Parsed` will have a method named after each spec's `id` that returns `&keyexpr`; except for specs whose pattern was `**`, these will return an `Option<&keyexpr>`, where `None` signifies that the pattern was matched by an empty list of chunks."
437 );
438 let support = keformat_support(&source);
439 quote! {
440 #[doc = #docstring]
441 #vis mod #name{
442 #support
443 }
444 }});
445 quote!(#(#content)*).into()
446}
447
448struct FormatUsage {
449 id: syn::Expr,
450 assigns: Vec<(syn::Expr, syn::Expr)>,
451}
452impl syn::parse::Parse for FormatUsage {
453 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
454 let id = input.parse()?;
455 let mut assigns = Vec::new();
456 if !input.is_empty() {
457 input.parse::<syn::Token!(,)>()?;
458 }
459 assigns.extend(
460 input
461 .parse_terminated(syn::Expr::parse, syn::Token![,])?
462 .into_iter()
463 .map(|a| match a {
464 syn::Expr::Assign(a) => (*a.left, *a.right),
465 a => (a.clone(), a),
466 }),
467 );
468 Ok(FormatUsage { id, assigns })
469 }
470}
471
472#[proc_macro]
482pub fn kewrite(tokens: TokenStream) -> TokenStream {
483 let FormatUsage { id, assigns } = syn::parse(tokens).unwrap();
484 let mut sets = None;
485 for (l, r) in assigns.iter().rev() {
486 if let Some(set) = sets {
487 sets = Some(quote!(.#l(#r).and_then(|x| x #set)));
488 } else {
489 sets = Some(quote!(.#l(#r)));
490 }
491 }
492 quote!(#id #sets).into()
493}
494
495#[proc_macro]
504pub fn keformat(tokens: TokenStream) -> TokenStream {
505 let formatted: proc_macro2::TokenStream = kewrite(tokens).into();
506 quote!(match #formatted {
507 Ok(ok) => ok.build(),
508 Err(e) => Err(e.into()),
509 })
510 .into()
511}
512
513#[proc_macro]
515pub fn ke(tokens: TokenStream) -> TokenStream {
516 let value: LitStr = syn::parse(tokens).unwrap();
517 let ke = value.value();
518 match zenoh_keyexpr::keyexpr::new(&ke) {
519 Ok(_) => quote!(unsafe { zenoh::key_expr::keyexpr::from_str_unchecked(#ke)}).into(),
520 Err(e) => panic!("{}", e),
521 }
522}
523
524#[proc_macro]
526pub fn nonwild_ke(tokens: TokenStream) -> TokenStream {
527 let value: LitStr = syn::parse(tokens).unwrap();
528 let ke = value.value();
529 match zenoh_keyexpr::nonwild_keyexpr::new(&ke) {
530 Ok(_) => quote!(unsafe { zenoh::key_expr::nonwild_keyexpr::from_str_unchecked(#ke)}).into(),
531 Err(e) => panic!("{}", e),
532 }
533}
534
535mod zenoh_runtime_derive;
536use syn::DeriveInput;
537use zenoh_runtime_derive::{derive_generic_runtime_param, derive_register_param};
538
539#[proc_macro_derive(GenericRuntimeParam)]
547pub fn generic_runtime_param(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
548 let input: DeriveInput = syn::parse_macro_input!(input);
549 derive_generic_runtime_param(input)
550 .unwrap_or_else(syn::Error::into_compile_error)
551 .into()
552}
553
554#[proc_macro_derive(RegisterParam, attributes(alias, param))]
563pub fn register_param(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
564 let input: DeriveInput = syn::parse_macro_input!(input);
565 derive_register_param(input)
566 .unwrap_or_else(syn::Error::into_compile_error)
567 .into()
568}
569
570#[proc_macro_attribute]
589pub fn internal_trait(_attr: TokenStream, item: TokenStream) -> TokenStream {
590 let input = parse_macro_input!(item as ItemImpl);
591 let trait_path = &input.trait_.as_ref().unwrap().1;
592 let struct_path = &input.self_ty;
593 let generics = &input.generics;
594 let mut struct_methods = quote! {};
597 for item_fn in input.items.iter() {
598 if let syn::ImplItem::Fn(method) = item_fn {
599 let method_name = &method.sig.ident;
600 let method_generic_params = &method.sig.generics.params;
601 let method_generic_params = if method_generic_params.is_empty() {
602 quote! {}
603 } else {
604 quote! {<#method_generic_params>}
605 };
606 let method_args = &method.sig.inputs;
607 let method_output = &method.sig.output;
608 let where_clause = &method.sig.generics.where_clause;
609 let mut method_call_args = quote! {};
610 for arg in method_args.iter() {
611 match arg {
612 syn::FnArg::Receiver(_) => {
613 method_call_args.extend(quote! { self, });
614 }
615 syn::FnArg::Typed(pat_type) => {
616 let pat = &pat_type.pat;
617 method_call_args.extend(quote! { #pat, });
618 }
619 }
620 }
621 let mut attributes = quote! {};
622 for attr in &method.attrs {
623 attributes.extend(quote! {
624 #attr
625 });
626 }
627 struct_methods.extend(quote! {
629 #attributes
630 pub fn #method_name #method_generic_params (#method_args) #method_output #where_clause {
631 <#struct_path as #trait_path>::#method_name(#method_call_args)
632 }
633 });
634 }
635 }
636 let struct_methods_output = quote! {
637 impl #generics #struct_path {
638 #struct_methods
639 }
640 };
641 (quote! {
642 #input
643 #struct_methods_output
644 })
645 .into()
646}