1use alux_shape::{FieldAlg, ShapeAlg, Sorts, Spelling, Words};
9use std::collections::BTreeMap;
10
11#[derive(Debug, Clone, Default, PartialEq, Eq)]
14pub struct Layout {
15 ty: String,
16 declarations: BTreeMap<String, String>,
17 product: Option<Product>,
18}
19
20#[derive(Debug, Clone, Default, PartialEq, Eq)]
22struct Product {
23 members: Vec<RustMember>,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum RustMember {
29 Field { words: Vec<String>, ty: String, declarations: BTreeMap<String, String> },
31 Merge { ty: String, declarations: BTreeMap<String, String> },
33}
34
35impl RustMember {
36 fn declarations(&self) -> &BTreeMap<String, String> {
38 match self {
39 Self::Field { declarations, .. } | Self::Merge { declarations, .. } => declarations,
40 }
41 }
42}
43
44impl Layout {
45 #[must_use]
47 pub fn ty(&self) -> &str {
48 &self.ty
49 }
50
51 #[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 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#[derive(Debug, Clone)]
77pub struct RustShape {
78 wire: Spelling,
79 known: Vec<(Vec<String>, String)>,
80}
81
82impl RustShape {
83 #[must_use]
85 pub fn new(wire: Spelling) -> Self {
86 Self { wire, known: Vec::new() }
87 }
88
89 #[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 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 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 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 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 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 Layout { ty: "()".to_owned(), declarations, product: Some(Product { members: fields }) }
211 }
212
213 fn choice(&self, alternatives: Vec<Layout>) -> Layout {
214 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 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 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
274fn 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}