use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::ops::Range;
use sv_lang::{AssertionExpr, Expression, Node, Symbol, SyntaxTree, TimingControl};
#[derive(Clone, Copy, Debug)]
pub struct Trees<'t>(&'t [SyntaxTree]);
impl<'t> Trees<'t> {
pub fn new(trees: &'t [SyntaxTree]) -> Self {
Trees(trees)
}
pub fn syntax<S: HasSyntax>(self, x: &S) -> Option<Node<'t>> {
self.0.iter().find_map(|t| x.syntax_in(t))
}
}
impl<'t> From<&'t SyntaxTree> for Trees<'t> {
fn from(tree: &'t SyntaxTree) -> Self {
Trees(std::slice::from_ref(tree))
}
}
impl<'t> From<&'t [SyntaxTree]> for Trees<'t> {
fn from(trees: &'t [SyntaxTree]) -> Self {
Trees(trees)
}
}
pub trait HasSyntax {
fn syntax_in<'t>(&self, tree: &'t SyntaxTree) -> Option<Node<'t>>;
}
macro_rules! has_syntax {
($($ty:ident),*) => {$(
impl HasSyntax for $ty<'_> {
fn syntax_in<'t>(&self, tree: &'t SyntaxTree) -> Option<Node<'t>> {
self.syntax(tree)
}
}
)*};
}
has_syntax!(AssertionExpr, Expression, Symbol, TimingControl);
thread_local! {
static TREE_IDS: RefCell<HashMap<u64, usize>> = RefCell::new(HashMap::new());
}
const TREE_SHIFT: u32 = 40;
pub fn key(node: Node<'_>) -> Range<usize> {
let mut root = node;
while let Some(parent) = root.parent() {
root = parent;
}
let mut hasher = DefaultHasher::new();
root.hash(&mut hasher);
let id = TREE_IDS.with(|ids| {
let mut ids = ids.borrow_mut();
let next = ids.len();
*ids.entry(hasher.finish()).or_insert(next)
});
let range = node.byte_range();
let base = id << TREE_SHIFT;
base + range.start..base + range.end
}
#[cfg(test)]
mod tests {
use super::*;
use sv_lang::kinds::SyntaxKind;
use sv_lang::{Compilation, Session};
fn declarator(tree: &SyntaxTree) -> Node<'_> {
tree.root()
.descendants()
.find(|n| n.kind() == SyntaxKind::Declarator)
.expect("the fixture declares `x`")
}
#[test]
fn equal_byte_ranges_in_two_files_have_distinct_keys() {
let session = Session::new();
let mut comp = Compilation::new(&session).unwrap();
comp.add_source("module m1; logic x; endmodule\n").unwrap();
comp.add_source("module m2; logic x; endmodule\n").unwrap();
let design = comp.compile().unwrap();
let trees = design.syntax_trees();
assert_eq!(trees.len(), 2, "one tree per source");
let set = Trees::new(&trees);
let decl: Vec<Node<'_>> = trees.iter().map(declarator).collect();
assert_eq!(
decl[0].byte_range(),
decl[1].byte_range(),
"the layout is the same in both files"
);
assert_ne!(key(decl[0]), key(decl[1]), "the keys are not");
assert_eq!(key(decl[0]), key(decl[0]), "a node's key is stable");
let found: Vec<Node<'_>> = design
.top_instances()
.map(|inst| {
let x = inst.instance_body().unwrap().find("x").unwrap();
set.syntax(&x).expect("a symbol of either file has syntax")
})
.collect();
assert_eq!(found.len(), 2);
for node in &found {
assert!(
decl.contains(node),
"found the declarator of its own tree: {node:?}"
);
}
assert_ne!(found[0], found[1], "each in its own tree");
assert_ne!(key(found[0]), key(found[1]));
}
#[test]
fn keys_within_one_tree_preserve_range_arithmetic() {
let session = Session::new();
let tree = session
.parse("module m; logic x; logic y; endmodule\n")
.unwrap();
let decls: Vec<Node<'_>> = tree
.root()
.descendants()
.filter(|n| n.kind() == SyntaxKind::Declarator)
.collect();
assert_eq!(decls.len(), 2);
let (a, b) = (key(decls[0]), key(decls[1]));
assert_eq!(b.start - a.start, 9, "`logic y` is 9 bytes after `logic x`");
assert_eq!(a.end - a.start, decls[0].byte_range().len());
}
}