#![doc = include_str!("../README.md")]
extern crate proc_macro;
use proc_macro::TokenStream;
use std::hash::{BuildHasher, RandomState};
use std::io::Write;
macro_rules! build_error {
($($arg:tt)*) => {
format!("compile_error!(r#\"{}\"#);", format!($($arg)*))
.parse::<TokenStream>()
.unwrap()
};
}
#[proc_macro]
#[doc = include_str!("../README.md")]
pub fn comptime(mut code: TokenStream) -> TokenStream {
let mut out_dir = None;
let mut externs = vec![];
let mut args = std::env::args();
while let Some(arg) = args.next() {
if arg == "--extern" {
let ext = args.next().unwrap();
if !externs.contains(&ext) {
externs.push(ext)
}
} else if arg == "--out-dir" {
out_dir = args.next().map(std::path::PathBuf::from);
}
}
let Some(out_dir) = out_dir else {
return build_error!("Could not find output directory.");
};
#[rustfmt::skip]
let wrapped_code = format!(r#"
fn main() {{
println!("{{:?}}", {{ {code} }});
}}
"#);
let code_hash = RandomState::new().hash_one(&wrapped_code);
let crate_name = std::env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "unknown".to_owned());
let constime_base = std::env::temp_dir().join("constime").join(crate_name);
if !constime_base.exists() {
if let Err(why) = std::fs::create_dir_all(&constime_base) {
return build_error!("Failed to create temp directory: {why}");
}
}
let evaluator_base = constime_base.join(code_hash.to_string());
if !evaluator_base.exists() {
let mut rustc = std::process::Command::new("rustc");
rustc
.stderr(std::process::Stdio::piped())
.stdin(std::process::Stdio::piped())
.current_dir(constime_base)
.arg("-L")
.arg(out_dir)
.arg("-o")
.arg(&evaluator_base)
.arg("-");
for ext in &externs {
rustc.arg("--extern").arg(ext);
}
let Ok(mut rustc) = rustc.spawn() else {
return build_error!("Failed to spawn rustc");
};
if let Some(mut stdin) = rustc.stdin.take() {
if stdin.write_all(wrapped_code.as_bytes()).is_err() {
return build_error!("Failed to write to rustc stdin");
};
} else {
return build_error!("Failed to open stdin for rustc");
}
let Ok(output) = rustc.wait_with_output() else {
return build_error!("Failed to wait for rustc");
};
if !output.status.success() {
code.extend(build_error!("{}", String::from_utf8_lossy(&output.stderr)));
return code;
}
}
let out = std::process::Command::new(&evaluator_base)
.stdout(std::process::Stdio::piped())
.output();
match out {
Err(why) => return build_error!("Failed to execute code: {why}"),
Ok(out) => {
let out = String::from_utf8_lossy(&out.stdout);
let Ok(out) = out.parse() else {
return build_error!("Failed to parse output into a TokenStream");
};
return out;
}
}
}