Skip to content

Commit d8f0112

Browse files
authored
Merge pull request #442 from brendanzab/move-name-generation
Move alphabetic name creation into string interner
2 parents 659a1a4 + c6f3b7d commit d8f0112

File tree

2 files changed

+69
-30
lines changed

2 files changed

+69
-30
lines changed

fathom/src/source.rs

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub type StringId = string_interner::symbol::SymbolU16;
99

1010
/// String interner.
1111
pub struct StringInterner {
12+
alphabetic_names: Vec<StringId>,
1213
tuple_labels: Vec<StringId>,
1314
strings: string_interner::StringInterner<
1415
string_interner::backend::BucketBackend<StringId>,
@@ -37,33 +38,70 @@ impl StringInterner {
3738
/// Construct an empty string interner.
3839
pub fn new() -> StringInterner {
3940
StringInterner {
41+
alphabetic_names: Vec::new(),
4042
tuple_labels: Vec::new(),
4143
strings: string_interner::StringInterner::new(),
4244
}
4345
}
4446

45-
/// Allocate and intern all tuple labels upto `max_index` if they are not already present
47+
/// Allocate and intern all alphabetic names up-to and including `max_index`
48+
/// if they are not already present.
49+
pub fn reserve_alphabetic_names(&mut self, max_index: usize) {
50+
fill_vec(&mut self.alphabetic_names, max_index, |index| {
51+
self.strings.get_or_intern(alphabetic_name(index))
52+
})
53+
}
54+
55+
/// Retrieve an alphabetic name based on a numeric count. This is useful for
56+
/// producing human-readable names for unnamed binders.
57+
///
58+
/// ## Example
59+
///
60+
/// ```rust
61+
/// use fathom::source::StringInterner;
62+
///
63+
/// let mut interner = StringInterner::new();
64+
/// assert_eq!(interner.get_alphabetic_name(0), interner.get_or_intern("a"));
65+
/// // ...
66+
/// assert_eq!(interner.get_alphabetic_name(25), interner.get_or_intern("z"));
67+
/// assert_eq!(interner.get_alphabetic_name(26), interner.get_or_intern("a1"));
68+
/// // ...
69+
/// assert_eq!(interner.get_alphabetic_name(51), interner.get_or_intern("z1"));
70+
/// assert_eq!(interner.get_alphabetic_name(52), interner.get_or_intern("a2"));
71+
/// // ...
72+
/// ```
73+
pub fn get_alphabetic_name(&mut self, index: usize) -> StringId {
74+
self.reserve_alphabetic_names(index);
75+
self.alphabetic_names[index]
76+
}
77+
78+
/// Allocate and intern all tuple labels up-to and including `max_index`
79+
/// if they are not already present.
4680
pub fn reserve_tuple_labels(&mut self, max_index: usize) {
47-
let len = self.tuple_labels.len();
48-
let cap = self.tuple_labels.capacity();
49-
if max_index >= len {
50-
self.tuple_labels.reserve(max_index.saturating_sub(cap));
51-
for index in len..=max_index {
52-
let label = self.get_or_intern(format!("_{index}"));
53-
self.tuple_labels.push(label);
54-
}
55-
}
81+
fill_vec(&mut self.tuple_labels, max_index, |index| {
82+
self.strings.get_or_intern(format!("_{index}"))
83+
})
5684
}
5785

5886
/// Get or intern a string in the form `_{index}`.
87+
///
88+
/// ## Example
89+
///
90+
/// ```rust
91+
/// use fathom::source::StringInterner;
92+
///
93+
/// let mut interner = StringInterner::new();
94+
/// assert_eq!(interner.get_tuple_label(0), interner.get_or_intern("_0"));
95+
/// assert_eq!(interner.get_tuple_label(1), interner.get_or_intern("_1"));
96+
/// ```
5997
pub fn get_tuple_label(&mut self, index: usize) -> StringId {
6098
self.reserve_tuple_labels(index);
6199
self.tuple_labels[index]
62100
}
63101

64102
/// Get or intern a slice of strings in the form `_{index}` for each index in `range`.
65103
pub fn get_tuple_labels(&mut self, range: Range<usize>) -> &[StringId] {
66-
self.reserve_tuple_labels(range.end);
104+
self.reserve_tuple_labels(range.end.saturating_sub(1));
67105
&self.tuple_labels[range]
68106
}
69107

@@ -78,6 +116,21 @@ impl StringInterner {
78116
}
79117
}
80118

119+
fn alphabetic_name(index: usize) -> String {
120+
let base = index / 26;
121+
let letter = index % 26;
122+
let letter = (letter as u8 + b'a') as char;
123+
if base == 0 {
124+
format!("{letter}")
125+
} else {
126+
format!("{letter}{base}")
127+
}
128+
}
129+
130+
fn fill_vec<T>(vec: &mut Vec<T>, max_index: usize, f: impl FnMut(usize) -> T) {
131+
vec.extend((vec.len()..=max_index).map(f))
132+
}
133+
81134
#[derive(Debug, Clone)]
82135
pub struct Spanned<T> {
83136
span: Span,

fathom/src/surface/distillation.rs

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -84,37 +84,23 @@ impl<'interner, 'arena, 'env> Context<'interner, 'arena, 'env> {
8484

8585
/// Generate a fresh name that does not appear in `self.local_names`
8686
fn gen_fresh_name(&mut self) -> StringId {
87-
fn to_str(x: u32) -> String {
88-
let base = x / 26;
89-
let letter = x % 26;
90-
let letter = (letter as u8 + b'a') as char;
91-
if base == 0 {
92-
format!("{letter}")
93-
} else {
94-
format!("{letter}{base}")
95-
}
96-
}
97-
9887
let mut counter = 0;
9988
loop {
100-
let name = to_str(counter);
101-
let name = self.interner.borrow_mut().get_or_intern(name);
89+
let name = self.interner.borrow_mut().get_alphabetic_name(counter);
10290
match self.local_names.iter().any(|symbol| *symbol == Some(name)) {
103-
true => {}
91+
true => counter += 1,
10492
false => return name,
10593
}
106-
counter += 1;
10794
}
10895
}
10996

11097
/// Replace `name` with a fresh name if it is `_` and occurs in `body`
11198
fn freshen_name(&mut self, name: Option<StringId>, body: &core::Term<'_>) -> Option<StringId> {
11299
match name {
113100
Some(name) => Some(name),
114-
None => match body.binds_local(Index::last()) {
115-
false => None,
116-
true => Some(self.gen_fresh_name()),
117-
},
101+
None => body
102+
.binds_local(Index::last())
103+
.then(|| self.gen_fresh_name()),
118104
}
119105
}
120106

0 commit comments

Comments
 (0)