use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::fmt;
use bitflags::bitflags;
use crate::tf;
#[derive(Debug, Clone)]
pub struct Registry {
kinds: HashMap<String, Kind>,
}
impl Registry {
pub fn builder() -> RegistryBuilder {
RegistryBuilder::default()
}
pub fn has_kind(&self, kind: &str) -> bool {
self.kinds.contains_key(kind)
}
pub fn base_kind(&self, kind: &str) -> Option<&str> {
self.kinds.get(kind)?.base.as_deref()
}
pub fn is_a(&self, derived: &str, base: &str) -> bool {
let mut current = derived;
loop {
if current == base {
return true;
}
match self.base_kind(current) {
Some(next) => current = next,
None => return false,
}
}
}
pub fn all_kinds(&self) -> Vec<&str> {
let mut kinds: Vec<&str> = self.kinds.keys().map(String::as_str).collect();
kinds.sort_unstable();
kinds
}
pub fn is_model(&self, kind: &str) -> bool {
self.lineage(kind).contains(Lineage::MODEL)
}
pub fn is_group(&self, kind: &str) -> bool {
self.lineage(kind).contains(Lineage::GROUP)
}
pub fn is_assembly(&self, kind: &str) -> bool {
self.lineage(kind).contains(Lineage::ASSEMBLY)
}
pub fn is_component(&self, kind: &str) -> bool {
self.lineage(kind).contains(Lineage::COMPONENT)
}
pub fn is_subcomponent(&self, kind: &str) -> bool {
self.lineage(kind).contains(Lineage::SUBCOMPONENT)
}
fn lineage(&self, kind: &str) -> Lineage {
self.kinds.get(kind).map_or(Lineage::empty(), |kind| kind.lineage)
}
}
impl Default for Registry {
fn default() -> Self {
Registry {
kinds: resolve(&built_in()),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct RegistryBuilder {
declared: Vec<(String, Declared)>,
}
impl RegistryBuilder {
#[must_use]
pub fn kind(mut self, decl: Decl<'_>) -> Self {
self.declared.push(decl.declared(Origin::Direct));
self
}
#[must_use]
pub fn kinds(mut self, source: &str, decls: &[Decl<'_>]) -> Self {
if decls.is_empty() {
return self;
}
let origin = Origin::Source(source.to_owned());
self.declared
.extend(decls.iter().map(|decl| decl.declared(origin.clone())));
self
}
pub fn build(self) -> Result<Registry, RegistryError> {
let mut kinds = built_in();
for (name, declared) in self.declared {
if !tf::is_valid_identifier(&name) {
return Err(RegistryError::InvalidName {
kind: name,
origin: declared.origin,
});
}
match kinds.entry(name) {
Entry::Occupied(first) => {
return Err(RegistryError::Duplicate {
kind: first.key().clone(),
first: first.get().origin.clone(),
second: declared.origin,
});
}
Entry::Vacant(slot) => {
slot.insert(declared);
}
}
}
let mut names: Vec<&String> = kinds.keys().collect();
names.sort_unstable();
for name in names {
let mut current = name;
let mut length = 0;
while let Some(base) = &kinds[current].base {
if !kinds.contains_key(base) {
return Err(RegistryError::UnknownBase {
kind: current.clone(),
base: base.clone(),
origin: kinds[current].origin.clone(),
});
}
length += 1;
if length > kinds.len() {
return Err(RegistryError::Cycle {
kind: name.clone(),
origin: kinds[name].origin.clone(),
});
}
current = base;
}
}
Ok(Registry { kinds: resolve(&kinds) })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Decl<'a> {
name: &'a str,
base: Option<&'a str>,
}
impl<'a> Decl<'a> {
pub const fn new(name: &'a str) -> Self {
Decl { name, base: None }
}
#[must_use]
pub const fn base(mut self, base: &'a str) -> Self {
self.base = if base.is_empty() { None } else { Some(base) };
self
}
pub const fn name(&self) -> &'a str {
self.name
}
pub const fn declared_base(&self) -> Option<&'a str> {
self.base
}
fn declared(&self, origin: Origin) -> (String, Declared) {
let declared = Declared {
base: self.base.map(str::to_owned),
origin,
};
(self.name.to_owned(), declared)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Origin {
BuiltIn,
Source(String),
Direct,
}
impl fmt::Display for Origin {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Origin::BuiltIn => f.write_str("the built-in hierarchy"),
Origin::Source(name) => write!(f, "`{name}`"),
Origin::Direct => f.write_str("a direct declaration"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum RegistryError {
#[error("kind `{kind}`, declared by {origin}, is not a valid identifier")]
InvalidName {
kind: String,
origin: Origin,
},
#[error("kind `{kind}` is declared by {first} and again by {second}")]
Duplicate {
kind: String,
first: Origin,
second: Origin,
},
#[error("kind `{kind}`, declared by {origin}, derives from the undeclared kind `{base}`")]
UnknownBase {
kind: String,
base: String,
origin: Origin,
},
#[error("kind `{kind}`, declared by {origin}, derives from itself")]
Cycle {
kind: String,
origin: Origin,
},
}
pub mod tokens {
pub const MODEL: &str = "model";
pub const GROUP: &str = "group";
pub const ASSEMBLY: &str = "assembly";
pub const COMPONENT: &str = "component";
pub const SUBCOMPONENT: &str = "subcomponent";
}
#[derive(Debug, Clone)]
struct Kind {
base: Option<String>,
lineage: Lineage,
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Lineage: u8 {
const MODEL = 1 << 0;
const GROUP = 1 << 1;
const ASSEMBLY = 1 << 2;
const COMPONENT = 1 << 3;
const SUBCOMPONENT = 1 << 4;
}
}
impl Lineage {
fn of(kind: &str) -> Lineage {
match kind {
tokens::MODEL => Lineage::MODEL,
tokens::GROUP => Lineage::GROUP,
tokens::ASSEMBLY => Lineage::ASSEMBLY,
tokens::COMPONENT => Lineage::COMPONENT,
tokens::SUBCOMPONENT => Lineage::SUBCOMPONENT,
_ => Lineage::empty(),
}
}
}
#[derive(Debug, Clone)]
struct Declared {
base: Option<String>,
origin: Origin,
}
fn built_in() -> HashMap<String, Declared> {
[
(tokens::SUBCOMPONENT, None),
(tokens::MODEL, None),
(tokens::COMPONENT, Some(tokens::MODEL)),
(tokens::GROUP, Some(tokens::MODEL)),
(tokens::ASSEMBLY, Some(tokens::GROUP)),
]
.into_iter()
.map(|(kind, base)| {
let declared = Declared {
base: base.map(str::to_owned),
origin: Origin::BuiltIn,
};
(kind.to_owned(), declared)
})
.collect()
}
fn resolve(declared: &HashMap<String, Declared>) -> HashMap<String, Kind> {
declared
.iter()
.map(|(name, kind)| {
let mut lineage = Lineage::empty();
let mut current = Some(name);
while let Some(kind) = current {
lineage |= Lineage::of(kind);
current = declared[kind].base.as_ref();
}
let resolved = Kind {
base: kind.base.clone(),
lineage,
};
(name.clone(), resolved)
})
.collect()
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
pub(crate) const SITE: &[Decl<'static>] = &[
Decl::new("chargroup").base("assembly"),
Decl::new("prop").base("component"),
Decl::new("rig").base("model"),
Decl::new("rivet").base("subcomponent"),
Decl::new("site_root"),
];
fn registry(decls: &[Decl<'_>]) -> Result<Registry, RegistryError> {
Registry::builder().kinds("site", decls).build()
}
#[test]
fn built_in_hierarchy() {
let kinds = Registry::default();
assert_eq!(
kinds.all_kinds(),
["assembly", "component", "group", "model", "subcomponent"]
);
assert_eq!(kinds.base_kind(tokens::ASSEMBLY), Some(tokens::GROUP));
assert_eq!(kinds.base_kind(tokens::GROUP), Some(tokens::MODEL));
assert_eq!(kinds.base_kind(tokens::COMPONENT), Some(tokens::MODEL));
assert_eq!(kinds.base_kind(tokens::MODEL), None);
assert_eq!(kinds.base_kind(tokens::SUBCOMPONENT), None);
assert!(kinds.is_model(tokens::ASSEMBLY) && kinds.is_group(tokens::ASSEMBLY));
assert!(kinds.is_assembly(tokens::ASSEMBLY) && !kinds.is_assembly(tokens::GROUP));
assert!(kinds.is_model(tokens::GROUP) && !kinds.is_component(tokens::GROUP));
assert!(kinds.is_model(tokens::COMPONENT) && !kinds.is_group(tokens::COMPONENT));
assert!(kinds.is_model(tokens::MODEL) && !kinds.is_group(tokens::MODEL));
assert!(kinds.is_subcomponent(tokens::SUBCOMPONENT) && !kinds.is_model(tokens::SUBCOMPONENT));
}
#[test]
fn unknown_kind_queries() {
let kinds = Registry::default();
assert!(!kinds.has_kind("chargroup"));
assert_eq!(kinds.base_kind("chargroup"), None);
assert!(!kinds.is_model("chargroup") && !kinds.is_subcomponent("chargroup"));
assert!(kinds.is_a("chargroup", "chargroup"), "equal names need no registration");
assert!(!kinds.is_a("chargroup", tokens::MODEL));
assert!(!kinds.is_a(tokens::MODEL, "chargroup"));
}
#[test]
fn declared_kinds() -> Result<(), RegistryError> {
let kinds = registry(&[Decl::new("test_model_kind").base("model"), Decl::new("test_root_kind")])?;
for kind in ["group", "model", "test_model_kind", "test_root_kind"] {
assert!(kinds.has_kind(kind), "{kind}");
assert!(kinds.all_kinds().contains(&kind), "{kind}");
}
assert_eq!(kinds.base_kind("test_root_kind"), None);
assert_eq!(kinds.base_kind("test_model_kind"), Some("model"));
assert!(kinds.is_model("test_model_kind"));
assert!(!kinds.is_group("test_model_kind") && !kinds.is_component("test_model_kind"));
assert!(!kinds.is_model("test_root_kind"));
Ok(())
}
#[test]
fn derived_lineage() -> Result<(), RegistryError> {
let kinds = registry(&[
Decl::new("chargroup").base("assembly"),
Decl::new("hero").base("chargroup"),
Decl::new("prop").base("component"),
Decl::new("rivet").base("subcomponent"),
])?;
assert!(kinds.is_assembly("hero") && kinds.is_group("hero") && kinds.is_model("hero"));
assert!(kinds.is_a("hero", "chargroup") && !kinds.is_a("chargroup", "hero"));
assert!(kinds.is_component("prop") && !kinds.is_group("prop"));
assert!(kinds.is_subcomponent("rivet") && !kinds.is_model("rivet"));
Ok(())
}
#[test]
fn empty_base_is_root() -> Result<(), RegistryError> {
assert_eq!(Decl::new("a").base(""), Decl::new("a"));
let kinds = registry(&[Decl::new("absent"), Decl::new("empty").base("")])?;
assert_eq!(kinds.base_kind("absent"), None);
assert_eq!(kinds.base_kind("empty"), None);
assert!(kinds.has_kind("absent") && kinds.has_kind("empty"));
Ok(())
}
#[test]
fn base_declared_later() -> Result<(), RegistryError> {
let kinds = Registry::builder()
.kinds("characters", &[Decl::new("hero").base("chargroup")])
.kinds("site", &[Decl::new("chargroup").base("assembly")])
.build()?;
assert!(kinds.is_group("hero"));
Ok(())
}
#[test]
fn invalid_name_refused() {
for name in ["", "9lives", "my:kind", "my-kind", "caf\u{e9}"] {
assert_eq!(
registry(&[Decl::new(name)]).unwrap_err(),
RegistryError::InvalidName {
kind: name.to_owned(),
origin: Origin::Source("site".to_owned()),
},
);
}
}
#[test]
fn duplicate_refused() {
let twice = Registry::builder()
.kinds("site", &[Decl::new("prop").base("component")])
.kind(Decl::new("prop").base("component"))
.build();
assert_eq!(
twice.unwrap_err(),
RegistryError::Duplicate {
kind: "prop".to_owned(),
first: Origin::Source("site".to_owned()),
second: Origin::Direct,
},
);
let built_in = registry(&[Decl::new("group").base("model")]);
assert_eq!(
built_in.unwrap_err(),
RegistryError::Duplicate {
kind: "group".to_owned(),
first: Origin::BuiltIn,
second: Origin::Source("site".to_owned()),
},
);
}
#[test]
fn unknown_base_refused() {
assert_eq!(
registry(&[Decl::new("hero").base("chargroup")]).unwrap_err(),
RegistryError::UnknownBase {
kind: "hero".to_owned(),
base: "chargroup".to_owned(),
origin: Origin::Source("site".to_owned()),
},
);
}
#[test]
fn cycle_refused() {
let own_base = registry(&[Decl::new("a").base("a")]);
assert!(matches!(own_base, Err(RegistryError::Cycle { kind, .. }) if kind == "a"));
let pair = registry(&[
Decl::new("a").base("b"),
Decl::new("b").base("a"),
Decl::new("c").base("model"),
]);
assert!(matches!(pair, Err(RegistryError::Cycle { kind, .. }) if kind == "a"));
}
#[test]
fn error_names_origins() {
let error = Registry::builder()
.kinds("site", &[Decl::new("group")])
.build()
.unwrap_err();
assert_eq!(
error.to_string(),
"kind `group` is declared by the built-in hierarchy and again by `site`"
);
}
}