hugr_model/v0/scope/
link.rs

1use std::hash::{BuildHasherDefault, Hash};
2
3use indexmap::IndexSet;
4use rustc_hash::FxHasher;
5
6use crate::v0::table::{LinkIndex, RegionId};
7
8type FxIndexSet<K> = IndexSet<K, BuildHasherDefault<FxHasher>>;
9
10/// Table for tracking links between ports.
11///
12/// Two ports are connected when they share the same link. Links are named and
13/// scoped via closed regions. Links from one closed region are not visible
14/// in another. Open regions are considered to form the same scope as their
15/// parent region. Links do not have a unique point of declaration.
16///
17/// The link table keeps track of an association between a key of type `K` and
18/// the link indices within each closed region. When resolving links from a text format,
19/// `K` is the name of the link as a string slice. However the link table might
20/// is also useful in other contexts where the key is not a string when constructing
21/// a module from a different representation.
22///
23/// # Examples
24///
25/// ```
26/// # pub use hugr_model::v0::table::RegionId;
27/// # pub use hugr_model::v0::scope::LinkTable;
28/// let mut links = LinkTable::new();
29/// links.enter(RegionId(0));
30/// let foo_0 = links.use_link("foo");
31/// let bar_0 = links.use_link("bar");
32/// assert_eq!(foo_0, links.use_link("foo"));
33/// assert_eq!(bar_0, links.use_link("bar"));
34/// let (num_links, num_ports) = links.exit();
35/// assert_eq!(num_links, 2);
36/// assert_eq!(num_ports, 4);
37/// ```
38#[derive(Debug, Clone)]
39pub struct LinkTable<K> {
40    /// The set of links in the currently active region and all parent regions.
41    ///
42    /// The order in this index set is the order in which links were added to the table.
43    /// This is used to efficiently remove all links from the current region when exiting a scope.
44    links: FxIndexSet<(RegionId, K)>,
45
46    /// The stack of scopes that are currently open.
47    scopes: Vec<LinkScope>,
48}
49
50impl<K> LinkTable<K>
51where
52    K: Copy + Eq + Hash,
53{
54    /// Create a new empty link table.
55    #[must_use]
56    pub fn new() -> Self {
57        Self {
58            links: FxIndexSet::default(),
59            scopes: Vec::new(),
60        }
61    }
62
63    /// Enter a new scope for the given closed region.
64    pub fn enter(&mut self, region: RegionId) {
65        self.scopes.push(LinkScope {
66            link_stack: self.links.len(),
67            link_count: 0,
68            port_count: 0,
69            region,
70        });
71    }
72
73    /// Exit a previously entered scope, returning the number of links and ports in the scope.
74    pub fn exit(&mut self) -> (u32, u32) {
75        let scope = self.scopes.pop().unwrap();
76        self.links.drain(scope.link_stack..);
77        debug_assert_eq!(self.links.len(), scope.link_stack);
78        (scope.link_count, scope.port_count)
79    }
80
81    /// Resolve a link key to a link index, adding one more port to the current scope.
82    ///
83    /// If the key has not been used in the current scope before, it will be added to the link table.
84    ///
85    /// # Panics
86    ///
87    /// Panics if there are no open scopes.
88    pub fn use_link(&mut self, key: K) -> LinkIndex {
89        let scope = self.scopes.last_mut().unwrap();
90        let (map_index, inserted) = self.links.insert_full((scope.region, key));
91
92        if inserted {
93            scope.link_count += 1;
94        }
95
96        scope.port_count += 1;
97        LinkIndex::new(map_index - scope.link_stack)
98    }
99
100    /// Reset the link table to an empty state while preserving allocated memory.
101    pub fn clear(&mut self) {
102        self.links.clear();
103        self.scopes.clear();
104    }
105}
106
107impl<K> Default for LinkTable<K>
108where
109    K: Copy + Eq + Hash,
110{
111    fn default() -> Self {
112        Self::new()
113    }
114}
115
116#[derive(Debug, Clone)]
117struct LinkScope {
118    /// The length of `LinkTable::links` when the scope was opened.
119    link_stack: usize,
120    /// The number of links in this scope.
121    link_count: u32,
122    /// The number of ports in this scope.
123    port_count: u32,
124    /// The region that introduces this scope.
125    region: RegionId,
126}