1use std::{
6 collections::HashSet,
7 path::{Path, PathBuf},
8};
9
10use wesl_core::StaticPackage;
11use wgsl_parse::{
12 SyntaxNode,
13 syntax::{Ident, ModulePath, PathOrigin, TranslationUnit, Visibility},
14};
15
16use crate::{
17 SyntaxUtil,
18 error::{Diagnostic, Error, ResolveError, UsageError},
19 mangler::{self, Mangler},
20 pass::{self, CompilerDriver, Features, Module, UsedItems},
21 resolver::{Constants, Resolver, StandardResolver},
22 sourcemap::{BasicSourceMap, SourceMapper},
23};
24
25#[derive(Clone, Debug, PartialEq)]
27pub struct CompileOptions {
28 pub imports: bool,
35 pub condcomp: bool,
39 pub visibility: bool,
44 pub generics: bool,
48 pub strip: bool,
58 pub lower: bool,
62 pub validate: bool,
69 pub sourcemap: bool,
71 pub mangler: ManglerKind,
73 pub mangle_main: bool,
77 pub keep: Option<Vec<String>>,
83 pub keep_main: bool,
88 pub features: Features,
94 pub constants: Constants,
98 pub dependencies: Vec<&'static StaticPackage>,
100}
101
102#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
104pub enum ManglerKind {
105 #[default]
108 Escape,
109 Hash,
112 Unicode,
115 None,
117}
118
119impl From<ManglerKind> for Box<dyn Mangler> {
120 fn from(kind: ManglerKind) -> Self {
121 match kind {
122 ManglerKind::Escape => Box::new(mangler::EscapeMangler),
123 ManglerKind::Hash => Box::new(mangler::HashMangler),
124 ManglerKind::Unicode => Box::new(mangler::UnicodeMangler),
125 ManglerKind::None => Box::new(mangler::NoMangler),
126 }
127 }
128}
129
130impl Default for CompileOptions {
131 fn default() -> Self {
132 Self {
133 imports: true,
134 condcomp: true,
135 visibility: true,
136 generics: false,
137 strip: true,
138 lower: false,
139 validate: true,
140 sourcemap: true,
141 mangler: Default::default(),
142 mangle_main: false,
143 keep: Default::default(),
144 keep_main: false,
145 features: Default::default(),
146 constants: Default::default(),
147 dependencies: Default::default(),
148 }
149 }
150}
151
152#[derive(Clone, Debug)]
177pub struct Compiler<R = ()> {
178 pub options: CompileOptions,
179 pub resolver: R,
180}
181
182impl Default for Compiler<()> {
183 fn default() -> Self {
184 Self {
185 options: Default::default(),
186 resolver: Default::default(),
187 }
188 }
189}
190
191impl<R1> Compiler<R1> {
192 pub fn with_resolver<R2>(self, resolver: R2) -> Compiler<R2> {
194 Compiler::<R2> {
195 options: self.options,
196 resolver,
197 }
198 }
199}
200
201impl<R> Compiler<R> {
202 pub fn new_with_resolver(options: CompileOptions, resolver: R) -> Self {
204 Self { options, resolver }
205 }
206}
207
208impl Compiler<()> {
209 pub fn new(options: CompileOptions) -> Self {
213 let resolver = ();
214 Self { options, resolver }
215 }
216}
217
218pub fn compile(
220 main_path: &ModulePath,
221 options: &CompileOptions,
222 resolver: &impl Resolver,
223) -> Result<CompileResult, Error> {
224 let mangler = Box::<dyn Mangler>::from(options.mangler);
225
226 if options.sourcemap {
227 let sourcemapper = SourceMapper::new(main_path.clone(), &resolver, &mangler);
228 let mut pass = CompilationPass::new(main_path, options, &sourcemapper, &sourcemapper);
229 let res = CompilerDriver::compile(&mut pass);
230 let sourcemap = sourcemapper.finish();
231 let res = res.map_err(|e| Diagnostic::from(e).with_sourcemap(&sourcemap))?;
232
233 Ok(CompileResult {
234 syntax: res.syntax,
235 modules: res.modules,
236 sourcemap: Some(sourcemap),
237 used_items: res.used_items,
238 })
239 } else {
240 let mut pass = CompilationPass::new(main_path, options, &resolver, &mangler);
241 let res = CompilerDriver::compile(&mut pass)?;
242 Ok(CompileResult {
243 syntax: res.syntax,
244 modules: res.modules,
245 sourcemap: None,
246 used_items: res.used_items,
247 })
248 }
249}
250
251pub async fn compile_async(
253 main_path: &ModulePath,
254 options: &CompileOptions,
255 resolver: &impl Resolver,
256) -> Result<CompileResult, Error> {
257 let mangler = Box::<dyn Mangler>::from(options.mangler);
258
259 if options.sourcemap {
260 let sourcemapper = SourceMapper::new(main_path.clone(), &resolver, &mangler);
261 let mut pass = CompilationPass::new(main_path, options, &sourcemapper, &sourcemapper);
262 let res = CompilerDriver::compile_async(&mut pass).await;
263 let sourcemap = sourcemapper.finish();
264 let res = res.map_err(|e| Diagnostic::from(e).with_sourcemap(&sourcemap))?;
265
266 Ok(CompileResult {
267 syntax: res.syntax,
268 modules: res.modules,
269 sourcemap: Some(sourcemap),
270 used_items: res.used_items,
271 })
272 } else {
273 let mut pass = CompilationPass::new(main_path, options, &resolver, &mangler);
274 let res = CompilerDriver::compile_async(&mut pass).await?;
275 Ok(CompileResult {
276 syntax: res.syntax,
277 modules: res.modules,
278 sourcemap: None,
279 used_items: res.used_items,
280 })
281 }
282}
283
284fn main_module_path(path: &Path, resolver: &impl Resolver) -> Result<ModulePath, Error> {
292 let mut main_path = resolver.module_path(path).or_else(|e| {
293 if matches!(e, ResolveError::FilesystemNotSupported) {
294 Ok(ModulePath::from_path(Path::new(".").join(path)))
295 } else {
296 Err(e)
297 }
298 })?;
299
300 main_path.origin = PathOrigin::Absolute;
302 Ok(main_path)
303}
304
305impl Compiler<()> {
306 pub fn compile(&self, path: impl AsRef<Path>) -> Result<CompileResult, Error> {
325 let (pkg_root_dir, main_path) = self.root_and_main(path.as_ref())?;
326 self.compile_module(pkg_root_dir, &main_path)
327 }
328
329 pub fn compile_module(
331 &self,
332 pkg_root_dir: impl AsRef<Path>,
333 main_path: &ModulePath,
334 ) -> Result<CompileResult, Error> {
335 let resolver = self.create_resolver(pkg_root_dir.as_ref());
336 compile(main_path, &self.options, &resolver)
337 }
338
339 pub async fn compile_async(&self, path: &Path) -> Result<CompileResult, Error> {
341 let (pkg_root_dir, main_path) = self.root_and_main(path.as_ref())?;
342 self.compile_module_async(&pkg_root_dir, &main_path).await
343 }
344
345 pub async fn compile_module_async(
347 &self,
348 pkg_root_dir: impl AsRef<Path>,
349 main_path: &ModulePath,
350 ) -> Result<CompileResult, Error> {
351 let resolver = self.create_resolver(pkg_root_dir.as_ref());
352 compile_async(main_path, &self.options, &resolver).await
353 }
354
355 fn create_resolver(&self, pkg_root_dir: &Path) -> StandardResolver {
356 let mut resolver = StandardResolver::new(pkg_root_dir);
357
358 for (name, value) in self.options.constants.iter() {
359 resolver.add_constant(name.clone(), *value);
360 }
361
362 for package in self.options.dependencies.iter() {
363 resolver.add_package(package);
364 }
365
366 resolver
367 }
368
369 fn root_and_main(&self, path: &Path) -> Result<(PathBuf, ModulePath), Error> {
370 let (pkg_root_dir, main_path) = if let Some(file_name) = path.file_name()
371 && file_name == "wesl.toml"
372 {
373 let cfg = crate::toml_cfg::WeslToml::from_file(path)?;
374 let root = path
375 .parent()
376 .unwrap()
377 .join(&cfg.package.root);
378 let main = ModulePath::new_root();
379 (root, main)
380 } else if let Some(name) = path.file_stem()
381 && !path.is_dir()
382 {
383 let root = path.parent().unwrap().to_path_buf();
384 let main = ModulePath::new(
385 PathOrigin::Absolute,
386 vec![name.to_string_lossy().to_string()],
387 );
388 (root, main)
389 } else {
390 let root = path.to_path_buf();
391 let main = ModulePath::new_root();
392 (root, main)
393 };
394
395 Ok((pkg_root_dir, main_path))
396 }
397}
398
399impl<R: Resolver> Compiler<R> {
400 pub fn compile_root(&self) -> Result<CompileResult, Error> {
405 compile(&ModulePath::new_root(), &self.options, &self.resolver)
406 }
407
408 pub fn compile_module(&self, main_path: &ModulePath) -> Result<CompileResult, Error> {
410 compile(main_path, &self.options, &self.resolver)
411 }
412
413 pub fn compile(&self, fs_main_path: impl AsRef<Path>) -> Result<CompileResult, Error> {
427 let main_path = main_module_path(fs_main_path.as_ref(), &self.resolver)?;
428 compile(&main_path, &self.options, &self.resolver)
429 }
430
431 pub async fn compile_root_async(&self) -> Result<CompileResult, Error> {
433 compile_async(&ModulePath::new_root(), &self.options, &self.resolver).await
434 }
435
436 pub async fn compile_module_async(
438 &self,
439 main_path: &ModulePath,
440 ) -> Result<CompileResult, Error> {
441 compile_async(main_path, &self.options, &self.resolver).await
442 }
443
444 pub async fn compile_async(
446 &self,
447 fs_main_path: &impl AsRef<Path>,
448 ) -> Result<CompileResult, Error> {
449 let main_path = main_module_path(fs_main_path.as_ref(), &self.resolver)?;
450 compile_async(&main_path, &self.options, &self.resolver).await
451 }
452}
453
454#[derive(Default, Clone)]
461pub struct CompileResult {
462 pub syntax: TranslationUnit,
464 pub modules: Vec<Module>,
465 pub sourcemap: Option<BasicSourceMap>,
466 pub used_items: UsedItems,
467}
468
469impl CompileResult {
470 pub fn write_to_file(&self, path: impl AsRef<Path>) -> std::io::Result<()> {
472 std::fs::write(path, self.to_string())
473 }
474
475 pub fn emit_rerun_if_changed(&self) {
478 let Some(sourcemap) = &self.sourcemap else {
479 println!("cargo::warning=cannot emit rerun-if-changed directive without a sourcemap");
480 return;
481 };
482
483 for (module_path, _) in self.used_items.iter() {
484 if module_path.origin.is_package() {
485 continue;
486 }
487 assert!(
488 !module_path.origin.is_relative(),
489 "the modules passed to emit_rerun_if_changed must be absolute"
490 );
491 if let Some(source) = sourcemap.file(module_path)
492 && let Some(fs_path) = &source.path
493 {
494 println!("cargo::rerun-if-changed={}", fs_path.display());
496
497 if fs_path.extension().unwrap() == "wgsl" {
499 let fs_path = fs_path.with_extension("wesl");
500 println!("cargo::rerun-if-changed={}", fs_path.display());
501 }
502 }
503 }
504 }
505
506 pub fn write_artifact(&self, artifact_name: &str) {
516 let dirname = std::env::var("OUT_DIR").unwrap();
517 let out_name = Path::new(artifact_name);
518 if out_name.iter().count() != 1 || out_name.extension().is_some() {
519 eprintln!("`out_name` cannot contain path separators or file extension");
520 panic!()
521 }
522 let mut output = Path::new(&dirname).join(out_name);
523 output.set_extension("wgsl");
524 self.write_to_file(output)
525 .expect("failed to write output shader");
526 }
527}
528
529impl std::fmt::Display for CompileResult {
530 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
531 self.syntax.fmt(f)
532 }
533}
534
535struct CompilationPass<'a> {
537 main_path: &'a ModulePath,
538 options: &'a CompileOptions,
539 resolver: &'a dyn Resolver,
540 mangler: &'a dyn Mangler,
541}
542
543impl<'a> CompilationPass<'a> {
544 fn new(
545 main_path: &'a ModulePath,
546 options: &'a CompileOptions,
547 resolver: &'a dyn Resolver,
548 mangler: &'a dyn Mangler,
549 ) -> Self {
550 Self {
551 main_path,
552 options,
553 resolver,
554 mangler,
555 }
556 }
557}
558
559impl CompilerDriver for CompilationPass<'_> {
560 fn main_path(&self) -> &ModulePath {
561 self.main_path
562 }
563
564 fn canonical_path(&self, path: &ModulePath) -> ModulePath {
565 self.resolver.canonical_path(path)
566 }
567
568 fn main_entry_points(&self, main_module: &TranslationUnit) -> Result<HashSet<Ident>, Error> {
569 if !self.options.strip || self.options.keep_main {
571 Ok(main_module
572 .global_declarations
573 .iter()
574 .filter_map(|decl| decl.ident())
575 .collect())
576 }
577 else if let Some(keep) = &self.options.keep {
579 keep.iter()
580 .map(|name| {
581 main_module.decl_ident(name).ok_or_else(|| {
582 UsageError::NotFound(self.main_path.clone(), name.to_string()).into()
583 })
584 })
585 .collect::<Result<HashSet<Ident>, Error>>()
586 }
587 else {
589 Ok(main_module.entry_points().collect())
590 }
591 }
592
593 fn module_usage_analysis(
594 &self,
595 module: &Module,
596 already_used: &mut UsedItems,
597 to_analyze: &mut UsedItems,
598 ) -> Result<(), Error> {
599 pass::module_usage_analysis(module, already_used, to_analyze, !self.options.visibility)?;
600
601 if !self.options.strip {
604 for decl in &module.syntax.global_declarations {
605 if let Some(ident) = decl.ident() {
606 self.usage_analysis(
607 module,
608 &ident.name(),
609 decl.visibility(),
610 already_used,
611 to_analyze,
612 )?;
613 }
614 }
615 }
616
617 Ok(())
618 }
619
620 fn usage_analysis(
621 &self,
622 module: &Module,
623 decl_name: &str,
624 min_vis: Visibility,
625 already_used: &mut UsedItems,
626 to_analyze: &mut UsedItems,
627 ) -> Result<(), Error> {
628 pass::usage_analysis(
629 module,
630 decl_name,
631 min_vis,
632 already_used,
633 to_analyze,
634 !self.options.visibility,
635 )?;
636 Ok(())
637 }
638
639 fn load_module(&mut self, path: &ModulePath) -> Result<TranslationUnit, Error> {
640 let mut module = pass::load_module(path, &self.resolver)?;
641
642 if self.options.condcomp {
643 pass::condcomp(&mut module, &self.options.features)?;
644 }
645
646 pass::retarget_idents(&mut module);
647
648 if self.options.validate {
649 pass::validate_wesl(&module)?;
650 }
651
652 Ok(module)
653 }
654
655 fn link(
656 &self,
657 modules: &mut Vec<Module>,
658 used_items: &UsedItems,
659 ) -> Result<TranslationUnit, Error> {
660 pass::retarget_modules(modules, used_items, &self.resolver);
661
662 for module in modules.iter_mut() {
663 if !self.options.mangle_main && module.path == *self.main_path {
664 continue;
665 }
666 pass::mangle(&mut module.syntax, &module.path, &self.mangler);
667 }
668
669 let mut module = pass::link(modules, self.options.strip.then_some(used_items));
670
671 if self.options.lower {
672 pass::lower(&mut module)?;
673 }
674
675 if self.options.validate {
676 pass::validate_wgsl(&module)?;
677 }
678
679 Ok(module)
680 }
681}
682
683#[test]
684fn test_send_sync() {
685 fn assert_send_sync<T: Send + Sync>() {}
686 assert_send_sync::<Compiler<()>>();
687}