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 keywords_case_insensitive: &'static CaseInsensitiveKeywordSet,
95 reserved_prefixes: Vec<&'static str>,
96}
97
98impl Namer {
99 fn sanitize<'s>(&self, string: &'s str) -> Cow<'s, str> {
110 let string = string
111 .trim_start_matches(|c: char| c.is_numeric())
112 .trim_end_matches(SEPARATOR);
113
114 let base = if !string.is_empty()
115 && !string.contains("__")
116 && string
117 .chars()
118 .all(|c: char| c.is_ascii_alphanumeric() || c == '_')
119 {
120 Cow::Borrowed(string)
121 } else {
122 let mut filtered = string.chars().fold(String::new(), |mut s, c| {
123 let c = match c {
124 ':' | '<' | '>' | ',' => '_',
127 c => c,
128 };
129 let had_underscore_at_end = s.ends_with('_');
130 if had_underscore_at_end && c == '_' {
131 return s;
132 }
133 if c.is_ascii_alphanumeric() || c == '_' {
134 s.push(c);
135 } else {
136 use core::fmt::Write as _;
137 if !s.is_empty() && !had_underscore_at_end {
138 s.push('_');
139 }
140 write!(s, "u{:04x}_", c as u32).unwrap();
141 }
142 s
143 });
144 let stripped_len = filtered.trim_end_matches(SEPARATOR).len();
145 filtered.truncate(stripped_len);
146 if filtered.is_empty() {
147 filtered.push_str("unnamed");
148 } else if filtered.starts_with(|c: char| c.is_ascii_digit()) {
149 unreachable!(
150 "internal error: invalid identifier starting with ASCII digit {:?}",
151 filtered.chars().nth(0)
152 )
153 }
154 Cow::Owned(filtered)
155 };
156
157 for prefix in &self.reserved_prefixes {
158 if base.starts_with(prefix) {
159 return format!("gen_{base}").into();
160 }
161 }
162
163 base
164 }
165
166 pub fn call(&mut self, label_raw: &str) -> String {
177 use core::fmt::Write as _; let base = self.sanitize(label_raw);
180 debug_assert!(!base.is_empty() && !base.ends_with(SEPARATOR));
181
182 match self.unique.get_mut(base.as_ref()) {
188 Some(count) => {
189 *count += 1;
190 let mut suffixed = base.into_owned();
192 write!(suffixed, "{}{}", SEPARATOR, *count).unwrap();
193 suffixed
194 }
195 None => {
196 let mut suffixed = base.to_string();
197 if base.ends_with(char::is_numeric)
198 || self.keywords.contains(base.as_ref())
199 || self.keywords_case_insensitive.contains(base.as_ref())
200 {
201 suffixed.push(SEPARATOR);
202 }
203 debug_assert!(!self.keywords.contains(&suffixed));
204 self.unique.insert(base.into_owned(), 0);
207 suffixed
208 }
209 }
210 }
211
212 pub fn call_or(&mut self, label: &Option<String>, fallback: &str) -> String {
213 self.call(match *label {
214 Some(ref name) => name,
215 None => fallback,
216 })
217 }
218
219 fn namespace(&mut self, capacity: usize, body: impl FnOnce(&mut Self)) {
225 let fresh = FastHashMap::with_capacity_and_hasher(capacity, Default::default());
226 let outer = core::mem::replace(&mut self.unique, fresh);
227 body(self);
228 self.unique = outer;
229 }
230
231 pub fn reset(
232 &mut self,
233 module: &crate::Module,
234 reserved_keywords: &'static KeywordSet,
235 reserved_keywords_case_insensitive: &'static CaseInsensitiveKeywordSet,
236 reserved_prefixes: &[&'static str],
237 output: &mut FastHashMap<NameKey, String>,
238 ) {
239 self.reserved_prefixes.clear();
240 self.reserved_prefixes.extend(reserved_prefixes.iter());
241
242 self.unique.clear();
243 self.keywords = reserved_keywords;
244 self.keywords_case_insensitive = reserved_keywords_case_insensitive;
245
246 let mut entrypoint_type_fallbacks = FastHashMap::default();
248 for ep in &module.entry_points {
249 if let Some(ref result) = ep.function.result {
250 if let crate::Type {
251 name: None,
252 inner: crate::TypeInner::Struct { .. },
253 } = module.types[result.ty]
254 {
255 let label = match ep.stage {
256 crate::ShaderStage::Vertex => "VertexOutput",
257 crate::ShaderStage::Fragment => "FragmentOutput",
258 crate::ShaderStage::Compute => "ComputeOutput",
259 crate::ShaderStage::Task | crate::ShaderStage::Mesh => unreachable!(),
260 };
261 entrypoint_type_fallbacks.insert(result.ty, label);
262 }
263 }
264 }
265
266 let mut temp = String::new();
267
268 for (ty_handle, ty) in module.types.iter() {
269 let raw_label = match ty.name {
272 Some(ref given_name) => given_name.as_str(),
273 None => entrypoint_type_fallbacks
274 .get(&ty_handle)
275 .cloned()
276 .unwrap_or("type"),
277 };
278 let ty_name = self.call(raw_label);
279 output.insert(NameKey::Type(ty_handle), ty_name);
280
281 if let crate::TypeInner::Struct { ref members, .. } = ty.inner {
282 self.namespace(members.len(), |namer| {
284 for (index, member) in members.iter().enumerate() {
285 let name = namer.call_or(&member.name, "member");
286 output.insert(NameKey::StructMember(ty_handle, index as u32), name);
287 }
288 })
289 }
290 }
291
292 for (ep_index, ep) in module.entry_points.iter().enumerate() {
293 let ep_name = self.call(&ep.name);
294 output.insert(NameKey::EntryPoint(ep_index as _), ep_name);
295 for (index, arg) in ep.function.arguments.iter().enumerate() {
296 let name = self.call_or(&arg.name, "param");
297 output.insert(
298 NameKey::EntryPointArgument(ep_index as _, index as u32),
299 name,
300 );
301 }
302 for (handle, var) in ep.function.local_variables.iter() {
303 let name = self.call_or(&var.name, "local");
304 output.insert(NameKey::EntryPointLocal(ep_index as _, handle), name);
305 }
306 }
307
308 for (fun_handle, fun) in module.functions.iter() {
309 let fun_name = self.call_or(&fun.name, "function");
310 output.insert(NameKey::Function(fun_handle), fun_name);
311 for (index, arg) in fun.arguments.iter().enumerate() {
312 let name = self.call_or(&arg.name, "param");
313 output.insert(NameKey::FunctionArgument(fun_handle, index as u32), name);
314
315 if matches!(
316 module.types[arg.ty].inner,
317 crate::TypeInner::Image {
318 class: crate::ImageClass::External,
319 ..
320 }
321 ) {
322 let base = arg.name.as_deref().unwrap_or("param");
323 for &(suffix, ext_key) in ExternalTextureNameKey::ALL {
324 let name = self.call(&format!("{base}_{suffix}"));
325 output.insert(
326 NameKey::ExternalTextureFunctionArgument(
327 fun_handle,
328 index as u32,
329 ext_key,
330 ),
331 name,
332 );
333 }
334 }
335 }
336 for (handle, var) in fun.local_variables.iter() {
337 let name = self.call_or(&var.name, "local");
338 output.insert(NameKey::FunctionLocal(fun_handle, handle), name);
339 }
340 }
341
342 for (handle, var) in module.global_variables.iter() {
343 let name = self.call_or(&var.name, "global");
344 output.insert(NameKey::GlobalVariable(handle), name);
345
346 if matches!(
347 module.types[var.ty].inner,
348 crate::TypeInner::Image {
349 class: crate::ImageClass::External,
350 ..
351 }
352 ) {
353 let base = var.name.as_deref().unwrap_or("global");
354 for &(suffix, ext_key) in ExternalTextureNameKey::ALL {
355 let name = self.call(&format!("{base}_{suffix}"));
356 output.insert(
357 NameKey::ExternalTextureGlobalVariable(handle, ext_key),
358 name,
359 );
360 }
361 }
362 }
363
364 for (handle, constant) in module.constants.iter() {
365 let label = match constant.name {
366 Some(ref name) => name,
367 None => {
368 use core::fmt::Write;
369 temp.clear();
371 write!(temp, "const_{}", output[&NameKey::Type(constant.ty)]).unwrap();
372 &temp
373 }
374 };
375 let name = self.call(label);
376 output.insert(NameKey::Constant(handle), name);
377 }
378
379 for (handle, override_) in module.overrides.iter() {
380 let label = match override_.name {
381 Some(ref name) => name,
382 None => {
383 use core::fmt::Write;
384 temp.clear();
386 write!(temp, "override_{}", output[&NameKey::Type(override_.ty)]).unwrap();
387 &temp
388 }
389 };
390 let name = self.call(label);
391 output.insert(NameKey::Override(handle), name);
392 }
393 }
394}
395
396#[test]
397fn test() {
398 let mut namer = Namer::default();
399 assert_eq!(namer.call("x"), "x");
400 assert_eq!(namer.call("x"), "x_1");
401 assert_eq!(namer.call("x1"), "x1_");
402 assert_eq!(namer.call("__x"), "_x");
403 assert_eq!(namer.call("1___x"), "_x_1");
404}