use crate::read::Fold;
use crate::registry::PropMeta;
use crate::spec::PropSpec;
pub trait Props: Sized {
const PROPS: &'static [PropMeta];
const PROP_SPECS: &'static [PropSpec];
#[doc(hidden)]
fn read_at(fold: &mut Fold<'_>, base: u16) -> Option<Self>;
}
pub const fn concat_prop_specs<const N: usize>(groups: &[&[PropSpec]]) -> [PropSpec; N] {
let mut out = [PropSpec::EMPTY; N];
let mut at = 0;
let mut g = 0;
while g < groups.len() {
let group = groups[g];
let mut i = 0;
while i < group.len() {
out[at] = group[i];
at += 1;
i += 1;
}
g += 1;
}
assert!(
at == N,
"`N` must be the summed length of the groups, or property spec metadata would not \
line up with its setting"
);
out
}
pub const fn concat_props<const N: usize>(groups: &[&[PropMeta]]) -> [PropMeta; N] {
let mut out = [PropMeta::new("", crate::ty::Ty::Any); N];
let mut at = 0;
let mut g = 0;
while g < groups.len() {
let group = groups[g];
let mut i = 0;
while i < group.len() {
out[at] = group[i];
at += 1;
i += 1;
}
g += 1;
}
assert!(
at == N,
"`N` must be the summed length of the groups, or the registry would describe a \
setting that does not exist"
);
let mut a = 0;
while a < N {
let mut b = a + 1;
while b < N {
assert!(
!str_eq(out[a].key, out[b].key),
"two flattened groups declare the same setting key, so one of them could \
never be reached: give one of them another key or prefix"
);
assert!(
!names_any(out[a].key, out[b].aliases),
"one flattened group's setting key is another's alias, so a lookup for that \
name could only ever reach one of them: rename one of the two"
);
assert!(
!names_any(out[b].key, out[a].aliases),
"one flattened group's setting key is another's alias, so a lookup for that \
name could only ever reach one of them: rename one of the two"
);
let mut i = 0;
while i < out[a].aliases.len() {
assert!(
!names_any(out[a].aliases[i], out[b].aliases),
"two flattened groups declare the same alias, so a lookup for it could \
only ever reach one of them: rename one of the two"
);
i += 1;
}
b += 1;
}
a += 1;
}
out
}
const fn names_any(name: &str, names: &[&str]) -> bool {
let mut i = 0;
while i < names.len() {
if str_eq(name, names[i]) {
return true;
}
i += 1;
}
false
}
const fn str_eq(a: &str, b: &str) -> bool {
let a = a.as_bytes();
let b = b.as_bytes();
if a.len() != b.len() {
return false;
}
let mut i = 0;
while i < a.len() {
if a[i] != b[i] {
return false;
}
i += 1;
}
true
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ty::Ty;
mod one_name_reaches_one_setting {
use super::*;
#[test]
#[should_panic(expected = "same setting key")]
fn two_groups_cannot_declare_one_key() {
static A: &[PropMeta] = &[PropMeta::new("jobs", Ty::Uint)];
static B: &[PropMeta] = &[PropMeta::new("jobs", Ty::Uint)];
let _ = concat_props::<2>(&[A, B]);
}
#[test]
#[should_panic(expected = "is another's alias")]
fn an_alias_cannot_shadow_another_groups_key() {
static A: &[PropMeta] = &[PropMeta {
aliases: &["threads"],
..PropMeta::new("jobs", Ty::Uint)
}];
static B: &[PropMeta] = &[PropMeta::new("threads", Ty::Uint)];
let _ = concat_props::<2>(&[A, B]);
}
#[test]
#[should_panic(expected = "is another's alias")]
fn a_key_cannot_be_shadowed_by_a_later_groups_alias() {
static A: &[PropMeta] = &[PropMeta::new("threads", Ty::Uint)];
static B: &[PropMeta] = &[PropMeta {
aliases: &["threads"],
..PropMeta::new("jobs", Ty::Uint)
}];
let _ = concat_props::<2>(&[A, B]);
}
#[test]
#[should_panic(expected = "same alias")]
fn two_groups_cannot_declare_one_alias() {
static A: &[PropMeta] = &[PropMeta {
aliases: &["shared"],
..PropMeta::new("jobs", Ty::Uint)
}];
static B: &[PropMeta] = &[PropMeta {
aliases: &["shared"],
..PropMeta::new("threads", Ty::Uint)
}];
let _ = concat_props::<2>(&[A, B]);
}
#[test]
fn distinct_names_join() {
static A: &[PropMeta] = &[PropMeta {
aliases: &["concurrency"],
..PropMeta::new("jobs", Ty::Uint)
}];
static B: &[PropMeta] = &[PropMeta {
aliases: &["task.concurrency"],
..PropMeta::new("task.jobs", Ty::Uint)
}];
let joined = concat_props::<2>(&[A, B]);
assert_eq!(joined[0].key, "jobs");
assert_eq!(joined[1].key, "task.jobs");
}
}
#[test]
fn groups_join_in_order_and_ids_are_positions() {
static OWN: &[PropMeta] = &[PropMeta::new("jobs", Ty::Uint)];
static CHILD: &[PropMeta] = &[
PropMeta::new("task.output", Ty::String),
PropMeta::new("task.jobs", Ty::Uint),
];
const N: usize = OWN.len() + CHILD.len();
static JOINED: [PropMeta; N] = concat_props(&[OWN, CHILD]);
assert_eq!(JOINED[0].key, "jobs");
assert_eq!(JOINED[1].key, "task.output");
assert_eq!(JOINED[2].key, "task.jobs");
}
#[test]
fn spec_metadata_joins_in_the_same_order_as_props() {
static OWN: &[PropSpec] = &[PropSpec {
help_heading: Some("Performance"),
writes_to: None,
extensions: &[],
}];
static CHILD: &[PropSpec] = &[
PropSpec {
writes_to: Some("git"),
..PropSpec::EMPTY
},
PropSpec::EMPTY,
];
const N: usize = OWN.len() + CHILD.len();
static JOINED: [PropSpec; N] = concat_prop_specs(&[OWN, CHILD]);
assert_eq!(JOINED[0].help_heading, Some("Performance"));
assert_eq!(JOINED[1].writes_to, Some("git"));
assert_eq!(JOINED[2], PropSpec::EMPTY);
}
}