1use alloc::{
2 borrow::Cow,
3 boxed::Box,
4 format,
5 string::{String, ToString},
6 vec::Vec,
7};
8use core::hash::{Hash, Hasher};
9
10use hashbrown::HashSet;
11use once_cell::race::OnceBox;
12
13use crate::{arena::Handle, FastHashMap, FastHashSet};
14
15pub type EntryPointIndex = u16;
16const SEPARATOR: char = '_';
17
18#[derive(Debug, Eq, Hash, PartialEq)]
19pub enum NameKey {
20 Constant(Handle<crate::Constant>),
21 GlobalVariable(Handle<crate::GlobalVariable>),
22 Type(Handle<crate::Type>),
23 StructMember(Handle<crate::Type>, u32),
24 Function(Handle<crate::Function>),
25 FunctionArgument(Handle<crate::Function>, u32),
26 FunctionLocal(Handle<crate::Function>, Handle<crate::LocalVariable>),
27
28 FunctionOobLocal(Handle<crate::Function>, Handle<crate::Type>),
33
34 EntryPoint(EntryPointIndex),
35 EntryPointLocal(EntryPointIndex, Handle<crate::LocalVariable>),
36 EntryPointArgument(EntryPointIndex, u32),
37
38 EntryPointOobLocal(EntryPointIndex, Handle<crate::Type>),
40}
41
42pub struct Namer {
45 unique: FastHashMap<String, u32>,
47 keywords: &'static HashSet<&'static str>,
48 keywords_case_insensitive: FastHashSet<AsciiUniCase<&'static str>>,
49 reserved_prefixes: Vec<&'static str>,
50}
51
52impl Default for Namer {
53 fn default() -> Self {
54 static DEFAULT_KEYWORDS: OnceBox<HashSet<&'static str>> = OnceBox::new();
55
56 Self {
57 unique: Default::default(),
58 keywords: DEFAULT_KEYWORDS.get_or_init(|| Box::new(HashSet::default())),
59 keywords_case_insensitive: Default::default(),
60 reserved_prefixes: Default::default(),
61 }
62 }
63}
64
65impl Namer {
66 fn sanitize<'s>(&self, string: &'s str) -> Cow<'s, str> {
77 let string = string
78 .trim_start_matches(|c: char| c.is_numeric())
79 .trim_end_matches(SEPARATOR);
80
81 let base = if !string.is_empty()
82 && !string.contains("__")
83 && string
84 .chars()
85 .all(|c: char| c.is_ascii_alphanumeric() || c == '_')
86 {
87 Cow::Borrowed(string)
88 } else {
89 let mut filtered = string
90 .chars()
91 .filter(|&c| c.is_ascii_alphanumeric() || c == '_')
92 .fold(String::new(), |mut s, c| {
93 if s.ends_with('_') && c == '_' {
94 return s;
95 }
96 s.push(c);
97 s
98 });
99 let stripped_len = filtered.trim_end_matches(SEPARATOR).len();
100 filtered.truncate(stripped_len);
101 if filtered.is_empty() {
102 filtered.push_str("unnamed");
103 }
104 Cow::Owned(filtered)
105 };
106
107 for prefix in &self.reserved_prefixes {
108 if base.starts_with(prefix) {
109 return format!("gen_{base}").into();
110 }
111 }
112
113 base
114 }
115
116 pub fn call(&mut self, label_raw: &str) -> String {
127 use core::fmt::Write as _; let base = self.sanitize(label_raw);
130 debug_assert!(!base.is_empty() && !base.ends_with(SEPARATOR));
131
132 match self.unique.get_mut(base.as_ref()) {
138 Some(count) => {
139 *count += 1;
140 let mut suffixed = base.into_owned();
142 write!(suffixed, "{}{}", SEPARATOR, *count).unwrap();
143 suffixed
144 }
145 None => {
146 let mut suffixed = base.to_string();
147 if base.ends_with(char::is_numeric)
148 || self.keywords.contains(base.as_ref())
149 || self
150 .keywords_case_insensitive
151 .contains(&AsciiUniCase(base.as_ref()))
152 {
153 suffixed.push(SEPARATOR);
154 }
155 debug_assert!(!self.keywords.contains::<str>(&suffixed));
156 self.unique.insert(base.into_owned(), 0);
159 suffixed
160 }
161 }
162 }
163
164 pub fn call_or(&mut self, label: &Option<String>, fallback: &str) -> String {
165 self.call(match *label {
166 Some(ref name) => name,
167 None => fallback,
168 })
169 }
170
171 fn namespace(&mut self, capacity: usize, body: impl FnOnce(&mut Self)) {
177 let fresh = FastHashMap::with_capacity_and_hasher(capacity, Default::default());
178 let outer = core::mem::replace(&mut self.unique, fresh);
179 body(self);
180 self.unique = outer;
181 }
182
183 pub fn reset(
184 &mut self,
185 module: &crate::Module,
186 reserved_keywords: &'static HashSet<&'static str>,
187 reserved_keywords_case_insensitive: &[&'static str],
188 reserved_prefixes: &[&'static str],
189 output: &mut FastHashMap<NameKey, String>,
190 ) {
191 self.reserved_prefixes.clear();
192 self.reserved_prefixes.extend(reserved_prefixes.iter());
193
194 self.unique.clear();
195 self.keywords = reserved_keywords;
196
197 debug_assert!(reserved_keywords_case_insensitive
198 .iter()
199 .all(|s| s.is_ascii()));
200 self.keywords_case_insensitive.clear();
201 self.keywords_case_insensitive.extend(
202 reserved_keywords_case_insensitive
203 .iter()
204 .map(|string| AsciiUniCase(*string)),
205 );
206
207 let mut entrypoint_type_fallbacks = FastHashMap::default();
209 for ep in &module.entry_points {
210 if let Some(ref result) = ep.function.result {
211 if let crate::Type {
212 name: None,
213 inner: crate::TypeInner::Struct { .. },
214 } = module.types[result.ty]
215 {
216 let label = match ep.stage {
217 crate::ShaderStage::Vertex => "VertexOutput",
218 crate::ShaderStage::Fragment => "FragmentOutput",
219 crate::ShaderStage::Compute => "ComputeOutput",
220 crate::ShaderStage::Task | crate::ShaderStage::Mesh => unreachable!(),
221 };
222 entrypoint_type_fallbacks.insert(result.ty, label);
223 }
224 }
225 }
226
227 let mut temp = String::new();
228
229 for (ty_handle, ty) in module.types.iter() {
230 let raw_label = match ty.name {
233 Some(ref given_name) => given_name.as_str(),
234 None => entrypoint_type_fallbacks
235 .get(&ty_handle)
236 .cloned()
237 .unwrap_or("type"),
238 };
239 let ty_name = self.call(raw_label);
240 output.insert(NameKey::Type(ty_handle), ty_name);
241
242 if let crate::TypeInner::Struct { ref members, .. } = ty.inner {
243 self.namespace(members.len(), |namer| {
245 for (index, member) in members.iter().enumerate() {
246 let name = namer.call_or(&member.name, "member");
247 output.insert(NameKey::StructMember(ty_handle, index as u32), name);
248 }
249 })
250 }
251 }
252
253 for (ep_index, ep) in module.entry_points.iter().enumerate() {
254 let ep_name = self.call(&ep.name);
255 output.insert(NameKey::EntryPoint(ep_index as _), ep_name);
256 for (index, arg) in ep.function.arguments.iter().enumerate() {
257 let name = self.call_or(&arg.name, "param");
258 output.insert(
259 NameKey::EntryPointArgument(ep_index as _, index as u32),
260 name,
261 );
262 }
263 for (handle, var) in ep.function.local_variables.iter() {
264 let name = self.call_or(&var.name, "local");
265 output.insert(NameKey::EntryPointLocal(ep_index as _, handle), name);
266 }
267 }
268
269 for (fun_handle, fun) in module.functions.iter() {
270 let fun_name = self.call_or(&fun.name, "function");
271 output.insert(NameKey::Function(fun_handle), fun_name);
272 for (index, arg) in fun.arguments.iter().enumerate() {
273 let name = self.call_or(&arg.name, "param");
274 output.insert(NameKey::FunctionArgument(fun_handle, index as u32), name);
275 }
276 for (handle, var) in fun.local_variables.iter() {
277 let name = self.call_or(&var.name, "local");
278 output.insert(NameKey::FunctionLocal(fun_handle, handle), name);
279 }
280 }
281
282 for (handle, var) in module.global_variables.iter() {
283 let name = self.call_or(&var.name, "global");
284 output.insert(NameKey::GlobalVariable(handle), name);
285 }
286
287 for (handle, constant) in module.constants.iter() {
288 let label = match constant.name {
289 Some(ref name) => name,
290 None => {
291 use core::fmt::Write;
292 temp.clear();
294 write!(temp, "const_{}", output[&NameKey::Type(constant.ty)]).unwrap();
295 &temp
296 }
297 };
298 let name = self.call(label);
299 output.insert(NameKey::Constant(handle), name);
300 }
301 }
302}
303
304struct AsciiUniCase<S: AsRef<str> + ?Sized>(S);
306
307impl<S: AsRef<str>> PartialEq<Self> for AsciiUniCase<S> {
308 #[inline]
309 fn eq(&self, other: &Self) -> bool {
310 self.0.as_ref().eq_ignore_ascii_case(other.0.as_ref())
311 }
312}
313
314impl<S: AsRef<str>> Eq for AsciiUniCase<S> {}
315
316impl<S: AsRef<str>> Hash for AsciiUniCase<S> {
317 #[inline]
318 fn hash<H: Hasher>(&self, hasher: &mut H) {
319 for byte in self
320 .0
321 .as_ref()
322 .as_bytes()
323 .iter()
324 .map(|b| b.to_ascii_lowercase())
325 {
326 hasher.write_u8(byte);
327 }
328 }
329}
330
331#[test]
332fn test() {
333 let mut namer = Namer::default();
334 assert_eq!(namer.call("x"), "x");
335 assert_eq!(namer.call("x"), "x_1");
336 assert_eq!(namer.call("x1"), "x1_");
337 assert_eq!(namer.call("__x"), "_x");
338 assert_eq!(namer.call("1___x"), "_x_1");
339}