1use alloc::{
2 borrow::Cow,
3 format,
4 string::{String, ToString},
5 vec::Vec,
6};
7
8use crate::{
9 arena::Handle,
10 proc::{keyword_set::CaseInsensitiveKeywordSet, KeywordSet},
11 FastHashMap,
12};
13
14pub type EntryPointIndex = u16;
15const SEPARATOR: char = '_';
16
17#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
37pub enum ExternalTextureNameKey {
38 Plane(usize),
39 Params,
40}
41
42impl ExternalTextureNameKey {
43 const ALL: &[(&str, ExternalTextureNameKey)] = &[
44 ("_plane0", ExternalTextureNameKey::Plane(0)),
45 ("_plane1", ExternalTextureNameKey::Plane(1)),
46 ("_plane2", ExternalTextureNameKey::Plane(2)),
47 ("_params", ExternalTextureNameKey::Params),
48 ];
49}
50
51#[derive(Debug, Eq, Hash, PartialEq)]
52pub enum NameKey {
53 Constant(Handle<crate::Constant>),
54 Override(Handle<crate::Override>),
55 GlobalVariable(Handle<crate::GlobalVariable>),
56 Type(Handle<crate::Type>),
57 StructMember(Handle<crate::Type>, u32),
58 Function(Handle<crate::Function>),
59 FunctionArgument(Handle<crate::Function>, u32),
60 FunctionLocal(Handle<crate::Function>, Handle<crate::LocalVariable>),
61
62 FunctionOobLocal(Handle<crate::Function>, Handle<crate::Type>),
67
68 EntryPoint(EntryPointIndex),
69 EntryPointLocal(EntryPointIndex, Handle<crate::LocalVariable>),
70 EntryPointArgument(EntryPointIndex, u32),
71
72 EntryPointOobLocal(EntryPointIndex, Handle<crate::Type>),
74
75 ExternalTextureGlobalVariable(Handle<crate::GlobalVariable>, ExternalTextureNameKey),
79
80 ExternalTextureFunctionArgument(Handle<crate::Function>, u32, ExternalTextureNameKey),
85}
86
87#[derive(Default)]
90pub struct Namer {
91 unique: FastHashMap<String, u32>,
93 keywords: &'static KeywordSet,
94 builtin_identifiers: &'static KeywordSet,
95 keywords_case_insensitive: &'static CaseInsensitiveKeywordSet,
96 reserved_prefixes: Vec<&'static str>,
97}
98
99impl Namer {
100 fn sanitize<'s>(&self, string: &'s str) -> Cow<'s, str> {
111 let string = string
112 .trim_start_matches(|c: char| c.is_numeric())
113 .trim_end_matches(SEPARATOR);
114
115 let base = if !string.is_empty()
116 && !string.contains("__")
117 && string
118 .chars()
119 .all(|c: char| c.is_ascii_alphanumeric() || c == '_')
120 {
121 Cow::Borrowed(string)
122 } else {
123 let mut filtered = string.chars().fold(String::new(), |mut s, c| {
124 let c = match c {
125 ':' | '<' | '>' | ',' => '_',
128 c => c,
129 };
130 let had_underscore_at_end = s.ends_with('_');
131 if had_underscore_at_end && c == '_' {
132 return s;
133 }
134 if c.is_ascii_alphanumeric() || c == '_' {
135 s.push(c);
136 } else {
137 use core::fmt::Write as _;
138 if !s.is_empty() && !had_underscore_at_end {
139 s.push('_');
140 }
141 write!(s, "u{:04x}_", c as u32).unwrap();
142 }
143 s
144 });
145 let stripped_len = filtered.trim_end_matches(SEPARATOR).len();
146 filtered.truncate(stripped_len);
147 if filtered.is_empty() {
148 filtered.push_str("unnamed");
149 } else if filtered.starts_with(|c: char| c.is_ascii_digit()) {
150 unreachable!(
151 "internal error: invalid identifier starting with ASCII digit {:?}",
152 filtered.chars().nth(0)
153 )
154 }
155 Cow::Owned(filtered)
156 };
157
158 for prefix in &self.reserved_prefixes {
159 if base.starts_with(prefix) {
160 return format!("gen_{base}").into();
161 }
162 }
163
164 base
165 }
166
167 pub fn call(&mut self, label_raw: &str) -> String {
178 use core::fmt::Write as _; let base = self.sanitize(label_raw);
181 debug_assert!(!base.is_empty() && !base.ends_with(SEPARATOR));
182
183 match self.unique.get_mut(base.as_ref()) {
189 Some(count) => {
190 *count += 1;
191 let mut suffixed = base.into_owned();
193 write!(suffixed, "{}{}", SEPARATOR, *count).unwrap();
194 suffixed
195 }
196 None => {
197 let mut suffixed = base.to_string();
198 if base.ends_with(char::is_numeric)
199 || self.keywords.contains(base.as_ref())
200 || self.keywords_case_insensitive.contains(base.as_ref())
201 || self.builtin_identifiers.contains(base.as_ref())
202 {
203 suffixed.push(SEPARATOR);
204 }
205 debug_assert!(!self.keywords.contains(&suffixed));
206 self.unique.insert(base.into_owned(), 0);
209 suffixed
210 }
211 }
212 }
213
214 pub fn call_or(&mut self, label: &Option<String>, fallback: &str) -> String {
215 self.call(match *label {
216 Some(ref name) => name,
217 None => fallback,
218 })
219 }
220
221 fn namespace(&mut self, capacity: usize, body: impl FnOnce(&mut Self)) {
227 let empty_unique = FastHashMap::with_capacity_and_hasher(capacity, Default::default());
228 let saved_unique = core::mem::replace(&mut self.unique, empty_unique);
229 let saved_builtin_identifiers = core::mem::take(&mut self.builtin_identifiers);
230 body(self);
231 self.unique = saved_unique;
232 self.builtin_identifiers = saved_builtin_identifiers;
233 }
234
235 pub fn reset(
236 &mut self,
237 module: &crate::Module,
238 reserved_keywords: &'static KeywordSet,
239 builtin_identifiers: &'static KeywordSet,
240 reserved_keywords_case_insensitive: &'static CaseInsensitiveKeywordSet,
241 reserved_prefixes: &[&'static str],
242 output: &mut FastHashMap<NameKey, String>,
243 ) {
244 self.reserved_prefixes.clear();
245 self.reserved_prefixes.extend(reserved_prefixes.iter());
246
247 self.unique.clear();
248 self.keywords = reserved_keywords;
249 self.builtin_identifiers = builtin_identifiers;
250 self.keywords_case_insensitive = reserved_keywords_case_insensitive;
251
252 let mut entrypoint_type_fallbacks = FastHashMap::default();
254 for ep in &module.entry_points {
255 if let Some(ref result) = ep.function.result {
256 if let crate::Type {
257 name: None,
258 inner: crate::TypeInner::Struct { .. },
259 } = module.types[result.ty]
260 {
261 let label = match ep.stage {
262 crate::ShaderStage::Vertex => "VertexOutput",
263 crate::ShaderStage::Fragment => "FragmentOutput",
264 crate::ShaderStage::Compute => "ComputeOutput",
265 crate::ShaderStage::Task
266 | crate::ShaderStage::Mesh
267 | crate::ShaderStage::RayGeneration
268 | crate::ShaderStage::ClosestHit
269 | crate::ShaderStage::AnyHit
270 | crate::ShaderStage::Miss => unreachable!(),
271 };
272 entrypoint_type_fallbacks.insert(result.ty, label);
273 }
274 }
275 }
276
277 let mut temp = String::new();
278
279 for (ty_handle, ty) in module.types.iter() {
280 let raw_label = match ty.name {
283 Some(ref given_name) => given_name.as_str(),
284 None => entrypoint_type_fallbacks
285 .get(&ty_handle)
286 .cloned()
287 .unwrap_or("type"),
288 };
289 let ty_name = self.call(raw_label);
290 output.insert(NameKey::Type(ty_handle), ty_name);
291
292 if let crate::TypeInner::Struct { ref members, .. } = ty.inner {
293 self.namespace(members.len(), |namer| {
295 for (index, member) in members.iter().enumerate() {
296 let name = namer.call_or(&member.name, "member");
297 output.insert(NameKey::StructMember(ty_handle, index as u32), name);
298 }
299 })
300 }
301 }
302
303 for (ep_index, ep) in module.entry_points.iter().enumerate() {
304 let ep_name = self.call(&ep.name);
305 output.insert(NameKey::EntryPoint(ep_index as _), ep_name);
306 for (index, arg) in ep.function.arguments.iter().enumerate() {
307 let name = self.call_or(&arg.name, "param");
308 output.insert(
309 NameKey::EntryPointArgument(ep_index as _, index as u32),
310 name,
311 );
312 }
313 for (handle, var) in ep.function.local_variables.iter() {
314 let name = self.call_or(&var.name, "local");
315 output.insert(NameKey::EntryPointLocal(ep_index as _, handle), name);
316 }
317 }
318
319 for (fun_handle, fun) in module.functions.iter() {
320 let fun_name = self.call_or(&fun.name, "function");
321 output.insert(NameKey::Function(fun_handle), fun_name);
322 for (index, arg) in fun.arguments.iter().enumerate() {
323 let name = self.call_or(&arg.name, "param");
324 output.insert(NameKey::FunctionArgument(fun_handle, index as u32), name);
325
326 if matches!(
327 module.types[arg.ty].inner,
328 crate::TypeInner::Image {
329 class: crate::ImageClass::External,
330 ..
331 }
332 ) {
333 let base = arg.name.as_deref().unwrap_or("param");
334 for &(suffix, ext_key) in ExternalTextureNameKey::ALL {
335 let name = self.call(&format!("{base}_{suffix}"));
336 output.insert(
337 NameKey::ExternalTextureFunctionArgument(
338 fun_handle,
339 index as u32,
340 ext_key,
341 ),
342 name,
343 );
344 }
345 }
346 }
347 for (handle, var) in fun.local_variables.iter() {
348 let name = self.call_or(&var.name, "local");
349 output.insert(NameKey::FunctionLocal(fun_handle, handle), name);
350 }
351 }
352
353 for (handle, var) in module.global_variables.iter() {
354 let name = self.call_or(&var.name, "global");
355 output.insert(NameKey::GlobalVariable(handle), name);
356
357 if matches!(
358 module.types[var.ty].inner,
359 crate::TypeInner::Image {
360 class: crate::ImageClass::External,
361 ..
362 }
363 ) {
364 let base = var.name.as_deref().unwrap_or("global");
365 for &(suffix, ext_key) in ExternalTextureNameKey::ALL {
366 let name = self.call(&format!("{base}_{suffix}"));
367 output.insert(
368 NameKey::ExternalTextureGlobalVariable(handle, ext_key),
369 name,
370 );
371 }
372 }
373 }
374
375 for (handle, constant) in module.constants.iter() {
376 let label = match constant.name {
377 Some(ref name) => name,
378 None => {
379 use core::fmt::Write;
380 temp.clear();
382 write!(temp, "const_{}", output[&NameKey::Type(constant.ty)]).unwrap();
383 &temp
384 }
385 };
386 let name = self.call(label);
387 output.insert(NameKey::Constant(handle), name);
388 }
389
390 for (handle, override_) in module.overrides.iter() {
391 let label = match override_.name {
392 Some(ref name) => name,
393 None => {
394 use core::fmt::Write;
395 temp.clear();
397 write!(temp, "override_{}", output[&NameKey::Type(override_.ty)]).unwrap();
398 &temp
399 }
400 };
401 let name = self.call(label);
402 output.insert(NameKey::Override(handle), name);
403 }
404 }
405}
406
407#[test]
408fn test() {
409 let mut namer = Namer::default();
410 assert_eq!(namer.call("x"), "x");
411 assert_eq!(namer.call("x"), "x_1");
412 assert_eq!(namer.call("x1"), "x1_");
413 assert_eq!(namer.call("__x"), "_x");
414 assert_eq!(namer.call("1___x"), "_x_1");
415}