#![cfg_attr(feature = "nightly", feature(track_path))]
mod dependency_graph;
use dependency_graph::DependencyGraph;
use lazy_static::lazy_static;
use proc_macro::{Literal, TokenStream, TokenTree};
use regex::Regex;
use std::fs::{canonicalize, read_to_string};
use std::path::{Path, PathBuf};
fn resolve_path(path: &str) -> PathBuf {
canonicalize(&path).unwrap_or_else(|e| {
panic!(
"An error occured while trying to resolve path: {:?}. Error: {}",
path, e
)
})
}
fn track_file(_path: &Path) {
#[cfg(feature = "nightly")]
proc_macro::tracked_path::path(_path.to_string_lossy());
}
fn process_file(path: &Path, dependency_graph: &mut DependencyGraph) -> String {
let content = read_to_string(path).unwrap_or_else(|e| {
panic!(
"An error occured while trying to read file: {}. Error: {}",
path.to_string_lossy(),
e
)
});
track_file(path);
process_includes(path, content, dependency_graph)
}
fn process_includes(
source_path: &Path,
source_file_content: String,
dependency_graph: &mut DependencyGraph,
) -> String {
lazy_static! {
static ref INCLUDE_RE: Regex = Regex::new(r#"#include\s+"(?P<file>.*)""#).unwrap();
}
let mut result = source_file_content;
while let Some(captures) = INCLUDE_RE.captures(&result.clone()) {
let capture = captures.get(0).unwrap();
let include_path = resolve_path(captures.name("file").unwrap().as_str());
dependency_graph.add_edge(
source_path.to_string_lossy().to_string(),
include_path.to_string_lossy().to_string(),
);
if let Some(cycle) = dependency_graph.find_cycle() {
panic!("Circular dependency detected: {}", cycle.join(" -> "));
}
result.replace_range(
capture.start()..capture.end(),
&process_file(&include_path, dependency_graph),
);
}
result
}
fn unwrap_string_literal(lit: &Literal) -> String {
let mut repr = lit.to_string();
repr.remove(0);
repr.pop();
repr
}
#[proc_macro]
pub fn include_shader(input: TokenStream) -> TokenStream {
let tokens: Vec<_> = input.into_iter().collect();
let arg = match tokens.as_slice() {
[TokenTree::Literal(lit)] => unwrap_string_literal(lit),
_ => panic!("Takes 1 argument and the argument must be a string literal"),
};
let root_path = resolve_path(&arg);
let mut dependency_graph = DependencyGraph::new();
let result = process_file(&root_path, &mut dependency_graph);
format!("{:?}", result).parse().unwrap()
}