1#![allow(missing_docs, reason = "not intended to be used directly")]
7
8use std::error::Error;
9use std::fmt;
10use std::fs;
11use std::path::PathBuf;
12
13use quote::quote;
14use syn::Token;
15
16use naga_rust_back::Config;
17use naga_rust_back::naga;
18
19#[proc_macro]
20pub fn include_wgsl_mr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
21 let ConfigAndStr {
22 config,
23 string: path_literal,
24 } = syn::parse_macro_input!(input as ConfigAndStr);
25
26 match include_wgsl_mr_impl(config, &path_literal) {
27 Ok(expansion) => expansion.into(),
28 Err(error) => error.to_compile_error().into(),
29 }
30}
31
32#[proc_macro]
33pub fn wgsl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
34 let ConfigAndStr {
35 config,
36 string: source_literal,
37 } = syn::parse_macro_input!(input as ConfigAndStr);
38
39 match parse_and_translate(config, source_literal.span(), &source_literal.value()) {
40 Ok(expansion) => expansion.into(),
41 Err(error) => error.to_compile_error().into(),
42 }
43}
44
45#[proc_macro_attribute]
47pub fn dummy_attribute(
48 _meta: proc_macro::TokenStream,
49 input: proc_macro::TokenStream,
50) -> proc_macro::TokenStream {
51 input
52}
53
54struct ConfigAndStr {
57 config: Config,
58 string: syn::LitStr,
59}
60
61impl syn::parse::Parse for ConfigAndStr {
62 fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
63 let mut config = macro_default_config();
64 loop {
65 let not_a_string_error = match input.parse::<syn::LitStr>() {
67 Ok(string) => {
68 if !input.is_empty() {
70 input.parse::<Token![,]>()?;
71 }
72 return Ok(Self { config, string });
73 }
74 Err(e) => e,
75 };
76
77 let option_name = input.parse::<syn::Ident>().map_err(|mut e| {
78 e.combine(not_a_string_error);
79 e
80 })?;
81 input.parse::<Token![=]>()?;
82 match &*option_name.to_string() {
83 "global_struct" => {
84 config = config.global_struct(input.parse::<syn::Ident>()?.to_string());
85 }
86 _ => {
88 return Err(syn::Error::new_spanned(
89 option_name,
90 "unrecognized configuration option name",
91 ));
92 }
93 }
94 input.parse::<Token![,]>()?;
95 }
96 }
97}
98
99fn macro_default_config() -> Config {
100 Config::default().runtime_path("::naga_rust_embed::rt")
101}
102
103fn include_wgsl_mr_impl(
106 config: Config,
107 path_literal: &syn::LitStr,
108) -> Result<proc_macro2::TokenStream, syn::Error> {
109 let mut absolute_path: PathBuf = PathBuf::from(
112 std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR must be set by Cargo"),
113 );
114 absolute_path.push(path_literal.value());
115
116 let absolute_path_str = absolute_path.to_str().ok_or_else(|| {
118 syn::Error::new_spanned(
119 path_literal,
120 format_args!(
121 "absolute path “{p:?}” must be UTF-8",
122 p = absolute_path.display()
123 ),
124 )
125 })?;
126
127 let wgsl_source_text: String = fs::read_to_string(&absolute_path).map_err(|error| {
128 syn::Error::new_spanned(
129 path_literal,
130 format_args!("failed to read “{absolute_path_str}”: {error}"),
131 )
132 })?;
133
134 let translated_tokens = parse_and_translate(config, path_literal.span(), &wgsl_source_text)?;
135
136 Ok(quote! {
137 const _: &str = include_str!(#absolute_path_str);
140
141 #translated_tokens
142 })
143}
144
145fn parse_and_translate(
146 config: Config,
147 wgsl_source_span: proc_macro2::Span,
148 wgsl_source_text: &str,
149) -> Result<proc_macro2::TokenStream, syn::Error> {
150 let module: naga::Module = naga::front::wgsl::parse_str(wgsl_source_text).map_err(|error| {
151 syn::Error::new(
152 wgsl_source_span,
153 format_args!("failed to parse WGSL text: {}", ErrorChain(&error)),
154 )
155 })?;
156
157 let module_info: naga::valid::ModuleInfo = naga::valid::Validator::new(
159 naga::valid::ValidationFlags::all(),
160 naga_rust_back::CAPABILITIES,
161 )
162 .subgroup_stages(naga::valid::ShaderStages::all())
163 .subgroup_operations(naga::valid::SubgroupOperationSet::empty())
165 .validate(&module)
166 .map_err(|error| {
167 syn::Error::new(
168 wgsl_source_span,
169 format_args!("failed to validate WGSL: {}", ErrorChain(&error)),
170 )
171 })?;
172
173 let translated_source: String = naga_rust_back::write_string(&module, &module_info, config)
174 .map_err(|error| {
175 syn::Error::new(
176 wgsl_source_span,
177 format_args!("failed to translate shader to Rust: {}", ErrorChain(&error)),
178 )
179 })?;
180
181 let translated_tokens: proc_macro2::TokenStream =
182 translated_source.parse().map_err(|error| {
183 syn::Error::new(
184 wgsl_source_span,
185 format_args!(
186 "internal error: translator did not produce valid Rust: {}",
187 ErrorChain(&error)
188 ),
189 )
190 })?;
191
192 Ok(translated_tokens)
193}
194
195#[derive(Clone, Copy, Debug)]
201struct ErrorChain<'a>(&'a (dyn Error + 'a));
202
203impl fmt::Display for ErrorChain<'_> {
204 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
205 format_error_chain(fmt, self.0)
206 }
207}
208
209fn format_error_chain(fmt: &mut fmt::Formatter<'_>, mut error: &(dyn Error + '_)) -> fmt::Result {
210 write!(fmt, "{error}")?;
211 while let Some(source) = error.source() {
212 error = source;
213 write!(fmt, "\n↳ {error}")?;
214 }
215
216 Ok(())
217}