Skip to main content

libsurfer/
variable_name_type.rs

1use derive_more::{Display, FromStr};
2use enum_iterator::Sequence;
3use itertools::Itertools;
4use std::collections::HashMap;
5
6use serde::{Deserialize, Serialize};
7
8use crate::displayed_item_tree::Node;
9use crate::wave_container::{ScopeRefExt, VariableRefExt};
10use crate::{displayed_item::DisplayedItem, wave_container::VariableRef, wave_data::WaveData};
11
12#[derive(PartialEq, Copy, Clone, Debug, Deserialize, Display, FromStr, Serialize, Sequence)]
13pub enum VariableNameType {
14    /// Local variable name only (i.e. for tb.dut.clk => clk)
15    Local,
16
17    /// Add unique prefix, prefix + local
18    Unique,
19
20    /// Full variable name (i.e. tb.dut.clk => tb.dut.clk)
21    Global,
22}
23
24const ELLIPSIS: &str = "…";
25
26impl WaveData {
27    pub fn compute_variable_display_names(&mut self) {
28        // First pass: collect all unique variable refs
29        let full_names: Vec<&VariableRef> = self
30            .items_tree
31            .iter()
32            .filter_map(|node| {
33                self.displayed_items
34                    .get(&node.item_ref)
35                    .and_then(|item| match item {
36                        DisplayedItem::Variable(variable) => Some(&variable.variable_ref),
37                        _ => None,
38                    })
39            })
40            .unique()
41            .collect();
42        // Compute minimal unique display names for collision groups.
43        let minimal_map = compute_minimal_display_map(&full_names);
44
45        // Single pass: update display names for all items using the precomputed map
46        for Node { item_ref, .. } in self.items_tree.iter() {
47            self.displayed_items
48                .entry(*item_ref)
49                .and_modify(|item| match item {
50                    DisplayedItem::Variable(variable) => {
51                        variable.display_name = match variable.display_name_type {
52                            VariableNameType::Local => variable.variable_ref.name.clone(),
53                            VariableNameType::Global => variable.variable_ref.full_path_string(),
54                            VariableNameType::Unique => minimal_map
55                                .get(&variable.variable_ref.full_path_string())
56                                .cloned()
57                                .unwrap_or_else(|| variable.variable_ref.name.clone()),
58                        };
59                        if self.display_variable_indices {
60                            let index = self
61                                .inner
62                                .as_waves()
63                                .unwrap()
64                                .variable_meta(&variable.variable_ref)
65                                .ok()
66                                .as_ref()
67                                .and_then(|meta| meta.index)
68                                .map(|index| format!(" {index}"))
69                                .unwrap_or_default();
70                            variable.display_name = format!("{}{}", variable.display_name, index);
71                        }
72                    }
73                    DisplayedItem::Divider(_) => {}
74                    DisplayedItem::Marker(_) => {}
75                    DisplayedItem::TimeLine(_) => {}
76                    DisplayedItem::Placeholder(_) => {}
77                    DisplayedItem::Stream(_) => {}
78                    DisplayedItem::Group(_) => {}
79                });
80        }
81    }
82
83    pub fn force_variable_name_type(&mut self, name_type: VariableNameType) {
84        for Node { item_ref, .. } in self.items_tree.iter() {
85            self.displayed_items.entry(*item_ref).and_modify(|item| {
86                if let DisplayedItem::Variable(variable) = item {
87                    variable.display_name_type = name_type;
88                }
89            });
90        }
91        self.default_variable_name_type = name_type;
92        self.compute_variable_display_names();
93    }
94}
95
96/// Compute minimal unique display names for a set of variables.
97/// Returns a map from `full_path_string()` -> minimal display name.
98fn compute_minimal_display_map(all_variables: &[&VariableRef]) -> HashMap<String, String> {
99    // Group variables by their local name: only collisions within the same
100    // local name need disambiguation.
101    let mut groups: HashMap<String, Vec<&VariableRef>> = HashMap::new();
102    for v in all_variables {
103        groups.entry(v.name.clone()).or_default().push(v);
104    }
105
106    let mut result: HashMap<String, String> = HashMap::with_capacity(all_variables.len());
107
108    for (_local, vars) in groups {
109        if vars.len() == 1 {
110            let v = vars[0];
111            result.insert(v.full_path_string(), v.name.clone());
112            continue;
113        }
114
115        // Build reversed scope component vectors for sorting and comparison.
116        let mut entries: Vec<(Vec<String>, &VariableRef)> = vars
117            .iter()
118            .map(|v| {
119                let rev: Vec<String> = v.path.strs().iter().rev().cloned().collect();
120                (rev, *v)
121            })
122            .collect();
123
124        entries.sort_by(|a, b| a.0.cmp(&b.0));
125
126        // Helper to compute common prefix length of two reversed component vectors.
127        fn common_prefix_len(a: &[String], b: &[String]) -> usize {
128            a.iter().zip(b.iter()).take_while(|(x, y)| x == y).count()
129        }
130
131        for (i, (path_i, var_i)) in entries.iter().enumerate() {
132            let mut need = 0usize;
133            if i > 0 {
134                let common = common_prefix_len(path_i, &entries[i - 1].0);
135                need = need.max(common + 1);
136            }
137            if i + 1 < entries.len() {
138                let common = common_prefix_len(path_i, &entries[i + 1].0);
139                need = need.max(common + 1);
140            }
141
142            // If need is zero, fallback to local name.
143            if need == 0 || path_i.is_empty() {
144                result.insert(var_i.full_path_string(), var_i.name.clone());
145                continue;
146            }
147
148            // Take up to `need` reversed components, then reverse them back for display.
149            let take = need.min(path_i.len());
150            let scope = path_i.iter().take(take).rev().cloned().join(".");
151            let prefix = if take < path_i.len() { ELLIPSIS } else { "" };
152
153            let display = if scope.is_empty() {
154                var_i.name.clone()
155            } else {
156                format!("{}{}.{}", prefix, scope, var_i.name)
157            };
158            result.insert(var_i.full_path_string(), display);
159        }
160    }
161
162    result
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn minimal_display_map_unique_locals() {
171        let v1 = VariableRef::from_hierarchy_string("top.a");
172        let v2 = VariableRef::from_hierarchy_string("top.b");
173        let vars = vec![&v1, &v2];
174        let map = compute_minimal_display_map(&vars);
175        assert_eq!(
176            map.get(&v1.full_path_string())
177                .map(std::string::String::as_str),
178            Some("a")
179        );
180        assert_eq!(
181            map.get(&v2.full_path_string())
182                .map(std::string::String::as_str),
183            Some("b")
184        );
185    }
186
187    #[test]
188    fn minimal_display_map_collisions() {
189        let v1 = VariableRef::from_hierarchy_string("top.dut.x");
190        let v2 = VariableRef::from_hierarchy_string("other.dut.x");
191        let v3 = VariableRef::from_hierarchy_string("top.sub.x");
192        let vars = vec![&v1, &v2, &v3];
193        let map = compute_minimal_display_map(&vars);
194
195        assert_eq!(
196            map.get(&v1.full_path_string())
197                .map(std::string::String::as_str),
198            Some("top.dut.x")
199        );
200        assert_eq!(
201            map.get(&v2.full_path_string())
202                .map(std::string::String::as_str),
203            Some("other.dut.x")
204        );
205        assert_eq!(
206            map.get(&v3.full_path_string())
207                .map(std::string::String::as_str),
208            Some(ELLIPSIS.to_owned() + "sub.x").as_deref()
209        );
210    }
211
212    #[test]
213    fn minimal_display_map_root_and_scoped() {
214        let v1 = VariableRef::from_hierarchy_string("x");
215        let v2 = VariableRef::from_hierarchy_string("a.x");
216        let vars = vec![&v1, &v2];
217        let map = compute_minimal_display_map(&vars);
218        assert_eq!(
219            map.get(&v1.full_path_string())
220                .map(std::string::String::as_str),
221            Some("x")
222        );
223        assert_eq!(
224            map.get(&v2.full_path_string())
225                .map(std::string::String::as_str),
226            Some("a.x")
227        );
228    }
229}