Skip to main content

rucc_pp/
hide.rs

1//! Hide sets: the set of macro names a token refuses to be expanded by again.
2//!
3//! Design: `spec/05-preprocessor.md` section 5.3.
4//!
5//! Prosser's algorithm gives every token a set of macro names that are already being
6//! expanded around it, and refuses to expand a token by a macro that is in its set. That is
7//! what makes mutually recursive macros terminate with the answer the standard asks for
8//! rather than with a depth counter's approximation.
9//!
10//! The obvious implementation, a `HashSet<Symbol>` per token, allocates once per token in
11//! the hottest loop of the preprocessor and is not affordable. Hide sets are instead
12//! interned: each distinct set is stored once as a sorted slice, and a token carries a
13//! four byte [`HideSet`] index into that table. Set equality is an integer compare, and the
14//! common case by a wide margin is the empty set, which is always index zero.
15
16use std::collections::HashMap;
17
18use rucc_base::Symbol;
19
20/// An interned set of macro names.
21///
22/// Only meaningful together with the [`HideSets`] table it was created by. There is one
23/// table per translation unit, so mixing two of them is a bug rather than a case to handle.
24#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
25pub struct HideSet(u32);
26
27impl HideSet {
28    /// The empty set, which is index zero in every table.
29    ///
30    /// Available as a constant because a token that came straight from the lexer has an
31    /// empty hide set and building one should not need the table.
32    pub const EMPTY: HideSet = HideSet(0);
33
34    /// Whether this is the empty set.
35    #[inline]
36    pub const fn is_empty(self) -> bool {
37        self.0 == 0
38    }
39
40    /// The underlying index, for packing a hide set into a token.
41    #[inline]
42    pub const fn raw(self) -> u32 {
43        self.0
44    }
45}
46
47/// The interning table for hide sets.
48///
49/// Sets are stored sorted so that membership is a binary search and so that two sets built
50/// in different orders intern to the same index.
51#[derive(Debug)]
52pub struct HideSets {
53    /// Every distinct set, sorted, indexed by [`HideSet`]. Index zero is the empty set.
54    sets: Vec<Box<[Symbol]>>,
55    /// Lookup from contents to index.
56    map: HashMap<Box<[Symbol]>, HideSet>,
57}
58
59impl Default for HideSets {
60    fn default() -> Self {
61        Self::new()
62    }
63}
64
65impl HideSets {
66    /// A table containing only the empty set.
67    pub fn new() -> Self {
68        let empty: Box<[Symbol]> = Box::new([]);
69        let mut map = HashMap::new();
70        map.insert(empty.clone(), HideSet::EMPTY);
71        Self { sets: vec![empty], map }
72    }
73
74    /// The members of a set, sorted.
75    ///
76    /// # Panics
77    ///
78    /// Panics if the set came from a different table.
79    pub fn members(&self, set: HideSet) -> &[Symbol] {
80        &self.sets[set.0 as usize]
81    }
82
83    /// How many distinct sets exist, which is the number worth watching on a macro heavy
84    /// translation unit.
85    pub fn len(&self) -> usize {
86        self.sets.len()
87    }
88
89    /// Always false: the table starts out holding the empty set.
90    pub fn is_empty(&self) -> bool {
91        false
92    }
93
94    /// Whether `name` is hidden by `set`.
95    ///
96    /// # Panics
97    ///
98    /// Panics if the set came from a different table.
99    #[inline]
100    pub fn contains(&self, set: HideSet, name: Symbol) -> bool {
101        // The empty set is by far the most common, and skipping the bounds check and the
102        // binary search for it is worth the branch.
103        !set.is_empty() && self.sets[set.0 as usize].binary_search(&name).is_ok()
104    }
105
106    /// The set `set` with `name` added.
107    ///
108    /// # Panics
109    ///
110    /// Panics if the set came from a different table, or if more than `u32::MAX` distinct
111    /// sets are interned, which would need a translation unit no machine can hold.
112    pub fn add(&mut self, set: HideSet, name: Symbol) -> HideSet {
113        let current = &self.sets[set.0 as usize];
114        let Err(at) = current.binary_search(&name) else {
115            return set;
116        };
117        let mut next = Vec::with_capacity(current.len() + 1);
118        next.extend_from_slice(&current[..at]);
119        next.push(name);
120        next.extend_from_slice(&current[at..]);
121        self.insert(next)
122    }
123
124    /// The union of two sets.
125    ///
126    /// Substitution unions the hide set being applied into whatever the token already
127    /// carried, because an argument token arrives with the hide set it picked up where it
128    /// was written and both restrictions have to hold.
129    ///
130    /// # Panics
131    ///
132    /// Panics if either set came from a different table.
133    pub fn union(&mut self, a: HideSet, b: HideSet) -> HideSet {
134        if a == b || b.is_empty() {
135            return a;
136        }
137        if a.is_empty() {
138            return b;
139        }
140        let merged = merge(&self.sets[a.0 as usize], &self.sets[b.0 as usize], Merge::Union);
141        self.insert(merged)
142    }
143
144    /// The intersection of two sets.
145    ///
146    /// This is the operation function-like expansion needs: the hide set of the result is
147    /// the intersection of the macro name token's set and the closing parenthesis token's
148    /// set, per `spec/05-preprocessor.md` section 5.3.
149    ///
150    /// # Panics
151    ///
152    /// Panics if either set came from a different table.
153    pub fn intersect(&mut self, a: HideSet, b: HideSet) -> HideSet {
154        if a == b {
155            return a;
156        }
157        if a.is_empty() || b.is_empty() {
158            return HideSet::EMPTY;
159        }
160        let merged = merge(&self.sets[a.0 as usize], &self.sets[b.0 as usize], Merge::Intersect);
161        self.insert(merged)
162    }
163
164    /// Interns a sorted set, returning the existing index if it has been seen.
165    fn insert(&mut self, sorted: Vec<Symbol>) -> HideSet {
166        let sorted: Box<[Symbol]> = sorted.into_boxed_slice();
167        if let Some(&found) = self.map.get(&sorted) {
168            return found;
169        }
170        let id = HideSet(u32::try_from(self.sets.len()).expect("too many hide sets"));
171        self.sets.push(sorted.clone());
172        self.map.insert(sorted, id);
173        id
174    }
175}
176
177/// Which of the two set operations [`merge`] is performing.
178#[derive(Clone, Copy)]
179enum Merge {
180    Union,
181    Intersect,
182}
183
184/// Merges two sorted slices. Kept out of the methods so that the borrow of the table ends
185/// before the result is interned back into it.
186fn merge(a: &[Symbol], b: &[Symbol], op: Merge) -> Vec<Symbol> {
187    let mut out = Vec::with_capacity(match op {
188        Merge::Union => a.len() + b.len(),
189        Merge::Intersect => a.len().min(b.len()),
190    });
191    let (mut i, mut j) = (0, 0);
192    while i < a.len() && j < b.len() {
193        match a[i].cmp(&b[j]) {
194            std::cmp::Ordering::Less => {
195                if matches!(op, Merge::Union) {
196                    out.push(a[i]);
197                }
198                i += 1;
199            }
200            std::cmp::Ordering::Greater => {
201                if matches!(op, Merge::Union) {
202                    out.push(b[j]);
203                }
204                j += 1;
205            }
206            std::cmp::Ordering::Equal => {
207                out.push(a[i]);
208                i += 1;
209                j += 1;
210            }
211        }
212    }
213    if matches!(op, Merge::Union) {
214        out.extend_from_slice(&a[i..]);
215        out.extend_from_slice(&b[j..]);
216    }
217    out
218}
219
220#[cfg(test)]
221mod tests {
222    use rucc_base::Interner;
223
224    use super::*;
225
226    fn syms(n: usize) -> (Interner, Vec<Symbol>) {
227        let mut interner = Interner::new();
228        let names = (0..n).map(|i| interner.intern(&format!("M{i}"))).collect();
229        (interner, names)
230    }
231
232    #[test]
233    fn the_empty_set_is_index_zero_and_hides_nothing() {
234        let (_i, s) = syms(1);
235        let sets = HideSets::new();
236        assert_eq!(HideSet::EMPTY.raw(), 0);
237        assert!(!sets.contains(HideSet::EMPTY, s[0]));
238    }
239
240    #[test]
241    fn adding_a_name_makes_it_hidden() {
242        let (_i, s) = syms(2);
243        let mut sets = HideSets::new();
244        let one = sets.add(HideSet::EMPTY, s[0]);
245        assert!(sets.contains(one, s[0]));
246        assert!(!sets.contains(one, s[1]));
247    }
248
249    #[test]
250    fn adding_the_same_name_twice_changes_nothing() {
251        let (_i, s) = syms(1);
252        let mut sets = HideSets::new();
253        let one = sets.add(HideSet::EMPTY, s[0]);
254        assert_eq!(sets.add(one, s[0]), one);
255    }
256
257    #[test]
258    fn sets_built_in_different_orders_are_the_same_set() {
259        let (_i, s) = syms(3);
260        let mut sets = HideSets::new();
261        let forward = {
262            let a = sets.add(HideSet::EMPTY, s[0]);
263            let b = sets.add(a, s[1]);
264            sets.add(b, s[2])
265        };
266        let backward = {
267            let a = sets.add(HideSet::EMPTY, s[2]);
268            let b = sets.add(a, s[0]);
269            sets.add(b, s[1])
270        };
271        assert_eq!(forward, backward, "interning must not depend on insertion order");
272        assert_eq!(sets.members(forward).len(), 3);
273    }
274
275    #[test]
276    fn union_keeps_everything_from_both() {
277        let (_i, s) = syms(3);
278        let mut sets = HideSets::new();
279        let a = sets.add(HideSet::EMPTY, s[0]);
280        let a = sets.add(a, s[1]);
281        let b = sets.add(HideSet::EMPTY, s[1]);
282        let b = sets.add(b, s[2]);
283        let u = sets.union(a, b);
284        assert_eq!(sets.members(u), &[s[0], s[1], s[2]]);
285    }
286
287    #[test]
288    fn intersect_keeps_only_what_is_in_both() {
289        let (_i, s) = syms(3);
290        let mut sets = HideSets::new();
291        let a = sets.add(HideSet::EMPTY, s[0]);
292        let a = sets.add(a, s[1]);
293        let b = sets.add(HideSet::EMPTY, s[1]);
294        let b = sets.add(b, s[2]);
295        let x = sets.intersect(a, b);
296        assert_eq!(sets.members(x), &[s[1]]);
297    }
298
299    #[test]
300    fn intersecting_with_the_empty_set_is_empty() {
301        let (_i, s) = syms(1);
302        let mut sets = HideSets::new();
303        let a = sets.add(HideSet::EMPTY, s[0]);
304        assert_eq!(sets.intersect(a, HideSet::EMPTY), HideSet::EMPTY);
305    }
306
307    #[test]
308    fn a_hide_set_is_four_bytes() {
309        assert_eq!(size_of::<HideSet>(), 4);
310    }
311}