wesl/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3
4#[cfg(feature = "eval")]
5pub mod eval;
6#[cfg(feature = "generics")]
7mod generics;
8#[cfg(feature = "package")]
9mod package;
10
11mod builtin;
12mod condcomp;
13mod error;
14mod import;
15mod lower;
16mod mangle;
17mod resolve;
18mod sourcemap;
19mod strip;
20mod syntax_util;
21mod validate;
22mod visit;
23
24#[cfg(feature = "eval")]
25pub use eval::{Eval, EvalError, Exec, Inputs, exec_entrypoint};
26
27#[cfg(feature = "generics")]
28pub use generics::GenericsError;
29
30#[cfg(feature = "package")]
31pub use package::PkgBuilder;
32
33pub use condcomp::{CondCompError, Feature, Features};
34pub use error::{Diagnostic, Error};
35pub use import::ImportError;
36pub use lower::lower;
37pub use mangle::{CacheMangler, EscapeMangler, HashMangler, Mangler, NoMangler, UnicodeMangler};
38pub use resolve::{
39    FileResolver, NoResolver, Pkg, PkgModule, PkgResolver, Preprocessor, ResolveError, Resolver,
40    Router, StandardResolver, VirtualResolver, emit_rerun_if_changed,
41};
42pub use sourcemap::{BasicSourceMap, NoSourceMap, SourceMap, SourceMapper};
43pub use syntax_util::SyntaxUtil;
44pub use validate::{ValidateError, validate_wesl, validate_wgsl};
45
46// re-exports
47pub use wesl_macros::*;
48pub use wgsl_parse::syntax;
49pub use wgsl_parse::syntax::ModulePath;
50
51#[cfg(feature = "eval")]
52use std::collections::HashMap;
53use std::{collections::HashSet, fmt::Display, path::Path};
54
55use import::{Module, Resolutions};
56use strip::strip_except;
57use wgsl_parse::syntax::{Ident, TranslationUnit};
58
59/// Compilation options. Used in [`compile`] and [`Wesl::set_options`].
60#[derive(Clone, Debug, PartialEq, Eq)]
61pub struct CompileOptions {
62    /// Toggle [WESL Imports](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/Imports.md).
63    ///
64    /// If disabled:
65    /// * The compiler will silently remove the import statements and inline paths.
66    /// * Validation will not trigger an error if referencing an imported item.
67    pub imports: bool,
68    /// Toggle [WESL Conditional Translation](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/ConditionalTranslation.md).
69    ///
70    /// See `features` to enable/disable each feature flag.
71    pub condcomp: bool,
72    /// Toggle generics. Generics are super experimental, don't expect anything from it.
73    ///
74    /// Requires the `generics` crate feature flag.
75    pub generics: bool,
76    /// Enable stripping (aka. Dead Code Elimination).
77    ///
78    /// By default, all declarations reachable by entrypoint functions, const_asserts and
79    /// pipeline-overridable constants are kept. See [`Self::keep`] and
80    /// [`Self::keep_root`] to control what gets stripped.
81    ///
82    /// Stripping can have side-effects in rare cases, refer to the WESL docs to learn
83    /// more.
84    pub strip: bool,
85    /// Enable lowering/polyfills. This transforms the output code in various ways.
86    ///
87    /// See [`lower`].
88    pub lower: bool,
89    /// Enable validation of individual WESL modules and the final output.
90    /// This will catch *some* errors, not all.
91    /// See [`validate_wesl`] and [`validate_wgsl`].
92    ///
93    /// Requires the `eval` crate feature flag.
94    pub validate: bool,
95    /// Make the import resolution lazy (This is the default mandated by WESL).
96    ///
97    /// The "lazy" import algorithm will only read a submodule is one of its item is used
98    /// by the entrypoints or `keep` declarations (recursively via static usage analysis).
99    ///
100    /// In contrast, the "eager" import algorithm will follow all import statements.
101    pub lazy: bool,
102    /// Enable mangling of declarations in the root module.
103    ///
104    /// By default, WESL does not mangle root module declarations.
105    pub mangle_root: bool,
106    /// If `Some`, specify a list of root module declarations to keep. If `None`, only the
107    /// entrypoint functions (and their dependencies) are kept.
108    ///
109    /// This option has no effect if [`Self::keep_root`] is enabled or  [`Self::strip`] is
110    /// disabled.
111    pub keep: Option<Vec<String>>,
112    /// If `true`, all root module declarations are preserved when stripping is enabled.
113    ///
114    /// This option takes precedence over [`Self::keep`], and has no effect if
115    /// [`Self::strip`] is disabled.
116    pub keep_root: bool,
117    /// [WESL Conditional Translation](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/ConditionalTranslation.md)
118    /// features to enable/disable.
119    ///
120    /// Conditional translation can be incremental. If not all feature flags are handled,
121    /// the output will contain unevaluated `@if` attributes and will therefore *not* be
122    /// valid WGSL.
123    ///
124    /// This option has no effect if [`Self::condcomp`] is disabled.
125    pub features: Features,
126}
127
128impl Default for CompileOptions {
129    fn default() -> Self {
130        Self {
131            imports: true,
132            condcomp: true,
133            generics: false,
134            strip: true,
135            lower: false,
136            validate: true,
137            lazy: true,
138            mangle_root: false,
139            keep: Default::default(),
140            keep_root: false,
141            features: Default::default(),
142        }
143    }
144}
145
146/// Mangling scheme. Used in [`Wesl::set_mangler`].
147#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
148pub enum ManglerKind {
149    /// Escaped path mangler.
150    /// `foo_bar::item -> _1foo_bar_item`
151    #[default]
152    Escape,
153    /// Hash mangler.
154    /// `foo::bar::item -> item_1985638328947`
155    Hash,
156    /// Make valid identifiers with unicode "confusables" characters.
157    /// `foo::bar<baz, moo> -> foo::barᐸbazˏmooᐳ`
158    Unicode,
159    /// Disable mangling. (warning: will break shaders if case of name conflicts!)
160    None,
161}
162
163/// Include a WGSL file compiled with [`Wesl::build_artifact`] as a string.
164///
165/// The argument corresponds to the `artifact_name` passed to [`Wesl::build_artifact`].
166///
167/// This is a very simple convenience macro. See the crate documentation for a usage
168/// example.
169#[macro_export]
170macro_rules! include_wesl {
171    ($root:literal) => {
172        include_str!(concat!(env!("OUT_DIR"), "/", $root, ".wgsl"))
173    };
174}
175
176/// Include a generated package.
177///
178/// See [`PkgBuilder`] for more information about building WESL packages.
179#[macro_export]
180macro_rules! wesl_pkg {
181    ($pkg_name:ident) => {
182        $crate::wesl_pkg!($pkg_name, concat!("/", stringify!($pkg_name), ".rs"));
183    };
184    ($pkg_name:ident, $source:expr) => {
185        pub mod $pkg_name {
186            use $crate::{Pkg, PkgModule};
187            include!(concat!(env!("OUT_DIR"), $source));
188        }
189    };
190}
191
192/// The WESL compiler high-level API.
193///
194/// # Basic Usage
195///
196/// ```rust
197/// # use wesl::{Wesl, VirtualResolver};
198/// #
199/// let compiler = Wesl::new("path/to/dir/containing/shaders");
200/// #
201/// # // just adding a virtual file here so the doctest runs without a filesystem
202/// # let mut resolver = VirtualResolver::new();
203/// # resolver.add_module("package::main".parse().unwrap(), "fn my_fn() {}".into());
204/// # let compiler = compiler.set_custom_resolver(resolver);
205/// #
206/// let wgsl_string = compiler
207///     .compile(&"package::main".parse().unwrap())
208///     .unwrap()
209///     .to_string();
210/// ```
211pub struct Wesl<R: Resolver> {
212    options: CompileOptions,
213    use_sourcemap: bool,
214    resolver: R,
215    mangler: Box<dyn Mangler + Send + Sync + 'static>,
216}
217
218impl Wesl<StandardResolver> {
219    /// Get a WESL compiler with all *mandatory* and *optional* WESL extensions enabled,
220    /// but not *experimental* and *non-standard* extensions.
221    ///
222    /// See also: [`Wesl::new_barebones`], [`Wesl::new_experimental`].
223    ///
224    /// # WESL Reference
225    /// This WESL compiler is spec-compliant.
226    ///
227    /// Mandatory extensions: imports, conditional translation.
228    /// Optional extrensions: stripping.
229    pub fn new(base: impl AsRef<Path>) -> Self {
230        Self {
231            options: CompileOptions::default(),
232            use_sourcemap: true,
233            resolver: StandardResolver::new(base),
234            mangler: Box::new(EscapeMangler),
235        }
236    }
237
238    /// Get a WESL compiler with all functionalities enabled, including *experimental* and
239    /// *non-standard* ones.
240    ///
241    /// See also: [`Wesl::new`] and [`Wesl::new_barebones`].
242    ///
243    /// # WESL Reference
244    /// This WESL compiler is *not* spec-compliant because it enables all extensions
245    /// including *experimental* and *non-standard* ones. See [`Wesl::new`].
246    ///
247    /// Experimental extensions: generics.
248    /// Non-standard extensions: `@const`.
249    pub fn new_experimental(base: impl AsRef<Path>) -> Self {
250        Self {
251            options: CompileOptions {
252                generics: true,
253                lower: true,
254                ..Default::default()
255            },
256            use_sourcemap: true,
257            resolver: StandardResolver::new(base),
258            mangler: Box::new(EscapeMangler),
259        }
260    }
261
262    /// Add a package dependency.
263    ///
264    /// Learn more about packages in [`PkgBuilder`].
265    pub fn add_package(&mut self, pkg: &'static Pkg) -> &mut Self {
266        self.resolver.add_package(pkg);
267        self
268    }
269
270    /// Add several package dependencies.
271    ///
272    /// Learn more about packages in [`PkgBuilder`].
273    pub fn add_packages(&mut self, pkgs: impl IntoIterator<Item = &'static Pkg>) -> &mut Self {
274        for pkg in pkgs {
275            self.resolver.add_package(pkg);
276        }
277        self
278    }
279
280    /// Add a const-declaration to the special `constants` module.
281    ///
282    /// See [`StandardResolver::add_constant`].
283    pub fn add_constant(&mut self, name: impl ToString, value: f64) -> &mut Self {
284        self.resolver.add_constant(name, value);
285        self
286    }
287
288    /// Add several const-declarations to the special `constants` module.
289    ///
290    /// See [`StandardResolver::add_constant`].
291    pub fn add_constants(
292        &mut self,
293        constants: impl IntoIterator<Item = (impl ToString, f64)>,
294    ) -> &mut Self {
295        for (name, value) in constants {
296            self.resolver.add_constant(name, value);
297        }
298        self
299    }
300}
301
302impl Wesl<NoResolver> {
303    /// Get a WESL compiler with no extensions, no mangler and no resolver.
304    ///
305    /// You must set a [`Mangler`] and a [`Resolver`] manually to use this compiler,
306    /// see [`Wesl::set_mangler`] and [`Wesl::set_custom_resolver`].
307    ///
308    /// # WESL Reference
309    /// This WESL compiler is *not* spec-compliant because it does not enable *mandatory*
310    /// WESL extensions. See [`Wesl::new`].
311    pub fn new_barebones() -> Self {
312        Self {
313            options: CompileOptions {
314                imports: false,
315                condcomp: false,
316                generics: false,
317                strip: false,
318                lower: false,
319                validate: false,
320                lazy: false,
321                mangle_root: false,
322                keep: None,
323                keep_root: false,
324                features: Default::default(),
325            },
326            use_sourcemap: false,
327            resolver: NoResolver,
328            mangler: Box::new(NoMangler),
329        }
330    }
331}
332
333impl<R: Resolver> Wesl<R> {
334    /// Set all compilation options.
335    pub fn set_options(&mut self, options: CompileOptions) -> &mut Self {
336        self.options = options;
337        self
338    }
339
340    /// Set the [`Mangler`].
341    ///
342    /// The default mangler is [`EscapeMangler`].
343    ///
344    /// # WESL Reference
345    /// Custom manglers *must* conform to the constraints described in [`Mangler`].
346    ///
347    /// Spec: not yet available.
348    pub fn set_mangler(&mut self, kind: ManglerKind) -> &mut Self {
349        self.mangler = match kind {
350            ManglerKind::Escape => Box::new(EscapeMangler),
351            ManglerKind::Hash => Box::new(HashMangler),
352            ManglerKind::Unicode => Box::new(UnicodeMangler),
353            ManglerKind::None => Box::new(NoMangler),
354        };
355        self
356    }
357
358    /// Set a custom [`Mangler`].
359    ///
360    /// The default mangler is [`EscapeMangler`].
361    pub fn set_custom_mangler(
362        &mut self,
363        mangler: impl Mangler + Send + Sync + 'static,
364    ) -> &mut Self {
365        self.mangler = Box::new(mangler);
366        self
367    }
368
369    /// Set a custom [`Resolver`] (customize how import paths are translated to WESL modules).
370    ///
371    ///```rust
372    /// # use wesl::{FileResolver, Router, VirtualResolver, Wesl};
373    /// // `import runtime::constants::PI` is in a custom module mounted at runtime.
374    /// let mut resolver = VirtualResolver::new();
375    /// resolver.add_module("constants".parse().unwrap(), "const PI = 3.1415; const TAU = PI * 2.0;".into());
376    /// let mut router = Router::new();
377    /// router.mount_fallback_resolver(FileResolver::new("src/shaders"));
378    /// router.mount_resolver("runtime".parse().unwrap(), resolver);
379    /// let compiler = Wesl::new("").set_custom_resolver(router);
380    /// ```
381    ///
382    /// # WESL Reference
383    /// Both [`FileResolver`] and [`VirtualResolver`] are spec-compliant.
384    /// Custom resolvers *must* conform to the constraints described in [`Resolver`].
385    pub fn set_custom_resolver<CustomResolver: Resolver>(
386        self,
387        resolver: CustomResolver,
388    ) -> Wesl<CustomResolver> {
389        Wesl {
390            options: self.options,
391            use_sourcemap: self.use_sourcemap,
392            mangler: self.mangler,
393            resolver,
394        }
395    }
396
397    /// Enable sourcemapping.
398    ///
399    /// Turning "on" this option improves the quality of error messages.
400    ///
401    /// # WESL Reference
402    /// Sourcemapping is not yet part of the WESL Specification and does not impact
403    /// compliance.
404    pub fn use_sourcemap(&mut self, val: bool) -> &mut Self {
405        self.use_sourcemap = val;
406        self
407    }
408
409    /// Enable imports.
410    ///
411    /// # WESL Reference
412    /// Imports is a *mandatory* WESL extension.
413    ///
414    /// Spec: [`Imports.md`](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/Imports.md)
415    pub fn use_imports(&mut self, val: bool) -> &mut Self {
416        self.options.imports = val;
417        self
418    }
419
420    /// Enable conditional translation.
421    ///
422    /// # WESL Reference
423    /// Conditional Compilation is a *mandatory* WESL extension.
424    /// Spec: [`ConditionalTranslation.md`](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/ConditionalTranslation.md)
425    pub fn use_condcomp(&mut self, val: bool) -> &mut Self {
426        self.options.condcomp = val;
427        self
428    }
429
430    /// Enable generics.
431    ///
432    /// # WESL Reference
433    /// Generics is an *experimental* WESL extension.
434    ///
435    /// Spec: not yet available.
436    #[cfg(feature = "generics")]
437    pub fn use_generics(&mut self, val: bool) -> &mut Self {
438        self.options.generics = val;
439        self
440    }
441    /// Set a conditional compilation feature flag.
442    ///
443    /// # WESL Reference
444    /// Conditional translation is a *mandatory* WESL extension.
445    ///
446    /// Spec: [`ConditionalTranslation.md`](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/ConditionalTranslation.md)
447    pub fn set_feature(&mut self, feat: &str, val: impl Into<Feature>) -> &mut Self {
448        self.options
449            .features
450            .flags
451            .insert(feat.to_string(), val.into());
452        self
453    }
454    /// Set conditional compilation feature flags.
455    ///
456    /// # WESL Reference
457    /// Conditional translation is a *mandatory* WESL extension.
458    /// Spec: [`ConditionalTranslation.md`](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/ConditionalTranslation.md)
459    pub fn set_features(
460        &mut self,
461        feats: impl IntoIterator<Item = (impl ToString, impl Into<Feature>)>,
462    ) -> &mut Self {
463        self.options
464            .features
465            .flags
466            .extend(feats.into_iter().map(|(k, v)| (k.to_string(), v.into())));
467        self
468    }
469    /// Unset a conditional compilation feature flag.
470    ///
471    /// # WESL Reference
472    /// Conditional translation is a *mandatory* WESL extension.
473    ///
474    /// Spec: [`ConditionalTranslation.md`](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/ConditionalTranslation.md)
475    pub fn unset_feature(&mut self, feat: &str) -> &mut Self {
476        self.options.features.flags.remove(feat);
477        self
478    }
479    /// Set the behavior for unspecified conditional compilation feature flags.
480    ///
481    /// Controls what happens when a feature flag is used in shader code but not set with
482    /// [`Wesl::set_feature`]. By default it turns off the feature, but it can also be set to leave
483    /// it in the code ([`Feature::Keep`]) or to trigger compilation error ([`Feature::Error`]).
484    ///
485    /// # WESL Reference
486    /// Conditional translation is a *mandatory* WESL extension.
487    ///
488    /// Spec: [`ConditionalTranslation.md`](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/ConditionalTranslation.md)
489    pub fn set_missing_feature_behavior(&mut self, val: impl Into<Feature>) -> &mut Self {
490        self.options.features.default = val.into();
491        self
492    }
493    /// Remove unused declarations from the final WGSL output.
494    ///
495    /// Unused declarations are all declarations not used (directly or indirectly) by any
496    /// of the entrypoints (functions marked `@compute`, `@vertex` or `@fragment`) in the
497    /// root module.
498    ///
499    /// see also: [`Wesl::keep_declarations`]
500    ///
501    /// # WESL Reference
502    /// Code stripping is an *optional* WESL extension.
503    /// Customizing entrypoints returned by the compiler is explicitly allowed by the spec.
504    ///
505    /// Spec: not yet available.
506    pub fn use_stripping(&mut self, val: bool) -> &mut Self {
507        self.options.strip = val;
508        self
509    }
510    /// Transform an output into a simplified WGSL that is better supported by
511    /// implementors.
512    ///
513    /// See also: [`lower`].
514    ///
515    /// # WESL Reference
516    /// Lowering is an *experimental* WESL extension.
517    ///
518    /// Spec: not yet available.
519    pub fn use_lower(&mut self, val: bool) -> &mut Self {
520        self.options.lower = val;
521        self
522    }
523    /// If stripping is enabled, specify which root module declarations to keep in the
524    /// final WGSL. Function entrypoints are kept by default.
525    ///
526    /// # WESL Reference
527    /// Code stripping is an *optional* WESL extension.
528    /// Customizing entrypoints returned by the compiler is explicitly allowed by the
529    /// spec.
530    ///
531    /// Spec: not yet available.
532    pub fn keep_declarations(&mut self, keep: Vec<String>) -> &mut Self {
533        self.options.keep = Some(keep);
534        self
535    }
536    /// If stripping is enabled, keep all entrypoints in the root WESL module.
537    ///
538    /// Entrypoints are functions with `@compute`, `@vertex` or `@fragment`.
539    /// This is the default. See also: [`Wesl::keep_declarations`].
540    ///
541    /// # WESL Reference
542    /// Code stripping is an *optional* WESL extension.
543    /// Customizing entrypoints returned by the compiler is explicitly allowed by the
544    /// spec.
545    ///
546    /// Spec: not yet available.
547    pub fn keep_all_entrypoints(&mut self) -> &mut Self {
548        self.options.keep = None;
549        self
550    }
551}
552
553/// The result of [`Wesl::compile`].
554///
555/// This type contains both the resulting WGSL syntax tree and the sourcemap if
556/// [`Wesl`] was invoked with sourcemapping enabled.
557///
558/// This type implements `Display`, call `to_string()` to get the compiled WGSL.
559#[derive(Clone, Default)]
560pub struct CompileResult {
561    pub syntax: TranslationUnit,
562    pub sourcemap: Option<BasicSourceMap>,
563    /// A list of absolute paths or packages.
564    pub modules: Vec<ModulePath>,
565}
566
567impl CompileResult {
568    pub fn write_to_file(&self, path: impl AsRef<Path>) -> std::io::Result<()> {
569        std::fs::write(path, self.to_string())
570    }
571
572    /// Write the result in rust's `OUT_DIR`.
573    ///
574    /// This function is meant to be used in a `build.rs` workflow. The output WGSL will
575    /// be accessed with the [`include_wesl`] macro. See the crate documentation for a
576    /// usage example.
577    ///
578    /// # Panics
579    /// Panics when the output file cannot be written.
580    pub fn write_artifact(&self, artifact_name: &str) {
581        let dirname = std::env::var("OUT_DIR").unwrap();
582        let out_name = Path::new(artifact_name);
583        if out_name.iter().count() != 1 || out_name.extension().is_some() {
584            eprintln!("`out_name` cannot contain path separators or file extension");
585            panic!()
586        }
587        let mut output = Path::new(&dirname).join(out_name);
588        output.set_extension("wgsl");
589        self.write_to_file(output)
590            .expect("failed to write output shader");
591    }
592}
593
594impl Display for CompileResult {
595    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
596        self.syntax.fmt(f)
597    }
598}
599
600/// The result of [`CompileResult::exec`].
601///
602/// This type contains both the return value of the function called (if any) and the
603/// evaluation context (including bindings).
604///
605/// This type implements `Display`, call `to_string()` to get the function return value.
606#[cfg(feature = "eval")]
607pub struct ExecResult<'a> {
608    /// The executed function return value
609    pub inst: Option<eval::Instance>,
610    /// Context after execution
611    pub ctx: eval::Context<'a>,
612}
613
614#[cfg(feature = "eval")]
615impl ExecResult<'_> {
616    /// Get the function return value.
617    pub fn return_value(&self) -> Option<&eval::Instance> {
618        self.inst.as_ref()
619    }
620
621    /// Get a [shader resource](https://www.w3.org/TR/WGSL/#resource).
622    ///
623    /// Shader resources (aka. bindings) with `write`
624    /// [access mode](https://www.w3.org/TR/WGSL/#memory-access-mode) can be modified
625    /// after executing an entry point.
626    pub fn resource(&self, group: u32, binding: u32) -> Option<&eval::RefInstance> {
627        self.ctx.resource(group, binding)
628    }
629}
630
631/// The result of [`CompileResult::eval`].
632///
633/// This type contains both the resulting WGSL instance and the evaluation context
634/// (including bindings).
635///
636/// This type implements `Display`, call `to_string()` to get the evaluation result.
637#[cfg(feature = "eval")]
638pub struct EvalResult<'a> {
639    /// The expression evaluation result
640    pub inst: eval::Instance,
641    /// Context after evaluation
642    pub ctx: eval::Context<'a>,
643}
644
645#[cfg(feature = "eval")]
646impl EvalResult<'_> {
647    // TODO: make context non-mut
648    /// Get the WGSL string representing the evaluated expression.
649    pub fn to_buffer(&mut self) -> Option<Vec<u8>> {
650        use eval::HostShareable;
651        self.inst.to_buffer(&mut self.ctx)
652    }
653}
654
655#[cfg(feature = "eval")]
656impl Display for EvalResult<'_> {
657    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
658        self.inst.fmt(f)
659    }
660}
661
662#[cfg(feature = "eval")]
663impl CompileResult {
664    /// Evaluate a const-expression in the context of this compilation result.
665    ///
666    /// Highly experimental. Not all builtin WGSL functions are supported yet.
667    /// Contrary to [`eval_str`], the provided expression can reference declarations
668    /// in the compiled WGSL: global const-declarations and user-defined functions with
669    /// the `@const` attribute.
670    ///
671    /// # WESL Reference
672    /// The user-defined `@const` attribute is non-standard.
673    /// See issue [#46](https://github.com/wgsl-tooling-wg/wesl-spec/issues/46#issuecomment-2389531479).
674    pub fn eval<'a>(&'a self, source: &str) -> Result<EvalResult<'a>, Error> {
675        let expr = source
676            .parse::<syntax::Expression>()
677            .map_err(|e| Error::Error(Diagnostic::from(e).with_source(source.to_string())))?;
678        let (inst, ctx) = eval(&expr, &self.syntax);
679        let inst = inst.map_err(|e| {
680            Diagnostic::from(e)
681                .with_source(source.to_string())
682                .with_ctx(&ctx)
683        });
684
685        let inst = if let Some(sourcemap) = &self.sourcemap {
686            inst.map_err(|e| Error::Error(e.with_sourcemap(sourcemap)))
687        } else {
688            inst.map_err(Error::Error)
689        }?;
690
691        let res = EvalResult { inst, ctx };
692        Ok(res)
693    }
694
695    /// Execute an entrypoint in the same way that it would be executed on the GPU.
696    ///
697    /// Highly experimental.
698    ///
699    /// # WESL Reference
700    /// The `@const` attribute is non-standard.
701    pub fn exec<'a>(
702        &'a self,
703        entrypoint: &str,
704        inputs: Inputs,
705        bindings: HashMap<(u32, u32), eval::RefInstance>,
706        overrides: HashMap<String, eval::Instance>,
707    ) -> Result<ExecResult<'a>, Error> {
708        let mut ctx = eval::Context::new(&self.syntax);
709        ctx.add_bindings(bindings);
710        ctx.add_overrides(overrides);
711        ctx.set_stage(eval::EvalStage::Exec);
712
713        let entry_fn = eval::SyntaxUtil::decl_function(ctx.source, entrypoint)
714            .ok_or_else(|| EvalError::UnknownFunction(entrypoint.to_string()))?;
715
716        let _ = self.syntax.exec(&mut ctx)?;
717
718        let inst = exec_entrypoint(entry_fn, inputs, &mut ctx).map_err(|e| {
719            if let Some(sourcemap) = &self.sourcemap {
720                Diagnostic::from(e).with_ctx(&ctx).with_sourcemap(sourcemap)
721            } else {
722                Diagnostic::from(e).with_ctx(&ctx)
723            }
724        })?;
725
726        Ok(ExecResult { inst, ctx })
727    }
728}
729
730impl<R: Resolver> Wesl<R> {
731    /// Compile a WESL program from a root file.
732    ///
733    /// # WESL Reference
734    /// Spec: not available yet.
735    pub fn compile(&self, root: &ModulePath) -> Result<CompileResult, Error> {
736        // TODO
737        // root.origin = PathOrigin::Absolute; // we force absolute paths
738
739        if self.use_sourcemap {
740            compile_sourcemap(root, &self.resolver, &self.mangler, &self.options)
741        } else {
742            compile(root, &self.resolver, &self.mangler, &self.options)
743        }
744    }
745
746    /// Compile a WESL program from a root file and output the result in Rust's `OUT_DIR`.
747    ///
748    /// This function is meant to be used in a `build.rs` workflow. The output WGSL will
749    /// be accessed with the [`include_wesl`] macro. See the crate documentation for a
750    /// usage example.
751    ///
752    /// * The first argument is the path to the root module relative to the base
753    ///   directory.
754    /// * The second argument is the name of the artifact, used in [`include_wesl`].
755    ///
756    /// Will emit `rerun-if-changed` instructions so the build script reruns only if the
757    /// shader files are modified.
758    ///
759    /// # Panics
760    /// Panics when compilation fails or if the output file cannot be written.
761    /// Pretty-prints the WESL error message to stderr.
762    pub fn build_artifact(&self, root: &ModulePath, artifact_name: &str) {
763        let compiled = self
764            .compile(root)
765            .inspect_err(|e| {
766                eprintln!("failed to build WESL shader `{root}`.\n{e}");
767                panic!();
768            })
769            .unwrap();
770        emit_rerun_if_changed(&compiled.modules, &self.resolver);
771        compiled.write_artifact(artifact_name);
772    }
773}
774
775/// What idents to keep from the root module. They should be either:
776/// * all named declarations, if `strip` is disabled or `keep_root` is enabled.
777/// * `keep` idents that exist, if it is set and `strip` is enabled,
778/// * all entrypoints, if `strip` is enabled and `keep` is not set
779fn keep_idents(
780    wesl: &TranslationUnit,
781    keep: &Option<Vec<String>>,
782    keep_root: bool,
783    strip: bool,
784) -> HashSet<Ident> {
785    if strip && !keep_root {
786        if let Some(keep) = keep {
787            wesl.global_declarations
788                .iter()
789                .filter_map(|decl| {
790                    let ident = decl.ident()?;
791                    keep.iter()
792                        .any(|name| name == &*ident.name())
793                        .then_some(ident.clone())
794                })
795                .collect()
796        } else {
797            // when stripping is enabled and keep is unset, we keep the entrypoints (default)
798            wesl.entry_points().cloned().collect()
799        }
800    } else {
801        // when stripping is disabled, we keep all declarations in the root module.
802        wesl.global_declarations
803            .iter()
804            .filter_map(|decl| decl.ident())
805            .cloned()
806            .collect()
807    }
808}
809
810fn compile_pre_assembly(
811    root: &ModulePath,
812    resolver: &impl Resolver,
813    opts: &CompileOptions,
814) -> Result<(Resolutions, HashSet<Ident>), Error> {
815    let resolver: Box<dyn Resolver> = if opts.condcomp {
816        Box::new(Preprocessor::new(resolver, |wesl| {
817            condcomp::run(wesl, &opts.features)?;
818            Ok(())
819        }))
820    } else {
821        Box::new(resolver)
822    };
823
824    let mut wesl = resolver.resolve_module(root)?;
825    wesl.retarget_idents();
826    let keep = keep_idents(&wesl, &opts.keep, opts.keep_root, opts.strip);
827
828    let mut resolutions = Resolutions::new();
829    let module = Module::new(wesl, root.clone())?;
830    resolutions.push_module(module);
831
832    if opts.imports {
833        if opts.lazy {
834            import::resolve_lazy(&keep, &mut resolutions, &resolver)?
835        } else {
836            import::resolve_eager(&mut resolutions, &resolver)?
837        }
838    }
839
840    if opts.validate {
841        for module in resolutions.modules() {
842            let module = module.borrow();
843            validate_wesl(&module.source).map_err(|d| {
844                d.with_module_path(module.path.clone(), resolver.display_name(&module.path))
845            })?;
846        }
847    }
848
849    Ok((resolutions, keep))
850}
851
852fn compile_post_assembly(
853    wesl: &mut TranslationUnit,
854    options: &CompileOptions,
855    keep: &HashSet<Ident>,
856) -> Result<(), Error> {
857    #[cfg(feature = "generics")]
858    if options.generics {
859        generics::generate_variants(wesl)?;
860        generics::replace_calls(wesl)?;
861    };
862    if options.validate {
863        validate_wgsl(wesl)?;
864    }
865    if options.lower {
866        lower(wesl)?;
867    }
868    if options.strip {
869        strip_except(wesl, keep);
870    }
871    Ok(())
872}
873
874/// Low-level version of [`Wesl::compile`].
875/// To get a source map, use [`compile_sourcemap`] instead.
876pub fn compile(
877    root: &ModulePath,
878    resolver: &impl Resolver,
879    mangler: &impl Mangler,
880    options: &CompileOptions,
881) -> Result<CompileResult, Error> {
882    let (mut resolutions, keep) = compile_pre_assembly(root, resolver, options)?;
883    resolutions.mangle(mangler, options.mangle_root);
884    let mut assembly = resolutions.assemble(options.strip && options.lazy);
885    // resolutions hold idents use-counts. We only need the list of modules now.
886    let modules = resolutions.into_module_order();
887    compile_post_assembly(&mut assembly, options, &keep)?;
888    Ok(CompileResult {
889        syntax: assembly,
890        sourcemap: None,
891        modules,
892    })
893}
894
895/// Like [`compile`], but provides better error diagnostics and returns the sourcemap.
896pub fn compile_sourcemap(
897    root: &ModulePath,
898    resolver: &impl Resolver,
899    mangler: &impl Mangler,
900    options: &CompileOptions,
901) -> Result<CompileResult, Error> {
902    let sourcemapper = SourceMapper::new(root, resolver, mangler);
903
904    match compile_pre_assembly(root, &sourcemapper, options) {
905        Ok((mut resolutions, keep)) => {
906            resolutions.mangle(&sourcemapper, options.mangle_root);
907            let sourcemap = sourcemapper.finish();
908            let mut assembly = resolutions.assemble(options.strip && options.lazy);
909            let modules = resolutions.into_module_order();
910            compile_post_assembly(&mut assembly, options, &keep)
911                .map_err(|e| {
912                    Diagnostic::from(e)
913                        .with_output(assembly.to_string())
914                        .with_sourcemap(&sourcemap)
915                        .unmangle(Some(&sourcemap), Some(&mangler))
916                        .into()
917                })
918                .map(|()| CompileResult {
919                    syntax: assembly,
920                    sourcemap: Some(sourcemap),
921                    modules,
922                })
923        }
924        Err(e) => {
925            let sourcemap = sourcemapper.finish();
926            Err(Diagnostic::from(e)
927                .with_sourcemap(&sourcemap)
928                .unmangle(Some(&sourcemap), Some(&mangler))
929                .into())
930        }
931    }
932}
933
934/// Evaluate a const-expression.
935///
936/// Only builtin function declarations marked `@const` can be called from
937/// const-expressions.
938///
939/// Highly experimental. Not all builtin `@const` WGSL functions are supported yet.
940#[cfg(feature = "eval")]
941pub fn eval_str(expr: &str) -> Result<eval::Instance, Error> {
942    let expr = expr
943        .parse::<syntax::Expression>()
944        .map_err(|e| Error::Error(Diagnostic::from(e).with_source(expr.to_string())))?;
945    let wgsl = TranslationUnit::default();
946    let (inst, ctx) = eval(&expr, &wgsl);
947    inst.map_err(|e| {
948        Error::Error(
949            Diagnostic::from(e)
950                .with_source(expr.to_string())
951                .with_ctx(&ctx),
952        )
953    })
954}
955
956/// Low-level version of [`eval_str`].
957#[cfg(feature = "eval")]
958pub fn eval<'s>(
959    expr: &syntax::Expression,
960    wgsl: &'s TranslationUnit,
961) -> (Result<eval::Instance, EvalError>, eval::Context<'s>) {
962    let mut ctx = eval::Context::new(wgsl);
963    let res = wgsl.exec(&mut ctx).and_then(|_| expr.eval(&mut ctx));
964    (res, ctx)
965}
966
967/// Low-level version of [`CompileResult::exec`].
968#[cfg(feature = "eval")]
969pub fn exec<'s>(
970    expr: &impl Eval,
971    wgsl: &'s TranslationUnit,
972    bindings: HashMap<(u32, u32), eval::RefInstance>,
973    overrides: HashMap<String, eval::Instance>,
974) -> (Result<Option<eval::Instance>, EvalError>, eval::Context<'s>) {
975    let mut ctx = eval::Context::new(wgsl);
976    ctx.add_bindings(bindings);
977    ctx.add_overrides(overrides);
978    ctx.set_stage(eval::EvalStage::Exec);
979
980    let res = wgsl.exec(&mut ctx).and_then(|_| match expr.eval(&mut ctx) {
981        Ok(ret) => Ok(Some(ret)),
982        Err(eval::EvalError::Void(_)) => Ok(None),
983        Err(e) => Err(e),
984    });
985    (res, ctx)
986}
987
988#[test]
989fn test_send_sync() {
990    fn assert_send_sync<T: Send + Sync>() {}
991    assert_send_sync::<Wesl<StandardResolver>>();
992}