#[derive(Clone)]
pub struct LayoutField {
pub path: &'static str,
pub ty: &'static str,
pub lo: u32,
pub hi: u32,
pub nested: Option<fn() -> &'static [LayoutField]>,
}
pub trait BodyLayout {
fn layout() -> &'static [LayoutField];
}
impl std::fmt::Debug for LayoutField {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.nested.is_some() {
write!(
f,
"{} bytes {:#04x}..{:#04x} ({})",
self.path,
self.lo / 8,
(self.hi + 1) / 8,
self.ty,
)
} else {
write!(
f,
"{} bits {}..={} ({})",
self.path, self.lo, self.hi, self.ty
)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cbin::{self, Cbin, Header};
use crate::fields::{ControlKind, Unit};
use nord_bits_derive::bitbody;
use std::io::Cursor;
#[bitbody(2)]
#[derive(Default)]
struct Inner {
#[bits(0..=0)]
pub flag: bool,
#[bits(4..=11)]
pub level: u8,
}
#[bitbody(6)]
struct Outer {
#[bits(0..=15)]
word: u16,
#[at(0x02..0x04)]
pub inner: Inner,
#[bits(40..=47)]
pub level: u8,
}
fn body() -> Outer {
let mut b = Outer::try_from([0xab, 0xcd, 0x0f, 0xf0, 0xff, 0x00]).unwrap();
b.word = 0x0102;
b.inner.level = 0x55;
b.level = 7;
b
}
#[test]
fn the_codec_is_the_declaration() {
let raw = <[u8; 6]>::from(&body());
assert_eq!(raw, [0x01, 0x02, 0x05, 0x50, 0xff, 0x07]);
let back = Outer::try_from(raw).unwrap();
assert_eq!(back.word, 0x0102);
assert_eq!(back.inner.level, 0x55);
assert_eq!(back.level, 7);
}
#[test]
fn a_bitbody_rides_the_container() {
for generation in [cbin::Generation::V1, cbin::Generation::V0] {
let mut header = Header::new("tstb", (2, 5), 7);
header.generation = generation;
let file = Cbin {
header,
body: body(),
};
let mut bytes = Cursor::new(Vec::new());
file.write_to(&mut bytes).unwrap();
let mut bytes = Cursor::new(bytes.into_inner());
let back: Cbin<Outer> = cbin::read(&mut bytes, "tstb").unwrap();
assert_eq!(back.header.slot(), (2, 5));
assert_eq!(<[u8; 6]>::from(&back.body), <[u8; 6]>::from(&body()));
}
}
#[test]
fn paths_recurse_through_nested_bodies() {
let b = body();
let paths: Vec<String> = b.fields().into_iter().map(|f| f.path).collect();
assert_eq!(paths, ["inner.flag", "inner.level", "level"]);
let mut b = body();
b.set_field("inner.level", "3").unwrap();
assert_eq!(b.inner.level, 3);
b.set_field("level", "9").unwrap();
assert_eq!(b.level, 9);
assert_eq!(b.inner.level, 3);
assert!(b.set_field("word", "1").is_err(), "private is not a path");
}
#[bitbody(6)]
struct Named {
#[bits(0..=6)]
pub volume: crate::components::Level,
#[bits(7..=14)]
pub volume_wheel: crate::components::MorphTarget,
#[bits(15..=22)]
pub absent_wheel: crate::components::MorphTarget,
#[bits(23..=26)]
pub drawbar_4: crate::components::Drawbar,
#[bits(27..=30)]
pub bar: crate::components::Drawbar,
#[bits(31..=38)]
#[morphs(volume)]
pub misnamed_wheel: crate::components::MorphTarget,
#[bits(39..=42)]
#[rank(7)]
pub seventh: crate::components::Drawbar,
}
#[test]
fn a_name_binds_a_morph_slot_and_places_a_drawbar() {
let specs = Named::field_specs();
let of = |name: &str| specs.iter().find(|s| s.name == name).expect(name).control;
assert_eq!(
of("volume_wheel"),
ControlKind::Morph { of: Some("volume") }
);
assert_eq!(of("absent_wheel"), ControlKind::Morph { of: None });
assert_eq!(
of("misnamed_wheel"),
ControlKind::Morph { of: Some("volume") }
);
let drawbar = <crate::components::Drawbar as crate::bits::Packed>::CONTROL;
assert_eq!(of("drawbar_4"), drawbar.ranked(4));
assert_eq!(of("bar"), drawbar);
assert_eq!(of("seventh"), drawbar.ranked(7));
assert_eq!(of("volume"), ControlKind::Knob(Unit::Panel10));
}
#[test]
fn the_layout_is_readable_as_data() {
let fields = Outer::layout();
let rendered: Vec<String> = fields.iter().map(|f| format!("{f:?}")).collect();
assert_eq!(
rendered,
[
"word bits 0..=15 (u16)",
"inner bytes 0x02..0x04 (Inner)",
"level bits 40..=47 (u8)",
],
);
let nested = fields[1].nested.expect("inner is nested");
let rendered: Vec<String> = nested().iter().map(|f| format!("{f:?}")).collect();
assert_eq!(
rendered,
["flag bits 0..=0 (bool)", "level bits 4..=11 (u8)"]
);
}
}