Skip to main content

oxilean_kernel/env_index/
functions.rs

1//! Functions for the indexed environment layer.
2//!
3//! Implements construction, insertion, lookup, and statistics operations for
4//! all index types declared in [`super::types`].
5
6use std::collections::HashMap;
7
8use super::types::{EnvIndex, IndexStats, LookupResult, ModuleIndex, NameIndex, TypeIndex};
9
10// ---------------------------------------------------------------------------
11// NameIndex
12// ---------------------------------------------------------------------------
13
14impl NameIndex {
15    /// Create a new, empty `NameIndex`.
16    pub fn new() -> Self {
17        NameIndex {
18            name_to_id: HashMap::new(),
19            id_to_name: Vec::new(),
20        }
21    }
22
23    /// Insert `name` and return its assigned id.
24    ///
25    /// If `name` was already inserted the existing id is returned unchanged;
26    /// no duplicate entry is created.
27    pub fn insert(&mut self, name: &str) -> u32 {
28        if let Some(&id) = self.name_to_id.get(name) {
29            return id;
30        }
31        let id = self.id_to_name.len() as u32;
32        self.id_to_name.push(name.to_owned());
33        self.name_to_id.insert(name.to_owned(), id);
34        id
35    }
36
37    /// Look up the id of `name`, returning `None` if it has not been inserted.
38    pub fn lookup_id(&self, name: &str) -> Option<u32> {
39        self.name_to_id.get(name).copied()
40    }
41
42    /// Look up the name string for `id`, returning `None` if out of range.
43    pub fn lookup_name(&self, id: u32) -> Option<&str> {
44        self.id_to_name.get(id as usize).map(String::as_str)
45    }
46
47    /// Return the number of indexed names.
48    pub fn len(&self) -> usize {
49        self.id_to_name.len()
50    }
51
52    /// Return `true` if no names have been inserted.
53    pub fn is_empty(&self) -> bool {
54        self.id_to_name.is_empty()
55    }
56}
57
58impl Default for NameIndex {
59    fn default() -> Self {
60        NameIndex::new()
61    }
62}
63
64// ---------------------------------------------------------------------------
65// ModuleIndex
66// ---------------------------------------------------------------------------
67
68impl ModuleIndex {
69    /// Create a new, empty `ModuleIndex`.
70    pub fn new() -> Self {
71        ModuleIndex {
72            modules: HashMap::new(),
73        }
74    }
75
76    /// Record that declaration `id` belongs to the namespace of `name`.
77    ///
78    /// The namespace is derived from `name` via [`namespace_of`].
79    pub fn add(&mut self, name: &str, id: u32) {
80        let ns = namespace_of(name).to_owned();
81        self.modules.entry(ns).or_default().push(id);
82    }
83
84    /// Return all declaration ids that belong to namespace `prefix`.
85    ///
86    /// Returns an empty `Vec` if no declarations with that prefix exist.
87    pub fn lookup_namespace(&self, prefix: &str) -> Vec<u32> {
88        self.modules.get(prefix).cloned().unwrap_or_default()
89    }
90
91    /// Return all namespace prefixes that have at least one declaration.
92    pub fn all_namespaces(&self) -> Vec<String> {
93        let mut ns: Vec<String> = self.modules.keys().cloned().collect();
94        ns.sort();
95        ns
96    }
97}
98
99impl Default for ModuleIndex {
100    fn default() -> Self {
101        ModuleIndex::new()
102    }
103}
104
105// ---------------------------------------------------------------------------
106// TypeIndex
107// ---------------------------------------------------------------------------
108
109impl TypeIndex {
110    /// Create a new, empty `TypeIndex`.
111    pub fn new() -> Self {
112        TypeIndex {
113            by_sort: HashMap::new(),
114            by_arity: HashMap::new(),
115        }
116    }
117
118    /// Record that declaration `id` has outermost sort level `sort_level`.
119    pub fn add_by_sort(&mut self, sort_level: u8, id: u32) {
120        self.by_sort.entry(sort_level).or_default().push(id);
121    }
122
123    /// Record that declaration `id` has Pi arity `arity`.
124    pub fn add_by_arity(&mut self, arity: u32, id: u32) {
125        self.by_arity.entry(arity).or_default().push(id);
126    }
127
128    /// Return all declaration ids with outermost sort level `sort_level`.
129    pub fn lookup_by_sort(&self, sort_level: u8) -> Vec<u32> {
130        self.by_sort.get(&sort_level).cloned().unwrap_or_default()
131    }
132
133    /// Return all declaration ids with Pi arity `arity`.
134    pub fn lookup_by_arity(&self, arity: u32) -> Vec<u32> {
135        self.by_arity.get(&arity).cloned().unwrap_or_default()
136    }
137}
138
139impl Default for TypeIndex {
140    fn default() -> Self {
141        TypeIndex::new()
142    }
143}
144
145// ---------------------------------------------------------------------------
146// EnvIndex
147// ---------------------------------------------------------------------------
148
149impl EnvIndex {
150    /// Create a new, empty `EnvIndex`.
151    pub fn new() -> Self {
152        EnvIndex {
153            name_idx: NameIndex::new(),
154            type_idx: TypeIndex::new(),
155            module_idx: ModuleIndex::new(),
156            size: 0,
157        }
158    }
159
160    /// Insert a declaration `name` into all sub-indices and return its id.
161    ///
162    /// The sort level is recorded as `0` and the arity as `0` for the base
163    /// call-site (callers that have type information should use the individual
164    /// sub-index methods directly after calling `insert`).
165    ///
166    /// If the name already exists, its existing id is returned and `size` is
167    /// not incremented again.
168    pub fn insert(&mut self, name: &str) -> u32 {
169        let already = self.name_idx.lookup_id(name).is_some();
170        let id = self.name_idx.insert(name);
171        if !already {
172            self.module_idx.add(name, id);
173            self.size += 1;
174        }
175        id
176    }
177
178    /// Look up `name` and return a [`LookupResult`] on success.
179    pub fn lookup<'a>(&'a self, name: &str) -> Option<LookupResult<'a>> {
180        let id = self.name_idx.lookup_id(name)?;
181        let stored_name = self.name_idx.lookup_name(id)?;
182        Some(LookupResult {
183            id,
184            name: stored_name,
185        })
186    }
187
188    /// Return all declaration ids whose namespace prefix equals `prefix`.
189    pub fn by_namespace(&self, prefix: &str) -> Vec<u32> {
190        self.module_idx.lookup_namespace(prefix)
191    }
192
193    /// Return an [`IndexStats`] snapshot for this index.
194    ///
195    /// `by_kind` is left empty; callers that track declaration kinds may
196    /// populate it externally.
197    pub fn stats(&self) -> IndexStats {
198        IndexStats {
199            total: self.size,
200            by_kind: HashMap::new(),
201            namespaces: self.module_idx.modules.len(),
202        }
203    }
204}
205
206impl Default for EnvIndex {
207    fn default() -> Self {
208        EnvIndex::new()
209    }
210}
211
212// ---------------------------------------------------------------------------
213// Free functions
214// ---------------------------------------------------------------------------
215
216/// Extract the namespace prefix from a dot-qualified name.
217///
218/// `"Nat.add"` → `"Nat"`, `"List.map"` → `"List"`, `"Nat"` → `""`.
219///
220/// The returned slice borrows from the input; no allocation occurs.
221pub fn namespace_of(name: &str) -> &str {
222    match name.rfind('.') {
223        Some(pos) => &name[..pos],
224        None => "",
225    }
226}
227
228/// Return `true` if `name` belongs to namespace `ns`.
229///
230/// A name belongs to namespace `ns` when its namespace prefix (as computed by
231/// [`namespace_of`]) is exactly `ns`.
232///
233/// ```
234/// use oxilean_kernel::env_index::{is_in_namespace, namespace_of};
235///
236/// assert!(is_in_namespace("Nat.add", "Nat"));
237/// assert!(!is_in_namespace("Nat.add", "List"));
238/// assert!(is_in_namespace("succ", ""));
239/// ```
240pub fn is_in_namespace(name: &str, ns: &str) -> bool {
241    namespace_of(name) == ns
242}
243
244// ---------------------------------------------------------------------------
245// Tests
246// ---------------------------------------------------------------------------
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251
252    // --- namespace_of ---
253
254    #[test]
255    fn test_namespace_of_dotted() {
256        assert_eq!(namespace_of("Nat.add"), "Nat");
257    }
258
259    #[test]
260    fn test_namespace_of_nested() {
261        assert_eq!(namespace_of("List.Lex.lt"), "List.Lex");
262    }
263
264    #[test]
265    fn test_namespace_of_top_level() {
266        assert_eq!(namespace_of("succ"), "");
267    }
268
269    #[test]
270    fn test_namespace_of_empty() {
271        assert_eq!(namespace_of(""), "");
272    }
273
274    // --- is_in_namespace ---
275
276    #[test]
277    fn test_is_in_namespace_true() {
278        assert!(is_in_namespace("Nat.add", "Nat"));
279    }
280
281    #[test]
282    fn test_is_in_namespace_false() {
283        assert!(!is_in_namespace("Nat.add", "List"));
284    }
285
286    #[test]
287    fn test_is_in_namespace_top_level() {
288        assert!(is_in_namespace("succ", ""));
289    }
290
291    #[test]
292    fn test_is_in_namespace_prefix_not_subset() {
293        // "Na" is not the namespace of "Nat.add"
294        assert!(!is_in_namespace("Nat.add", "Na"));
295    }
296
297    // --- NameIndex ---
298
299    #[test]
300    fn test_name_index_new_empty() {
301        let idx = NameIndex::new();
302        assert!(idx.is_empty());
303        assert_eq!(idx.len(), 0);
304    }
305
306    #[test]
307    fn test_name_index_insert_and_lookup_id() {
308        let mut idx = NameIndex::new();
309        let id = idx.insert("Nat.add");
310        assert_eq!(idx.lookup_id("Nat.add"), Some(id));
311    }
312
313    #[test]
314    fn test_name_index_insert_idempotent() {
315        let mut idx = NameIndex::new();
316        let id1 = idx.insert("Nat.add");
317        let id2 = idx.insert("Nat.add");
318        assert_eq!(id1, id2);
319        assert_eq!(idx.len(), 1);
320    }
321
322    #[test]
323    fn test_name_index_lookup_name() {
324        let mut idx = NameIndex::new();
325        let id = idx.insert("List.map");
326        assert_eq!(idx.lookup_name(id), Some("List.map"));
327    }
328
329    #[test]
330    fn test_name_index_lookup_id_missing() {
331        let idx = NameIndex::new();
332        assert_eq!(idx.lookup_id("unknown"), None);
333    }
334
335    #[test]
336    fn test_name_index_lookup_name_out_of_range() {
337        let idx = NameIndex::new();
338        assert_eq!(idx.lookup_name(99), None);
339    }
340
341    #[test]
342    fn test_name_index_multiple_entries() {
343        let mut idx = NameIndex::new();
344        let ids: Vec<u32> = ["a", "b", "c"].iter().map(|n| idx.insert(n)).collect();
345        assert_eq!(ids, vec![0, 1, 2]);
346        assert_eq!(idx.len(), 3);
347    }
348
349    // --- ModuleIndex ---
350
351    #[test]
352    fn test_module_index_new_empty() {
353        let idx = ModuleIndex::new();
354        assert!(idx.all_namespaces().is_empty());
355    }
356
357    #[test]
358    fn test_module_index_add_and_lookup() {
359        let mut idx = ModuleIndex::new();
360        idx.add("Nat.add", 0);
361        idx.add("Nat.sub", 1);
362        let mut ids = idx.lookup_namespace("Nat");
363        ids.sort();
364        assert_eq!(ids, vec![0, 1]);
365    }
366
367    #[test]
368    fn test_module_index_lookup_missing() {
369        let idx = ModuleIndex::new();
370        assert!(idx.lookup_namespace("List").is_empty());
371    }
372
373    #[test]
374    fn test_module_index_all_namespaces_sorted() {
375        let mut idx = ModuleIndex::new();
376        idx.add("Nat.add", 0);
377        idx.add("List.map", 1);
378        idx.add("succ", 2);
379        let ns = idx.all_namespaces();
380        let mut expected = vec!["", "List", "Nat"];
381        expected.sort();
382        assert_eq!(ns, expected);
383    }
384
385    // --- TypeIndex ---
386
387    #[test]
388    fn test_type_index_by_sort() {
389        let mut idx = TypeIndex::new();
390        idx.add_by_sort(0, 10);
391        idx.add_by_sort(0, 20);
392        idx.add_by_sort(1, 30);
393        let mut s0 = idx.lookup_by_sort(0);
394        s0.sort();
395        assert_eq!(s0, vec![10, 20]);
396        assert_eq!(idx.lookup_by_sort(1), vec![30]);
397    }
398
399    #[test]
400    fn test_type_index_by_arity() {
401        let mut idx = TypeIndex::new();
402        idx.add_by_arity(2, 5);
403        idx.add_by_arity(2, 6);
404        idx.add_by_arity(0, 7);
405        let mut a2 = idx.lookup_by_arity(2);
406        a2.sort();
407        assert_eq!(a2, vec![5, 6]);
408    }
409
410    #[test]
411    fn test_type_index_missing_sort() {
412        let idx = TypeIndex::new();
413        assert!(idx.lookup_by_sort(5).is_empty());
414    }
415
416    #[test]
417    fn test_type_index_missing_arity() {
418        let idx = TypeIndex::new();
419        assert!(idx.lookup_by_arity(3).is_empty());
420    }
421
422    // --- EnvIndex ---
423
424    #[test]
425    fn test_env_index_new_empty() {
426        let idx = EnvIndex::new();
427        assert_eq!(idx.size, 0);
428    }
429
430    #[test]
431    fn test_env_index_insert_and_lookup() {
432        let mut idx = EnvIndex::new();
433        let id = idx.insert("Nat.add");
434        let result = idx.lookup("Nat.add").expect("should find inserted name");
435        assert_eq!(result.id, id);
436        assert_eq!(result.name, "Nat.add");
437    }
438
439    #[test]
440    fn test_env_index_insert_idempotent() {
441        let mut idx = EnvIndex::new();
442        idx.insert("Nat.add");
443        idx.insert("Nat.add");
444        assert_eq!(idx.size, 1);
445    }
446
447    #[test]
448    fn test_env_index_lookup_missing() {
449        let idx = EnvIndex::new();
450        assert!(idx.lookup("ghost").is_none());
451    }
452
453    #[test]
454    fn test_env_index_by_namespace() {
455        let mut idx = EnvIndex::new();
456        idx.insert("Nat.add");
457        idx.insert("Nat.sub");
458        idx.insert("List.map");
459        let mut nat = idx.by_namespace("Nat");
460        nat.sort();
461        assert_eq!(nat.len(), 2);
462        assert!(idx.by_namespace("List").len() == 1);
463    }
464
465    #[test]
466    fn test_env_index_stats() {
467        let mut idx = EnvIndex::new();
468        idx.insert("Nat.add");
469        idx.insert("List.map");
470        idx.insert("succ");
471        let s = idx.stats();
472        assert_eq!(s.total, 3);
473        assert_eq!(s.namespaces, 3); // "Nat", "List", ""
474    }
475
476    #[test]
477    fn test_env_index_stats_display() {
478        let idx = EnvIndex::new();
479        let s = idx.stats();
480        let text = format!("{}", s);
481        assert!(text.contains("IndexStats"));
482    }
483
484    #[test]
485    fn test_env_index_type_idx_sort() {
486        let mut idx = EnvIndex::new();
487        let id = idx.insert("Prop");
488        idx.type_idx.add_by_sort(0, id);
489        let results = idx.type_idx.lookup_by_sort(0);
490        assert_eq!(results, vec![id]);
491    }
492
493    #[test]
494    fn test_env_index_type_idx_arity() {
495        let mut idx = EnvIndex::new();
496        let id = idx.insert("Nat.add");
497        idx.type_idx.add_by_arity(2, id);
498        let results = idx.type_idx.lookup_by_arity(2);
499        assert_eq!(results, vec![id]);
500    }
501}