pub mod export;
pub mod import;
pub mod option;
pub use option::ConfigOption;
use {
crate::{document::export::DocumentExport, syntax::DirectiveIter},
alloc::{
boxed::Box,
collections::{
BTreeMap,
btree_map::{Range, RangeMut},
},
string::String,
},
core::{
any::type_name,
fmt::{Debug, Display},
hash::{BuildHasher, Hash},
ops::Bound,
str::FromStr,
},
derive_more::{Display, Error, From},
hashbrown::{HashMap, HashTable, hash_map::EntryRef},
import::Importer,
lasso::{Rodeo, Spur},
};
#[derive(Debug, Default)]
pub struct Document {
sections: BTreeMap<SectionKey, SectionRecord>,
names: HashTable<(u64, SectionKey)>,
hash_state: hashbrown::DefaultHashBuilder,
types: Rodeo,
}
impl PartialEq for Document {
fn eq(&self, other: &Self) -> bool {
self.sections == other.sections
}
}
impl Document {
pub fn clear(&mut self) {
self.sections.clear();
self.names.clear();
self.types.clear();
}
pub fn insert(
&mut self,
type_: &str,
name: Option<impl AsRef<str> + Into<Box<str>>>,
value: Section,
) -> (SectionKey, Option<Section>) {
let type_key = TypeSymbol(self.types.get_or_intern(type_));
let maybe_conflicting_key = name
.as_ref()
.and_then(|name| self.section_key_of_name(name.as_ref()))
.copied();
let key = maybe_conflicting_key.unwrap_or_else(|| SectionKey {
type_key,
index: self.free_index(type_key),
});
let previous_section = match self.sections.entry(key) {
alloc::collections::btree_map::Entry::Vacant(e) => {
if let Some(name) = &name {
let hash = self.hash_state.hash_one(name.as_ref());
self.names.insert_unique(hash, (hash, key), |(h, _)| *h);
}
e.insert(SectionRecord {
name: name.map(Into::into),
section: value,
});
None
}
alloc::collections::btree_map::Entry::Occupied(e) => {
Some(core::mem::replace(&mut e.into_mut().section, value))
}
};
(key, previous_section)
}
pub fn merge_or_insert(
&mut self,
type_: &str,
name: Option<impl AsRef<str> + Into<Box<str>>>,
value: Section,
) -> Result<SectionKey, MergeTypeMismatch> {
let maybe_key = name
.as_ref()
.and_then(|name| self.section_key_of_name(name.as_ref()));
let Some(&key) = maybe_key else {
let (key, _) = self.insert(type_, name, value);
return Ok(key);
};
let type_key = TypeSymbol(self.types.get_or_intern(type_));
if type_key != key.type_key {
return Err(MergeTypeMismatch {
stored: key.type_key,
inserting: type_key,
});
}
let stored = self.get_mut(&key).unwrap_or_else(|| {
unreachable!(
"state consistency violation: we got name to a section which doesn't exist"
)
});
stored.merge(value);
Ok(key)
}
pub fn lookup_by_type<'this>(&'this self, type_: &str) -> LookupRange<'this> {
let Some(type_key) = self.type_key(type_) else {
return LookupRange::default();
};
LookupRange {
range: self.sections.range(lookup_range_by_type(type_key)),
}
}
pub fn lookup_by_type_mut<'this>(&'this mut self, type_: &str) -> LookupRangeMut<'this> {
let Some(type_key) = self.type_key(type_) else {
return LookupRangeMut::default();
};
LookupRangeMut {
range: self.sections.range_mut(lookup_range_by_type(type_key)),
}
}
#[must_use]
pub fn lookup_by_name(&self, name: &str) -> Option<&Section> {
self.get(self.section_key_of_name(name)?)
}
pub fn lookup_by_name_mut(&mut self, name: &str) -> Option<&mut Section> {
let &key = self.section_key_of_name(name)?;
self.get_mut(&key)
}
#[must_use]
pub fn lookup_by_type_name(&self, ty: &str, name: &str) -> Option<&Section> {
let key = self.section_key_of_name(name)?;
let type_key = self.type_key(ty)?;
(key.type_key == type_key).then(|| self.get(key)).flatten()
}
#[must_use]
pub fn lookup_by_type_name_mut(&mut self, ty: &str, name: &str) -> Option<&mut Section> {
let key = *self.section_key_of_name(name)?;
let type_key = self.type_key(ty)?;
(key.type_key == type_key)
.then(|| self.get_mut(&key))
.flatten()
}
#[must_use]
pub fn get(&self, key: &SectionKey) -> Option<&Section> {
self.sections.get(key).map(|r| &r.section)
}
#[must_use]
pub fn get_mut(&mut self, key: &SectionKey) -> Option<&mut Section> {
self.sections.get_mut(key).map(|r| &mut r.section)
}
pub fn remove(&mut self, key: SectionKey) -> Option<Section> {
let record = self.sections.remove(&key)?;
if let Some(name) = record.name {
let hash = self.hash_state.hash_one(&name[..]);
let maybe_entry = self.names.find_entry(hash, |(got_hash, got_key)| {
got_hash == &hash && got_key == &key
});
if let Ok(entry) = maybe_entry {
entry.remove();
}
}
Some(record.section)
}
#[must_use]
pub fn section_name(&self, key: SectionKey) -> Option<&str> {
self.sections.get(&key)?.name.as_deref()
}
#[must_use]
pub fn type_key(&self, name: &str) -> Option<TypeSymbol> {
self.types.get(name).map(TypeSymbol)
}
#[must_use]
pub fn type_name(&self, key: TypeSymbol) -> Option<&str> {
self.types.try_resolve(&key.0)
}
pub fn export(&self) -> DocumentExport<'_> {
DocumentExport::from_document(self)
}
pub const fn importer(&mut self) -> Importer<'_> {
Importer::new(self)
}
fn free_index(&self, type_key: TypeSymbol) -> usize {
self.sections
.range(lookup_range_by_type(type_key))
.last()
.map_or(0, |(key, _)| key.index + 1)
}
fn section_key_of_name(&self, name: &str) -> Option<&SectionKey> {
let (hash, eq) = section_name_lookup(&self.hash_state, name, &self.sections);
let (_, key) = self.names.find(hash, eq)?;
Some(key)
}
}
impl Display for Document {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.export()
.try_for_each(|directive| Display::fmt(&directive, f))
}
}
impl FromStr for Document {
type Err = FromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut document = Self::default();
let mut importer = document.importer();
for result in DirectiveIter::new(s) {
importer.import(result?)?;
}
Ok(document)
}
}
#[derive(Clone, Debug, Display, Error, From, PartialEq, Eq)]
pub enum FromStrError {
Syntax(crate::syntax::ParseError),
Import(import::ImportError),
}
#[derive(Clone, Copy, Display, Debug, PartialEq, Eq, Error)]
#[display("merge type mismatch, stored: {stored:?} but we are inserting {inserting:?}")]
pub struct MergeTypeMismatch {
pub stored: TypeSymbol,
pub inserting: TypeSymbol,
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct Section {
options: HashMap<String, ConfigOption>,
}
impl Section {
#[inline]
#[must_use]
pub fn get(&self, key: &str) -> Option<&ConfigOption> {
self.options.get(key)
}
#[inline]
#[must_use]
pub fn get_mut(&mut self, key: &str) -> Option<&mut ConfigOption> {
self.options.get_mut(key)
}
pub fn insert(&mut self, key: &str, option: ConfigOption) {
match self.options.entry_ref(key) {
EntryRef::Occupied(entry) => {
entry.replace_entry_with(|_, current| {
Some(match (current, option) {
(_, new @ ConfigOption::Scalar(_)) => new,
(ConfigOption::Scalar(current), ConfigOption::List(mut items)) => {
items.insert(0, current);
ConfigOption::List(items)
}
(ConfigOption::List(mut current), ConfigOption::List(new)) => {
current.extend_from_slice(&new);
ConfigOption::List(current)
}
})
});
}
EntryRef::Vacant(entry) => {
entry.insert(option);
}
}
}
pub fn merge(&mut self, other: Self) {
for (key, value) in other.options {
self.insert(&key, value);
}
}
pub fn remove(&mut self, key: &str) -> Option<ConfigOption> {
self.options.remove(key)
}
#[inline]
#[must_use]
pub fn iter(&self) -> <&Self as IntoIterator>::IntoIter {
<&Self as IntoIterator>::into_iter(self)
}
}
impl IntoIterator for Section {
type Item = (String, ConfigOption);
type IntoIter = hashbrown::hash_map::IntoIter<String, ConfigOption>;
fn into_iter(self) -> Self::IntoIter {
self.options.into_iter()
}
}
impl<'a> IntoIterator for &'a Section {
type Item = (&'a String, &'a ConfigOption);
type IntoIter = hashbrown::hash_map::Iter<'a, String, ConfigOption>;
fn into_iter(self) -> Self::IntoIter {
self.options.iter()
}
}
impl<K: AsRef<str>> Extend<(K, ConfigOption)> for Section {
fn extend<T: IntoIterator<Item = (K, ConfigOption)>>(&mut self, iter: T) {
for (key, value) in iter {
self.insert(key.as_ref(), value);
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SectionKey {
type_key: TypeSymbol,
index: usize,
}
impl Debug for SectionKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct(type_name::<Self>())
.field("type_key", &format_args!("{:#x?}", self.type_key))
.field("index", &self.index)
.finish()
}
}
#[derive(Debug, Default, PartialEq, Eq)]
struct SectionRecord {
name: Option<Box<str>>,
section: Section,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct TypeSymbol(Spur);
#[derive(Debug, Default)]
#[must_use]
pub struct LookupRange<'doc> {
range: Range<'doc, SectionKey, SectionRecord>,
}
impl<'doc> Iterator for LookupRange<'doc> {
type Item = (&'doc SectionKey, &'doc Section);
fn next(&mut self) -> Option<Self::Item> {
let (key, value) = self.range.next()?;
Some((key, &value.section))
}
}
impl DoubleEndedIterator for LookupRange<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
let (key, value) = self.range.next_back()?;
Some((key, &value.section))
}
}
#[derive(Debug, Default)]
#[must_use]
pub struct LookupRangeMut<'doc> {
range: RangeMut<'doc, SectionKey, SectionRecord>,
}
impl<'doc> Iterator for LookupRangeMut<'doc> {
type Item = (&'doc SectionKey, &'doc mut Section);
fn next(&mut self) -> Option<Self::Item> {
let (key, value) = self.range.next()?;
Some((key, &mut value.section))
}
}
impl DoubleEndedIterator for LookupRangeMut<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
let (key, value) = self.range.next_back()?;
Some((key, &mut value.section))
}
}
const fn lookup_range_by_type(type_key: TypeSymbol) -> (Bound<SectionKey>, Bound<SectionKey>) {
let from = SectionKey { type_key, index: 0 };
let to = SectionKey {
type_key,
index: usize::MAX,
};
(Bound::Included(from), Bound::Included(to))
}
fn section_name_lookup(
hash_state: &impl BuildHasher,
name: &str,
sections: &BTreeMap<SectionKey, SectionRecord>,
) -> (u64, impl Fn(&(u64, SectionKey)) -> bool) {
let hash = hash_state.hash_one(name);
let pred = move |(got_hash, key): &_| {
let name_matches =
|| sections.get(key).and_then(|record| record.name.as_deref()) == Some(name);
*got_hash == hash && name_matches()
};
(hash, pred)
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::syntax::Directive,
alloc::string::ToString,
assert2::{assert, let_assert},
};
#[test]
fn import_export() {
let source = [
Directive::Section {
type_: "kitty".into(),
name: Some("emily".into()),
},
Directive::Option {
key: "foo".into(),
value: Some("bar".into()),
},
Directive::Section {
type_: "kitty".into(),
name: None,
},
Directive::List {
key: "list".into(),
value: ":".into(),
},
Directive::List {
key: "list".into(),
value: "3".into(),
},
];
let mut doc = Document::default();
let mut importer = doc.importer();
source
.iter()
.cloned()
.try_for_each(|dir| importer.import(dir))
.unwrap();
let exported: alloc::vec::Vec<_> = doc.export().collect();
assert!(exported == source);
}
#[test]
fn document_reuse() {
let mut doc = Document::default();
let mut section = Section::default();
section.insert("A", ConfigOption::from("value"));
doc.insert("section", Some(":3"), section);
let_assert!(Some(key) = doc.lookup_by_type_name("section", ":3"));
assert!(key.get("A").is_some());
doc.clear();
assert!(doc.lookup_by_type_name("section", ":3").is_none());
}
#[test]
fn parse_fmt() {
let source = r#"
config "A" "B"
option "c" "d"
list "e" "f"
"#;
let document = Document::from_str(source).unwrap();
let from_fmt = Document::from_str(&document.to_string()).unwrap();
assert!(document == from_fmt);
}
}