1#![doc = include_str!("../README.md")]
2#![no_std]
3#![deny(warnings, missing_docs)]
4
5#[macro_use]
6mod macros;
7pub mod types;
8
9#[cfg(test)]
10extern crate alloc;
11
12#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
14#[repr(C)]
15pub struct DigitLayout {
16 code: u32,
17 group: u16,
18 size: u16,
19}
20
21#[derive(Clone, Copy, PartialEq, Eq, Debug)]
23pub enum LayoutContent {
24 Unsigned {
26 width: u32,
28 },
29 Real {
31 exponent: u32,
33 mantissa: u32,
35 },
36 Named {
38 name: [u8; 8],
40 },
41}
42
43#[repr(u32)]
44enum DigitLayoutType {
45 Unsigned = 0xE000_0000, Real = 0xC000_0000, Named = 0, }
49const UNSIGNED: u32 = DigitLayoutType::Unsigned as _;
50const SIGNED: u32 = DigitLayoutType::Real as _;
51const HEAD: u32 = UNSIGNED;
52
53impl DigitLayout {
54 #[inline]
56 pub const fn unsigned(width: u16, group: u16) -> Self {
57 assert!(width.is_power_of_two() && width >= 8);
58
59 let body = width as u32;
60 assert!(body & HEAD == 0);
61 Self::new(DigitLayoutType::Unsigned, body, group, width / 8 * group)
62 }
63
64 #[inline]
66 pub const fn real(exponent: u16, mantissa: u16, group: u16) -> Self {
67 let width = 1 + exponent + mantissa;
68 assert!(width.is_power_of_two() && width >= 8);
69
70 let body = ((exponent as u32) << 16) | mantissa as u32;
71 assert!(body & HEAD == 0);
72 Self::new(DigitLayoutType::Real, body, group, width / 8 * group)
73 }
74
75 pub const fn named(name: &str, group: u16, size: u16) -> Self {
77 let mut exp = 1;
78 let mut bytes = name.as_bytes();
79 let mut body = 0;
80 while let [b, tail @ ..] = bytes {
81 bytes = tail;
82
83 let b = match b {
84 b'0'..=b'9' => *b - b'0',
85 b'a'..=b'z' => *b - b'a' + 10,
86 b'A'..=b'Z' => *b - b'A' + 10,
87 b'_' | b'.' => continue,
88 _ => panic!("Invalid character in digit name"),
89 };
90 body += (b as u32 + 1) * exp;
91 const GUARD: u32 = 0xC000_0000; assert!(body & GUARD != GUARD);
93 assert!(exp & GUARD != GUARD);
94 exp *= 37; }
96 Self::new(DigitLayoutType::Named, body, group, size)
97 }
98
99 #[inline(always)]
100 const fn new(ty: DigitLayoutType, body: u32, group: u16, size: u16) -> Self {
101 Self {
102 code: (ty as u32) | body,
103 group,
104 size,
105 }
106 }
107
108 #[inline]
110 pub const fn to_u64(self) -> u64 {
111 unsafe { core::mem::transmute(self) }
112 }
113
114 pub const fn group_size(self) -> usize {
116 self.group as _
117 }
118
119 pub const fn nbytes(self) -> usize {
121 self.size as usize
122 }
123
124 pub const fn decode(self) -> LayoutContent {
126 let head = self.code & HEAD;
127 match head {
128 UNSIGNED => LayoutContent::Unsigned {
129 width: self.decode_unsigned(),
130 },
131 SIGNED => LayoutContent::Real {
132 exponent: self.decode_exponent(),
133 mantissa: self.decode_mantissa(),
134 },
135 _ => {
136 let mut name = [0; 8];
137 let mut body = self.code;
138 let mut i = 0;
139 while body > 0 {
140 let b = (body % 37) as u8 - 1;
141 name[i] = b + if b < 10 { b'0' } else { b'a' - 10 };
142 body /= 37;
143 i += 1;
144 }
145 LayoutContent::Named { name }
146 }
147 }
148 }
149
150 #[inline(always)]
151 const fn decode_unsigned(self) -> u32 {
152 self.code & !HEAD
153 }
154
155 #[inline(always)]
156 const fn decode_exponent(self) -> u32 {
157 ((self.code & !HEAD) >> 16) & 0xFF
158 }
159
160 #[inline(always)]
161 const fn decode_mantissa(self) -> u32 {
162 self.code & 0xFFFF
163 }
164}
165
166use core::fmt;
167
168impl fmt::Display for DigitLayout {
169 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
170 use LayoutContent::*;
171 match self.decode() {
172 Unsigned { width } => {
173 if self.group == 1 {
174 write!(f, "u{width}")
175 } else {
176 write!(f, "[u{width}; {}]", self.group)
177 }
178 }
179 Real { exponent, mantissa } => {
180 let width = 1 + exponent + mantissa;
181 if self.group == 1 {
182 write!(f, "f{width}_e{exponent}m{mantissa}")
183 } else {
184 write!(f, "[f{width}_e{exponent}m{mantissa}; {}]", self.group)
185 }
186 }
187 Named { name } => {
188 for c in name {
189 if c == 0 {
190 break;
191 }
192 write!(f, "{}", c as char)?;
193 }
194 Ok(())
195 }
196 }
197 }
198}
199
200#[test]
201fn test_unsigned() {
202 assert!(matches!(
203 types::U8.decode(),
204 LayoutContent::Unsigned { width: 8 }
205 ));
206
207 assert!(matches!(
208 types::U16.decode(),
209 LayoutContent::Unsigned { width: 16 }
210 ));
211
212 assert!(matches!(
213 types::U32.decode(),
214 LayoutContent::Unsigned { width: 32 }
215 ));
216
217 assert!(matches!(
218 types::U64.decode(),
219 LayoutContent::Unsigned { width: 64 }
220 ));
221}
222
223#[test]
224fn test_real() {
225 assert!(matches!(
226 types::I8.decode(),
227 LayoutContent::Real {
228 exponent: 0,
229 mantissa: 7,
230 }
231 ));
232
233 assert!(matches!(
234 types::I16.decode(),
235 LayoutContent::Real {
236 exponent: 0,
237 mantissa: 15,
238 }
239 ));
240
241 assert!(matches!(
242 types::I32.decode(),
243 LayoutContent::Real {
244 exponent: 0,
245 mantissa: 31,
246 }
247 ));
248
249 assert!(matches!(
250 types::I64.decode(),
251 LayoutContent::Real {
252 exponent: 0,
253 mantissa: 63,
254 }
255 ));
256
257 assert!(matches!(
258 types::F16.decode(),
259 LayoutContent::Real {
260 exponent: 5,
261 mantissa: 10,
262 }
263 ));
264
265 assert!(matches!(
266 types::BF16.decode(),
267 LayoutContent::Real {
268 exponent: 8,
269 mantissa: 7,
270 }
271 ));
272
273 assert!(matches!(
274 types::F32.decode(),
275 LayoutContent::Real {
276 exponent: 8,
277 mantissa: 23,
278 }
279 ));
280
281 assert!(matches!(
282 types::F64.decode(),
283 LayoutContent::Real {
284 exponent: 11,
285 mantissa: 52,
286 }
287 ));
288}
289
290#[test]
291fn test_named() {
292 assert!(matches!(
293 types::Bool.decode(),
294 LayoutContent::Named {
295 name: [b'b', b'o', b'o', b'l', 0, 0, 0, 0]
296 }
297 ));
298
299 let q8_0 = DigitLayout::named("Q8_0", 32, 34);
300 assert!(matches!(
301 q8_0.decode(),
302 LayoutContent::Named {
303 name: [b'q', b'8', b'0', 0, 0, 0, 0, 0]
304 }
305 ));
306
307 let iq2xxs = DigitLayout::named("IQ2XXS", 256, 66);
308 assert!(matches!(
309 iq2xxs.decode(),
310 LayoutContent::Named {
311 name: [b'i', b'q', b'2', b'x', b'x', b's', 0, 0]
312 }
313 ));
314
315 let zzzzzz = DigitLayout::named("zzzzzz", 1, 1);
316 assert!(matches!(
317 zzzzzz.decode(),
318 LayoutContent::Named {
319 name: [b'z', b'z', b'z', b'z', b'z', b'z', 0, 0]
320 }
321 ));
322}
323
324#[test]
325fn test_decode_methods() {
326 let u8_layout = DigitLayout::unsigned(8, 1);
328 assert_eq!(u8_layout.decode_unsigned(), 8);
329
330 let u16_layout = DigitLayout::unsigned(16, 1);
331 assert_eq!(u16_layout.decode_unsigned(), 16);
332
333 let f32_layout = DigitLayout::real(8, 23, 1);
334 assert_eq!(f32_layout.decode_exponent(), 8);
335 assert_eq!(f32_layout.decode_mantissa(), 23);
336
337 let f64_layout = DigitLayout::real(11, 52, 1);
338 assert_eq!(f64_layout.decode_exponent(), 11);
339 assert_eq!(f64_layout.decode_mantissa(), 52);
340}
341
342#[test]
343fn test_group_size_and_nbytes() {
344 let layout1 = DigitLayout::unsigned(32, 4);
346 assert_eq!(layout1.group_size(), 4);
347 assert_eq!(layout1.nbytes(), 16); let layout2 = DigitLayout::real(8, 23, 2);
350 assert_eq!(layout2.group_size(), 2);
351 assert_eq!(layout2.nbytes(), 8); let layout3 = DigitLayout::named("test", 3, 12);
354 assert_eq!(layout3.group_size(), 3);
355 assert_eq!(layout3.nbytes(), 12);
356}
357
358#[test]
359fn test_to_u64() {
360 let layout = DigitLayout::unsigned(32, 1);
361 let u64_value = layout.to_u64();
362 assert_ne!(u64_value, 0);
363
364 let same_layout = DigitLayout::unsigned(32, 1);
365 assert_eq!(layout.to_u64(), same_layout.to_u64());
366 let different_layout = DigitLayout::unsigned(64, 1);
367 assert_ne!(layout.to_u64(), different_layout.to_u64());
368}
369
370#[test]
371fn test_display_impl() {
372 use alloc::string::String;
373 use core::fmt::Write;
374
375 struct TestWriter(String);
376
377 impl Write for TestWriter {
378 fn write_str(&mut self, s: &str) -> core::fmt::Result {
379 self.0.push_str(s);
380 Ok(())
381 }
382 }
383
384 let u8_layout = DigitLayout::unsigned(8, 1);
385 let mut writer = TestWriter(String::new());
386 write!(writer, "{}", u8_layout).unwrap();
387 assert_eq!(writer.0, "u8");
388
389 let u8_array_layout = DigitLayout::unsigned(8, 4);
390 let mut writer = TestWriter(String::new());
391 write!(writer, "{}", u8_array_layout).unwrap();
392 assert_eq!(writer.0, "[u8; 4]");
393
394 let f32_layout = DigitLayout::real(8, 23, 1);
395 let mut writer = TestWriter(String::new());
396 write!(writer, "{}", f32_layout).unwrap();
397 assert_eq!(writer.0, "f32_e8m23");
398
399 let f32_array_layout = DigitLayout::real(8, 23, 2);
400 let mut writer = TestWriter(String::new());
401 write!(writer, "{}", f32_array_layout).unwrap();
402 assert_eq!(writer.0, "[f32_e8m23; 2]");
403
404 let named_layout = DigitLayout::named("test", 1, 4);
405 let mut writer = TestWriter(String::new());
406 write!(writer, "{}", named_layout).unwrap();
407 assert_eq!(writer.0, "test");
408}
409
410#[test]
411fn test_named_edge_cases() {
412 use alloc::string::String;
413 use core::fmt::Write;
414
415 struct TestWriter(String);
416
417 impl Write for TestWriter {
418 fn write_str(&mut self, s: &str) -> core::fmt::Result {
419 self.0.push_str(s);
420 Ok(())
421 }
422 }
423
424 let empty_name = DigitLayout::named("", 1, 1);
425 let mut writer = TestWriter(String::new());
426 let _ = write!(writer, "{}", empty_name);
427
428 let alphanumeric = DigitLayout::named("a1B2c3", 1, 1);
429 assert!(matches!(
430 alphanumeric.decode(),
431 LayoutContent::Named {
432 name: [b'a', b'1', b'b', b'2', b'c', b'3', 0, 0]
433 }
434 ));
435
436 let with_special = DigitLayout::named("a_b.c", 1, 1);
437 assert!(matches!(
438 with_special.decode(),
439 LayoutContent::Named {
440 name: [b'a', b'b', b'c', 0, 0, 0, 0, 0]
441 }
442 ));
443}