1pub mod export;
23pub mod import;
24pub mod option;
25
26pub use option::ConfigOption;
27
28use {
29 crate::{document::export::DocumentExport, syntax::DirectiveIter},
30 alloc::{
31 boxed::Box,
32 collections::{
33 BTreeMap,
34 btree_map::{Range, RangeMut},
35 },
36 string::String,
37 },
38 core::{
39 any::type_name,
40 fmt::{Debug, Display},
41 hash::{BuildHasher, Hash},
42 ops::Bound,
43 str::FromStr,
44 },
45 derive_more::{Display, Error, From},
46 hashbrown::{HashMap, HashTable, hash_map::EntryRef},
47 import::Importer,
48 lasso::{Rodeo, Spur},
49};
50
51#[derive(Debug, Default)]
55pub struct Document {
56 sections: BTreeMap<SectionKey, SectionRecord>,
58 names: HashTable<(u64, SectionKey)>,
60 hash_state: hashbrown::DefaultHashBuilder,
62 types: Rodeo,
64}
65
66impl PartialEq for Document {
67 fn eq(&self, other: &Self) -> bool {
68 self.sections == other.sections
70 }
71}
72
73impl Document {
74 pub fn clear(&mut self) {
76 self.sections.clear();
77 self.names.clear();
78 self.types.clear();
79 }
80
81 pub fn insert(
94 &mut self,
95 type_: &str,
96 name: Option<impl AsRef<str> + Into<Box<str>>>,
97 value: Section,
98 ) -> (SectionKey, Option<Section>) {
99 let type_key = TypeSymbol(self.types.get_or_intern(type_));
100
101 let maybe_conflicting_key = name
102 .as_ref()
103 .and_then(|name| self.section_key_of_name(name.as_ref()))
104 .copied();
105
106 let key = maybe_conflicting_key.unwrap_or_else(|| SectionKey {
107 type_key,
108 index: self.free_index(type_key),
109 });
110
111 let previous_section = match self.sections.entry(key) {
112 alloc::collections::btree_map::Entry::Vacant(e) => {
113 if let Some(name) = &name {
114 let hash = self.hash_state.hash_one(name.as_ref());
115 self.names.insert_unique(hash, (hash, key), |(h, _)| *h);
116 }
117
118 e.insert(SectionRecord {
119 name: name.map(Into::into),
120 section: value,
121 });
122 None
123 }
124 alloc::collections::btree_map::Entry::Occupied(e) => {
125 Some(core::mem::replace(&mut e.into_mut().section, value))
126 }
127 };
128
129 (key, previous_section)
130 }
131
132 pub fn merge_or_insert(
138 &mut self,
139 type_: &str,
140 name: Option<impl AsRef<str> + Into<Box<str>>>,
141 value: Section,
142 ) -> Result<SectionKey, MergeTypeMismatch> {
143 let maybe_key = name
144 .as_ref()
145 .and_then(|name| self.section_key_of_name(name.as_ref()));
146
147 let Some(&key) = maybe_key else {
149 let (key, _) = self.insert(type_, name, value);
150 return Ok(key);
151 };
152
153 let type_key = TypeSymbol(self.types.get_or_intern(type_));
154 if type_key != key.type_key {
155 return Err(MergeTypeMismatch {
156 stored: key.type_key,
157 inserting: type_key,
158 });
159 }
160
161 let stored = self.get_mut(&key).unwrap_or_else(|| {
162 unreachable!(
163 "state consistency violation: we got name to a section which doesn't exist"
164 )
165 });
166
167 stored.merge(value);
168
169 Ok(key)
170 }
171
172 pub fn lookup_by_type<'this>(&'this self, type_: &str) -> LookupRange<'this> {
187 let Some(type_key) = self.type_key(type_) else {
188 return LookupRange::default();
189 };
190
191 LookupRange {
192 range: self.sections.range(lookup_range_by_type(type_key)),
193 }
194 }
195
196 pub fn lookup_by_type_mut<'this>(&'this mut self, type_: &str) -> LookupRangeMut<'this> {
217 let Some(type_key) = self.type_key(type_) else {
218 return LookupRangeMut::default();
219 };
220
221 LookupRangeMut {
222 range: self.sections.range_mut(lookup_range_by_type(type_key)),
223 }
224 }
225
226 #[must_use]
237 pub fn lookup_by_name(&self, name: &str) -> Option<&Section> {
238 self.get(self.section_key_of_name(name)?)
239 }
240
241 pub fn lookup_by_name_mut(&mut self, name: &str) -> Option<&mut Section> {
263 let &key = self.section_key_of_name(name)?;
264 self.get_mut(&key)
265 }
266
267 #[must_use]
283 pub fn lookup_by_type_name(&self, ty: &str, name: &str) -> Option<&Section> {
284 let key = self.section_key_of_name(name)?;
285 let type_key = self.type_key(ty)?;
286 (key.type_key == type_key).then(|| self.get(key)).flatten()
287 }
288
289 #[must_use]
309 pub fn lookup_by_type_name_mut(&mut self, ty: &str, name: &str) -> Option<&mut Section> {
310 let key = *self.section_key_of_name(name)?;
311 let type_key = self.type_key(ty)?;
312 (key.type_key == type_key)
313 .then(|| self.get_mut(&key))
314 .flatten()
315 }
316
317 #[must_use]
330 pub fn get(&self, key: &SectionKey) -> Option<&Section> {
331 self.sections.get(key).map(|r| &r.section)
332 }
333
334 #[must_use]
345 pub fn get_mut(&mut self, key: &SectionKey) -> Option<&mut Section> {
346 self.sections.get_mut(key).map(|r| &mut r.section)
347 }
348
349 pub fn remove(&mut self, key: SectionKey) -> Option<Section> {
368 let record = self.sections.remove(&key)?;
369
370 if let Some(name) = record.name {
371 let hash = self.hash_state.hash_one(&name[..]);
372 let maybe_entry = self.names.find_entry(hash, |(got_hash, got_key)| {
373 got_hash == &hash && got_key == &key
374 });
375
376 if let Ok(entry) = maybe_entry {
377 entry.remove();
378 }
379 }
380
381 Some(record.section)
382 }
383
384 #[must_use]
386 pub fn section_name(&self, key: SectionKey) -> Option<&str> {
387 self.sections.get(&key)?.name.as_deref()
388 }
389
390 #[must_use]
392 pub fn type_key(&self, name: &str) -> Option<TypeSymbol> {
393 self.types.get(name).map(TypeSymbol)
394 }
395
396 #[must_use]
398 pub fn type_name(&self, key: TypeSymbol) -> Option<&str> {
399 self.types.try_resolve(&key.0)
400 }
401
402 pub fn export(&self) -> DocumentExport<'_> {
404 DocumentExport::from_document(self)
405 }
406
407 pub const fn importer(&mut self) -> Importer<'_> {
409 Importer::new(self)
410 }
411
412 fn free_index(&self, type_key: TypeSymbol) -> usize {
414 self.sections
415 .range(lookup_range_by_type(type_key))
416 .last()
417 .map_or(0, |(key, _)| key.index + 1)
418 }
419
420 fn section_key_of_name(&self, name: &str) -> Option<&SectionKey> {
422 let (hash, eq) = section_name_lookup(&self.hash_state, name, &self.sections);
423 let (_, key) = self.names.find(hash, eq)?;
424
425 Some(key)
426 }
427}
428
429impl Display for Document {
432 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
433 self.export()
434 .try_for_each(|directive| Display::fmt(&directive, f))
435 }
436}
437
438impl FromStr for Document {
440 type Err = FromStrError;
441
442 fn from_str(s: &str) -> Result<Self, Self::Err> {
443 let mut document = Self::default();
444 let mut importer = document.importer();
445
446 for result in DirectiveIter::new(s) {
447 importer.import(result?)?;
448 }
449
450 Ok(document)
451 }
452}
453
454#[derive(Clone, Debug, Display, Error, From, PartialEq, Eq)]
456pub enum FromStrError {
457 Syntax(crate::syntax::ParseError),
461 Import(import::ImportError),
466}
467
468#[derive(Clone, Copy, Display, Debug, PartialEq, Eq, Error)]
470#[display("merge type mismatch, stored: {stored:?} but we are inserting {inserting:?}")]
471pub struct MergeTypeMismatch {
472 pub stored: TypeSymbol,
474 pub inserting: TypeSymbol,
476}
477
478#[derive(Debug, Default, PartialEq, Eq)]
482pub struct Section {
483 options: HashMap<String, ConfigOption>,
485}
486
487impl Section {
488 #[inline]
490 #[must_use]
491 pub fn get(&self, key: &str) -> Option<&ConfigOption> {
492 self.options.get(key)
493 }
494
495 #[inline]
497 #[must_use]
498 pub fn get_mut(&mut self, key: &str) -> Option<&mut ConfigOption> {
499 self.options.get_mut(key)
500 }
501
502 pub fn insert(&mut self, key: &str, option: ConfigOption) {
507 match self.options.entry_ref(key) {
508 EntryRef::Occupied(entry) => {
509 entry.replace_entry_with(|_, current| {
510 Some(match (current, option) {
511 (_, new @ ConfigOption::Scalar(_)) => new,
512 (ConfigOption::Scalar(current), ConfigOption::List(mut items)) => {
513 items.insert(0, current);
514 ConfigOption::List(items)
515 }
516 (ConfigOption::List(mut current), ConfigOption::List(new)) => {
517 current.extend_from_slice(&new);
518 ConfigOption::List(current)
519 }
520 })
521 });
522 }
523 EntryRef::Vacant(entry) => {
524 entry.insert(option);
525 }
526 }
527 }
528
529 pub fn merge(&mut self, other: Self) {
533 for (key, value) in other.options {
534 self.insert(&key, value);
535 }
536 }
537
538 pub fn remove(&mut self, key: &str) -> Option<ConfigOption> {
542 self.options.remove(key)
543 }
544
545 #[inline]
547 #[must_use]
548 pub fn iter(&self) -> <&Self as IntoIterator>::IntoIter {
549 <&Self as IntoIterator>::into_iter(self)
550 }
551}
552
553impl IntoIterator for Section {
554 type Item = (String, ConfigOption);
555 type IntoIter = hashbrown::hash_map::IntoIter<String, ConfigOption>;
556
557 fn into_iter(self) -> Self::IntoIter {
558 self.options.into_iter()
559 }
560}
561
562impl<'a> IntoIterator for &'a Section {
563 type Item = (&'a String, &'a ConfigOption);
564 type IntoIter = hashbrown::hash_map::Iter<'a, String, ConfigOption>;
565
566 fn into_iter(self) -> Self::IntoIter {
567 self.options.iter()
568 }
569}
570
571impl<K: AsRef<str>> Extend<(K, ConfigOption)> for Section {
572 fn extend<T: IntoIterator<Item = (K, ConfigOption)>>(&mut self, iter: T) {
573 for (key, value) in iter {
574 self.insert(key.as_ref(), value);
575 }
576 }
577}
578
579#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
582pub struct SectionKey {
583 type_key: TypeSymbol,
585 index: usize,
587}
588
589impl Debug for SectionKey {
590 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
591 f.debug_struct(type_name::<Self>())
592 .field("type_key", &format_args!("{:#x?}", self.type_key))
593 .field("index", &self.index)
594 .finish()
595 }
596}
597
598#[derive(Debug, Default, PartialEq, Eq)]
604struct SectionRecord {
605 name: Option<Box<str>>,
607 section: Section,
609}
610
611#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
615#[repr(transparent)]
616pub struct TypeSymbol(Spur);
617
618#[derive(Debug, Default)]
620#[must_use]
621pub struct LookupRange<'doc> {
622 range: Range<'doc, SectionKey, SectionRecord>,
624}
625
626impl<'doc> Iterator for LookupRange<'doc> {
627 type Item = (&'doc SectionKey, &'doc Section);
628
629 fn next(&mut self) -> Option<Self::Item> {
630 let (key, value) = self.range.next()?;
631 Some((key, &value.section))
632 }
633}
634
635impl DoubleEndedIterator for LookupRange<'_> {
636 fn next_back(&mut self) -> Option<Self::Item> {
637 let (key, value) = self.range.next_back()?;
638 Some((key, &value.section))
639 }
640}
641
642#[derive(Debug, Default)]
644#[must_use]
645pub struct LookupRangeMut<'doc> {
646 range: RangeMut<'doc, SectionKey, SectionRecord>,
648}
649
650impl<'doc> Iterator for LookupRangeMut<'doc> {
651 type Item = (&'doc SectionKey, &'doc mut Section);
652
653 fn next(&mut self) -> Option<Self::Item> {
654 let (key, value) = self.range.next()?;
655 Some((key, &mut value.section))
656 }
657}
658
659impl DoubleEndedIterator for LookupRangeMut<'_> {
660 fn next_back(&mut self) -> Option<Self::Item> {
661 let (key, value) = self.range.next_back()?;
662 Some((key, &mut value.section))
663 }
664}
665
666const fn lookup_range_by_type(type_key: TypeSymbol) -> (Bound<SectionKey>, Bound<SectionKey>) {
668 let from = SectionKey { type_key, index: 0 };
669 let to = SectionKey {
670 type_key,
671 index: usize::MAX,
672 };
673
674 (Bound::Included(from), Bound::Included(to))
675}
676
677fn section_name_lookup(
681 hash_state: &impl BuildHasher,
682 name: &str,
683 sections: &BTreeMap<SectionKey, SectionRecord>,
684) -> (u64, impl Fn(&(u64, SectionKey)) -> bool) {
685 let hash = hash_state.hash_one(name);
686 let pred = move |(got_hash, key): &_| {
687 let name_matches =
688 || sections.get(key).and_then(|record| record.name.as_deref()) == Some(name);
689
690 *got_hash == hash && name_matches()
691 };
692
693 (hash, pred)
694}
695
696#[cfg(test)]
697mod tests {
698 use {
699 super::*,
700 crate::syntax::Directive,
701 alloc::string::ToString,
702 assert2::{assert, let_assert},
703 };
704
705 #[test]
706 fn import_export() {
707 let source = [
708 Directive::Section {
709 type_: "kitty".into(),
710 name: Some("emily".into()),
711 },
712 Directive::Option {
713 key: "foo".into(),
714 value: Some("bar".into()),
715 },
716 Directive::Section {
717 type_: "kitty".into(),
718 name: None,
719 },
720 Directive::List {
721 key: "list".into(),
722 value: ":".into(),
723 },
724 Directive::List {
725 key: "list".into(),
726 value: "3".into(),
727 },
728 ];
729
730 let mut doc = Document::default();
731 let mut importer = doc.importer();
732 source
733 .iter()
734 .cloned()
735 .try_for_each(|dir| importer.import(dir))
736 .unwrap();
737
738 let exported: alloc::vec::Vec<_> = doc.export().collect();
739 assert!(exported == source);
740 }
741
742 #[test]
743 fn document_reuse() {
744 let mut doc = Document::default();
745 let mut section = Section::default();
746 section.insert("A", ConfigOption::from("value"));
747 doc.insert("section", Some(":3"), section);
748
749 let_assert!(Some(key) = doc.lookup_by_type_name("section", ":3"));
750 assert!(key.get("A").is_some());
751
752 doc.clear();
753 assert!(doc.lookup_by_type_name("section", ":3").is_none());
754 }
755
756 #[test]
757 fn parse_fmt() {
758 let source = r#"
759 config "A" "B"
760 option "c" "d"
761 list "e" "f"
762 "#;
763
764 let document = Document::from_str(source).unwrap();
765 let from_fmt = Document::from_str(&document.to_string()).unwrap();
766
767 assert!(document == from_fmt);
768 }
769}