naga_oil/compose/
parse_imports.rs

1use indexmap::IndexMap;
2
3use super::{
4    tokenizer::{Token, Tokenizer},
5    Composer, ImportDefWithOffset, ImportDefinition,
6};
7
8pub fn parse_imports<'a>(
9    input: &'a str,
10    declared_imports: &mut IndexMap<String, Vec<String>>,
11) -> Result<(), (&'a str, usize)> {
12    let mut tokens = Tokenizer::new(input, false).peekable();
13
14    match tokens.next() {
15        Some(Token::Other('#', _)) => (),
16        Some(other) => return Err(("expected `#import`", other.pos())),
17        None => return Err(("expected #import", input.len())),
18    };
19    match tokens.next() {
20        Some(Token::Identifier("import", _)) => (),
21        Some(other) => return Err(("expected `#import`", other.pos())),
22        None => return Err(("expected `#import`", input.len())),
23    };
24
25    let mut stack = Vec::default();
26    let mut current = String::default();
27    let mut as_name = None;
28    let mut is_deprecated_itemlist = false;
29
30    loop {
31        match tokens.peek() {
32            Some(Token::Identifier(ident, _)) => {
33                current.push_str(ident);
34                tokens.next();
35
36                if tokens.peek().and_then(Token::identifier) == Some("as") {
37                    let pos = tokens.next().unwrap().pos();
38                    let Some(Token::Identifier(name, _)) = tokens.next() else {
39                        return Err(("expected identifier after `as`", pos));
40                    };
41
42                    as_name = Some(name);
43                }
44
45                // support deprecated #import mod item
46                if let Some(Token::Identifier(..)) = tokens.peek() {
47                    #[cfg(not(feature = "allow_deprecated"))]
48                    tracing::warn!("item list imports are deprecated, please use `rust::style::item_imports` (or use feature `allow_deprecated`)`\n| {}", input);
49
50                    is_deprecated_itemlist = true;
51                    stack.push(format!("{}::", current));
52                    current = String::default();
53                    as_name = None;
54                }
55
56                continue;
57            }
58            Some(Token::Other('{', pos)) => {
59                if !current.ends_with("::") {
60                    return Err(("open brace must follow `::`", *pos));
61                }
62                stack.push(current);
63                current = String::default();
64                as_name = None;
65            }
66            Some(Token::Other(',', _))
67            | Some(Token::Other('}', _))
68            | Some(Token::Other('\n', _))
69            | None => {
70                if !current.is_empty() {
71                    let used_name = as_name.map(ToString::to_string).unwrap_or_else(|| {
72                        current
73                            .rsplit_once("::")
74                            .map(|(_, name)| name.to_owned())
75                            .unwrap_or(current.clone())
76                    });
77                    declared_imports.entry(used_name).or_default().push(format!(
78                        "{}{}",
79                        stack.join(""),
80                        current
81                    ));
82                    current = String::default();
83                    as_name = None;
84                }
85
86                if let Some(Token::Other('}', pos)) = tokens.peek() {
87                    if stack.pop().is_none() {
88                        return Err(("close brace without open", *pos));
89                    }
90                }
91
92                if tokens.peek().is_none() {
93                    break;
94                }
95            }
96            Some(Token::Other(';', _)) => {
97                tokens.next();
98                if let Some(token) = tokens.peek() {
99                    return Err(("unexpected token after ';'", token.pos()));
100                }
101            }
102            Some(Token::Other(_, pos)) => return Err(("unexpected token", *pos)),
103            Some(Token::Whitespace(..)) => unreachable!(),
104        }
105
106        tokens.next();
107    }
108
109    if !(stack.is_empty() || is_deprecated_itemlist && stack.len() == 1) {
110        return Err(("missing close brace", input.len()));
111    }
112
113    Ok(())
114}
115
116pub fn substitute_identifiers(
117    input: &str,
118    offset: usize,
119    declared_imports: &IndexMap<String, Vec<String>>,
120    used_imports: &mut IndexMap<String, ImportDefWithOffset>,
121    allow_ambiguous: bool,
122) -> Result<String, usize> {
123    let tokens = Tokenizer::new(input, true);
124    let mut output = String::with_capacity(input.len());
125    let mut in_substitution_position = true;
126
127    for token in tokens {
128        match token {
129            Token::Identifier(ident, token_pos) => {
130                if in_substitution_position {
131                    let (first, residual) = ident.split_once("::").unwrap_or((ident, ""));
132                    let full_paths = declared_imports
133                        .get(first)
134                        .cloned()
135                        .unwrap_or(vec![first.to_owned()]);
136
137                    if !allow_ambiguous && full_paths.len() > 1 {
138                        return Err(offset + token_pos);
139                    }
140
141                    for mut full_path in full_paths {
142                        if !residual.is_empty() {
143                            full_path.push_str("::");
144                            full_path.push_str(residual);
145                        }
146
147                        if let Some((module, item)) = full_path.rsplit_once("::") {
148                            used_imports
149                                .entry(module.to_owned())
150                                .or_insert_with(|| ImportDefWithOffset {
151                                    definition: ImportDefinition {
152                                        import: module.to_owned(),
153                                        ..Default::default()
154                                    },
155                                    offset: offset + token_pos,
156                                })
157                                .definition
158                                .items
159                                .push(item.to_owned());
160                            output.push_str(item);
161                            output.push_str(&Composer::decorate(module));
162                        } else if full_path.find('"').is_some() {
163                            // we don't want to replace local variables that shadow quoted module imports with the
164                            // quoted name as that won't compile.
165                            // since quoted items always refer to modules, we can just emit the original ident
166                            // in this case
167                            output.push_str(ident);
168                        } else {
169                            // if there are no quotes we do the replacement. this means that individually imported
170                            // items can be used, and any shadowing local variables get harmlessly renamed.
171                            // TODO: it can lead to weird errors, but such is life
172                            output.push_str(&full_path);
173                        }
174                    }
175                } else {
176                    output.push_str(ident);
177                }
178            }
179            Token::Other(other, _) => {
180                output.push(other);
181                if other == '.' || other == '@' {
182                    in_substitution_position = false;
183                    continue;
184                }
185            }
186            Token::Whitespace(ws, _) => output.push_str(ws),
187        }
188
189        in_substitution_position = true;
190    }
191
192    Ok(output)
193}
194
195#[cfg(test)]
196fn test_parse(input: &str) -> Result<IndexMap<String, Vec<String>>, (&str, usize)> {
197    let mut declared_imports = IndexMap::default();
198    parse_imports(input, &mut declared_imports)?;
199    Ok(declared_imports)
200}
201
202#[test]
203fn import_tokens() {
204    let input = r"
205        #import a::b
206    ";
207    assert_eq!(
208        test_parse(input),
209        Ok(IndexMap::from_iter([(
210            "b".to_owned(),
211            vec!("a::b".to_owned())
212        )]))
213    );
214
215    let input = r"
216        #import a::{b, c}
217    ";
218    assert_eq!(
219        test_parse(input),
220        Ok(IndexMap::from_iter([
221            ("b".to_owned(), vec!("a::b".to_owned())),
222            ("c".to_owned(), vec!("a::c".to_owned())),
223        ]))
224    );
225
226    let input = r"
227        #import a::{b as d, c}
228    ";
229    assert_eq!(
230        test_parse(input),
231        Ok(IndexMap::from_iter([
232            ("d".to_owned(), vec!("a::b".to_owned())),
233            ("c".to_owned(), vec!("a::c".to_owned())),
234        ]))
235    );
236
237    let input = r"
238        #import a::{b::{c, d}, e}
239    ";
240    assert_eq!(
241        test_parse(input),
242        Ok(IndexMap::from_iter([
243            ("c".to_owned(), vec!("a::b::c".to_owned())),
244            ("d".to_owned(), vec!("a::b::d".to_owned())),
245            ("e".to_owned(), vec!("a::e".to_owned())),
246        ]))
247    );
248
249    let input = r"
250        #import a::b::{c, d}, e
251    ";
252    assert_eq!(
253        test_parse(input),
254        Ok(IndexMap::from_iter([
255            ("c".to_owned(), vec!("a::b::c".to_owned())),
256            ("d".to_owned(), vec!("a::b::d".to_owned())),
257            ("e".to_owned(), vec!("e".to_owned())),
258        ]))
259    );
260
261    let input = r"
262        #import a, b
263    ";
264    assert_eq!(
265        test_parse(input),
266        Ok(IndexMap::from_iter([
267            ("a".to_owned(), vec!("a".to_owned())),
268            ("b".to_owned(), vec!("b".to_owned())),
269        ]))
270    );
271
272    let input = r"
273        #import a::b c, d
274    ";
275    assert_eq!(
276        test_parse(input),
277        Ok(IndexMap::from_iter([
278            ("c".to_owned(), vec!("a::b::c".to_owned())),
279            ("d".to_owned(), vec!("a::b::d".to_owned())),
280        ]))
281    );
282
283    let input = r"
284        #import a::b c
285    ";
286    assert_eq!(
287        test_parse(input),
288        Ok(IndexMap::from_iter([(
289            "c".to_owned(),
290            vec!("a::b::c".to_owned())
291        ),]))
292    );
293
294    let input = r"
295        #import a::b::{c::{d, e}, f, g::{h as i, j}}
296    ";
297    assert_eq!(
298        test_parse(input),
299        Ok(IndexMap::from_iter([
300            ("d".to_owned(), vec!("a::b::c::d".to_owned())),
301            ("e".to_owned(), vec!("a::b::c::e".to_owned())),
302            ("f".to_owned(), vec!("a::b::f".to_owned())),
303            ("i".to_owned(), vec!("a::b::g::h".to_owned())),
304            ("j".to_owned(), vec!("a::b::g::j".to_owned())),
305        ]))
306    );
307
308    let input = r"
309        #import a::b::{
310            c::{d, e}, 
311            f, 
312            g::{
313                h as i, 
314                j::k::l as m,
315            }
316        }
317    ";
318    assert_eq!(
319        test_parse(input),
320        Ok(IndexMap::from_iter([
321            ("d".to_owned(), vec!("a::b::c::d".to_owned())),
322            ("e".to_owned(), vec!("a::b::c::e".to_owned())),
323            ("f".to_owned(), vec!("a::b::f".to_owned())),
324            ("i".to_owned(), vec!("a::b::g::h".to_owned())),
325            ("m".to_owned(), vec!("a::b::g::j::k::l".to_owned())),
326        ]))
327    );
328
329    let input = r#"
330        #import "path//with\ all sorts of .stuff"::{a, b}
331    "#;
332    assert_eq!(
333        test_parse(input),
334        Ok(IndexMap::from_iter([
335            (
336                "a".to_owned(),
337                vec!(r#""path//with\ all sorts of .stuff"::a"#.to_owned())
338            ),
339            (
340                "b".to_owned(),
341                vec!(r#""path//with\ all sorts of .stuff"::b"#.to_owned())
342            ),
343        ]))
344    );
345
346    let input = r"
347        #import a::b::{
348    ";
349    assert!(test_parse(input).is_err());
350
351    let input = r"
352        #import a::b::{{c}
353    ";
354    assert!(test_parse(input).is_err());
355
356    let input = r"
357        #import a::b::{c}}
358    ";
359    assert!(test_parse(input).is_err());
360
361    let input = r"
362        #import a::b{{c,d}}
363    ";
364    assert!(test_parse(input).is_err());
365
366    let input = r"
367        #import a:b
368    ";
369    assert!(test_parse(input).is_err());
370}