Skip to main content

macroonz_compiler/kind/
stamp.rs

1//! The kind home's declaration stamps: one closed vocabulary from one declaration, and one related kind set with its complete disposition record.
2
3/// Declares one closed vocabulary: the enum, its complete roster, and one declared name per row.
4///
5/// For a list of names and nothing else: a role is written by hand instead, because a role also names a destination and an implementation says that better than a stamp with an extra column.
6///
7/// # Examples
8///
9/// ```rust
10/// macroonz_compiler::roster! {
11///     /// Which direction a codec covers.
12///     pub enum Direction {
13///         /// Typed value to canonical bytes.
14///         Encode = "encode",
15///         /// Canonical bytes to typed value.
16///         Decode = "decode",
17///     }
18/// }
19///
20/// assert_eq!(Direction::ALL, &[Direction::Encode, Direction::Decode]);
21/// assert_eq!(Direction::Decode.name(), "decode");
22/// ```
23#[macro_export]
24macro_rules! roster {
25    (
26        $(#[$note:meta])*
27        $vis:vis enum $name:ident {
28            $( $(#[$row:meta])* $variant:ident = $declared:literal ),+ $(,)?
29        }
30    ) => {
31        $(#[$note])*
32        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33        $vis enum $name {
34            $( $(#[$row])* $variant, )+
35        }
36
37        impl $name {
38            /// The complete roster, in declaration order.
39            $vis const ALL: &'static [Self] = &[$( Self::$variant ),+];
40
41            /// This row's declared name.
42            #[must_use]
43            $vis const fn name(self) -> &'static str {
44                match self {
45                    $( Self::$variant => $declared, )+
46                }
47            }
48        }
49    };
50}
51
52/// Declares one set of kinds: a marker type and its [`Kind`](crate::kind::Kind) implementation per row, the enumerated set, its [`KindSet`](crate::kind::KindSet) implementation, and its [`DispositionRecord`](crate::kind::DispositionRecord).
53///
54/// One declaration, so the marker, the set, and the record cannot drift apart.
55/// A kind added to a declaration grows all three together and stops the compiler at every construction of the record until somebody says what happens to it.
56/// The record then becomes a [`DispositionSet`](crate::kind::DispositionSet) only after the compiler independently checks every surrendered name and the whole row count against the set's declaration.
57///
58/// The seat is the field name the record carries a row's answer under, declared beside the kind rather than composed from the marker's spelling, for the same reason the declared name beside it is: a field renamed by every refactor of a Rust identifier is a field nobody can rely on.
59///
60/// # Examples
61///
62/// ```rust
63/// pub type Greeting = &'static str;
64///
65/// macroonz_compiler::kinds! {
66///     set = GreetKinds;
67///     dispositions = GreetDispositions;
68///
69///     /// Projects a declaration into the implementation that greets.
70///     GreetImpl = "greet.impl", greet_impl => Greeting, SoleRole, NoQuestions;
71/// }
72///
73/// use macroonz_compiler::{Disposition, DispositionSet, KindSet, NoQuestions, OwnerFact, SoleRole};
74///
75/// assert_eq!(<GreetKinds as KindSet>::NAMES, &["greet.impl"]);
76/// assert_eq!(GreetKinds::GreetImpl.name(), "greet.impl");
77///
78/// let record = GreetDispositions {
79///     greet_impl: Disposition::NotApplicable {
80///         because: OwnerFact { home: "greet", name: "not-applicable" },
81///     },
82/// };
83/// assert!(DispositionSet::<GreetKinds>::complete(record).is_ok());
84/// ```
85#[macro_export]
86macro_rules! kinds {
87    (
88        set = $set:ident;
89        dispositions = $record:ident;
90        $(
91            $(#[$note:meta])*
92            $kind:ident = $declared:literal, $seat:ident => $content:ty, $role:ty, $question:ty
93        );+ $(;)?
94    ) => {
95        $(
96            $(#[$note])*
97            #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
98            pub struct $kind;
99
100            impl $crate::kind::Kind for $kind {
101                const NAME: &'static str = $declared;
102                type Content = $content;
103                type Role = $role;
104                type Question = $question;
105            }
106        )+
107
108        /// The kinds this set names, one row each.
109        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
110        pub enum $set {
111            $( $(#[$note])* $kind ),+
112        }
113
114        impl $set {
115            /// The complete set, in declaration order.
116            pub const ALL: &'static [Self] = &[$( Self::$kind ),+];
117
118            /// This row's kind's declared name, read off the kind itself.
119            #[must_use]
120            pub const fn name(self) -> &'static str {
121                match self {
122                    $( Self::$kind => <$kind as $crate::kind::Kind>::NAME ),+
123                }
124            }
125        }
126
127        impl $crate::kind::KindSet for $set {
128            type Dispositions = $record;
129
130            const NAMES: &'static [&'static str] =
131                &[$( <$kind as $crate::kind::Kind>::NAME ),+];
132        }
133
134        /// What happened to every kind of the set: one required seat per row.
135        #[must_use = "a disposition record is what happened to every kind of the set"]
136        #[derive(Debug, Clone, PartialEq, Eq)]
137        pub struct $record {
138            $(
139                #[doc = concat!("What happened to the `", $declared, "` kind.")]
140                pub $seat: $crate::kind::Disposition
141            ),+
142        }
143
144        impl $record {
145            /// What happened to one kind of the set.
146            ///
147            /// Total: every row reads to exactly one seat, and a row admitted later stops the compiler here until somebody says which seat carries it.
148            #[must_use]
149            pub const fn under(&self, row: $set) -> &$crate::kind::Disposition {
150                match row {
151                    $( $set::$kind => &self.$seat ),+
152                }
153            }
154        }
155
156        impl $crate::kind::DispositionRecord for $record {
157            fn into_dispositions(
158                self,
159            ) -> impl Iterator<Item = (&'static str, $crate::kind::Disposition)> {
160                [$( (<$kind as $crate::kind::Kind>::NAME, self.$seat) ),+].into_iter()
161            }
162        }
163    };
164}