diff --git a/commons/zenoh-macros/src/lib.rs b/commons/zenoh-macros/src/lib.rs index 3649d633af..3b3b14cd3d 100644 --- a/commons/zenoh-macros/src/lib.rs +++ b/commons/zenoh-macros/src/lib.rs @@ -209,6 +209,285 @@ fn is_doc_attribute(attr: &Attribute) -> bool { .is_some_and(|ident| &ident.to_string() == "doc") } +fn keformat_support(source: &str) -> proc_macro2::TokenStream { + let format = match KeFormat::new(&source) { + Ok(format) => format, + Err(e) => panic!("{}", e), + }; + let specs = unsafe { macro_support::specs(&format) }; + let len = specs.len(); + let setters = specs.iter().map(|spec| { + let id = &source[spec.spec_start..(spec.spec_start + spec.id_end as usize)]; + let set_id = quote::format_ident!("{}", id); + quote! { + pub fn #set_id (&mut self, value: S) -> Result<&mut Self, ::zenoh::key_expr::format::FormatSetError> { + match self.0.set(#id, value) { + Ok(_) => Ok(self), + Err(e) => Err(e) + } + } + } + }); + let getters = specs.iter().map(|spec| { + let source = &source[spec.spec_start..spec.spec_end]; + let id = &source[..(spec.id_end as usize)]; + let get_id = quote::format_ident!("{}", id); + let pattern = unsafe { + keyexpr::from_str_unchecked(if spec.pattern_end != u16::MAX { + &source[(spec.id_end as usize + 1)..(spec.spec_start + spec.pattern_end as usize)] + } else { + &source[(spec.id_end as usize + 1)..] + }) + }; + let doc = format!("Get the parsed value for `{id}`.\n\nThis value is guaranteed to be a valid key expression intersecting with `{pattern}`"); + if pattern.as_bytes() == b"**" { + quote! { + #[doc = #doc] + /// Since the pattern is `**`, this may return `None` if the pattern didn't consume any chunks. + pub fn #get_id (&self) -> Option<& ::zenoh::key_expr::keyexpr> { + unsafe { + let s =self._0.get(#id).unwrap_unchecked(); + (!s.is_empty()).then(|| ::zenoh::key_expr::keyexpr::from_str_unchecked(s)) + } + } + } + } else { + quote! { + #[doc = #doc] + pub fn #get_id (&self) -> &::zenoh::key_expr::keyexpr { + unsafe {::zenoh::key_expr::keyexpr::from_str_unchecked(self._0.get(#id).unwrap_unchecked())} + } + } + } + }); + let segments = specs.iter().map(|spec| { + let SegmentBuilder { + segment_start, + prefix_end, + spec_start, + id_end, + pattern_end, + spec_end, + segment_end, + } = spec; + quote! { + ::zenoh::key_expr::format::macro_support::SegmentBuilder { + segment_start: #segment_start, + prefix_end: #prefix_end, + spec_start: #spec_start, + id_end: #id_end, + pattern_end: #pattern_end, + spec_end: #spec_end, + segment_end: #segment_end, + }, + } + }); + + let format_doc = format!("The `{source}` format, as a zero-sized-type."); + let formatter_doc = format!("And instance of a formatter for `{source}`."); + + quote! { + use ::zenoh::core::Result as ZResult; + const FORMAT_INNER: ::zenoh::key_expr::format::KeFormat<'static, [::zenoh::key_expr::format::Segment<'static>; #len]> = unsafe { + ::zenoh::key_expr::format::macro_support::const_new(#source, [#(#segments)*]) + }; + #[doc = #format_doc] + #[derive(Copy, Clone, Hash)] + pub struct Format; + + #[doc = #formatter_doc] + #[derive(Clone)] + pub struct Formatter(::zenoh::key_expr::format::KeFormatter<'static, [::zenoh::key_expr::format::Segment<'static>; #len]>); + impl ::core::fmt::Debug for Format { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Debug::fmt(&FORMAT_INNER, f) + } + } + impl ::core::fmt::Debug for Formatter { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Debug::fmt(&self.0, f) + } + } + impl ::core::fmt::Display for Format { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Display::fmt(&FORMAT_INNER, f) + } + } + impl ::core::ops::Deref for Format { + type Target = ::zenoh::key_expr::format::KeFormat<'static, [::zenoh::key_expr::format::Segment<'static>; #len]>; + fn deref(&self) -> &Self::Target {&FORMAT_INNER} + } + impl ::core::ops::Deref for Formatter { + type Target = ::zenoh::key_expr::format::KeFormatter<'static, [::zenoh::key_expr::format::Segment<'static>; #len]>; + fn deref(&self) -> &Self::Target {&self.0} + } + impl ::core::ops::DerefMut for Formatter { + fn deref_mut(&mut self) -> &mut Self::Target {&mut self.0} + } + impl Formatter { + #(#setters)* + } + pub struct Parsed<'s>{_0: ::zenoh::key_expr::format::Parsed<'s, [::zenoh::key_expr::format::Segment<'s>; #len]>} + impl<'s> ::core::ops::Deref for Parsed<'s> { + type Target = ::zenoh::key_expr::format::Parsed<'s, [::zenoh::key_expr::format::Segment<'s>; #len]>; + fn deref(&self) -> &Self::Target {&self._0} + } + impl Parsed<'_> { + #(#getters)* + } + impl Format { + pub fn formatter() -> Formatter { + Formatter(Format.formatter()) + } + pub fn parse<'s>(target: &'s ::zenoh::key_expr::keyexpr) -> ZResult> { + Ok(Parsed{_0: Format.parse(target)?}) + } + pub fn into_inner(self) -> ::zenoh::key_expr::format::KeFormat<'static, [::zenoh::key_expr::format::Segment<'static>; #len]> { + FORMAT_INNER + } + } + pub fn formatter() -> Formatter { + Format::formatter() + } + pub fn parse<'s>(target: &'s ::zenoh::key_expr::keyexpr) -> ZResult> { + Format::parse(target) + } + } +} + +struct FormatDeclaration { + vis: syn::Visibility, + name: syn::Ident, + lit: syn::LitStr, +} +impl syn::parse::Parse for FormatDeclaration { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let vis = input.parse()?; + let name = input.parse()?; + let _: syn::Token!(:) = input.parse()?; + let lit = input.parse()?; + Ok(FormatDeclaration { vis, name, lit }) + } +} +struct FormatDeclarations(syn::punctuated::Punctuated); +impl syn::parse::Parse for FormatDeclarations { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + Ok(Self(input.parse_terminated( + FormatDeclaration::parse, + syn::Token![,], + )?)) + } +} + +/// Create format modules from a format specification. +/// +/// `kedefine!($($vis $ident: $lit),*)` will validate each `$lit` to be a valid KeFormat, and declare a module called `$ident` with `$vis` visibility at its call-site for each format. +/// The modules contain the following elements: +/// - `Format`, a zero-sized type that represents your format. +/// - `formatter()`, a function that constructs a `Formatter` specialized for your format: +/// - 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>`. +/// - `parse(target: &keyexpr) -> ZResult>` 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. +/// - 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. +#[proc_macro] +pub fn kedefine(tokens: TokenStream) -> TokenStream { + let declarations: FormatDeclarations = syn::parse(tokens).unwrap(); + let content = declarations.0.into_iter().map(|FormatDeclaration { vis, name, lit }| + { + let source = lit.value(); + let docstring = format!( + r"The module associated with the `{source}` format, it contains: +- `Format`, a zero-sized type that represents your format. +- `formatter()`, a function that constructs a `Formatter` specialized for your format: + - 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>`. +- `parse(target: &keyexpr) -> ZResult>` 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. + - 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." + ); + let support = keformat_support(&source); + quote! { + #[doc = #docstring] + #vis mod #name{ + #support + } + }}); + quote!(#(#content)*).into() +} + +struct FormatUsage { + id: syn::Expr, + assigns: Vec<(syn::Expr, syn::Expr)>, +} +impl syn::parse::Parse for FormatUsage { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let id = input.parse()?; + let mut assigns = Vec::new(); + if !input.is_empty() { + input.parse::()?; + } + assigns.extend( + input + .parse_terminated(syn::Expr::parse, syn::Token![,])? + .into_iter() + .map(|a| match a { + syn::Expr::Assign(a) => (*a.left, *a.right), + a => (a.clone(), a), + }), + ); + Ok(FormatUsage { id, assigns }) + } +} + +/// Write a set of values into a `Formatter`, stopping as soon as a value doesn't fit the specification for its field. +/// Contrary to `keformat` doesn't build the Formatter into a Key Expression. +/// +/// `kewrite!($formatter, $($ident [= $expr]),*)` will attempt to write `$expr` into their respective `$ident` fields for `$formatter`. +/// `$formatter` must be an expression that dereferences to `&mut Formatter`. +/// `$expr` must resolve to a value that implements `core::fmt::Display`. +/// `$expr` defaults to `$ident` if omitted. +/// +/// This macro always results in an expression that resolves to `Result<&mut Formatter, FormatSetError>`. +#[proc_macro] +pub fn kewrite(tokens: TokenStream) -> TokenStream { + let FormatUsage { id, assigns } = syn::parse(tokens).unwrap(); + let mut sets = None; + for (l, r) in assigns.iter().rev() { + if let Some(set) = sets { + sets = Some(quote!(.#l(#r).and_then(|x| x #set))); + } else { + sets = Some(quote!(.#l(#r))); + } + } + quote!(#id #sets).into() +} + +/// Write a set of values into a `Formatter` and then builds it into an `OwnedKeyExpr`, stopping as soon as a value doesn't fit the specification for its field. +/// +/// `keformat!($formatter, $($ident [= $expr]),*)` will attempt to write `$expr` into their respective `$ident` fields for `$formatter`. +/// `$formatter` must be an expression that dereferences to `&mut Formatter`. +/// `$expr` must resolve to a value that implements `core::fmt::Display`. +/// `$expr` defaults to `$ident` if omitted. +/// +/// This macro always results in an expression that resolves to `ZResult`, and leaves `$formatter` in its written state. +#[proc_macro] +pub fn keformat(tokens: TokenStream) -> TokenStream { + let formatted: proc_macro2::TokenStream = kewrite(tokens).into(); + quote!(match #formatted { + Ok(ok) => ok.build(), + Err(e) => Err(e.into()), + }) + .into() +} + +/// Equivalent to [`keyexpr::new`](zenoh_keyexpr::keyexpr::new), but the check is run at compile-time and will throw a compile error in case of failure. +#[proc_macro] +pub fn ke(tokens: TokenStream) -> TokenStream { + let value: LitStr = syn::parse(tokens).unwrap(); + let ke = value.value(); + match zenoh_keyexpr::keyexpr::new(&ke) { + Ok(_) => quote!(unsafe {::zenoh::key_expr::keyexpr::from_str_unchecked(#ke)}).into(), + Err(e) => panic!("{}", e), + } +} + mod zenoh_runtime_derive; use syn::DeriveInput; use zenoh_runtime_derive::{derive_generic_runtime_param, derive_register_param}; diff --git a/plugins/zenoh-plugin-storage-manager/src/replica/storage.rs b/plugins/zenoh-plugin-storage-manager/src/replica/storage.rs index a915510c63..c99b322a12 100644 --- a/plugins/zenoh-plugin-storage-manager/src/replica/storage.rs +++ b/plugins/zenoh-plugin-storage-manager/src/replica/storage.rs @@ -21,6 +21,7 @@ use async_std::sync::{Arc, Mutex, RwLock}; use async_trait::async_trait; use flume::{Receiver, Sender}; use futures::select; +use zenoh::internal::buffers::{SplitBuffer, ZBuf}; use zenoh::{ core::Result as ZResult, internal::{bail, zenoh_home, Timed, TimedEvent, Timer}, @@ -37,7 +38,6 @@ use zenoh::{ time::{new_reception_timestamp, Timestamp, NTP64}, value::Value, }; -use zenoh::internal::buffers::{SplitBuffer, ZBuf}; use zenoh_backend_traits::{ config::{GarbageCollectionConfig, StorageConfig}, Capability, History, Persistence, StorageInsertionResult, StoredData, diff --git a/zenoh/src/api/bytes.rs b/zenoh/src/api/bytes.rs index 0ee1a4557d..446b2bc270 100644 --- a/zenoh/src/api/bytes.rs +++ b/zenoh/src/api/bytes.rs @@ -23,7 +23,7 @@ use zenoh_buffers::{ buffer::{Buffer, SplitBuffer}, reader::HasReader, writer::HasWriter, - ZBufReader, ZBufWriter, ZSlice, ZBuf, + ZBuf, ZBufReader, ZBufWriter, ZSlice, }; use zenoh_codec::{RCodec, WCodec, Zenoh080}; use zenoh_protocol::{core::Properties, zenoh::ext::AttachmentType}; diff --git a/zenoh/src/api/session.rs b/zenoh/src/api/session.rs index a483a7ac81..e633bd88b8 100644 --- a/zenoh/src/api/session.rs +++ b/zenoh/src/api/session.rs @@ -72,7 +72,9 @@ use super::{ info::SessionInfo, key_expr::{KeyExpr, KeyExprInner}, publisher::{Priority, PublisherState}, - query::{ConsolidationMode, SessionGetBuilder, QueryConsolidation, QueryState, QueryTarget, Reply}, + query::{ + ConsolidationMode, QueryConsolidation, QueryState, QueryTarget, Reply, SessionGetBuilder, + }, queryable::{Query, QueryInner, QueryableBuilder, QueryableState}, sample::{DataInfo, DataInfoIntoSample, Locality, QoS, Sample, SampleKind}, selector::{Selector, TIME_RANGE_KEY},