Skip to main content

corium_core/
keyword.rs

1//! Keyword representation and simple in-memory interning.
2
3use std::collections::BTreeMap;
4
5use crate::KwId;
6
7/// Namespaced keyword.
8#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
9pub struct Keyword {
10    /// Optional namespace.
11    pub namespace: Option<String>,
12    /// Keyword name.
13    pub name: String,
14}
15
16impl Keyword {
17    /// Constructs a keyword from namespace and name parts.
18    #[must_use]
19    pub fn new(namespace: Option<&str>, name: &str) -> Self {
20        Self {
21            namespace: namespace.map(str::to_owned),
22            name: name.to_owned(),
23        }
24    }
25
26    /// Parses `"ns/name"` or `"name"` (without a leading colon).
27    ///
28    /// Only the first `/` separates the namespace, matching EDN symbol rules.
29    #[must_use]
30    pub fn parse(text: &str) -> Self {
31        match text.split_once('/') {
32            Some((namespace, name)) if !namespace.is_empty() && !name.is_empty() => {
33                Self::new(Some(namespace), name)
34            }
35            _ => Self::new(None, text),
36        }
37    }
38}
39
40impl std::fmt::Display for Keyword {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        match &self.namespace {
43            Some(namespace) => write!(f, ":{namespace}/{}", self.name),
44            None => write!(f, ":{}", self.name),
45        }
46    }
47}
48
49/// Deterministic keyword interner for tests and bootstrap metadata.
50#[derive(Clone, Debug, Default)]
51pub struct KeywordInterner {
52    by_keyword: BTreeMap<Keyword, KwId>,
53    by_id: BTreeMap<KwId, Keyword>,
54    next: KwId,
55}
56
57impl KeywordInterner {
58    /// Interns a keyword, returning the stable id assigned by this interner.
59    pub fn intern(&mut self, keyword: Keyword) -> KwId {
60        if let Some(id) = self.by_keyword.get(&keyword) {
61            return *id;
62        }
63        let id = self.next;
64        self.next += 1;
65        self.by_id.insert(id, keyword.clone());
66        self.by_keyword.insert(keyword, id);
67        id
68    }
69
70    /// Looks up an already interned keyword without interning it.
71    #[must_use]
72    pub fn get(&self, keyword: &Keyword) -> Option<KwId> {
73        self.by_keyword.get(keyword).copied()
74    }
75
76    /// Resolves an id back to a keyword.
77    #[must_use]
78    pub fn resolve(&self, id: KwId) -> Option<&Keyword> {
79        self.by_id.get(&id)
80    }
81
82    /// Iterates every interned keyword in id order. Ids are dense from zero,
83    /// so re-interning the keywords in this order reproduces the same ids.
84    pub fn iter(&self) -> impl Iterator<Item = (KwId, &Keyword)> {
85        self.by_id.iter().map(|(id, keyword)| (*id, keyword))
86    }
87
88    /// Number of interned keywords.
89    #[must_use]
90    pub fn len(&self) -> usize {
91        self.by_id.len()
92    }
93
94    /// Whether the interner is empty.
95    #[must_use]
96    pub fn is_empty(&self) -> bool {
97        self.by_id.is_empty()
98    }
99}