#[doc(hidden)]
#[macro_export]
macro_rules! struct_elaborate {
(
$next:ident $( ($($next_args:tt)*) )? =>
$( #[ $sdoc:meta ] )*
struct $name:ident <$lt:lifetime> $(: $super:ident)? {
$(
$( #[ doc = $fdoc:literal ] )* $field:ident :
$ty:ty
$( = $value:literal)?
),*
$(,)?
}
) => {
$crate::paste!($crate::struct_elaborate!(__builder_type__
fields($(
[
type( $ty )( $ty ),
value($($value)?),
docs($($fdoc)*),
name($field),
]
)*)
accum()
original($next $( ($($next_args)*) )?
=> $(#[$sdoc])* struct $name <$lt> $(: $super)? {})
););
};
// End of push-down automation - jumps to `__finalize__`
(__builder_type__ fields() accum($($faccum:tt)*) original($($original:tt)*)) => {
$crate::struct_elaborate!(__finalize__ accum($($faccum)*) original($($original)*));
};
(__builder_type__ fields([type(len)(len), value(), $($rest:tt)*] $($frest:tt)*) $($srest:tt)*) => {
$crate::struct_elaborate!(__builder_docs__ fields([type($crate::prelude::Length), value(auto=auto), $($rest)*] $($frest)*) $($srest)*);
};
(__builder_type__ fields([type(len)(len), value($($value:tt)+), $($rest:tt)*] $($frest:tt)*) $($srest:tt)*) => {
$crate::struct_elaborate!(__builder_docs__ fields([type($crate::prelude::Length), value(value=($($value)*)), $($rest)*] $($frest)*) $($srest)*);
};
(__builder_type__ fields([type([$elem:ty; $len:literal])($ty:ty), $($rest:tt)*] $($frest:tt)*) $($srest:tt)*) => {
$crate::struct_elaborate!(__builder_value__ fields([type([$elem;$len]), $($rest)*] $($frest)*) $($srest)*);
};
(__builder_type__ fields([type($ty:ty)($ty2:ty), $($rest:tt)*] $($frest:tt)*) $($srest:tt)*) => {
$crate::struct_elaborate!(__builder_value__ fields([type($ty), $($rest)*] $($frest)*) $($srest)*);
};
(__builder_value__ fields([
type($ty:ty), value(), $($rest:tt)*
] $($frest:tt)*) $($srest:tt)*) => {
$crate::struct_elaborate!(__builder_docs__ fields([type($ty), value(no_value=no_value), $($rest)*] $($frest)*) $($srest)*);
};
(__builder_value__ fields([
type($ty:ty), value($($value:tt)+), $($rest:tt)*
] $($frest:tt)*) $($srest:tt)*) => {
$crate::struct_elaborate!(__builder_docs__ fields([type($ty), value(value=($($value)*)), $($rest)*] $($frest)*) $($srest)*);
};
(__builder_docs__ fields([
type($ty:ty), value($($value:tt)*), docs(), name($field:ident), $($rest:tt)*
] $($frest:tt)*) $($srest:tt)*) => {
$crate::struct_elaborate!(__builder__ fields([type($ty), value($($value)*), docs(concat!("`", stringify!($field), "` field.")), name($field), $($rest)*] $($frest)*) $($srest)*);
};
(__builder_docs__ fields([
type($ty:ty), value($($value:tt)*), docs($($fdoc:literal)+), $($rest:tt)*
] $($frest:tt)*) $($srest:tt)*) => {
$crate::struct_elaborate!(__builder__ fields([type($ty), value($($value)*), docs(concat!($($fdoc)+)), $($rest)*] $($frest)*) $($srest)*);
};
(__builder__ fields([
type($ty:ty), value($($value:tt)*), docs($fdoc:expr), name($field:ident), $($rest:tt)*
] $($frest:tt)*) accum($($faccum:tt)*) original($($original:tt)*)) => {
$crate::struct_elaborate!(__builder_type__ fields($($frest)*) accum(
$($faccum)*
{
name($field),
type($ty),
value($($value)*),
docs($fdoc),
},
) original($($original)*));
};
(__finalize__
accum($($accum:tt)*) original($next:ident $( ($($next_args:tt)*) )? =>
$( #[ $sdoc:meta ] )* struct $name:ident <$lt:lifetime> $(: $super:ident)? {})
) => {
$next ! (
$( $($next_args)* , )?
struct $name <$lt> {
super($($super)?),
docs($($sdoc),*),
fields(
$($accum)*
),
}
);
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! __protocol {
(
$( $( #[ doc = $sdoc:literal ] )*
struct $name:ident <$lt:lifetime> $(: $super:ident)? { $($struct:tt)+ }
)+
$( #[repr($repr:ty)] $( #[ doc = $edoc:literal ] )*
enum $ename:ident { $($(#[$default:meta])? $emname:ident = $emvalue:literal),+ $(,)? }
)*
) => {
use $crate::protocol_builder;
#[allow(unused)]
use $crate::prelude::*;
$(
$crate::paste!(
$crate::struct_elaborate!(protocol_builder(__struct__) => $( #[ doc = $sdoc ] )* struct $name <$lt> $(: $super)? { $($struct)+ } );
$crate::struct_elaborate!(protocol_builder(__meta__) => $( #[ doc = $sdoc ] )* struct $name <$lt> $(: $super)? { $($struct)+ } );
$crate::struct_elaborate!(protocol_builder(__builder__) => $( #[ doc = $sdoc ] )* struct $name <$lt> $(: $super)? { $($struct)+ } );
);
)+
$(
$crate::paste!(
$(#[doc = $edoc])*
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
#[repr($repr)]
pub enum $ename {
$($(#[$default])? $emname = $emvalue),+
}
impl $crate::prelude::EnumMeta for $ename {
const VALUES: &'static [(&'static str, usize)] = &[
$((stringify!($emname), $emvalue as _)),+
];
}
$crate::declare_type!(DataType, $ename, flags=[enum], {
});
impl<'a> $crate::prelude::DecoderFor<'a, $ename> for $ename {
fn decode_for(buf: &mut &'a [u8]) -> Result<Self, ParseError> {
let repr = <$repr as $crate::prelude::DecoderFor<$repr>>::decode_for(buf)?;
match repr {
$(
$emvalue => Ok($ename::$emname),
)+
_ => Err(ParseError::InvalidData(stringify!($ename), repr as usize)),
}
}
}
impl $crate::prelude::EncoderFor<$ename> for $ename {
fn encode_for(&self, buf: &mut $crate::BufWriter<'_>) {
<$repr as $crate::prelude::EncoderFor<$repr>>::encode_for(&(*self as $repr), buf);
}
}
impl $crate::prelude::EncoderFor<$ename> for &'_ $ename {
fn encode_for(&self, buf: &mut $crate::BufWriter<'_>) {
<$repr as $crate::prelude::EncoderFor<$repr>>::encode_for(&(**self as $repr), buf);
}
}
);
)*
};
}
#[doc(inline)]
pub use __protocol as protocol;
#[macro_export]
#[doc(hidden)]
macro_rules! r#if {
(__is_empty__ [] {$($true:tt)*} else {$($false:tt)*}) => {
$($true)*
};
(__is_empty__ [$($x:tt)+] {$($true:tt)*} else {$($false:tt)*}) => {
$($false)*
};
(__has__ [$($x:tt)+] {$($true:tt)*}) => {
$($true)*
};
(__has__ [] {$($true:tt)*}) => {
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! make_static {
(type=$ty:ty) => { $crate::type_mapper::map_types!(match $ty {
_T<'a> => _T<'static>,
_T<'a, _T2> => _T<'static, recurse!(_T2)>,
_T<'a, _T2, _T3> => _T<'static, recurse!(_T2), recurse!(_T3)>,
_T<_T2> => _T<recurse!(_T2)>,
_T<_T2, _T3> => _T<recurse!(_T2), recurse!(_T3)>,
_T => _T,
}) };
}
#[doc(hidden)]
#[macro_export]
macro_rules! protocol_builder {
(__struct__, struct $name:ident <$lt:lifetime> {
super($($super:ident)?),
docs($($sdoc:meta),*),
fields($({
name($field:ident),
type($type:ty),
value($(value = ($value:expr))? $(no_value = $no_value:ident)? $(auto = $auto:ident)?),
docs($fdoc:expr),
$($rest:tt)*
},)*),
}) => {
$crate::paste!(
$( #[$sdoc] )?
#[doc = concat!("\n\nAvailable fields: \n\n" $(
, " - [`", stringify!($field), "`](Self::", stringify!($field), "()): ", $fdoc,
$( " (value = `", stringify!($value), "`)", )?
"\n\n"
)* )]
#[derive(Copy, Clone, Default)]
pub struct $name<$lt> {
pub(crate) buf: &$lt [u8],
$(
$field: $type,
)*
}
impl PartialEq for $name<'_> {
fn eq(&self, other: &Self) -> bool {
self.buf.eq(other.buf)
}
}
impl Eq for $name<'_> {}
impl std::fmt::Debug for $name<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut s = f.debug_struct(stringify!($name));
$(
s.field(stringify!($field), &self.$field);
)*
s.finish()
}
}
impl AsRef<[u8]> for $name<'_> {
fn as_ref(&self) -> &[u8] {
self.buf.as_ref()
}
}
#[allow(unused)]
impl <'a> $name<'a> {
#[inline]
pub const fn is_buffer(buf: &'a [u8]) -> bool {
<Self as $crate::prelude::StructMeta>::FIELDS.matches_field_constants(buf)
}
#[inline]
pub fn new(mut buf: &'a [u8]) -> Result<Self, ParseError> {
let res = <$name<'a> as $crate::prelude::DecoderFor<$name<'a>>>::decode_for(&mut buf);
if buf.len() > 0 {
return Err(ParseError::TooLong(buf.len()));
}
res
}
$(
#[doc = $fdoc]
pub fn $field(&self) -> $type {
self.$field
}
)*
pub fn to_vec(self) -> Vec<u8> {
self.buf.to_vec()
}
}
);
};
(__meta__, struct $name:ident <$lt:lifetime> {
super($($super:ident)?),
docs($($sdoc:meta),*),
fields($({
name($field:ident),
type($type:ty),
value($(value = ($value:expr))? $(no_value = $no_value:ident)? $(auto = $auto:ident)?),
docs($fdoc:expr),
$($rest:tt)*
},)*),
}) => {
$crate::paste!(
#[allow(unused)]
#[allow(non_camel_case_types)]
#[derive(Eq, PartialEq)]
#[repr(u8)]
enum [<$name Fields>] {
$(
$field,
)*
}
impl <$lt> $crate::prelude::StructMeta for $name<$lt> {
const FIELDS: $crate::prelude::StructFields = $crate::prelude::StructFields::new(&
$crate::prelude::StructFieldComputed::new([
$(
$crate::prelude::StructField {
name: stringify!($field),
meta: &(<$type as DataType>::META),
value: $crate::r#if!(__is_empty__ [$($value)?] { None } else { Some($($value)? as usize) }),
},
)*
]));
type Struct<'__struct> = $name<'__struct>;
fn new<'__next_lifetime>(buf: &'__next_lifetime [u8]) -> Result<Self::Struct<'__next_lifetime>, ParseError> {
Self::Struct::<'__next_lifetime>::new(buf)
}
fn to_vec(&self) -> Vec<u8> {
self.buf.to_vec()
}
}
impl $crate::prelude::StructAttributeFixedSize<{<$name<'_> as $crate::prelude::StructMeta>::IS_FIXED_SIZE}> for $name<'_> {
}
impl $crate::prelude::StructAttributeHasLengthField<{<$name<'_> as $crate::prelude::StructMeta>::HAS_LENGTH_FIELD}> for $name<'_> {
}
impl $crate::prelude::StructAttributeFieldCount<{<$name<'_> as $crate::prelude::StructMeta>::FIELD_COUNT}> for $name<'_> {
}
$crate::declare_type!(DataType, $name<'a>, builder: [<$name Builder>], flags=[struct], {});
impl<'a> $crate::prelude::DecoderFor<'a, $name<'a>> for $name<'a> {
fn decode_for(buf: &mut &'a [u8]) -> Result<Self, ParseError> {
let mut new = $name::default();
let start_buf = *buf;
$(
new.$field = <$type as $crate::prelude::DecoderFor<$type>>::decode_for(buf)?;
)*
new.buf = start_buf;
Ok(new)
}
}
impl<'a> $crate::prelude::EncoderFor<$name<'static>> for $name<'a> {
fn encode_for(&self, buf: &mut $crate::BufWriter<'_>) {
buf.write(&self.buf);
}
}
);
};
(__struct_builder__, $( #[$sdoc:meta] )? struct $orig_name:ident $name:ident<$lt:lifetime> $($use_default:ident)? ($(
(
docs($sfdoc:expr)
name($sfield:ident)
type($stype:ty)
generic($sgeneric:ident)
no_value($sno_value:ident)
)
)*)
fields($({
name($field:ident),
type($type:ty),
value($(value = ($value:expr))? $(no_value = $no_value:ident)? $(auto = $auto:ident)?),
docs($fdoc:expr)
},)*),
) => {
#[derive(Debug, Default)]
pub struct $name<$($sgeneric = $crate::make_static!(type=$stype)),*> where $(
$sgeneric: $crate::prelude::EncoderFor<$crate::make_static!(type=$stype)>,
)* {
$(
#[doc = $sfdoc]
pub $sfield: $sgeneric,
)*
}
impl <$($sgeneric),*> $crate::prelude::BuilderFor for $name<$($sgeneric),*> where $(
$sgeneric: $crate::prelude::EncoderFor<$crate::make_static!(type=$stype)>,
)* {
type Message = $orig_name<'static>;
}
impl <$($sgeneric),*> $crate::prelude::EncoderFor<$orig_name<'static>> for $name<$($sgeneric),*> where $(
$sgeneric: $crate::prelude::EncoderFor<$crate::make_static!(type=$stype)>,
)* {
fn encode_for(&self, buf: &mut $crate::BufWriter<'_>) {
#[allow(unused)]
let value = self;
$(
$crate::r#if!(__is_empty__ [$($value)?] {
$crate::r#if!(__is_empty__ [$($auto)?] {
$crate::prelude::EncoderFor::<$crate::make_static!(type=$type)>::encode_for(&value.$field, buf);
} else {
let auto_offset = buf.size();
$crate::prelude::EncoderFor::<$crate::make_static!(type=$type)>::encode_for(&<$type as Default>::default(), buf);
});
} else {
<$type as DataType>::encode_usize(buf, $($value)? as usize);
});
)*
$(
$crate::r#if!(__has__ [$($auto)?] {
let len = (buf.size() - auto_offset) as u32;
buf.write_rewind(auto_offset, &len.to_be_bytes());
});
)*
}
}
impl <$($sgeneric),*> $crate::prelude::EncoderFor<$orig_name<'static>> for &'_ $name<$($sgeneric),*> where $(
$sgeneric: $crate::prelude::EncoderFor<$crate::make_static!(type=$stype)>,
)* {
fn encode_for(&self, buf: &mut $crate::BufWriter<'_>) {
<$name<$($sgeneric),*> as $crate::prelude::EncoderFor<$orig_name<'static>>>::encode_for(self, buf);
}
}
};
(__builder__, struct $name:ident <$lt:lifetime> {
super($($super:ident)?),
docs($($sdoc:meta),*),
fields($({
name($field:ident),
type($type:ty),
value($(value = ($value:expr))? $(no_value = $no_value:ident)? $(auto = $auto:ident)?),
docs($fdoc:expr),
$($rest:tt)*
},)*),
}) => {
$crate::paste!(
$crate::r#if!(__is_empty__ [$($($no_value)?)*] {
$crate::protocol_builder!(__struct_builder__, $( #[$sdoc] )? struct $name [<$name Builder>]<$lt> __use_default_to_construct
()
fields($({
name($field),
type($type),
value($(value = ($value))? $(no_value = $no_value)? $(auto = $auto)?),
docs($fdoc)
},)*),
);
} else {
$crate::protocol_builder!(__struct_builder__, $( #[$sdoc] )? struct $name [<$name Builder>]<$lt>
($($(
(
docs($fdoc)
name($field)
type($type)
generic([<$field:upper>])
no_value($no_value)
)
)?)*) fields($({
name($field),
type($type),
value($(value = ($value))? $(no_value = $no_value)? $(auto = $auto)?),
docs($fdoc)
},)*),
);
});
);
};
}
#[cfg(test)]
mod tests {
use crate::prelude::StructAttributeHasLengthField;
use pretty_assertions::assert_eq;
mod fixed_only {
use super::*;
crate::protocol!(
struct FixedOnly<'a> {
a: u8,
}
);
static_assertions::assert_impl_any!(FixedOnly::<'static>: StructAttributeHasLengthField<false>);
static_assertions::assert_not_impl_any!(FixedOnly::<'static>: StructAttributeHasLengthField<true>);
static_assertions::assert_impl_all!(FixedOnly<'static>: DecoderFor<'static, FixedOnly<'static>>, EncoderFor<FixedOnly<'static>>);
}
mod fixed_only_value {
crate::protocol!(
struct FixedOnlyValue <'a> {
a: u8 = 1,
}
);
}
mod mixed {
crate::protocol!(
struct Mixed <'a> {
a: u8 = 1,
s: ZTString<'a>,
}
);
}
mod docs {
crate::protocol!(
struct Docs <'a> {
a: u8 = 1,
s: ZTString<'a>,
}
);
}
mod length {
use super::*;
crate::protocol!(
struct WithLength<'a> {
a: u8,
l: len,
}
);
static_assertions::assert_impl_any!(WithLength::<'static>: StructAttributeHasLengthField<true>);
static_assertions::assert_not_impl_any!(WithLength::<'static>: StructAttributeHasLengthField<false>);
}
mod array {
crate::protocol!(
struct StaticArray<'a> {
a: u8,
l: [u8; 4],
}
);
}
mod string {
crate::protocol!(
struct HasLString<'a> {
s: LString<'a>,
}
);
}
mod has_enum {
crate::protocol!(
struct HasEnum<'a> {
e: MyEnum,
}
#[repr(u8)]
enum MyEnum {
#[default]
A = 1,
B = 2,
}
);
}
macro_rules! assert_stringify {
(($($struct:tt)*), ($($expected:tt)*)) => {
$crate::struct_elaborate!(assert_stringify(__internal__ ($($expected)*)) => $($struct)*);
};
(__internal__ ($($expected:tt)*), $($struct:tt)*) => {
if stringify!($($struct)*).replace(char::is_whitespace, "") != stringify!($($expected)*).replace(char::is_whitespace, "") {
assert_eq!(stringify!($($struct)*), stringify!($($expected)*));
}
};
}
#[test]
fn empty_struct() {
assert_stringify!((struct Foo <'a> {}), (struct Foo <'a> { super (), docs(), fields(), }));
}
#[test]
fn fixed_size_fields() {
assert_stringify!((struct Foo<'a> {
a: u8,
b: u8,
}), (struct Foo<'a>
{
super (),
docs(),
fields({
name(a), type (u8), value(no_value = no_value),
docs(concat!("`", stringify! (a), "` field.")),
},
{
name(b), type (u8), value(no_value = no_value),
docs(concat!("`", stringify! (b), "` field.")),
},),
}));
}
#[test]
fn mixed_fields() {
assert_stringify!((struct Foo<'a> {
a: u8,
l: len,
s: ZTString,
c: i16,
d: [u8; 4],
e: ZTArray<ZTString>,
}), (struct Foo<'a>
{
super (),
docs(),
fields({
name(a), type (u8), value(no_value = no_value),
docs(concat!("`", stringify! (a), "` field.")),
},
{
name(l), type ($crate::prelude::Length),
value(auto = auto), docs(concat!("`", stringify! (l), "` field.")),
},
{
name(s), type (ZTString),
value(no_value = no_value),
docs(concat!("`", stringify! (s), "` field.")),
},
{
name(c), type (i16), value(no_value = no_value),
docs(concat!("`", stringify! (c), "` field.")),
},
{
name(d), type ([u8; 4]),
value(no_value = no_value),
docs(concat!("`", stringify! (d), "` field.")),
},
{
name(e), type (ZTArray<ZTString>),
value(no_value = no_value),
docs(concat!("`", stringify! (e), "` field.")),
},
),
}));
}
}