Skip to main content

xsd_schema/document/
type_remap.rs

1//! Binding remap table for mapping 20-bit node indices to full [`NodeSchemaBinding`] values.
2//!
3//! Each element/attribute node in the [`BufferDocument`](super) carries a 20-bit
4//! `binding_index` field.  The [`BindingRemapTable`] maps those compact indices back
5//! to [`NodeSchemaBinding`] records containing the type key and optional declaration keys.
6
7use crate::ids::{AttributeKey, ElementKey, SimpleTypeKey, TypeKey};
8use crate::validation::info::ContentType;
9
10use super::error::BufferDocumentError;
11
12/// Full schema binding for a single document node.
13///
14/// Two bindings are considered equal (for deduplication) when **all** fields match.
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub struct NodeSchemaBinding {
17    pub type_key: TypeKey,
18    pub element_decl: Option<ElementKey>,
19    pub attribute_decl: Option<AttributeKey>,
20    pub content_type: Option<ContentType>,
21}
22
23/// Maps compact 20-bit indices ↔ full [`NodeSchemaBinding`] values.
24///
25/// Index 0 is an unbound sentinel (returned as `None` by [`get`](Self::get))
26/// and is never stored in the entries list.
27pub struct BindingRemapTable {
28    /// Indexed entries (index 0 = unbound sentinel).
29    entries: Vec<NodeSchemaBinding>,
30}
31
32/// Maximum entry count: 2^20 − 1 (index 0 is the sentinel).
33const MAX_ENTRIES: u32 = (1 << 20) - 1;
34
35impl BindingRemapTable {
36    /// Creates a new table with an unbound sentinel at index 0.
37    pub fn new() -> Self {
38        use slotmap::Key;
39
40        // Dummy binding at index 0 — never returned by `get`.
41        let sentinel = NodeSchemaBinding {
42            type_key: TypeKey::Simple(SimpleTypeKey::null()),
43            element_decl: None,
44            attribute_decl: None,
45            content_type: None,
46        };
47
48        Self {
49            entries: vec![sentinel],
50        }
51    }
52
53    /// Registers a [`NodeSchemaBinding`] and returns its 20-bit index.
54    ///
55    /// If an identical binding was already registered, returns the existing index.
56    /// Uses linear scan for deduplication (expected table size is small).
57    ///
58    /// Returns [`BufferDocumentError::Overflow`] if the table would exceed 2^20 − 1 entries.
59    pub fn register(&mut self, binding: NodeSchemaBinding) -> Result<u32, BufferDocumentError> {
60        // Linear scan dedup (skip sentinel at 0).
61        for (i, entry) in self.entries.iter().enumerate().skip(1) {
62            if *entry == binding {
63                return Ok(i as u32);
64            }
65        }
66        let idx = self.entries.len() as u32;
67        if idx > MAX_ENTRIES {
68            return Err(BufferDocumentError::Overflow);
69        }
70        self.entries.push(binding);
71        Ok(idx)
72    }
73
74    /// Returns the [`NodeSchemaBinding`] for the given index, or `None` for index 0.
75    pub fn get(&self, idx: u32) -> Option<&NodeSchemaBinding> {
76        if idx == 0 {
77            return None;
78        }
79        self.entries.get(idx as usize)
80    }
81}
82
83impl Default for BindingRemapTable {
84    fn default() -> Self {
85        Self::new()
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use crate::ids::{ComplexTypeKey, SimpleTypeKey, TypeKey};
93    use slotmap::SlotMap;
94
95    /// Helper: create a real `SimpleTypeKey` via slotmap.
96    fn make_simple_key() -> SimpleTypeKey {
97        let mut sm = SlotMap::with_key();
98        sm.insert(())
99    }
100
101    /// Helper: create a real `ComplexTypeKey` via slotmap.
102    fn make_complex_key() -> ComplexTypeKey {
103        let mut sm = SlotMap::with_key();
104        sm.insert(())
105    }
106
107    /// Helper: create a binding with just a type key.
108    fn binding_from_type(tk: TypeKey) -> NodeSchemaBinding {
109        NodeSchemaBinding {
110            type_key: tk,
111            element_decl: None,
112            attribute_decl: None,
113            content_type: None,
114        }
115    }
116
117    #[test]
118    fn index_zero_returns_none() {
119        let table = BindingRemapTable::new();
120        assert!(table.get(0).is_none());
121    }
122
123    #[test]
124    fn register_and_get_simple() {
125        let mut table = BindingRemapTable::new();
126        let sk = make_simple_key();
127        let binding = binding_from_type(TypeKey::Simple(sk));
128
129        let idx = table.register(binding).unwrap();
130        assert_eq!(idx, 1);
131        assert_eq!(table.get(idx), Some(&binding));
132    }
133
134    #[test]
135    fn register_and_get_complex() {
136        let mut table = BindingRemapTable::new();
137        let ck = make_complex_key();
138        let binding = binding_from_type(TypeKey::Complex(ck));
139
140        let idx = table.register(binding).unwrap();
141        assert_eq!(idx, 1);
142        assert_eq!(table.get(idx), Some(&binding));
143    }
144
145    #[test]
146    fn register_same_binding_twice_returns_same_index() {
147        let mut table = BindingRemapTable::new();
148        let sk = make_simple_key();
149        let binding = binding_from_type(TypeKey::Simple(sk));
150
151        let idx1 = table.register(binding).unwrap();
152        let idx2 = table.register(binding).unwrap();
153        assert_eq!(idx1, idx2);
154    }
155
156    #[test]
157    fn simple_vs_complex_preserved() {
158        let mut table = BindingRemapTable::new();
159        let sk = make_simple_key();
160        let ck = make_complex_key();
161        let b_s = binding_from_type(TypeKey::Simple(sk));
162        let b_c = binding_from_type(TypeKey::Complex(ck));
163
164        let idx_s = table.register(b_s).unwrap();
165        let idx_c = table.register(b_c).unwrap();
166        assert_ne!(idx_s, idx_c);
167
168        match table.get(idx_s).unwrap().type_key {
169            TypeKey::Simple(_) => {}
170            other => panic!("Expected Simple, got {other:?}"),
171        }
172        match table.get(idx_c).unwrap().type_key {
173            TypeKey::Complex(_) => {}
174            other => panic!("Expected Complex, got {other:?}"),
175        }
176    }
177
178    #[test]
179    fn multiple_distinct_bindings_distinct_indices() {
180        let mut table = BindingRemapTable::new();
181
182        let mut sm: SlotMap<SimpleTypeKey, ()> = SlotMap::with_key();
183        let mut indices = Vec::new();
184        for _ in 0..10 {
185            let sk = sm.insert(());
186            indices.push(
187                table
188                    .register(binding_from_type(TypeKey::Simple(sk)))
189                    .unwrap(),
190            );
191        }
192
193        let unique: std::collections::HashSet<_> = indices.iter().collect();
194        assert_eq!(unique.len(), indices.len());
195    }
196
197    #[test]
198    fn get_out_of_range_returns_none() {
199        let table = BindingRemapTable::new();
200        assert!(table.get(999).is_none());
201    }
202
203    #[test]
204    fn same_type_different_decl_are_distinct() {
205        let mut table = BindingRemapTable::new();
206        let sk = make_simple_key();
207        let tk = TypeKey::Simple(sk);
208
209        let mut sm_elem: SlotMap<ElementKey, ()> = SlotMap::with_key();
210        let ek1 = sm_elem.insert(());
211        let ek2 = sm_elem.insert(());
212
213        let b1 = NodeSchemaBinding {
214            type_key: tk,
215            element_decl: Some(ek1),
216            attribute_decl: None,
217            content_type: None,
218        };
219        let b2 = NodeSchemaBinding {
220            type_key: tk,
221            element_decl: Some(ek2),
222            attribute_decl: None,
223            content_type: None,
224        };
225
226        let idx1 = table.register(b1).unwrap();
227        let idx2 = table.register(b2).unwrap();
228        assert_ne!(
229            idx1, idx2,
230            "same type with different element_decl must get distinct indices"
231        );
232        assert_eq!(table.get(idx1), Some(&b1));
233        assert_eq!(table.get(idx2), Some(&b2));
234    }
235}