inline_rust/
lib.rs

1use std::{
2	sync::atomic::AtomicUsize,
3	time::SystemTime,
4	fs::File,
5	io::Write,
6	process::Command,
7	str::FromStr
8};
9
10use syn::{
11	parse::Parser,
12	punctuated::Punctuated,
13	spanned::Spanned,
14	Token
15};
16
17use proc_macro::TokenStream;
18use quote::ToTokens;
19use sha2::Digest;
20
21mod storage;
22use storage::{StorageDir, TargetDir};
23
24mod cargo;
25mod rustc;
26
27enum InlineRustError {
28    CargoError(String),
29    RustcError(String),
30    RuntimeError(String),
31    Other(Box<dyn std::error::Error>),
32}
33impl<E: std::error::Error + 'static> From<E> for InlineRustError {
34    fn from(err: E) -> Self {
35        InlineRustError::Other(Box::new(err))
36    }
37}
38impl Into<TokenStream> for InlineRustError {
39    fn into(self) -> TokenStream {
40        let str = match self {
41            InlineRustError::RuntimeError(str)
42            | InlineRustError::CargoError(str)
43            | InlineRustError::RustcError(str) => str,
44            InlineRustError::Other(err) => err.to_string(),
45        };
46
47        syn::Error::new(str.span(), str).to_compile_error().into()
48    }
49}
50
51fn exec_id(code: &str) -> String {
52    static INVOKE_ID: AtomicUsize = AtomicUsize::new(0);
53
54    let invoke_id = INVOKE_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
55
56    let mut sha256 = sha2::Sha256::new();
57
58    sha256.update(&invoke_id.to_ne_bytes());
59
60    if let Ok(systime) = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
61        sha256.update(&systime.as_nanos().to_ne_bytes());
62    }
63
64    sha256.update(code.as_bytes());
65
66    format!("inline_rust_{:x}", sha256.finalize())[0..32].to_string()
67}
68
69#[proc_macro]
70/// Inline the output of Rust code into your code.
71///
72/// # Examples
73///
74/// ```no_run
75/// #[macro_use] extern crate inline_rust;
76///
77/// // Compiles using cargo
78/// const CONST_HASH: &'static str = inline_rust!(
79///     r#"
80///         [dependencies]
81///         sha2 = "0.9.8"
82///     "#,
83///     {
84///         use sha2::Digest;
85///
86///         let mut sum: i32 = 0;
87///         for n in 0..30 {
88///             sum += n;
89///         }
90///
91///         format!("\"{:x}\"", sha2::Sha256::digest(&sum.to_ne_bytes()))
92///     }
93/// );
94///
95/// // Compiles using rustc
96/// const CONST_FOR_LOOP: i32 = inline_rust!({
97/// 	let mut sum: i32 = 0;
98/// 	for n in 0..30 {
99/// 		sum += n;
100/// 	}
101/// 	format!("{}", sum)
102/// });
103pub fn inline_rust(tokens: TokenStream) -> TokenStream {
104    let parser = Punctuated::<syn::Expr, Token![,]>::parse_separated_nonempty;
105    let mut parsed = match parser.parse(tokens) {
106        Ok(parsed) => parsed,
107        Err(error) => return error.into_compile_error().into(),
108    };
109
110    let code = match parsed.pop() {
111        Some(code) => code.into_value().into_token_stream().to_string(),
112        None => return TokenStream::default(),
113    };
114
115    let manifest = match parsed.pop().map(|pair| pair.into_value()) {
116        Some(manifest) => loop {
117            if let syn::Expr::Lit(ref str) = manifest {
118                if let syn::Lit::Str(ref str) = str.lit {
119                    break Some(str.value());
120                }
121            }
122            return syn::Error::new(
123                manifest.span(),
124                "Expected string literal for Cargo manifest",
125            )
126            .to_compile_error()
127            .into();
128        },
129        None => None,
130    };
131
132    let code = format!("fn inline_rust() -> impl std::fmt::Display {{\n{}\n}} fn main() {{println!(\"{{}}\", inline_rust())}}", code.trim());
133
134    let exec_id = exec_id(&code);
135    let storage_dir = storage::StorageDir::create(&exec_id).expect("Failed to create storage directory");
136
137    let result = if let Some(manifest) = manifest {
138        cargo::try_inline(storage_dir.target_dir(exec_id).expect("Failed to create Cargo target directory"), storage_dir, manifest.trim(), &code)
139    } else {
140        rustc::try_inline(storage_dir, &code)
141    };
142
143    match result {
144        Ok(tokens) => tokens,
145        Err(err) => err.into(),
146    }
147}