wgsl_parser/decl/
import.rs

1//! Helpers for working with [`naga-oil`] modules.
2//!
3//! [`naga-oil`]: https://github.com/bevyengine/naga_oil
4
5use std::sync::Arc;
6
7use gramatika::{Parse, ParseStreamer, Span, Spanned, SpannedError, Token as _};
8
9use crate::{
10	parser::ErrorRecoveringParseStream,
11	token::{brace, directive, ident, punct, Token, TokenKind},
12	ParseStream,
13};
14
15/// Represents a `#define_import_path` statement like the ones supported by
16/// [`naga_oil`](https://github.com/bevyengine/naga_oil):
17///
18/// ```wgsl
19/// #define_import_path my::shader::module
20/// ```
21#[derive(Clone, DebugLisp)]
22pub struct ImportPathDecl {
23	pub keyword: Token,
24	pub path: ImportPath,
25}
26
27/// Represents an `#import` statement like the ones supported by
28/// [`naga_oil`](https://github.com/bevyengine/naga_oil):
29///
30/// ```wgsl
31/// #import my::shader::{
32///     module_a,
33///     module_b::{TypeA, TypeB},
34///     module_c::function,
35///     module_d::function as d_function,
36/// }
37/// ```
38#[derive(Clone, DebugLisp)]
39pub struct ImportDecl {
40	pub keyword: Token,
41	pub path: ImportPath,
42}
43
44#[derive(Clone, DebugLisp)]
45pub enum ImportPath {
46	Namespaced(NamespacedImportPath),
47	Block(ImportPathBlock),
48	Leaf(ImportPathLeaf),
49}
50
51#[derive(Clone, DebugLisp)]
52pub struct NamespacedImportPath {
53	pub namespace: Token,
54	pub path: Arc<ImportPath>,
55}
56
57#[derive(Clone, DebugLisp)]
58pub struct ImportPathBlock {
59	pub brace_open: Token,
60	pub paths: Arc<[ImportPath]>,
61	pub brace_close: Token,
62}
63
64#[derive(Clone, DebugLisp)]
65pub struct ImportPathLeaf {
66	pub name: Token,
67	pub as_binding: Option<Token>,
68}
69
70impl ImportPathDecl {
71	/// Returns the token at the leaf node of this declaration.
72	///
73	/// I.e., for the following import path declaration:
74	/// ```wgsl
75	/// #define_import_path foo::bar::baz
76	/// ```
77	/// This function will return the `bar` token at the end of the line.
78	pub fn name(&self) -> &Token {
79		let mut import_path = &self.path;
80		loop {
81			match import_path {
82				ImportPath::Namespaced(NamespacedImportPath { path, .. }) => {
83					import_path = path.as_ref();
84				}
85				ImportPath::Leaf(ImportPathLeaf { name, .. }) => {
86					break name;
87				}
88				ImportPath::Block(_) => {
89					unreachable!();
90				}
91			}
92		}
93	}
94}
95
96impl Parse for ImportPathDecl {
97	type Stream = ParseStream;
98
99	fn parse(input: &mut Self::Stream) -> gramatika::Result<Self> {
100		let keyword = input.consume(directive!["#define_import_path"])?;
101		let path = input.parse()?;
102
103		// Return an error if any portion of the path is a block or has an `as` binding
104		let mut parsed_path = &path;
105		loop {
106			match parsed_path {
107				ImportPath::Namespaced(NamespacedImportPath { path, .. }) => {
108					parsed_path = path.as_ref();
109				}
110				ImportPath::Block(block) => {
111					return Err(SpannedError {
112						message: "Path blocks are not valid in `#define_import_path`".into(),
113						source: input.source(),
114						span: Some(block.span()),
115					});
116				}
117				ImportPath::Leaf(leaf) => {
118					if let Some(as_binding) = leaf.as_binding.as_ref() {
119						return Err(SpannedError {
120							message: "`as` bindings are not valid in `#define_import_path`".into(),
121							source: input.source(),
122							span: Some(as_binding.span()),
123						});
124					} else {
125						break;
126					}
127				}
128			}
129		}
130
131		Ok(Self { keyword, path })
132	}
133}
134
135impl Spanned for ImportPathDecl {
136	fn span(&self) -> Span {
137		self.keyword.span().through(self.path.span())
138	}
139}
140
141impl Parse for ImportDecl {
142	type Stream = ParseStream;
143
144	fn parse(input: &mut Self::Stream) -> gramatika::Result<Self> {
145		let keyword = input.consume(directive!["#import"])?;
146		let path = input.parse()?;
147
148		Ok(Self { keyword, path })
149	}
150}
151
152impl Spanned for ImportDecl {
153	fn span(&self) -> Span {
154		self.keyword.span().through(self.path.span())
155	}
156}
157
158impl Parse for ImportPath {
159	type Stream = ParseStream;
160
161	fn parse(input: &mut Self::Stream) -> gramatika::Result<Self> {
162		use TokenKind::*;
163
164		match input.next() {
165			Some(mut next) => match next.as_matchable() {
166				(Ident | Path, _, _) => {
167					next = match next.kind() {
168						TokenKind::Ident => input.upgrade_last(TokenKind::Ident, Token::module)?,
169						TokenKind::Path => next,
170						_ => unreachable!(),
171					};
172
173					if input.check(punct![::]) {
174						input.discard();
175
176						Ok(ImportPath::Namespaced(NamespacedImportPath {
177							namespace: next,
178							path: Arc::new(input.parse()?),
179						}))
180					} else {
181						let name = next;
182						let as_binding = if input.check(ident![as]) {
183							let _ = input.consume_as(TokenKind::Ident, Token::keyword)?;
184							Some(input.consume_as(TokenKind::Ident, Token::module)?)
185						} else {
186							None
187						};
188
189						Ok(ImportPath::Leaf(ImportPathLeaf { name, as_binding }))
190					}
191				}
192				(Brace, "{", _) => {
193					let brace_open = next;
194
195					let paths =
196						input.parse_seq_separated(punct![,], |input| !input.check(brace!("}")))?;
197
198					let brace_close = input.consume(brace!("}"))?;
199
200					Ok(ImportPath::Block(ImportPathBlock {
201						brace_open,
202						paths: paths.into(),
203						brace_close,
204					}))
205				}
206				(_, _, span) => Err(SpannedError {
207					message: "Expected identifier or `{`".into(),
208					source: input.source(),
209					span: Some(span),
210				}),
211			},
212			None => Err(SpannedError {
213				message: "Unexpected end of input".into(),
214				source: input.source(),
215				span: input.prev().map(|token| token.span()),
216			}),
217		}
218	}
219}
220
221impl Spanned for ImportPath {
222	fn span(&self) -> Span {
223		match self {
224			ImportPath::Namespaced(NamespacedImportPath { namespace, path }) => {
225				namespace.span().through(path.span())
226			}
227			ImportPath::Block(inner) => inner.span(),
228			ImportPath::Leaf(inner) => inner.span(),
229		}
230	}
231}
232
233impl Spanned for ImportPathBlock {
234	fn span(&self) -> Span {
235		self.brace_open.span().through(self.brace_close.span())
236	}
237}
238
239impl Spanned for ImportPathLeaf {
240	fn span(&self) -> Span {
241		if let Some(binding) = self.as_binding.as_ref() {
242			self.name.span().through(binding.span())
243		} else {
244			self.name.span()
245		}
246	}
247}