use fxhash::FxHashMap;
use mangle_ir::{Inst, InstId, Ir};
use crate::type_expr;
#[derive(Debug, Default)]
pub struct NameTrie {
children: FxHashMap<String, NameTrie>,
is_terminal: bool,
}
impl NameTrie {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, name: &str) {
let parts = split_name(name);
let mut node = self;
for part in parts {
node = node
.children
.entry(part.to_string())
.or_default();
}
node.is_terminal = true;
}
pub fn contains(&self, name: &str) -> bool {
let parts = split_name(name);
let mut node = self;
for part in parts {
match node.children.get(part) {
Some(child) => node = child,
None => return false,
}
}
node.is_terminal
}
pub fn prefix_name(&self, name: &str) -> String {
let parts = split_name(name);
let mut node = self;
let mut last_terminal_idx: Option<usize> = None;
for (i, part) in parts.iter().enumerate() {
match node.children.get(*part) {
Some(child) => {
if child.is_terminal {
last_terminal_idx = Some(i);
}
node = child;
}
None => break,
}
}
match last_terminal_idx {
Some(idx) => {
let prefix_parts = &parts[..=idx];
format!("/{}", prefix_parts.join("/"))
}
None => "/name".to_string(),
}
}
pub fn collect(&mut self, ir: &Ir, id: InstId) {
match ir.get(id) {
Inst::Name(n) => {
let name = ir.resolve_name(*n);
if !type_expr::is_base_type(ir, id) && name.starts_with('/') {
self.add(name);
}
}
Inst::ApplyFn { function, args } => {
let fname = ir.resolve_name(*function);
if fname == type_expr::FN_TAGGED_UNION && args.len() >= 3 {
for i in (2..args.len()).step_by(2) {
self.collect(ir, args[i]);
}
} else {
for arg in args {
self.collect(ir, *arg);
}
}
}
_ => {}
}
}
}
fn split_name(name: &str) -> Vec<&str> {
name.split('/')
.filter(|s| !s.is_empty())
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_trie_operations() {
let mut trie = NameTrie::new();
trie.add("/animal");
trie.add("/animal/dog");
trie.add("/color");
assert!(trie.contains("/animal"));
assert!(trie.contains("/animal/dog"));
assert!(trie.contains("/color"));
assert!(!trie.contains("/animal/cat"));
assert!(!trie.contains("/plant"));
}
#[test]
fn prefix_name_lookup() {
let mut trie = NameTrie::new();
trie.add("/animal");
trie.add("/animal/dog");
assert_eq!(trie.prefix_name("/animal/dog/poodle"), "/animal/dog");
assert_eq!(trie.prefix_name("/animal/cat"), "/animal");
assert_eq!(trie.prefix_name("/animal"), "/animal");
assert_eq!(trie.prefix_name("/plant/rose"), "/name");
}
#[test]
fn collect_from_type_expr() {
let mut ir = Ir::new();
let mut trie = NameTrie::new();
let x = {
let n = ir.intern_name("/x");
ir.add_inst(Inst::Name(n))
};
let animal = {
let n = ir.intern_name("/animal");
ir.add_inst(Inst::Name(n))
};
let y = {
let n = ir.intern_name("/y");
ir.add_inst(Inst::Name(n))
};
let color = {
let n = ir.intern_name("/color");
ir.add_inst(Inst::Name(n))
};
let struct_name = ir.intern_name("fn:Struct");
let struct_type = ir.add_inst(Inst::ApplyFn {
function: struct_name,
args: vec![x, animal, y, color],
});
trie.collect(&ir, struct_type);
assert!(trie.contains("/x"));
assert!(trie.contains("/animal"));
assert!(trie.contains("/y"));
assert!(trie.contains("/color"));
}
#[test]
fn collect_from_tagged_union_skips_tags() {
let mut ir = Ir::new();
let mut trie = NameTrie::new();
let kind = {
let n = ir.intern_name("/kind");
ir.add_inst(Inst::Name(n))
};
let move_ = {
let n = ir.intern_name("/move");
ir.add_inst(Inst::Name(n))
};
let x = {
let n = ir.intern_name("/x");
ir.add_inst(Inst::Name(n))
};
let number = {
let n = ir.intern_name("/number");
ir.add_inst(Inst::Name(n))
};
let struct_name = ir.intern_name("fn:Struct");
let move_struct = ir.add_inst(Inst::ApplyFn {
function: struct_name,
args: vec![x, number],
});
let quit = {
let n = ir.intern_name("/quit");
ir.add_inst(Inst::Name(n))
};
let quit_struct = ir.add_inst(Inst::ApplyFn {
function: struct_name,
args: vec![],
});
let tu_name = ir.intern_name("fn:TaggedUnion");
let tu = ir.add_inst(Inst::ApplyFn {
function: tu_name,
args: vec![kind, move_, move_struct, quit, quit_struct],
});
trie.collect(&ir, tu);
assert!(trie.contains("/x"));
assert!(!trie.contains("/number"));
assert!(!trie.contains("/kind"));
assert!(!trie.contains("/move"));
assert!(!trie.contains("/quit"));
}
}