use std::collections::HashMap;
use std::sync::Arc;
use log::warn;
use crate::parser::ast::{MagicRule, MetaType, TypeKind};
#[derive(Debug, Default, Clone)]
pub(crate) struct NameTable {
inner: HashMap<String, Arc<[MagicRule]>>,
}
impl NameTable {
#[must_use]
pub(crate) fn empty() -> Self {
Self {
inner: HashMap::new(),
}
}
#[must_use]
pub(crate) fn get(&self, name: &str) -> Option<Arc<[MagicRule]>> {
self.inner.get(name).cloned()
}
pub(crate) fn sort_subroutines(&mut self, mut sort_fn: impl FnMut(&mut Vec<MagicRule>)) {
for arc in self.inner.values_mut() {
let mut vec: Vec<MagicRule> = arc.iter().cloned().collect();
sort_fn(&mut vec);
*arc = Arc::from(vec);
}
}
pub(crate) fn merge(&mut self, other: Self) {
for (name, rules) in other.inner {
if self.inner.contains_key(&name) {
warn!("duplicate name definition '{name}' across magic files; keeping first");
continue;
}
self.inner.insert(name, rules);
}
}
}
pub(crate) fn extract_name_table(rules: Vec<MagicRule>) -> (Vec<MagicRule>, NameTable) {
let mut table = NameTable::empty();
let mut kept = Vec::with_capacity(rules.len());
for rule in rules {
if let TypeKind::Meta(MetaType::Name(ref name)) = rule.typ {
if table.inner.contains_key(name) {
warn!("duplicate name definition '{name}'; keeping first");
continue;
}
let children = scrub_nested_names(rule.children, rule.level);
table.inner.insert(name.clone(), Arc::from(children));
} else {
let scrubbed_children = scrub_nested_names(rule.children, rule.level);
kept.push(MagicRule {
children: scrubbed_children,
..rule
});
}
}
(kept, table)
}
fn scrub_nested_names(children: Vec<MagicRule>, parent_level: u32) -> Vec<MagicRule> {
let mut kept = Vec::with_capacity(children.len());
for child in children {
if let TypeKind::Meta(MetaType::Name(ref name)) = child.typ {
warn!(
"name directive '{name}' at level {} under parent level {parent_level} is not top-level; skipping",
child.level
);
continue;
}
let scrubbed = scrub_nested_names(child.children, child.level);
kept.push(MagicRule {
children: scrubbed,
..child
});
}
kept
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::ast::{OffsetSpec, Operator, Value};
fn make_rule(level: u32, typ: TypeKind, message: &str, children: Vec<MagicRule>) -> MagicRule {
MagicRule {
offset: OffsetSpec::Absolute(0),
typ,
op: Operator::Equal,
value: Value::Uint(0),
message: message.to_string(),
children,
level,
strength_modifier: None,
value_transform: None,
}
}
#[test]
fn test_extract_empty() {
let (rules, table) = extract_name_table(vec![]);
assert!(rules.is_empty());
assert!(table.get("anything").is_none());
}
#[test]
fn test_extract_single_name_rule() {
let child = make_rule(1, TypeKind::Byte { signed: false }, "child", vec![]);
let name_rule = make_rule(
0,
TypeKind::Meta(MetaType::Name("sub".to_string())),
"",
vec![child],
);
let (rules, table) = extract_name_table(vec![name_rule]);
assert!(rules.is_empty());
let subroutine = table.get("sub").expect("sub subroutine");
assert_eq!(subroutine.len(), 1);
assert_eq!(subroutine[0].message, "child");
}
#[test]
fn test_extract_preserves_non_name_rules() {
let byte_rule = make_rule(0, TypeKind::Byte { signed: false }, "hello", vec![]);
let (rules, table) = extract_name_table(vec![byte_rule]);
assert_eq!(rules.len(), 1);
assert_eq!(rules[0].message, "hello");
assert!(table.get("anything").is_none());
}
#[test]
fn test_extract_duplicate_name_keeps_first() {
let first = make_rule(
0,
TypeKind::Meta(MetaType::Name("dup".to_string())),
"first",
vec![make_rule(
1,
TypeKind::Byte { signed: false },
"first-child",
vec![],
)],
);
let second = make_rule(
0,
TypeKind::Meta(MetaType::Name("dup".to_string())),
"second",
vec![make_rule(
1,
TypeKind::Byte { signed: false },
"second-child",
vec![],
)],
);
let (_, table) = extract_name_table(vec![first, second]);
let subroutine = table.get("dup").expect("first dup kept");
assert_eq!(subroutine.len(), 1);
assert_eq!(subroutine[0].message, "first-child");
}
#[test]
fn test_merge_combines_tables() {
let sub_a = make_rule(
0,
TypeKind::Meta(MetaType::Name("a".to_string())),
"",
vec![],
);
let sub_b = make_rule(
0,
TypeKind::Meta(MetaType::Name("b".to_string())),
"",
vec![],
);
let (_, mut table_a) = extract_name_table(vec![sub_a]);
let (_, table_b) = extract_name_table(vec![sub_b]);
table_a.merge(table_b);
assert!(table_a.get("a").is_some());
assert!(table_a.get("b").is_some());
}
#[test]
fn test_merge_duplicate_keeps_existing() {
let first = make_rule(
0,
TypeKind::Meta(MetaType::Name("dup".to_string())),
"",
vec![make_rule(
1,
TypeKind::Byte { signed: false },
"first-child",
vec![],
)],
);
let second = make_rule(
0,
TypeKind::Meta(MetaType::Name("dup".to_string())),
"",
vec![make_rule(
1,
TypeKind::Byte { signed: false },
"second-child",
vec![],
)],
);
let (_, mut table_a) = extract_name_table(vec![first]);
let (_, table_b) = extract_name_table(vec![second]);
table_a.merge(table_b);
let subroutine = table_a.get("dup").expect("dup kept from first table");
assert_eq!(subroutine[0].message, "first-child");
}
#[test]
fn test_sort_subroutines_reorders_rule_bodies() {
let body = vec![
make_rule(1, TypeKind::Byte { signed: false }, "c", vec![]),
make_rule(1, TypeKind::Byte { signed: false }, "a", vec![]),
make_rule(1, TypeKind::Byte { signed: false }, "b", vec![]),
];
let name_rule = make_rule(
0,
TypeKind::Meta(MetaType::Name("sorted".to_string())),
"",
body,
);
let (_, mut table) = extract_name_table(vec![name_rule]);
table.sort_subroutines(|rules| rules.sort_by(|x, y| x.message.cmp(&y.message)));
let after = table.get("sorted").expect("subroutine retained");
let messages: Vec<&str> = after.iter().map(|r| r.message.as_str()).collect();
assert_eq!(messages, vec!["a", "b", "c"]);
}
#[test]
fn test_sort_subroutines_on_empty_table_is_noop() {
let (_, mut table) = extract_name_table(vec![]);
table.sort_subroutines(|_| unreachable!("empty table must not invoke sort_fn"));
assert!(table.get("any").is_none());
}
#[test]
fn test_sort_subroutines_preserves_merge_policy() {
let first = make_rule(
0,
TypeKind::Meta(MetaType::Name("dup".to_string())),
"",
vec![make_rule(
1,
TypeKind::Byte { signed: false },
"first",
vec![],
)],
);
let second = make_rule(
0,
TypeKind::Meta(MetaType::Name("dup".to_string())),
"",
vec![make_rule(
1,
TypeKind::Byte { signed: false },
"second",
vec![],
)],
);
let (_, mut table_a) = extract_name_table(vec![first]);
table_a.sort_subroutines(|_| {}); let (_, table_b) = extract_name_table(vec![second]);
table_a.merge(table_b);
let subroutine = table_a.get("dup").expect("dup kept from first table");
assert_eq!(subroutine[0].message, "first");
}
}