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