1#[derive(Clone)]
14pub struct LayoutField {
15 pub path: &'static str,
18 pub ty: &'static str,
20 pub lo: u32,
21 pub hi: u32,
22 pub nested: Option<fn() -> &'static [LayoutField]>,
24}
25
26pub trait BodyLayout {
28 fn layout() -> &'static [LayoutField];
32}
33
34impl std::fmt::Debug for LayoutField {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 if self.nested.is_some() {
37 write!(
38 f,
39 "{} bytes {:#04x}..{:#04x} ({})",
40 self.path,
41 self.lo / 8,
42 (self.hi + 1) / 8,
43 self.ty,
44 )
45 } else {
46 write!(
47 f,
48 "{} bits {}..={} ({})",
49 self.path, self.lo, self.hi, self.ty
50 )
51 }
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58 use crate::cbin::{self, Cbin, Header};
59 use crate::fields::{ControlKind, Unit};
60 use nord_bits_derive::bitbody;
61 use std::io::Cursor;
62
63 #[bitbody(2)]
65 #[derive(Default)]
66 struct Inner {
67 #[bits(0..=0)]
68 pub flag: bool,
69 #[bits(4..=11)]
70 pub level: u8,
71 }
72
73 #[bitbody(6)]
76 struct Outer {
77 #[bits(0..=15)]
78 word: u16,
79
80 #[at(0x02..0x04)]
81 pub inner: Inner,
82
83 #[bits(40..=47)]
84 pub level: u8,
85 }
86
87 fn body() -> Outer {
88 let mut b = Outer::try_from([0xab, 0xcd, 0x0f, 0xf0, 0xff, 0x00]).unwrap();
89 b.word = 0x0102;
90 b.inner.level = 0x55;
91 b.level = 7;
92 b
93 }
94
95 #[test]
98 fn the_codec_is_the_declaration() {
99 let raw = <[u8; 6]>::from(&body());
100 assert_eq!(raw, [0x01, 0x02, 0x05, 0x50, 0xff, 0x07]);
102 let back = Outer::try_from(raw).unwrap();
103 assert_eq!(back.word, 0x0102);
104 assert_eq!(back.inner.level, 0x55);
105 assert_eq!(back.level, 7);
106 }
107
108 #[test]
111 fn a_bitbody_rides_the_container() {
112 for generation in [cbin::Generation::V1, cbin::Generation::V0] {
113 let mut header = Header::new("tstb", (2, 5), 7);
114 header.generation = generation;
115 let file = Cbin {
116 header,
117 body: body(),
118 };
119 let mut bytes = Cursor::new(Vec::new());
120 file.write_to(&mut bytes).unwrap();
121 let mut bytes = Cursor::new(bytes.into_inner());
122 let back: Cbin<Outer> = cbin::read(&mut bytes, "tstb").unwrap();
123 assert_eq!(back.header.slot(), (2, 5));
124 assert_eq!(<[u8; 6]>::from(&back.body), <[u8; 6]>::from(&body()));
125 }
126 }
127
128 #[test]
131 fn paths_recurse_through_nested_bodies() {
132 let b = body();
133 let paths: Vec<String> = b.fields().into_iter().map(|f| f.path).collect();
134 assert_eq!(paths, ["inner.flag", "inner.level", "level"]);
135
136 let mut b = body();
137 b.set_field("inner.level", "3").unwrap();
138 assert_eq!(b.inner.level, 3);
139 b.set_field("level", "9").unwrap();
142 assert_eq!(b.level, 9);
143 assert_eq!(b.inner.level, 3);
144 assert!(b.set_field("word", "1").is_err(), "private is not a path");
145 }
146
147 #[bitbody(6)]
150 struct Named {
151 #[bits(0..=6)]
152 pub volume: crate::components::Level,
153 #[bits(7..=14)]
154 pub volume_wheel: crate::components::MorphTarget,
155 #[bits(15..=22)]
156 pub absent_wheel: crate::components::MorphTarget,
157 #[bits(23..=26)]
158 pub drawbar_4: crate::components::Drawbar,
159 #[bits(27..=30)]
160 pub bar: crate::components::Drawbar,
161 #[bits(31..=38)]
162 #[morphs(volume)]
163 pub misnamed_wheel: crate::components::MorphTarget,
164 #[bits(39..=42)]
165 #[rank(7)]
166 pub seventh: crate::components::Drawbar,
167 }
168
169 #[test]
173 fn a_name_binds_a_morph_slot_and_places_a_drawbar() {
174 let specs = Named::field_specs();
175 let of = |name: &str| specs.iter().find(|s| s.name == name).expect(name).control;
176
177 assert_eq!(
178 of("volume_wheel"),
179 ControlKind::Morph { of: Some("volume") }
180 );
181 assert_eq!(of("absent_wheel"), ControlKind::Morph { of: None });
183 assert_eq!(
184 of("misnamed_wheel"),
185 ControlKind::Morph { of: Some("volume") }
186 );
187 let drawbar = <crate::components::Drawbar as crate::bits::Packed>::CONTROL;
188 assert_eq!(of("drawbar_4"), drawbar.ranked(4));
189 assert_eq!(of("bar"), drawbar);
190 assert_eq!(of("seventh"), drawbar.ranked(7));
191 assert_eq!(of("volume"), ControlKind::Knob(Unit::Panel10));
193 }
194
195 #[test]
198 fn the_layout_is_readable_as_data() {
199 let fields = Outer::layout();
200 let rendered: Vec<String> = fields.iter().map(|f| format!("{f:?}")).collect();
201 assert_eq!(
202 rendered,
203 [
204 "word bits 0..=15 (u16)",
205 "inner bytes 0x02..0x04 (Inner)",
206 "level bits 40..=47 (u8)",
207 ],
208 );
209
210 let nested = fields[1].nested.expect("inner is nested");
211 let rendered: Vec<String> = nested().iter().map(|f| format!("{f:?}")).collect();
212 assert_eq!(
213 rendered,
214 ["flag bits 0..=0 (bool)", "level bits 4..=11 (u8)"]
215 );
216 }
217}