1#![cfg_attr(feature = "track-path", feature(track_path))]
18#![cfg_attr(feature = "relative-path", feature(proc_macro_span))]
19
20mod dependency_graph;
21
22use dependency_graph::DependencyGraph;
23use lazy_static::lazy_static;
24use proc_macro::{Literal, TokenStream, TokenTree};
25use regex::Regex;
26use std::fs::{canonicalize, read_to_string};
27use std::io;
28use std::path::{Path, PathBuf};
29
30fn resolve_path(path: &str, parent_dir_path: Option<PathBuf>) -> io::Result<PathBuf> {
31 let mut path = PathBuf::from(path);
32 if let Some(p) = parent_dir_path {
33 if !path.is_absolute() {
34 path = p.join(path);
35 }
36 }
37 canonicalize(&path)
38}
39
40fn track_file(_path: &Path) {
41 #[cfg(feature = "track-path")]
42 proc_macro::tracked_path::path(_path.to_string_lossy());
43}
44
45fn process_file(path: &Path, dependency_graph: &mut DependencyGraph) -> String {
46 let content = read_to_string(path).unwrap_or_else(|e| {
47 panic!(
48 "An error occured while trying to read file: {}. Error: {}",
49 path.to_string_lossy(),
50 e
51 )
52 });
53 track_file(path);
54 process_includes(path, content, dependency_graph)
55}
56
57fn process_includes(
58 source_path: &Path,
59 source_file_content: String,
60 dependency_graph: &mut DependencyGraph,
61) -> String {
62 lazy_static! {
63 static ref INCLUDE_RE: Regex = Regex::new(r#"#include\s+"(?P<file>.*)""#).unwrap();
64 }
65 let mut result = source_file_content;
66
67 while let Some(captures) = INCLUDE_RE.captures(&result.clone()) {
68 let capture = captures.get(0).unwrap();
69 let file_path = captures.name("file").unwrap().as_str();
70
71 #[allow(unused_assignments, unused_mut)]
72 let mut include_parent_dir_path = None;
73
74 #[cfg(feature = "relative-path")]
75 {
76 let mut path = source_path.to_path_buf();
77 path.pop();
78 include_parent_dir_path = Some(path);
79 }
80
81 let include_path = match resolve_path(&file_path, include_parent_dir_path) {
82 Ok(path) => path,
83 Err(e) => {
84 panic!(
85 r#"An error occured while trying to resolve a dependency path: "{}". Error: {}"#,
86 &file_path,
87 e
88 )
89 }
90 };
91
92 dependency_graph.add_edge(
93 source_path.to_string_lossy().to_string(),
94 include_path.to_string_lossy().to_string(),
95 );
96
97 if let Some(cycle) = dependency_graph.find_cycle() {
98 panic!("Circular dependency detected: {}", cycle.join(" -> "));
99 }
100
101 result.replace_range(
102 capture.start()..capture.end(),
103 &process_file(&include_path, dependency_graph),
104 );
105 }
106
107 result
108}
109
110fn expr_to_string(expr: &Literal) -> Option<String> {
111 let mut expr = expr.to_string();
112 if !expr.starts_with(r#"""#) || !expr.ends_with(r#"""#) {
113 return None;
114 }
115 expr.remove(0);
116 expr.pop();
117 Some(expr)
118}
119
120fn get_single_string_from_token_stream(token_stream: TokenStream) -> Option<String> {
121 let tokens: Vec<_> = token_stream.into_iter().collect();
122 match tokens.as_slice() {
123 [TokenTree::Literal(expr)] => expr_to_string(expr),
124 _ => None,
125 }
126}
127
128#[proc_macro]
182pub fn include_shader(input: TokenStream) -> TokenStream {
183 let arg = match get_single_string_from_token_stream(input) {
184 Some(string) => string,
185 None => panic!("Takes 1 argument and the argument must be a string literal"),
186 };
187
188 #[allow(unused_assignments, unused_mut)]
189 let mut call_parent_dir_path = None;
190
191 #[cfg(feature = "relative-path")]
192 {
193 let mut path = proc_macro::Span::call_site().source_file().path();
194 path.pop();
195 call_parent_dir_path = Some(path);
196 }
197
198 let root_path = match resolve_path(&arg, call_parent_dir_path) {
199 Ok(path) => path,
200 Err(e) => {
201 panic!(
202 r#"An error occured while trying to resolve root shader path: "{}". Error: {}"#,
203 &arg,
204 e
205 )
206 }
207 };
208 let mut dependency_graph = DependencyGraph::new();
209 let result = process_file(&root_path, &mut dependency_graph);
210
211 format!("{:?}", result).parse().unwrap()
212}