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}