gel_db_protocol/
gen.rs

1/// Performs a first-pass parse on a struct, filling out some additional
2/// metadata that makes the jobs of further macro passes much simpler.
3///
4/// This macro takes a `next` parameter which allows you to funnel the
5/// structured data from the macro into the next macro.
6///
7/// This is a "push-down automation" and refers to how metadata and parsed
8/// information are "pushed down" through the macro’s recursive structure. Each
9/// level of the macro adds its own layer of processing and metadata
10/// accumulation, eventually leading to the final output.
11///
12/// It begins by extracting and analyzing the fields of the `struct`, capturing
13/// associated metadata such as attributes and types. This macro takes a `next`
14/// parameter, which is another macro to be invoked after the current one
15/// completes its task, allowing for a seamless chaining of macros where each
16/// one builds upon the results of the previous.
17///
18/// The macro first makes some initial classifications of the fields based on
19/// their types, then processes presence or absence of values, and finally
20/// handles missing documentation.
21///
22/// As it processes each field, the macro recursively calls itself, accumulating
23/// metadata and updating the state.
24///
25/// Once all fields have been processed, the macro enters the final stage, where
26/// it reconstructs an enriched `struct`-like data blob using the accumulated
27/// metadata. It then passes this enriched `struct` to the `next` macro for
28/// further processing.
29#[doc(hidden)]
30#[macro_export]
31macro_rules! struct_elaborate {
32    (
33        $next:ident $( ($($next_args:tt)*) )? =>
34        $( #[ $sdoc:meta ] )*
35        struct $name:ident <$lt:lifetime> $(: $super:ident)? {
36            $(
37                $( #[ doc = $fdoc:literal ] )* $field:ident :
38                    $ty:ty
39                    $( = $value:literal)?
40            ),*
41            $(,)?
42        }
43    ) => {
44        // paste! is necessary here because it allows us to re-interpret a "ty"
45        // as an explicit type pattern below.
46        $crate::paste!($crate::struct_elaborate!(__builder_type__
47            fields($(
48                [
49                    // Note that we double the type so we can re-use some output
50                    // patterns in `__builder_type__`
51                    type( $ty )( $ty ),
52                    value($($value)?),
53                    docs($($fdoc)*),
54                    name($field),
55                ]
56            )*)
57            // Accumulator for field data.
58            accum()
59            // Save the original struct parts so we can build the remainder of
60            // the struct at the end.
61            original($next $( ($($next_args)*) )?
62                => $(#[$sdoc])* struct $name <$lt> $(: $super)? {})
63        ););
64    };
65
66    // End of push-down automation - jumps to `__finalize__`
67    (__builder_type__ fields() accum($($faccum:tt)*) original($($original:tt)*)) => {
68        $crate::struct_elaborate!(__finalize__ accum($($faccum)*) original($($original)*));
69    };
70
71    // Translate 'len' to Length (with auto value).
72    (__builder_type__ fields([type(len)(len), value(), $($rest:tt)*] $($frest:tt)*) $($srest:tt)*) => {
73        $crate::struct_elaborate!(__builder_docs__ fields([type($crate::prelude::Length), value(auto=auto), $($rest)*] $($frest)*) $($srest)*);
74    };
75    // Translate 'len' to Length (with a value present).
76    (__builder_type__ fields([type(len)(len), value($($value:tt)+), $($rest:tt)*] $($frest:tt)*) $($srest:tt)*) => {
77        $crate::struct_elaborate!(__builder_docs__ fields([type($crate::prelude::Length), value(value=($($value)*)), $($rest)*] $($frest)*) $($srest)*);
78    };
79    // Translate fixed-size arrays to FixedArray.
80    (__builder_type__ fields([type([$elem:ty; $len:literal])($ty:ty), $($rest:tt)*] $($frest:tt)*) $($srest:tt)*) => {
81        $crate::struct_elaborate!(__builder_value__ fields([type([$elem;$len]), $($rest)*] $($frest)*) $($srest)*);
82    };
83
84    // Fallback for other types - variable sized
85    (__builder_type__ fields([type($ty:ty)($ty2:ty), $($rest:tt)*] $($frest:tt)*) $($srest:tt)*) => {
86        $crate::struct_elaborate!(__builder_value__ fields([type($ty), $($rest)*] $($frest)*) $($srest)*);
87    };
88
89    // Next, mark the presence or absence of a value
90    (__builder_value__ fields([
91        type($ty:ty), value(), $($rest:tt)*
92    ] $($frest:tt)*) $($srest:tt)*) => {
93        $crate::struct_elaborate!(__builder_docs__ fields([type($ty), value(no_value=no_value), $($rest)*] $($frest)*) $($srest)*);
94    };
95    (__builder_value__ fields([
96        type($ty:ty), value($($value:tt)+), $($rest:tt)*
97    ] $($frest:tt)*) $($srest:tt)*) => {
98        $crate::struct_elaborate!(__builder_docs__ fields([type($ty), value(value=($($value)*)), $($rest)*] $($frest)*) $($srest)*);
99    };
100
101    // Next, handle missing docs by generating a stand-in.
102    (__builder_docs__ fields([
103        type($ty:ty), value($($value:tt)*), docs(), name($field:ident), $($rest:tt)*
104    ] $($frest:tt)*) $($srest:tt)*) => {
105        $crate::struct_elaborate!(__builder__ fields([type($ty), value($($value)*), docs(concat!("`", stringify!($field), "` field.")), name($field), $($rest)*] $($frest)*) $($srest)*);
106    };
107    (__builder_docs__ fields([
108        type($ty:ty), value($($value:tt)*), docs($($fdoc:literal)+), $($rest:tt)*
109    ] $($frest:tt)*) $($srest:tt)*) => {
110        $crate::struct_elaborate!(__builder__ fields([type($ty), value($($value)*), docs(concat!($($fdoc)+)), $($rest)*] $($frest)*) $($srest)*);
111    };
112
113
114    // Push down the field to the accumulator
115    (__builder__ fields([
116        type($ty:ty), value($($value:tt)*), docs($fdoc:expr), name($field:ident), $($rest:tt)*
117    ] $($frest:tt)*) accum($($faccum:tt)*) original($($original:tt)*)) => {
118        $crate::struct_elaborate!(__builder_type__ fields($($frest)*) accum(
119            $($faccum)*
120            {
121                name($field),
122                type($ty),
123                value($($value)*),
124                docs($fdoc),
125            },
126        ) original($($original)*));
127    };
128
129    // Write the final "elaborated" struct into a call to the `next` macro.
130    (__finalize__
131        accum($($accum:tt)*) original($next:ident $( ($($next_args:tt)*) )? =>
132        $( #[ $sdoc:meta ] )* struct $name:ident <$lt:lifetime> $(: $super:ident)? {})
133    ) => {
134        $next ! (
135            $( $($next_args)* , )?
136            struct $name <$lt> {
137                super($($super)?),
138                docs($($sdoc),*),
139                fields(
140                    $($accum)*
141                ),
142            }
143        );
144    }
145}
146
147/// Generates a protocol definition from a Rust-like DSL.
148///
149/// LIMITATION: Enums must appear after structs.
150///
151/// ```nocompile
152/// struct Foo {
153///     bar: u8,
154///     baz: u16 = 123,
155/// }
156///
157/// #[repr(u8)]
158/// enum MyEnum {
159///     A = 1,
160///     B = 2,
161/// }
162/// ```
163#[doc(hidden)]
164#[macro_export]
165macro_rules! __protocol {
166    (
167        $( $( #[ doc = $sdoc:literal ] )*
168            struct $name:ident <$lt:lifetime> $(: $super:ident)? { $($struct:tt)+ }
169        )+
170        $(  #[repr($repr:ty)] $( #[ doc = $edoc:literal ] )*
171            enum $ename:ident { $($(#[$default:meta])? $emname:ident = $emvalue:literal),+ $(,)? }
172        )*
173    ) => {
174        use $crate::protocol_builder;
175        use $crate::prelude::*;
176
177        $(
178            $crate::paste!(
179                $crate::struct_elaborate!(protocol_builder(__struct__) => $( #[ doc = $sdoc ] )* struct $name <$lt> $(: $super)? { $($struct)+ } );
180                $crate::struct_elaborate!(protocol_builder(__meta__) => $( #[ doc = $sdoc ] )* struct $name <$lt> $(: $super)? { $($struct)+ } );
181                $crate::struct_elaborate!(protocol_builder(__builder__) => $( #[ doc = $sdoc ] )* struct $name <$lt> $(: $super)? { $($struct)+ } );
182            );
183        )+
184
185        $(
186            $crate::paste!(
187                $(#[doc = $edoc])*
188                #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
189                #[repr($repr)]
190                pub enum $ename {
191                    $($(#[$default])? $emname = $emvalue),+
192                }
193
194                impl $crate::prelude::EnumMeta for $ename {
195                    const VALUES: &'static [(&'static str, usize)] = &[
196                        $((stringify!($emname), $emvalue as _)),+
197                    ];
198                }
199
200                $crate::declare_type!(DataType, $ename, flags=[enum], {
201                    fn decode(buf: [u8; N]) -> Result<Self, ParseError> {
202                        let repr = $repr::from_be_bytes(buf);
203                        match repr {
204                            $(
205                                $emvalue => Ok($ename::$emname),
206                            )+
207                            _ => Err(ParseError::InvalidData),
208                        }
209                    }
210                    fn encode(value: Self) -> [u8; N] {
211                        $repr::to_be_bytes(value as _)
212                    }
213                });
214
215            );
216        )*
217    };
218}
219
220#[doc(inline)]
221pub use __protocol as protocol;
222
223/// Simple conditional macro to check whether values are present.
224#[macro_export]
225#[doc(hidden)]
226macro_rules! r#if {
227    (__is_empty__ [] {$($true:tt)*} else {$($false:tt)*}) => {
228        $($true)*
229    };
230    (__is_empty__ [$($x:tt)+] {$($true:tt)*} else {$($false:tt)*}) => {
231        $($false)*
232    };
233    (__has__ [$($x:tt)+] {$($true:tt)*}) => {
234        $($true)*
235    };
236    (__has__ [] {$($true:tt)*}) => {
237    };
238}
239
240#[doc(hidden)]
241#[macro_export]
242macro_rules! protocol_builder {
243    (__struct__, struct $name:ident <$lt:lifetime> {
244        super($($super:ident)?),
245        docs($($sdoc:meta),*),
246        fields($({
247            name($field:ident),
248            type($type:ty),
249            value($(value = ($value:expr))? $(no_value = $no_value:ident)? $(auto = $auto:ident)?),
250            docs($fdoc:expr),
251            $($rest:tt)*
252        },)*),
253    }) => {
254        $crate::paste!(
255            $( #[$sdoc] )?
256            #[doc = concat!("\n\nAvailable fields: \n\n" $(
257                , " - [`", stringify!($field), "`](Self::", stringify!($field), "()): ", $fdoc,
258                $( "  (value = `", stringify!($value), "`)", )?
259                "\n\n"
260            )* )]
261            #[derive(Copy, Clone, Default)]
262            pub struct $name<$lt> {
263                pub(crate) buf: &$lt [u8],
264                $(
265                    $field: $type,
266                )*
267            }
268
269            impl PartialEq for $name<'_> {
270                fn eq(&self, other: &Self) -> bool {
271                    self.buf.eq(other.buf)
272                }
273            }
274
275            impl Eq for $name<'_> {}
276
277            impl std::fmt::Debug for $name<'_> {
278                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279                    let mut s = f.debug_struct(stringify!($name));
280                    $(
281                        s.field(stringify!($field), &self.$field);
282                    )*
283                    s.finish()
284                }
285            }
286
287            impl AsRef<[u8]> for $name<'_> {
288                fn as_ref(&self) -> &[u8] {
289                    self.buf.as_ref()
290                }
291            }
292
293            #[allow(unused)]
294            impl <'a> $name<'a> {
295                /// Checks the constant values for this struct to determine whether
296                /// this message matches.
297                #[inline]
298                pub const fn is_buffer(buf: &'a [u8]) -> bool {
299                    <Self as $crate::prelude::StructMeta>::FIELDS.matches_field_constants(buf)
300                }
301
302                /// Creates a new instance of this struct from a given buffer.
303                #[inline]
304                pub fn new(mut buf: &'a [u8]) -> Result<Self, ParseError> {
305                    let mut new = $name::<'a>::default();
306                    new.buf = buf;
307                    $(
308                        new.$field = <$type as DataType>::decode(&mut buf)?;
309                    )*
310                    Ok(new)
311                }
312
313                $(
314                    #[doc = $fdoc]
315                    pub fn $field(&self) -> $type {
316                        self.$field
317                    }
318                )*
319
320                pub fn to_vec(self) -> Vec<u8> {
321                    self.buf.to_vec()
322                }
323            }
324        );
325    };
326
327    (__meta__, struct $name:ident <$lt:lifetime> {
328        super($($super:ident)?),
329        docs($($sdoc:meta),*),
330        fields($({
331            name($field:ident),
332            type($type:ty),
333            value($(value = ($value:expr))? $(no_value = $no_value:ident)? $(auto = $auto:ident)?),
334            docs($fdoc:expr),
335            $($rest:tt)*
336        },)*),
337    }) => {
338        $crate::paste!(
339            #[allow(unused)]
340            #[allow(non_camel_case_types)]
341            #[derive(Eq, PartialEq)]
342            #[repr(u8)]
343            enum [<$name Fields>] {
344                $(
345                    $field,
346                )*
347            }
348
349            #[allow(unused)]
350            impl $name<'_> {
351                // pub const FIELD_COUNT: usize = [$(stringify!($field)),*].len();
352                // $($(pub const [<$field:upper _VALUE>]: <$type as $crate::prelude::DataType>::WithLifetime<'static> = super::access::FieldAccess::<$type>::constant($value as usize);)?)*
353            }
354
355            /// Implements a trait containing the fields of the struct, allowing
356            /// us to compute some useful things.
357            impl <$lt> $crate::prelude::StructMeta for $name<$lt> {
358                const FIELDS: $crate::prelude::StructFields = $crate::prelude::StructFields::new(&
359                    $crate::prelude::StructFieldComputed::new([
360                        $(
361                            $crate::prelude::StructField {
362                                name: stringify!($field),
363                                meta: &(<$type as DataType>::META),
364                                value: $crate::r#if!(__is_empty__ [$($value)?] { None } else { Some($($value)? as usize) }),
365                            },
366                        )*
367                    ]));
368
369                type Struct<'__struct> = $name<'__struct>;
370
371                fn new<'__next_lifetime>(buf: &'__next_lifetime [u8]) -> Result<Self::Struct<'__next_lifetime>, ParseError> {
372                    Self::Struct::<'__next_lifetime>::new(buf)
373                }
374
375                fn to_vec(&self) -> Vec<u8> {
376                    self.buf.to_vec()
377                }
378            }
379
380            /// Implements a trait indicating that the struct has a fixed size.
381            /// This needs to be a trait-generic rather than and associated
382            /// constant for us to use elsewhere.
383            impl $crate::prelude::StructAttributeFixedSize<{<$name<'_> as $crate::prelude::StructMeta>::IS_FIXED_SIZE}> for $name<'_> {
384            }
385
386            /// Implements a trait indicating that the struct has a length field.
387            impl $crate::prelude::StructAttributeHasLengthField<{<$name<'_> as $crate::prelude::StructMeta>::HAS_LENGTH_FIELD}> for $name<'_> {
388            }
389
390            /// Implements a trait indicating that the struct has a field count.
391            impl $crate::prelude::StructAttributeFieldCount<{<$name<'_> as $crate::prelude::StructMeta>::FIELD_COUNT}> for $name<'_> {
392            }
393
394            $crate::declare_type!(DataType, $name<'a>, builder: [<$name Builder>] <'a>, flags=[struct],
395            {
396                fn decode(buf: &mut &[u8]) -> Result<Self, ParseError> {
397                    let mut new = $name::default();
398                    $(
399                        new.$field = <$type as DataType>::decode(buf)?;
400                    )*
401                    Ok(new)
402                }
403                fn encode(buf: &mut BufWriter, value: &Self) {
404                    // For structs that don't use it
405                    _ = value;
406                    $(
407                        $crate::r#if!(__is_empty__ [$($value)?] {
408                            $crate::r#if!(__is_empty__ [$($auto)?] {
409                                // value is no_value (present in builder)
410                                <$type as DataType>::encode(buf, &value.$field);
411                            } else {
412                                // value is auto (not present in builder)
413                                let auto_offset = buf.size();
414                                <$type as DataType>::encode(buf, &Default::default());
415                            });
416                        } else {
417                            // value is set, not present in builder
418                            <$type as DataType>::encode_usize(buf, $($value)? as usize);
419                        });
420                    )*
421
422                    $(
423                        $crate::r#if!(__has__ [$($auto)?] {
424                            let len = (buf.size() - auto_offset) as u32;
425                            buf.write_rewind(auto_offset, &len.to_be_bytes());
426                        });
427                    )*
428
429                }
430            });
431        );
432    };
433
434    (__builder__, struct $name:ident <$lt:lifetime> {
435        super($($super:ident)?),
436        docs($($sdoc:meta),*),
437        fields($({
438            name($field:ident),
439            type($type:ty),
440            value($(value = ($value:expr))? $(no_value = $no_value:ident)? $(auto = $auto:ident)?),
441            docs($fdoc:expr),
442            $($rest:tt)*
443        },)*),
444    }) => {
445        $crate::paste!(
446            $crate::r#if!(__is_empty__ [$($($no_value)?)*] {
447                $( #[$sdoc] )?
448                // No unfixed-value fields
449                #[derive(Default, Eq, PartialEq)]
450                pub struct [<$name Builder>]<'a> {
451                    __no_fields_use_default: std::marker::PhantomData<&'a ()>
452                }
453
454                impl <'a> std::fmt::Debug for [<$name Builder>]<'a> {
455                    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
456                        write!(f, "{}Builder", stringify!($name))
457                    }
458                }
459            } else {
460                $( #[$sdoc] )?
461                #[derive(Debug, Default, Eq, PartialEq)]
462                pub struct [<$name Builder>]<$lt> {
463                    // Because of how macros may expand in the context of struct
464                    // fields, we need to do a * repeat, then a ? repeat and
465                    // somehow use $no_value in the remainder of the pattern.
466                    $($(
467                        #[doc = $fdoc]
468                        pub $field: $crate::r#if!(__has__ [$no_value] {
469                            <$type as DataType>::BuilderForStruct<$lt>
470                        }),
471                    )?)*
472                }
473            });
474
475            impl [<$name Builder>]<'_> {
476                /// Convert this builder into a vector of bytes. This is generally
477                /// not the most efficient way to perform serialization.
478                #[allow(unused)]
479                pub fn to_vec(&self) -> Vec<u8> {
480                    let mut vec = Vec::with_capacity(256);
481                    let mut buf = $crate::BufWriter::new(&mut vec);
482                    <$name as DataType>::encode(&mut buf, self);
483                    match buf.finish() {
484                        Ok(size) => {
485                            vec.truncate(size);
486                            vec
487                        },
488                        Err(size) => {
489                            vec.resize(size, 0);
490                            let mut buf = $crate::BufWriter::new(&mut vec);
491                            <$name as DataType>::encode(&mut buf, self);
492                            // Will not fail this second time
493                            let size = buf.finish().unwrap();
494                            vec.truncate(size);
495                            vec
496                        }
497                    }
498                }
499
500                #[allow(unused)]
501                pub fn measure(&self) -> usize {
502                    let mut buf = Vec::new();
503                    let mut writer = $crate::BufWriter::new(&mut buf);
504                    <$name as DataType>::encode(&mut writer, self);
505                    writer.finish().unwrap_err()
506                }
507            }
508        );
509    };
510}
511
512#[cfg(test)]
513mod tests {
514    use crate::prelude::StructAttributeHasLengthField;
515    use pretty_assertions::assert_eq;
516
517    mod fixed_only {
518        use super::*;
519
520        crate::protocol!(
521            struct FixedOnly<'a> {
522                a: u8,
523            }
524        );
525
526        static_assertions::assert_impl_any!(FixedOnly::<'static>: StructAttributeHasLengthField<false>);
527        static_assertions::assert_not_impl_any!(FixedOnly::<'static>: StructAttributeHasLengthField<true>);
528    }
529
530    mod fixed_only_value {
531        crate::protocol!(
532            struct FixedOnlyValue <'a> {
533                a: u8 = 1,
534            }
535        );
536    }
537
538    mod mixed {
539        crate::protocol!(
540            struct Mixed <'a> {
541                a: u8 = 1,
542                s: ZTString<'a>,
543            }
544        );
545    }
546
547    mod docs {
548        crate::protocol!(
549            /// Docs
550            struct Docs <'a> {
551                /// Docs
552                a: u8 = 1,
553                /// Docs
554                s: ZTString<'a>,
555            }
556        );
557    }
558
559    mod length {
560        use super::*;
561
562        crate::protocol!(
563            struct WithLength<'a> {
564                a: u8,
565                l: len,
566            }
567        );
568
569        static_assertions::assert_impl_any!(WithLength::<'static>: StructAttributeHasLengthField<true>);
570        static_assertions::assert_not_impl_any!(WithLength::<'static>: StructAttributeHasLengthField<false>);
571    }
572
573    mod array {
574        crate::protocol!(
575            struct StaticArray<'a> {
576                a: u8,
577                l: [u8; 4],
578            }
579        );
580    }
581
582    mod string {
583        crate::protocol!(
584            struct HasLString<'a> {
585                s: LString<'a>,
586            }
587        );
588    }
589
590    mod has_enum {
591        crate::protocol!(
592            struct HasEnum<'a> {
593                e: MyEnum,
594            }
595
596            #[repr(u8)]
597            enum MyEnum {
598                #[default]
599                A = 1,
600                B = 2,
601            }
602        );
603    }
604
605    macro_rules! assert_stringify {
606        (($($struct:tt)*), ($($expected:tt)*)) => {
607            $crate::struct_elaborate!(assert_stringify(__internal__ ($($expected)*)) => $($struct)*);
608        };
609        (__internal__ ($($expected:tt)*), $($struct:tt)*) => {
610            // We don't want whitespace to impact this comparison
611            if stringify!($($struct)*).replace(char::is_whitespace, "") != stringify!($($expected)*).replace(char::is_whitespace, "") {
612                assert_eq!(stringify!($($struct)*), stringify!($($expected)*));
613            }
614        };
615    }
616
617    #[test]
618    fn empty_struct() {
619        assert_stringify!((struct Foo <'a> {}), (struct Foo <'a> { super (), docs(), fields(), }));
620    }
621
622    #[test]
623    fn fixed_size_fields() {
624        assert_stringify!((struct Foo<'a>  {
625                    a: u8,
626                    b: u8,
627                }), (struct Foo<'a>
628        {
629            super (),
630            docs(),
631            fields({
632                name(a), type (u8), value(no_value = no_value),
633                docs(concat!("`", stringify! (a), "` field.")),
634            },
635            {
636                name(b), type (u8), value(no_value = no_value),
637                docs(concat!("`", stringify! (b), "` field.")),
638            },),
639        }));
640    }
641
642    #[test]
643    fn mixed_fields() {
644        assert_stringify!((struct Foo<'a> {
645                    a: u8,
646                    l: len,
647                    s: ZTString,
648                    c: i16,
649                    d: [u8; 4],
650                    e: ZTArray<ZTString>,
651                }), (struct Foo<'a>
652        {
653            super (),
654            docs(),
655            fields({
656                name(a), type (u8), value(no_value = no_value),
657                docs(concat!("`", stringify! (a), "` field.")),
658            },
659            {
660                name(l), type ($crate::prelude::Length),
661                value(auto = auto), docs(concat!("`", stringify! (l), "` field.")),
662            },
663            {
664                name(s), type (ZTString),
665                value(no_value = no_value),
666                docs(concat!("`", stringify! (s), "` field.")),
667            },
668            {
669                name(c), type (i16), value(no_value = no_value),
670                docs(concat!("`", stringify! (c), "` field.")),
671            },
672            {
673                name(d), type ([u8; 4]),
674                value(no_value = no_value),
675                docs(concat!("`", stringify! (d), "` field.")),
676            },
677            {
678                name(e), type (ZTArray<ZTString>),
679                value(no_value = no_value),
680                docs(concat!("`", stringify! (e), "` field.")),
681            },
682        ),
683        }));
684    }
685}