Skip to main content

alux_shape_rust/
layout.rs

1//! Interpreting a shape as a Rust layout.
2//!
3//! The derive reads a term out of a layout; this reads a layout out of a term. Neither direction is
4//! privileged, which is what it means for a struct to be a carrier for a shape rather than its
5//! source — so a shape stated with no layout behind it can be given one when domain code wants a
6//! value to hold.
7
8use alux_shape::{FieldAlg, ShapeAlg, Sorts, Spelling, Words};
9use std::collections::BTreeMap;
10
11/// A shape, as this interpretation carries one: the type written at a use site, the declarations that
12/// use requires, and — when it is a product — the members another product would merge.
13#[derive(Debug, Clone, Default, PartialEq, Eq)]
14pub struct Layout {
15    ty: String,
16    declarations: BTreeMap<String, String>,
17    product: Option<Product>,
18}
19
20/// What a product states, before a name turns it into a declaration.
21#[derive(Debug, Clone, Default, PartialEq, Eq)]
22struct Product {
23    members: Vec<RustMember>,
24}
25
26/// One member of a product, as a field would state it.
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum RustMember {
29    /// A field of its own, under the words naming it.
30    Field { words: Vec<String>, ty: String, declarations: BTreeMap<String, String> },
31    /// Another product's members, held by a field that carries them.
32    Merge { ty: String, declarations: BTreeMap<String, String> },
33}
34
35impl RustMember {
36    /// The declarations this member's shape depends on.
37    fn declarations(&self) -> &BTreeMap<String, String> {
38        match self {
39            Self::Field { declarations, .. } | Self::Merge { declarations, .. } => declarations,
40        }
41    }
42}
43
44impl Layout {
45    /// The type written where this shape is used.
46    #[must_use]
47    pub fn ty(&self) -> &str {
48        &self.ty
49    }
50
51    /// Every declaration this layout depends on, in name order.
52    #[must_use]
53    pub fn module(&self) -> String {
54        let declarations: Vec<&str> = self.declarations.values().map(String::as_str).collect();
55
56        declarations.join("\n\n")
57    }
58
59    /// A layout written as `ty`, depending on the declarations gathered from `parts`.
60    fn of(ty: impl Into<String>, parts: &[&Self]) -> Self {
61        let mut declarations = BTreeMap::new();
62
63        for part in parts {
64            declarations.extend(part.declarations.clone());
65        }
66
67        Self { ty: ty.into(), declarations, product: None }
68    }
69}
70
71/// Interprets a shape as a Rust layout whose serialization writes names this way.
72///
73/// A field is named in snake case, as Rust names one, and the spelling the surface writes is stated
74/// once for the whole declaration — which is available to state because the term carries words and
75/// not a spelling.
76#[derive(Debug, Clone)]
77pub struct RustShape {
78    wire: Spelling,
79    known: Vec<(Vec<String>, String)>,
80}
81
82impl RustShape {
83    /// Emits layouts whose serialization spells names this way.
84    #[must_use]
85    pub fn new(wire: Spelling) -> Self {
86        Self { wire, known: Vec::new() }
87    }
88
89    /// States that a name is already a type here, and what it is called.
90    ///
91    /// A domain's leaves are not the primitives they are written as: two of them may share
92    /// `int(false, 64)` and remain different types. A name is what tells them apart, so a name this
93    /// host already has a type for is written as that type rather than declared again.
94    #[must_use]
95    pub fn known(mut self, words: Words<'_>, path: impl Into<String>) -> Self {
96        self.known.push((words.iter().map(|word| (*word).to_owned()).collect(), path.into()));
97
98        self
99    }
100
101    /// The type this host already has for a name, if it has one.
102    fn known_type(&self, words: Words<'_>) -> Option<&str> {
103        self.known
104            .iter()
105            .find(|(known, _)| known.iter().map(String::as_str).eq(words.iter().copied()))
106            .map(|(_, path)| path.as_str())
107    }
108
109    /// The attribute stating how this layout's names are written, when it is not how Rust writes them.
110    fn rename_all(&self) -> Option<&'static str> {
111        match self.wire {
112            Spelling::Snake => None,
113            Spelling::LowerCamel => Some("camelCase"),
114            Spelling::UpperCamel => Some("PascalCase"),
115            Spelling::Kebab => Some("kebab-case"),
116            Spelling::Screaming => Some("SCREAMING_SNAKE_CASE"),
117        }
118    }
119
120    /// Writes the attributes a declaration carries.
121    fn attributes(&self) -> String {
122        let derive = "#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]";
123
124        match self.rename_all() {
125            Some(rename) => format!("{derive}\n#[serde(rename_all = \"{rename}\")]"),
126            None => derive.to_owned(),
127        }
128    }
129}
130
131impl Sorts for RustShape {
132    type Ty = Layout;
133    type Field = RustMember;
134}
135
136impl ShapeAlg for RustShape {
137    fn truth(&self) -> Layout {
138        Layout::of("bool", &[])
139    }
140
141    fn unit(&self) -> Layout {
142        Layout::of("()", &[])
143    }
144
145    fn text(&self) -> Layout {
146        Layout::of("String", &[])
147    }
148
149    fn literal(&self, _text: &str) -> Layout {
150        // A constant is not a type. What a layout can hold is the text, and what the constant states
151        // is left to whatever writes it.
152        Layout::of("String", &[])
153    }
154
155    fn name_word(&self, _words: Words<'_>) -> Layout {
156        Layout::of("String", &[])
157    }
158
159    fn int(&self, signed: bool, bits: u16) -> Layout {
160        let sign = if signed { 'i' } else { 'u' };
161
162        Layout::of(format!("{sign}{bits}"), &[])
163    }
164
165    fn float(&self, bits: u16) -> Layout {
166        Layout::of(format!("f{bits}"), &[])
167    }
168
169    fn bytes(&self, len: Option<usize>) -> Layout {
170        match len {
171            Some(len) => Layout::of(format!("[u8; {len}]"), &[]),
172            None => Layout::of("Vec<u8>", &[]),
173        }
174    }
175
176    fn hex(&self, item: Layout) -> Layout {
177        // The writing is a wrapper, so the layout keeps the value and the wrapper states how it is
178        // written. A layout is generated against wrappers that serialize accordingly.
179        Layout::of(format!("Hex<{}>", item.ty), &[&item])
180    }
181
182    fn decimal(&self, item: Layout) -> Layout {
183        Layout::of(format!("Decimal<{}>", item.ty), &[&item])
184    }
185
186    fn base64(&self, item: Layout) -> Layout {
187        Layout::of(format!("Base64<{}>", item.ty), &[&item])
188    }
189
190    fn opt(&self, item: Layout) -> Layout {
191        Layout::of(format!("Option<{}>", item.ty), &[&item])
192    }
193
194    fn seq(&self, item: Layout) -> Layout {
195        Layout::of(format!("Vec<{}>", item.ty), &[&item])
196    }
197
198    fn map(&self, key: Layout, value: Layout) -> Layout {
199        Layout::of(format!("std::collections::BTreeMap<{}, {}>", key.ty, value.ty), &[&key, &value])
200    }
201
202    fn product(&self, fields: Vec<RustMember>) -> Layout {
203        let mut declarations = BTreeMap::new();
204
205        for field in &fields {
206            declarations.extend(field.declarations().clone());
207        }
208
209        // A product becomes a type only once it is named, since Rust states no anonymous record.
210        Layout { ty: "()".to_owned(), declarations, product: Some(Product { members: fields }) }
211    }
212
213    fn choice(&self, alternatives: Vec<Layout>) -> Layout {
214        // A choice is a type once it is named, and until then it is only its alternatives.
215        let parts: Vec<&Layout> = alternatives.iter().collect();
216        let mut layout = Layout::of("()", &parts);
217        layout.product = None;
218        layout.ty = alternatives.iter().map(|alternative| alternative.ty.clone()).collect::<Vec<_>>().join(" | ");
219
220        layout
221    }
222
223    fn named(&self, words: Words<'_>, body: Layout) -> Layout {
224        // A name this host already has a type for is that type; nothing is declared for it.
225        if let Some(path) = self.known_type(words) {
226            return Layout::of(path, &[&body]);
227        }
228
229        let name = Spelling::UpperCamel.spell(words);
230        let declaration = self.declare(&name, &body);
231        let mut layout = Layout::of(name.clone(), &[&body]);
232        layout.declarations.insert(name, declaration);
233        layout.product = body.product;
234
235        layout
236    }
237
238    fn reference(&self, words: Words<'_>) -> Layout {
239        Layout::of(Spelling::UpperCamel.spell(words), &[])
240    }
241}
242
243impl RustShape {
244    /// Declares a name for a shape: a struct where the shape is a product, an alias otherwise.
245    fn declare(&self, name: &str, body: &Layout) -> String {
246        let attributes = self.attributes();
247
248        match &body.product {
249            Some(product) => {
250                let fields = product
251                    .members
252                    .iter()
253                    .map(|member| match member {
254                        RustMember::Field { words, ty, .. } => {
255                            let field: Vec<&str> = words.iter().map(String::as_str).collect();
256
257                            format!("    pub {}: {ty},", Spelling::Snake.spell(&field))
258                        }
259                        RustMember::Merge { ty, .. } => format!(
260                            "    #[serde(flatten)]\n    pub {}: {ty},",
261                            Spelling::Snake.spell(&[&camel_to_snake(ty)]),
262                        ),
263                    })
264                    .collect::<Vec<_>>()
265                    .join("\n");
266
267                format!("{attributes}\npub struct {name} {{\n{fields}\n}}")
268            }
269            None => format!("pub type {name} = {};", body.ty),
270        }
271    }
272}
273
274/// Names a type in the case a field is named in.
275fn camel_to_snake(ty: &str) -> String {
276    let mut out = String::new();
277
278    for (index, character) in ty.chars().enumerate() {
279        if character.is_uppercase() && index > 0 {
280            out.push('_');
281        }
282
283        out.extend(character.to_lowercase());
284    }
285
286    out
287}
288
289impl FieldAlg for RustShape {
290    fn field(&self, words: Words<'_>, shape: Layout) -> RustMember {
291        RustMember::Field {
292            words: words.iter().map(|word| (*word).to_owned()).collect(),
293            ty: shape.ty,
294            declarations: shape.declarations,
295        }
296    }
297
298    fn merge(&self, shape: Layout) -> RustMember {
299        RustMember::Merge { ty: shape.ty, declarations: shape.declarations }
300    }
301}