1use std::collections::{HashMap, HashSet};
2
3use indexmap::IndexMap;
4use regex::Regex;
5
6use super::{
7 comment_strip_iter::CommentReplaceExt,
8 parse_imports::{parse_imports, substitute_identifiers},
9 wgsl_directives::{DiagnosticDirective, EnableDirective, RequiresDirective, WgslDirectives},
10 ComposerErrorInner, ImportDefWithOffset, ShaderDefValue,
11};
12
13#[derive(Debug)]
14pub struct Preprocessor {
15 version_regex: Regex,
16 ifdef_regex: Regex,
17 ifndef_regex: Regex,
18 ifop_regex: Regex,
19 else_regex: Regex,
20 endif_regex: Regex,
21 def_regex: Regex,
22 def_regex_delimited: Regex,
23 import_regex: Regex,
24 define_import_path_regex: Regex,
25 define_shader_def_regex: Regex,
26 enable_regex: Regex,
27 requires_regex: Regex,
28 diagnostic_regex: Regex,
29}
30
31impl Default for Preprocessor {
32 fn default() -> Self {
33 Self {
34 version_regex: Regex::new(r"^\s*#version\s+([0-9]+)").unwrap(),
35 ifdef_regex: Regex::new(r"^\s*#\s*(else\s+)?\s*ifdef\s+([\w|\d|_]+)").unwrap(),
36 ifndef_regex: Regex::new(r"^\s*#\s*(else\s+)?\s*ifndef\s+([\w|\d|_]+)").unwrap(),
37 ifop_regex: Regex::new(
38 r"^\s*#\s*(else\s+)?\s*if\s+([\w|\d|_]+)\s*([=!<>]*)\s*([-\w|\d]+)",
39 )
40 .unwrap(),
41 else_regex: Regex::new(r"^\s*#\s*else").unwrap(),
42 endif_regex: Regex::new(r"^\s*#\s*endif").unwrap(),
43 def_regex: Regex::new(r"#\s*([\w|\d|_]+)").unwrap(),
44 def_regex_delimited: Regex::new(r"#\s*\{([\w|\d|_]+)\}").unwrap(),
45 import_regex: Regex::new(r"^\s*#\s*import\s").unwrap(),
46 define_import_path_regex: Regex::new(r"^\s*#\s*define_import_path\s+([^\s]+)").unwrap(),
47 define_shader_def_regex: Regex::new(r"^\s*#\s*define\s+([\w|\d|_]+)\s*([-\w|\d]+)?")
48 .unwrap(),
49 enable_regex: Regex::new(r"^\s*enable\s+([^;]+)\s*;").unwrap(),
50 requires_regex: Regex::new(r"^\s*requires\s+([^;]+)\s*;").unwrap(),
51 diagnostic_regex: Regex::new(r"^\s*diagnostic\s*\(\s*([^,]+)\s*,\s*([^)]+)\s*\)\s*;")
52 .unwrap(),
53 }
54 }
55}
56
57#[derive(Debug)]
58pub struct PreprocessorMetaData {
59 pub name: Option<String>,
60 pub imports: Vec<ImportDefWithOffset>,
61 pub defines: HashMap<String, ShaderDefValue>,
62 pub effective_defs: HashSet<String>,
63 pub wgsl_directives: WgslDirectives,
64 pub cleaned_source: String,
65}
66
67enum ScopeLevel {
68 Active, PreviouslyActive, NotActive, }
72
73struct Scope(Vec<ScopeLevel>);
74
75impl Scope {
76 fn new() -> Self {
77 Self(vec![ScopeLevel::Active])
78 }
79
80 fn branch(
81 &mut self,
82 is_else: bool,
83 condition: bool,
84 offset: usize,
85 ) -> Result<(), ComposerErrorInner> {
86 if is_else {
87 let prev_scope = self.0.pop().unwrap();
88 let parent_scope = self
89 .0
90 .last()
91 .ok_or(ComposerErrorInner::ElseWithoutCondition(offset))?;
92 let new_scope = if !matches!(parent_scope, ScopeLevel::Active) {
93 ScopeLevel::NotActive
94 } else if !matches!(prev_scope, ScopeLevel::NotActive) {
95 ScopeLevel::PreviouslyActive
96 } else if condition {
97 ScopeLevel::Active
98 } else {
99 ScopeLevel::NotActive
100 };
101
102 self.0.push(new_scope);
103 } else {
104 let parent_scope = self.0.last().unwrap_or(&ScopeLevel::Active);
105 let new_scope = if matches!(parent_scope, ScopeLevel::Active) && condition {
106 ScopeLevel::Active
107 } else {
108 ScopeLevel::NotActive
109 };
110
111 self.0.push(new_scope);
112 }
113
114 Ok(())
115 }
116
117 fn pop(&mut self, offset: usize) -> Result<(), ComposerErrorInner> {
118 self.0.pop();
119 if self.0.is_empty() {
120 Err(ComposerErrorInner::TooManyEndIfs(offset))
121 } else {
122 Ok(())
123 }
124 }
125
126 fn active(&self) -> bool {
127 matches!(self.0.last().unwrap(), ScopeLevel::Active)
128 }
129
130 fn finish(&self, offset: usize) -> Result<(), ComposerErrorInner> {
131 if self.0.len() != 1 {
132 Err(ComposerErrorInner::NotEnoughEndIfs(offset))
133 } else {
134 Ok(())
135 }
136 }
137}
138
139#[derive(Debug)]
140pub struct PreprocessOutput {
141 pub preprocessed_source: String,
142 pub imports: Vec<ImportDefWithOffset>,
143}
144
145impl Preprocessor {
146 fn parse_enable_directive(
147 &self,
148 line: &str,
149 line_idx: usize,
150 ) -> Result<Option<EnableDirective>, ComposerErrorInner> {
151 if let Some(cap) = self.enable_regex.captures(line) {
152 let extensions_str = cap.get(1).unwrap().as_str().trim();
153 let extensions: Vec<String> = extensions_str
154 .split(',')
155 .map(|s| s.trim().to_string())
156 .filter(|s| !s.is_empty())
157 .collect();
158
159 if extensions.is_empty() {
160 return Err(ComposerErrorInner::InvalidWgslDirective {
161 directive: line.to_string(),
162 position: line_idx,
163 reason: "No extensions specified".to_string(),
164 });
165 }
166
167 Ok(Some(EnableDirective {
168 extensions,
169 source_location: line_idx,
170 }))
171 } else {
172 Ok(None)
173 }
174 }
175
176 fn parse_requires_directive(
177 &self,
178 line: &str,
179 line_idx: usize,
180 ) -> Result<Option<RequiresDirective>, ComposerErrorInner> {
181 if let Some(cap) = self.requires_regex.captures(line) {
182 let extensions_str = cap.get(1).unwrap().as_str().trim();
183 let extensions: Vec<String> = extensions_str
184 .split(',')
185 .map(|s| s.trim().to_string())
186 .filter(|s| !s.is_empty())
187 .collect();
188
189 if extensions.is_empty() {
190 return Err(ComposerErrorInner::InvalidWgslDirective {
191 directive: line.to_string(),
192 position: line_idx,
193 reason: "No extensions specified".to_string(),
194 });
195 }
196
197 Ok(Some(RequiresDirective {
198 extensions,
199 source_location: line_idx,
200 }))
201 } else {
202 Ok(None)
203 }
204 }
205
206 fn parse_diagnostic_directive(
207 &self,
208 line: &str,
209 line_idx: usize,
210 ) -> Result<Option<DiagnosticDirective>, ComposerErrorInner> {
211 if let Some(cap) = self.diagnostic_regex.captures(line) {
212 let severity = cap.get(1).unwrap().as_str().trim().to_string();
213 let rule = cap.get(2).unwrap().as_str().trim().to_string();
214
215 match severity.as_str() {
216 "off" | "info" | "warn" | "error" => {}
217 _ => {
218 return Err(ComposerErrorInner::InvalidWgslDirective {
219 directive: line.to_string(),
220 position: line_idx,
221 reason: format!(
222 "Invalid severity '{severity}'. Must be one of: off, info, warn, error"
223 ),
224 });
225 }
226 }
227
228 Ok(Some(DiagnosticDirective {
229 severity,
230 rule,
231 source_location: line_idx,
232 }))
233 } else {
234 Ok(None)
235 }
236 }
237
238 pub fn extract_wgsl_directives(
239 &self,
240 source: &str,
241 ) -> Result<(String, WgslDirectives), ComposerErrorInner> {
242 let mut directives = WgslDirectives::default();
243 let mut cleaned_lines = Vec::new();
244 let mut in_directive_section = true;
245
246 for (line_idx, line) in source.lines().enumerate() {
247 let trimmed = line.trim();
248
249 if trimmed.is_empty() || trimmed.starts_with("//") {
250 cleaned_lines.push(line);
251 continue;
252 }
253
254 if in_directive_section {
255 if let Some(enable) = self.parse_enable_directive(trimmed, line_idx)? {
256 cleaned_lines.push("");
257 directives.enables.push(enable);
258 continue;
259 } else if let Some(requires) = self.parse_requires_directive(trimmed, line_idx)? {
260 cleaned_lines.push("");
261 directives.requires.push(requires);
262 continue;
263 } else if let Some(diagnostic) =
264 self.parse_diagnostic_directive(trimmed, line_idx)?
265 {
266 cleaned_lines.push("");
267 directives.diagnostics.push(diagnostic);
268 continue;
269 } else if !trimmed.starts_with("enable")
270 && !trimmed.starts_with("requires")
271 && !trimmed.starts_with("diagnostic")
272 {
273 in_directive_section = false;
274 }
275 }
276
277 cleaned_lines.push(line);
278 }
279
280 let cleaned_source = cleaned_lines.join("\n");
281 Ok((cleaned_source, directives))
282 }
283
284 fn check_scope<'a>(
285 &self,
286 shader_defs: &HashMap<String, ShaderDefValue>,
287 line: &'a str,
288 scope: Option<&mut Scope>,
289 offset: usize,
290 ) -> Result<(bool, Option<&'a str>), ComposerErrorInner> {
291 if let Some(cap) = self.ifdef_regex.captures(line) {
292 let is_else = cap.get(1).is_some();
293 let def = cap.get(2).unwrap().as_str();
294 let cond = shader_defs.contains_key(def);
295 scope.map_or(Ok(()), |scope| scope.branch(is_else, cond, offset))?;
296 return Ok((true, Some(def)));
297 } else if let Some(cap) = self.ifndef_regex.captures(line) {
298 let is_else = cap.get(1).is_some();
299 let def = cap.get(2).unwrap().as_str();
300 let cond = !shader_defs.contains_key(def);
301 scope.map_or(Ok(()), |scope| scope.branch(is_else, cond, offset))?;
302 return Ok((true, Some(def)));
303 } else if let Some(cap) = self.ifop_regex.captures(line) {
304 let is_else = cap.get(1).is_some();
305 let def = cap.get(2).unwrap().as_str();
306 let op = cap.get(3).unwrap();
307 let val = cap.get(4).unwrap();
308
309 if scope.is_none() {
310 return Ok((true, Some(def)));
312 }
313
314 fn act_on<T: Eq + Ord>(
315 a: T,
316 b: T,
317 op: &str,
318 pos: usize,
319 ) -> Result<bool, ComposerErrorInner> {
320 match op {
321 "==" => Ok(a == b),
322 "!=" => Ok(a != b),
323 ">" => Ok(a > b),
324 ">=" => Ok(a >= b),
325 "<" => Ok(a < b),
326 "<=" => Ok(a <= b),
327 _ => Err(ComposerErrorInner::UnknownShaderDefOperator {
328 pos,
329 operator: op.to_string(),
330 }),
331 }
332 }
333
334 let def_value = shader_defs
335 .get(def)
336 .ok_or(ComposerErrorInner::UnknownShaderDef {
337 pos: offset,
338 shader_def_name: def.to_string(),
339 })?;
340
341 let invalid_def = |ty: &str| ComposerErrorInner::InvalidShaderDefComparisonValue {
342 pos: offset,
343 shader_def_name: def.to_string(),
344 value: val.as_str().to_string(),
345 expected: ty.to_string(),
346 };
347
348 let new_scope = match def_value {
349 ShaderDefValue::Bool(def_value) => {
350 let val = val.as_str().parse().map_err(|_| invalid_def("bool"))?;
351 act_on(*def_value, val, op.as_str(), offset)?
352 }
353 ShaderDefValue::Int(def_value) => {
354 let val = val.as_str().parse().map_err(|_| invalid_def("int"))?;
355 act_on(*def_value, val, op.as_str(), offset)?
356 }
357 ShaderDefValue::UInt(def_value) => {
358 let val = val.as_str().parse().map_err(|_| invalid_def("uint"))?;
359 act_on(*def_value, val, op.as_str(), offset)?
360 }
361 };
362
363 scope.map_or(Ok(()), |scope| scope.branch(is_else, new_scope, offset))?;
364 return Ok((true, Some(def)));
365 } else if self.else_regex.is_match(line) {
366 scope.map_or(Ok(()), |scope| scope.branch(true, true, offset))?;
367 return Ok((true, None));
368 } else if self.endif_regex.is_match(line) {
369 scope.map_or(Ok(()), |scope| scope.pop(offset))?;
370 return Ok((true, None));
371 }
372
373 Ok((false, None))
374 }
375
376 pub fn preprocess(
381 &self,
382 shader_str: &str,
383 shader_defs: &HashMap<String, ShaderDefValue>,
384 ) -> Result<PreprocessOutput, ComposerErrorInner> {
385 let mut declared_imports = IndexMap::new();
386 let mut used_imports = IndexMap::new();
387 let mut scope = Scope::new();
388 let mut final_string = String::new();
389 let mut offset = 0;
390
391 let mut lines = shader_str.lines();
393 let mut lines = lines.replace_comments().zip(shader_str.lines()).peekable();
394
395 while let Some((mut line, original_line)) = lines.next() {
396 let mut output = false;
397
398 if let Some(cap) = self.version_regex.captures(&line) {
399 let v = cap.get(1).unwrap().as_str();
400 if v != "440" && v != "450" {
401 return Err(ComposerErrorInner::GlslInvalidVersion(offset));
402 }
403 } else if self
404 .check_scope(shader_defs, &line, Some(&mut scope), offset)?
405 .0
406 || self.define_import_path_regex.captures(&line).is_some()
407 || self.define_shader_def_regex.captures(&line).is_some()
408 {
409 } else if scope.active() {
411 if self.import_regex.is_match(&line) {
412 let mut import_lines = String::default();
413 let mut open_count = 0;
414 let initial_offset = offset;
415
416 loop {
417 final_string.extend(std::iter::repeat_n(" ", line.len()));
419 offset += line.len() + 1;
420
421 open_count += line.match_indices('{').count();
424 open_count = open_count.saturating_sub(line.match_indices('}').count());
425
426 import_lines.push_str(&line);
430 import_lines.push('\n');
431
432 if open_count == 0 || lines.peek().is_none() {
433 break;
434 }
435
436 final_string.push('\n');
437 line = lines.next().unwrap().0;
438 }
439
440 parse_imports(import_lines.as_str(), &mut declared_imports).map_err(
441 |(err, line_offset)| {
442 ComposerErrorInner::ImportParseError(
443 err.to_owned(),
444 initial_offset + line_offset,
445 )
446 },
447 )?;
448 output = true;
449 } else {
450 let replaced_lines = [original_line, &line].map(|input| {
451 let mut output = input.to_string();
452 for capture in self.def_regex.captures_iter(input) {
453 let def = capture.get(1).unwrap();
454 if let Some(def) = shader_defs.get(def.as_str()) {
455 output = self
456 .def_regex
457 .replace(&output, def.value_as_string())
458 .to_string();
459 }
460 }
461 for capture in self.def_regex_delimited.captures_iter(input) {
462 let def = capture.get(1).unwrap();
463 if let Some(def) = shader_defs.get(def.as_str()) {
464 output = self
465 .def_regex_delimited
466 .replace(&output, def.value_as_string())
467 .to_string();
468 }
469 }
470 output
471 });
472
473 let original_line = &replaced_lines[0];
474 let decommented_line = &replaced_lines[1];
475
476 let item_replaced_line = substitute_identifiers(
478 original_line,
479 offset,
480 &declared_imports,
481 &mut Default::default(),
482 true,
483 )
484 .unwrap();
485 let _ = substitute_identifiers(
487 decommented_line,
488 offset,
489 &declared_imports,
490 &mut used_imports,
491 false,
492 )
493 .map_err(|pos| {
494 ComposerErrorInner::ImportParseError(
495 "Ambiguous import path for item".to_owned(),
496 pos,
497 )
498 })?;
499
500 final_string.push_str(&item_replaced_line);
501 let diff = line.len().saturating_sub(item_replaced_line.len());
502 final_string.extend(std::iter::repeat_n(" ", diff));
503 offset += original_line.len() + 1;
504 output = true;
505 }
506 }
507
508 if !output {
509 final_string.extend(std::iter::repeat_n(" ", line.len()));
511 offset += line.len() + 1;
512 }
513 final_string.push('\n');
514 }
515
516 scope.finish(offset)?;
517
518 Ok(PreprocessOutput {
519 preprocessed_source: final_string,
520 imports: used_imports.into_values().collect(),
521 })
522 }
523
524 pub fn get_preprocessor_metadata(
526 &self,
527 shader_str: &str,
528 allow_defines: bool,
529 ) -> Result<PreprocessorMetaData, ComposerErrorInner> {
530 let (shader_str, wgsl_directives) = self.extract_wgsl_directives(shader_str)?;
531
532 let mut declared_imports = IndexMap::default();
533 let mut used_imports = IndexMap::default();
534 let mut name = None;
535 let mut offset = 0;
536 let mut defines = HashMap::default();
537 let mut effective_defs = HashSet::default();
538
539 let mut lines = shader_str.lines();
540 let mut lines = lines.replace_comments().peekable();
541
542 while let Some(mut line) = lines.next() {
543 let (is_scope, def) = self.check_scope(&HashMap::default(), &line, None, offset)?;
544
545 if is_scope {
546 if let Some(def) = def {
547 effective_defs.insert(def.to_owned());
548 }
549 } else if self.import_regex.is_match(&line) {
550 let mut import_lines = String::default();
551 let mut open_count = 0;
552 let initial_offset = offset;
553
554 loop {
555 open_count += line.match_indices('{').count();
558 open_count = open_count.saturating_sub(line.match_indices('}').count());
559
560 import_lines.push_str(&line);
564 import_lines.push('\n');
565
566 if open_count == 0 || lines.peek().is_none() {
567 break;
568 }
569
570 offset += line.len() + 1;
572
573 line = lines.next().unwrap();
574 }
575
576 parse_imports(import_lines.as_str(), &mut declared_imports).map_err(
577 |(err, line_offset)| {
578 ComposerErrorInner::ImportParseError(
579 err.to_owned(),
580 initial_offset + line_offset,
581 )
582 },
583 )?;
584 } else if let Some(cap) = self.define_import_path_regex.captures(&line) {
585 name = Some(cap.get(1).unwrap().as_str().to_string());
586 } else if let Some(cap) = self.define_shader_def_regex.captures(&line) {
587 if allow_defines {
588 let def = cap.get(1).unwrap();
589 let name = def.as_str().to_string();
590
591 let value = if let Some(val) = cap.get(2) {
592 if let Ok(val) = val.as_str().parse::<u32>() {
593 ShaderDefValue::UInt(val)
594 } else if let Ok(val) = val.as_str().parse::<i32>() {
595 ShaderDefValue::Int(val)
596 } else if let Ok(val) = val.as_str().parse::<bool>() {
597 ShaderDefValue::Bool(val)
598 } else {
599 ShaderDefValue::Bool(false) }
601 } else {
602 ShaderDefValue::Bool(true)
603 };
604
605 defines.insert(name, value);
606 } else {
607 return Err(ComposerErrorInner::DefineInModule(offset));
608 }
609 } else {
610 for cap in self
611 .def_regex
612 .captures_iter(&line)
613 .chain(self.def_regex_delimited.captures_iter(&line))
614 {
615 effective_defs.insert(cap.get(1).unwrap().as_str().to_owned());
616 }
617
618 substitute_identifiers(&line, offset, &declared_imports, &mut used_imports, true)
619 .unwrap();
620 }
621
622 offset += line.len() + 1;
623 }
624
625 Ok(PreprocessorMetaData {
626 name,
627 imports: used_imports.into_values().collect(),
628 defines,
629 effective_defs,
630 wgsl_directives,
631 cleaned_source: shader_str.to_string(),
632 })
633 }
634}
635
636#[cfg(test)]
637mod test {
638 use super::*;
639
640 #[rustfmt::skip]
641 const WGSL_ELSE_IFDEF: &str = r"
642struct View {
643 view_proj: mat4x4<f32>,
644 world_position: vec3<f32>,
645};
646@group(0) @binding(0)
647var<uniform> view: View;
648
649#ifdef TEXTURE
650// Main texture
651@group(1) @binding(0)
652var sprite_texture: texture_2d<f32>;
653#else ifdef SECOND_TEXTURE
654// Second texture
655@group(1) @binding(0)
656var sprite_texture: texture_2d<f32>;
657#else ifdef THIRD_TEXTURE
658// Third texture
659@group(1) @binding(0)
660var sprite_texture: texture_2d<f32>;
661#else
662@group(1) @binding(0)
663var sprite_texture: texture_2d_array<f32>;
664#endif
665
666struct VertexOutput {
667 @location(0) uv: vec2<f32>,
668 @builtin(position) position: vec4<f32>,
669};
670
671@vertex
672fn vertex(
673 @location(0) vertex_position: vec3<f32>,
674 @location(1) vertex_uv: vec2<f32>
675) -> VertexOutput {
676 var out: VertexOutput;
677 out.uv = vertex_uv;
678 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
679 return out;
680}
681";
682
683 #[test]
685 fn process_shader_def_unknown_operator() {
686 #[rustfmt::skip]
687 const WGSL: &str = r"
688struct View {
689 view_proj: mat4x4<f32>,
690 world_position: vec3<f32>,
691};
692@group(0) @binding(0)
693var<uniform> view: View;
694#if TEXTURE !! true
695@group(1) @binding(0)
696var sprite_texture: texture_2d<f32>;
697#endif
698struct VertexOutput {
699 @location(0) uv: vec2<f32>,
700 @builtin(position) position: vec4<f32>,
701};
702@vertex
703fn vertex(
704 @location(0) vertex_position: vec3<f32>,
705 @location(1) vertex_uv: vec2<f32>
706) -> VertexOutput {
707 var out: VertexOutput;
708 out.uv = vertex_uv;
709 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
710 return out;
711}
712";
713
714 let processor = Preprocessor::default();
715
716 let result_missing = processor.preprocess(
717 WGSL,
718 &[("TEXTURE".to_owned(), ShaderDefValue::Bool(true))].into(),
719 );
720
721 let expected: Result<Preprocessor, ComposerErrorInner> =
722 Err(ComposerErrorInner::UnknownShaderDefOperator {
723 pos: 124,
724 operator: "!!".to_string(),
725 });
726
727 assert_eq!(format!("{result_missing:?}"), format!("{expected:?}"),);
728 }
729 #[test]
730 fn process_shader_def_equal_int() {
731 #[rustfmt::skip]
732 const WGSL: &str = r"
733struct View {
734 view_proj: mat4x4<f32>,
735 world_position: vec3<f32>,
736};
737@group(0) @binding(0)
738var<uniform> view: View;
739#if TEXTURE == 3
740@group(1) @binding(0)
741var sprite_texture: texture_2d<f32>;
742#endif
743struct VertexOutput {
744 @location(0) uv: vec2<f32>,
745 @builtin(position) position: vec4<f32>,
746};
747@vertex
748fn vertex(
749 @location(0) vertex_position: vec3<f32>,
750 @location(1) vertex_uv: vec2<f32>
751) -> VertexOutput {
752 var out: VertexOutput;
753 out.uv = vertex_uv;
754 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
755 return out;
756}
757";
758
759 #[rustfmt::skip]
760 const EXPECTED_EQ: &str = r"
761struct View {
762 view_proj: mat4x4<f32>,
763 world_position: vec3<f32>,
764};
765@group(0) @binding(0)
766var<uniform> view: View;
767
768@group(1) @binding(0)
769var sprite_texture: texture_2d<f32>;
770
771struct VertexOutput {
772 @location(0) uv: vec2<f32>,
773 @builtin(position) position: vec4<f32>,
774};
775@vertex
776fn vertex(
777 @location(0) vertex_position: vec3<f32>,
778 @location(1) vertex_uv: vec2<f32>
779) -> VertexOutput {
780 var out: VertexOutput;
781 out.uv = vertex_uv;
782 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
783 return out;
784}
785";
786
787 #[rustfmt::skip]
788 const EXPECTED_NEQ: &str = r"
789struct View {
790 view_proj: mat4x4<f32>,
791 world_position: vec3<f32>,
792};
793@group(0) @binding(0)
794var<uniform> view: View;
795
796
797
798
799struct VertexOutput {
800 @location(0) uv: vec2<f32>,
801 @builtin(position) position: vec4<f32>,
802};
803@vertex
804fn vertex(
805 @location(0) vertex_position: vec3<f32>,
806 @location(1) vertex_uv: vec2<f32>
807) -> VertexOutput {
808 var out: VertexOutput;
809 out.uv = vertex_uv;
810 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
811 return out;
812}
813";
814 let processor = Preprocessor::default();
815 let result_eq = processor
816 .preprocess(
817 WGSL,
818 &[("TEXTURE".to_string(), ShaderDefValue::Int(3))].into(),
819 )
820 .unwrap();
821 assert_eq!(result_eq.preprocessed_source, EXPECTED_EQ);
822
823 let result_neq = processor
824 .preprocess(
825 WGSL,
826 &[("TEXTURE".to_string(), ShaderDefValue::Int(7))].into(),
827 )
828 .unwrap();
829 assert_eq!(result_neq.preprocessed_source, EXPECTED_NEQ);
830
831 let result_missing = processor.preprocess(WGSL, &Default::default());
832
833 let expected_err: Result<
834 (Option<String>, String, Vec<ImportDefWithOffset>),
835 ComposerErrorInner,
836 > = Err(ComposerErrorInner::UnknownShaderDef {
837 pos: 124,
838 shader_def_name: "TEXTURE".to_string(),
839 });
840 assert_eq!(format!("{result_missing:?}"), format!("{expected_err:?}"),);
841
842 let result_wrong_type = processor.preprocess(
843 WGSL,
844 &[("TEXTURE".to_string(), ShaderDefValue::Bool(true))].into(),
845 );
846
847 let expected_err: Result<
848 (Option<String>, String, Vec<ImportDefWithOffset>),
849 ComposerErrorInner,
850 > = Err(ComposerErrorInner::InvalidShaderDefComparisonValue {
851 pos: 124,
852 shader_def_name: "TEXTURE".to_string(),
853 expected: "bool".to_string(),
854 value: "3".to_string(),
855 });
856
857 assert_eq!(
858 format!("{result_wrong_type:?}"),
859 format!("{expected_err:?}")
860 );
861 }
862
863 #[test]
864 fn process_shader_def_equal_bool() {
865 #[rustfmt::skip]
866 const WGSL: &str = r"
867struct View {
868 view_proj: mat4x4<f32>,
869 world_position: vec3<f32>,
870};
871@group(0) @binding(0)
872var<uniform> view: View;
873#if TEXTURE == true
874@group(1) @binding(0)
875var sprite_texture: texture_2d<f32>;
876#endif
877struct VertexOutput {
878 @location(0) uv: vec2<f32>,
879 @builtin(position) position: vec4<f32>,
880};
881@vertex
882fn vertex(
883 @location(0) vertex_position: vec3<f32>,
884 @location(1) vertex_uv: vec2<f32>
885) -> VertexOutput {
886 var out: VertexOutput;
887 out.uv = vertex_uv;
888 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
889 return out;
890}
891";
892
893 #[rustfmt::skip]
894 const EXPECTED_EQ: &str = r"
895struct View {
896 view_proj: mat4x4<f32>,
897 world_position: vec3<f32>,
898};
899@group(0) @binding(0)
900var<uniform> view: View;
901
902@group(1) @binding(0)
903var sprite_texture: texture_2d<f32>;
904
905struct VertexOutput {
906 @location(0) uv: vec2<f32>,
907 @builtin(position) position: vec4<f32>,
908};
909@vertex
910fn vertex(
911 @location(0) vertex_position: vec3<f32>,
912 @location(1) vertex_uv: vec2<f32>
913) -> VertexOutput {
914 var out: VertexOutput;
915 out.uv = vertex_uv;
916 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
917 return out;
918}
919";
920
921 #[rustfmt::skip]
922 const EXPECTED_NEQ: &str = r"
923struct View {
924 view_proj: mat4x4<f32>,
925 world_position: vec3<f32>,
926};
927@group(0) @binding(0)
928var<uniform> view: View;
929
930
931
932
933struct VertexOutput {
934 @location(0) uv: vec2<f32>,
935 @builtin(position) position: vec4<f32>,
936};
937@vertex
938fn vertex(
939 @location(0) vertex_position: vec3<f32>,
940 @location(1) vertex_uv: vec2<f32>
941) -> VertexOutput {
942 var out: VertexOutput;
943 out.uv = vertex_uv;
944 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
945 return out;
946}
947";
948 let processor = Preprocessor::default();
949 let result_eq = processor
950 .preprocess(
951 WGSL,
952 &[("TEXTURE".to_string(), ShaderDefValue::Bool(true))].into(),
953 )
954 .unwrap();
955 assert_eq!(result_eq.preprocessed_source, EXPECTED_EQ);
956
957 let result_neq = processor
958 .preprocess(
959 WGSL,
960 &[("TEXTURE".to_string(), ShaderDefValue::Bool(false))].into(),
961 )
962 .unwrap();
963 assert_eq!(result_neq.preprocessed_source, EXPECTED_NEQ);
964 }
965
966 #[test]
967 fn process_shader_def_not_equal_bool() {
968 #[rustfmt::skip]
969 const WGSL: &str = r"
970struct View {
971 view_proj: mat4x4<f32>,
972 world_position: vec3<f32>,
973};
974@group(0) @binding(0)
975var<uniform> view: View;
976#if TEXTURE != false
977@group(1) @binding(0)
978var sprite_texture: texture_2d<f32>;
979#endif
980struct VertexOutput {
981 @location(0) uv: vec2<f32>,
982 @builtin(position) position: vec4<f32>,
983};
984@vertex
985fn vertex(
986 @location(0) vertex_position: vec3<f32>,
987 @location(1) vertex_uv: vec2<f32>
988) -> VertexOutput {
989 var out: VertexOutput;
990 out.uv = vertex_uv;
991 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
992 return out;
993}
994";
995
996 #[rustfmt::skip]
997 const EXPECTED_EQ: &str = r"
998struct View {
999 view_proj: mat4x4<f32>,
1000 world_position: vec3<f32>,
1001};
1002@group(0) @binding(0)
1003var<uniform> view: View;
1004
1005@group(1) @binding(0)
1006var sprite_texture: texture_2d<f32>;
1007
1008struct VertexOutput {
1009 @location(0) uv: vec2<f32>,
1010 @builtin(position) position: vec4<f32>,
1011};
1012@vertex
1013fn vertex(
1014 @location(0) vertex_position: vec3<f32>,
1015 @location(1) vertex_uv: vec2<f32>
1016) -> VertexOutput {
1017 var out: VertexOutput;
1018 out.uv = vertex_uv;
1019 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
1020 return out;
1021}
1022";
1023
1024 #[rustfmt::skip]
1025 const EXPECTED_NEQ: &str = r"
1026struct View {
1027 view_proj: mat4x4<f32>,
1028 world_position: vec3<f32>,
1029};
1030@group(0) @binding(0)
1031var<uniform> view: View;
1032
1033
1034
1035
1036struct VertexOutput {
1037 @location(0) uv: vec2<f32>,
1038 @builtin(position) position: vec4<f32>,
1039};
1040@vertex
1041fn vertex(
1042 @location(0) vertex_position: vec3<f32>,
1043 @location(1) vertex_uv: vec2<f32>
1044) -> VertexOutput {
1045 var out: VertexOutput;
1046 out.uv = vertex_uv;
1047 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
1048 return out;
1049}
1050";
1051 let processor = Preprocessor::default();
1052 let result_eq = processor
1053 .preprocess(
1054 WGSL,
1055 &[("TEXTURE".to_string(), ShaderDefValue::Bool(true))].into(),
1056 )
1057 .unwrap();
1058 assert_eq!(result_eq.preprocessed_source, EXPECTED_EQ);
1059
1060 let result_neq = processor
1061 .preprocess(
1062 WGSL,
1063 &[("TEXTURE".to_string(), ShaderDefValue::Bool(false))].into(),
1064 )
1065 .unwrap();
1066 assert_eq!(result_neq.preprocessed_source, EXPECTED_NEQ);
1067
1068 let result_missing = processor.preprocess(WGSL, &[].into());
1069 let expected_err: Result<
1070 (Option<String>, String, Vec<ImportDefWithOffset>),
1071 ComposerErrorInner,
1072 > = Err(ComposerErrorInner::UnknownShaderDef {
1073 pos: 124,
1074 shader_def_name: "TEXTURE".to_string(),
1075 });
1076 assert_eq!(format!("{result_missing:?}"), format!("{expected_err:?}"),);
1077
1078 let result_wrong_type = processor.preprocess(
1079 WGSL,
1080 &[("TEXTURE".to_string(), ShaderDefValue::Int(7))].into(),
1081 );
1082
1083 let expected_err: Result<
1084 (Option<String>, String, Vec<ImportDefWithOffset>),
1085 ComposerErrorInner,
1086 > = Err(ComposerErrorInner::InvalidShaderDefComparisonValue {
1087 pos: 124,
1088 shader_def_name: "TEXTURE".to_string(),
1089 expected: "int".to_string(),
1090 value: "false".to_string(),
1091 });
1092 assert_eq!(
1093 format!("{result_wrong_type:?}"),
1094 format!("{expected_err:?}"),
1095 );
1096 }
1097
1098 #[test]
1099 fn process_shader_def_replace() {
1100 #[rustfmt::skip]
1101 const WGSL: &str = r"
1102struct View {
1103 view_proj: mat4x4<f32>,
1104 world_position: vec3<f32>,
1105};
1106@group(0) @binding(0)
1107var<uniform> view: View;
1108struct VertexOutput {
1109 @location(0) uv: vec2<f32>,
1110 @builtin(position) position: vec4<f32>,
1111};
1112@vertex
1113fn vertex(
1114 @location(0) vertex_position: vec3<f32>,
1115 @location(1) vertex_uv: vec2<f32>
1116) -> VertexOutput {
1117 var out: VertexOutput;
1118 out.uv = vertex_uv;
1119 var a: i32 = #FIRST_VALUE;
1120 var b: i32 = #FIRST_VALUE * #SECOND_VALUE;
1121 var c: i32 = #MISSING_VALUE;
1122 var d: bool = #BOOL_VALUE;
1123 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
1124 return out;
1125}
1126";
1127
1128 #[rustfmt::skip]
1129 const EXPECTED_REPLACED: &str = r"
1130struct View {
1131 view_proj: mat4x4<f32>,
1132 world_position: vec3<f32>,
1133};
1134@group(0) @binding(0)
1135var<uniform> view: View;
1136struct VertexOutput {
1137 @location(0) uv: vec2<f32>,
1138 @builtin(position) position: vec4<f32>,
1139};
1140@vertex
1141fn vertex(
1142 @location(0) vertex_position: vec3<f32>,
1143 @location(1) vertex_uv: vec2<f32>
1144) -> VertexOutput {
1145 var out: VertexOutput;
1146 out.uv = vertex_uv;
1147 var a: i32 = 5;
1148 var b: i32 = 5 * 3;
1149 var c: i32 = #MISSING_VALUE;
1150 var d: bool = true;
1151 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
1152 return out;
1153}
1154";
1155 let processor = Preprocessor::default();
1156 let result = processor
1157 .preprocess(
1158 WGSL,
1159 &[
1160 ("BOOL_VALUE".to_string(), ShaderDefValue::Bool(true)),
1161 ("FIRST_VALUE".to_string(), ShaderDefValue::Int(5)),
1162 ("SECOND_VALUE".to_string(), ShaderDefValue::Int(3)),
1163 ]
1164 .into(),
1165 )
1166 .unwrap();
1167 assert_eq!(result.preprocessed_source, EXPECTED_REPLACED);
1168 }
1169
1170 #[test]
1171 fn process_shader_define_in_shader() {
1172 #[rustfmt::skip]
1173 const WGSL: &str = r"
1174#define NOW_DEFINED
1175#ifdef NOW_DEFINED
1176defined
1177#endif
1178";
1179
1180 #[rustfmt::skip]
1181 const EXPECTED: &str = r"
1182
1183
1184defined
1185
1186";
1187 let processor = Preprocessor::default();
1188 let PreprocessorMetaData {
1189 defines: shader_defs,
1190 ..
1191 } = processor.get_preprocessor_metadata(WGSL, true).unwrap();
1192 println!("defines: {shader_defs:?}");
1193 let result = processor.preprocess(WGSL, &shader_defs).unwrap();
1194 assert_eq!(result.preprocessed_source, EXPECTED);
1195 }
1196
1197 #[test]
1198 fn process_shader_define_in_shader_with_value() {
1199 #[rustfmt::skip]
1200 const WGSL: &str = r"
1201#define DEFUINT 1
1202#define DEFINT -1
1203#define DEFBOOL false
1204#if DEFUINT == 1
1205uint: #DEFUINT
1206#endif
1207#if DEFINT == -1
1208int: #DEFINT
1209#endif
1210#if DEFBOOL == false
1211bool: #DEFBOOL
1212#endif
1213";
1214
1215 #[rustfmt::skip]
1216 const EXPECTED: &str = r"
1217
1218
1219
1220
1221uint: 1
1222
1223
1224int: -1
1225
1226
1227bool: false
1228
1229";
1230 let processor = Preprocessor::default();
1231 let PreprocessorMetaData {
1232 defines: shader_defs,
1233 ..
1234 } = processor.get_preprocessor_metadata(WGSL, true).unwrap();
1235 println!("defines: {shader_defs:?}");
1236 let result = processor.preprocess(WGSL, &shader_defs).unwrap();
1237 assert_eq!(result.preprocessed_source, EXPECTED);
1238 }
1239
1240 #[test]
1241 fn process_shader_def_else_ifdef_ends_up_in_else() {
1242 #[rustfmt::skip]
1243 const EXPECTED: &str = r"
1244struct View {
1245 view_proj: mat4x4<f32>,
1246 world_position: vec3<f32>,
1247};
1248@group(0) @binding(0)
1249var<uniform> view: View;
1250@group(1) @binding(0)
1251var sprite_texture: texture_2d_array<f32>;
1252struct VertexOutput {
1253 @location(0) uv: vec2<f32>,
1254 @builtin(position) position: vec4<f32>,
1255};
1256@vertex
1257fn vertex(
1258 @location(0) vertex_position: vec3<f32>,
1259 @location(1) vertex_uv: vec2<f32>
1260) -> VertexOutput {
1261 var out: VertexOutput;
1262 out.uv = vertex_uv;
1263 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
1264 return out;
1265}
1266";
1267 let processor = Preprocessor::default();
1268 let result = processor.preprocess(WGSL_ELSE_IFDEF, &[].into()).unwrap();
1269 assert_eq!(
1270 result
1271 .preprocessed_source
1272 .replace(" ", "")
1273 .replace("\n", "")
1274 .replace("\r", ""),
1275 EXPECTED
1276 .replace(" ", "")
1277 .replace("\n", "")
1278 .replace("\r", "")
1279 );
1280 }
1281
1282 #[test]
1283 fn process_shader_def_else_ifdef_no_match_and_no_fallback_else() {
1284 #[rustfmt::skip]
1285 const WGSL_ELSE_IFDEF_NO_ELSE_FALLBACK: &str = r"
1286struct View {
1287 view_proj: mat4x4<f32>,
1288 world_position: vec3<f32>,
1289};
1290@group(0) @binding(0)
1291var<uniform> view: View;
1292
1293#ifdef TEXTURE
1294// Main texture
1295@group(1) @binding(0)
1296var sprite_texture: texture_2d<f32>;
1297#else ifdef OTHER_TEXTURE
1298// Other texture
1299@group(1) @binding(0)
1300var sprite_texture: texture_2d<f32>;
1301#endif
1302
1303struct VertexOutput {
1304 @location(0) uv: vec2<f32>,
1305 @builtin(position) position: vec4<f32>,
1306};
1307
1308@vertex
1309fn vertex(
1310 @location(0) vertex_position: vec3<f32>,
1311 @location(1) vertex_uv: vec2<f32>
1312) -> VertexOutput {
1313 var out: VertexOutput;
1314 out.uv = vertex_uv;
1315 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
1316 return out;
1317}
1318";
1319
1320 #[rustfmt::skip]
1321 const EXPECTED: &str = r"
1322struct View {
1323 view_proj: mat4x4<f32>,
1324 world_position: vec3<f32>,
1325};
1326@group(0) @binding(0)
1327var<uniform> view: View;
1328struct VertexOutput {
1329 @location(0) uv: vec2<f32>,
1330 @builtin(position) position: vec4<f32>,
1331};
1332@vertex
1333fn vertex(
1334 @location(0) vertex_position: vec3<f32>,
1335 @location(1) vertex_uv: vec2<f32>
1336) -> VertexOutput {
1337 var out: VertexOutput;
1338 out.uv = vertex_uv;
1339 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
1340 return out;
1341}
1342";
1343 let processor = Preprocessor::default();
1344 let result = processor
1345 .preprocess(WGSL_ELSE_IFDEF_NO_ELSE_FALLBACK, &[].into())
1346 .unwrap();
1347 assert_eq!(
1348 result
1349 .preprocessed_source
1350 .replace(" ", "")
1351 .replace("\n", "")
1352 .replace("\r", ""),
1353 EXPECTED
1354 .replace(" ", "")
1355 .replace("\n", "")
1356 .replace("\r", "")
1357 );
1358 }
1359
1360 #[test]
1361 fn process_shader_def_else_ifdef_ends_up_in_first_clause() {
1362 #[rustfmt::skip]
1363 const EXPECTED: &str = r"
1364struct View {
1365 view_proj: mat4x4<f32>,
1366 world_position: vec3<f32>,
1367};
1368@group(0) @binding(0)
1369var<uniform> view: View;
1370
1371// Main texture
1372@group(1) @binding(0)
1373var sprite_texture: texture_2d<f32>;
1374
1375struct VertexOutput {
1376 @location(0) uv: vec2<f32>,
1377 @builtin(position) position: vec4<f32>,
1378};
1379
1380@vertex
1381fn vertex(
1382 @location(0) vertex_position: vec3<f32>,
1383 @location(1) vertex_uv: vec2<f32>
1384) -> VertexOutput {
1385 var out: VertexOutput;
1386 out.uv = vertex_uv;
1387 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
1388 return out;
1389}
1390";
1391 let processor = Preprocessor::default();
1392 let result = processor
1393 .preprocess(
1394 WGSL_ELSE_IFDEF,
1395 &[("TEXTURE".to_string(), ShaderDefValue::Bool(true))].into(),
1396 )
1397 .unwrap();
1398 assert_eq!(
1399 result
1400 .preprocessed_source
1401 .replace(" ", "")
1402 .replace("\n", "")
1403 .replace("\r", ""),
1404 EXPECTED
1405 .replace(" ", "")
1406 .replace("\n", "")
1407 .replace("\r", "")
1408 );
1409 }
1410
1411 #[test]
1412 fn process_shader_def_else_ifdef_ends_up_in_second_clause() {
1413 #[rustfmt::skip]
1414 const EXPECTED: &str = r"
1415struct View {
1416 view_proj: mat4x4<f32>,
1417 world_position: vec3<f32>,
1418};
1419@group(0) @binding(0)
1420var<uniform> view: View;
1421// Second texture
1422@group(1) @binding(0)
1423var sprite_texture: texture_2d<f32>;
1424struct VertexOutput {
1425 @location(0) uv: vec2<f32>,
1426 @builtin(position) position: vec4<f32>,
1427};
1428@vertex
1429fn vertex(
1430 @location(0) vertex_position: vec3<f32>,
1431 @location(1) vertex_uv: vec2<f32>
1432) -> VertexOutput {
1433 var out: VertexOutput;
1434 out.uv = vertex_uv;
1435 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
1436 return out;
1437}
1438";
1439 let processor = Preprocessor::default();
1440 let result = processor
1441 .preprocess(
1442 WGSL_ELSE_IFDEF,
1443 &[("SECOND_TEXTURE".to_string(), ShaderDefValue::Bool(true))].into(),
1444 )
1445 .unwrap();
1446 assert_eq!(
1447 result
1448 .preprocessed_source
1449 .replace(" ", "")
1450 .replace("\n", "")
1451 .replace("\r", ""),
1452 EXPECTED
1453 .replace(" ", "")
1454 .replace("\n", "")
1455 .replace("\r", "")
1456 );
1457 }
1458
1459 #[test]
1460 fn process_shader_def_else_ifdef_ends_up_in_third_clause() {
1461 #[rustfmt::skip]
1462 const EXPECTED: &str = r"
1463struct View {
1464 view_proj: mat4x4<f32>,
1465 world_position: vec3<f32>,
1466};
1467@group(0) @binding(0)
1468var<uniform> view: View;
1469// Third texture
1470@group(1) @binding(0)
1471var sprite_texture: texture_2d<f32>;
1472struct VertexOutput {
1473 @location(0) uv: vec2<f32>,
1474 @builtin(position) position: vec4<f32>,
1475};
1476@vertex
1477fn vertex(
1478 @location(0) vertex_position: vec3<f32>,
1479 @location(1) vertex_uv: vec2<f32>
1480) -> VertexOutput {
1481 var out: VertexOutput;
1482 out.uv = vertex_uv;
1483 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
1484 return out;
1485}
1486";
1487 let processor = Preprocessor::default();
1488 let result = processor
1489 .preprocess(
1490 WGSL_ELSE_IFDEF,
1491 &[("THIRD_TEXTURE".to_string(), ShaderDefValue::Bool(true))].into(),
1492 )
1493 .unwrap();
1494 assert_eq!(
1495 result
1496 .preprocessed_source
1497 .replace(" ", "")
1498 .replace("\n", "")
1499 .replace("\r", ""),
1500 EXPECTED
1501 .replace(" ", "")
1502 .replace("\n", "")
1503 .replace("\r", "")
1504 );
1505 }
1506
1507 #[test]
1508 fn process_shader_def_else_ifdef_only_accepts_one_valid_else_ifdef() {
1509 #[rustfmt::skip]
1510 const EXPECTED: &str = r"
1511struct View {
1512 view_proj: mat4x4<f32>,
1513 world_position: vec3<f32>,
1514};
1515@group(0) @binding(0)
1516var<uniform> view: View;
1517// Second texture
1518@group(1) @binding(0)
1519var sprite_texture: texture_2d<f32>;
1520struct VertexOutput {
1521 @location(0) uv: vec2<f32>,
1522 @builtin(position) position: vec4<f32>,
1523};
1524@vertex
1525fn vertex(
1526 @location(0) vertex_position: vec3<f32>,
1527 @location(1) vertex_uv: vec2<f32>
1528) -> VertexOutput {
1529 var out: VertexOutput;
1530 out.uv = vertex_uv;
1531 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
1532 return out;
1533}
1534";
1535 let processor = Preprocessor::default();
1536 let result = processor
1537 .preprocess(
1538 WGSL_ELSE_IFDEF,
1539 &[
1540 ("SECOND_TEXTURE".to_string(), ShaderDefValue::Bool(true)),
1541 ("THIRD_TEXTURE".to_string(), ShaderDefValue::Bool(true)),
1542 ]
1543 .into(),
1544 )
1545 .unwrap();
1546 assert_eq!(
1547 result
1548 .preprocessed_source
1549 .replace(" ", "")
1550 .replace("\n", "")
1551 .replace("\r", ""),
1552 EXPECTED
1553 .replace(" ", "")
1554 .replace("\n", "")
1555 .replace("\r", "")
1556 );
1557 }
1558
1559 #[test]
1560 fn process_shader_def_else_ifdef_complicated_nesting() {
1561 #[rustfmt::skip]
1567 const WGSL_COMPLICATED_ELSE_IFDEF: &str = r"
1568#ifdef NOT_DEFINED
1569// not defined
1570#else ifdef IS_DEFINED
1571// defined 1
1572#ifdef NOT_DEFINED
1573// not defined
1574#else
1575// should be here
1576#ifdef NOT_DEFINED
1577// not defined
1578#else ifdef ALSO_NOT_DEFINED
1579// not defined
1580#else ifdef IS_DEFINED
1581// defined 2
1582#endif
1583#endif
1584#endif
1585";
1586
1587 #[rustfmt::skip]
1588 const EXPECTED: &str = r"
1589// defined 1
1590// should be here
1591// defined 2
1592";
1593 let processor = Preprocessor::default();
1594 let result = processor
1595 .preprocess(
1596 WGSL_COMPLICATED_ELSE_IFDEF,
1597 &[("IS_DEFINED".to_string(), ShaderDefValue::Bool(true))].into(),
1598 )
1599 .unwrap();
1600 assert_eq!(
1601 result
1602 .preprocessed_source
1603 .replace(" ", "")
1604 .replace("\n", "")
1605 .replace("\r", ""),
1606 EXPECTED
1607 .replace(" ", "")
1608 .replace("\n", "")
1609 .replace("\r", "")
1610 );
1611 }
1612
1613 #[test]
1614 fn process_shader_def_else_ifndef() {
1615 #[rustfmt::skip]
1616 const INPUT: &str = r"
1617#ifdef NOT_DEFINED
1618fail 1
1619#else ifdef ALSO_NOT_DEFINED
1620fail 2
1621#else ifndef ALSO_ALSO_NOT_DEFINED
1622ok
1623#else
1624fail 3
1625#endif
1626";
1627
1628 const EXPECTED: &str = r"ok";
1629 let processor = Preprocessor::default();
1630 let result = processor.preprocess(INPUT, &[].into()).unwrap();
1631 assert_eq!(
1632 result
1633 .preprocessed_source
1634 .replace(" ", "")
1635 .replace("\n", "")
1636 .replace("\r", ""),
1637 EXPECTED
1638 .replace(" ", "")
1639 .replace("\n", "")
1640 .replace("\r", "")
1641 );
1642 }
1643
1644 #[test]
1645 fn process_shader_def_else_if() {
1646 #[rustfmt::skip]
1647 const INPUT: &str = r"
1648#ifdef NOT_DEFINED
1649fail 1
1650#else if x == 1
1651fail 2
1652#else if x == 2
1653ok
1654#else
1655fail 3
1656#endif
1657";
1658
1659 const EXPECTED: &str = r"ok";
1660 let processor = Preprocessor::default();
1661 let result = processor
1662 .preprocess(INPUT, &[("x".to_owned(), ShaderDefValue::Int(2))].into())
1663 .unwrap();
1664 assert_eq!(
1665 result
1666 .preprocessed_source
1667 .replace(" ", "")
1668 .replace("\n", "")
1669 .replace("\r", ""),
1670 EXPECTED
1671 .replace(" ", "")
1672 .replace("\n", "")
1673 .replace("\r", "")
1674 );
1675 }
1676}