├── .editorconfig ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── cargo.rs ├── lib.rs ├── rustc.rs └── storage.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | charset = utf-8 7 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "block-buffer" 7 | version = "0.9.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 10 | dependencies = [ 11 | "generic-array", 12 | ] 13 | 14 | [[package]] 15 | name = "cfg-if" 16 | version = "1.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 19 | 20 | [[package]] 21 | name = "cpufeatures" 22 | version = "0.2.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" 25 | dependencies = [ 26 | "libc", 27 | ] 28 | 29 | [[package]] 30 | name = "digest" 31 | version = "0.9.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 34 | dependencies = [ 35 | "generic-array", 36 | ] 37 | 38 | [[package]] 39 | name = "generic-array" 40 | version = "0.14.4" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 43 | dependencies = [ 44 | "typenum", 45 | "version_check", 46 | ] 47 | 48 | [[package]] 49 | name = "inline-rust" 50 | version = "1.0.0" 51 | dependencies = [ 52 | "proc-macro2", 53 | "quote", 54 | "sha2", 55 | "syn", 56 | ] 57 | 58 | [[package]] 59 | name = "libc" 60 | version = "0.2.108" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" 63 | 64 | [[package]] 65 | name = "opaque-debug" 66 | version = "0.3.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 69 | 70 | [[package]] 71 | name = "proc-macro2" 72 | version = "1.0.32" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" 75 | dependencies = [ 76 | "unicode-xid", 77 | ] 78 | 79 | [[package]] 80 | name = "quote" 81 | version = "1.0.10" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 84 | dependencies = [ 85 | "proc-macro2", 86 | ] 87 | 88 | [[package]] 89 | name = "sha2" 90 | version = "0.9.8" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" 93 | dependencies = [ 94 | "block-buffer", 95 | "cfg-if", 96 | "cpufeatures", 97 | "digest", 98 | "opaque-debug", 99 | ] 100 | 101 | [[package]] 102 | name = "syn" 103 | version = "1.0.81" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" 106 | dependencies = [ 107 | "proc-macro2", 108 | "quote", 109 | "unicode-xid", 110 | ] 111 | 112 | [[package]] 113 | name = "typenum" 114 | version = "1.14.0" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" 117 | 118 | [[package]] 119 | name = "unicode-xid" 120 | version = "0.2.2" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 123 | 124 | [[package]] 125 | name = "version_check" 126 | version = "0.9.3" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 129 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "inline-rust" 3 | version = "1.0.0" 4 | edition = "2021" 5 | authors = ["William Venner "] 6 | description = "A stupid macro that compiles and executes Rust and spits the output directly into your Rust code" 7 | keywords = ["inline", "macro", "embed", "rustc", "cargo"] 8 | license = "MIT" 9 | repository = "https://github.com/WilliamVenner/inline-rust" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | syn = { version = "1", features = ["full"] } 16 | proc-macro2 = "1" 17 | quote = "1" 18 | sha2 = "0.10" 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 William Venner 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![crates.io](https://img.shields.io/crates/v/inline-rust.svg)](https://crates.io/crates/inline-rust) 2 | [![docs.rs](https://docs.rs/inline-rust/badge.svg)](https://docs.rs/inline-rust/) 3 | [![license](https://img.shields.io/crates/l/inline-rust)](https://github.com/WilliamVenner/inline-rust/blob/master/LICENSE) 4 | 5 | # inline-rust 6 | 7 | This is a stupid macro inspired by [`inline-python`](https://github.com/fusion-engineering/inline-python) that compiles and executes Rust and spits the output directly into your Rust code. 8 | 9 | There is a use case of using it to evaluate advanced "const" expressions, see the example below... if you dare. 10 | 11 | # Usage 12 | 13 | ```toml 14 | [dependencies] 15 | inline-rust = "*" 16 | ``` 17 | 18 | # Example 19 | 20 | ```rust 21 | // Compiles using cargo 22 | const CONST_HASH: &'static str = inline_rust!( 23 | r#" 24 | [dependencies] 25 | sha2 = "0.9.8" 26 | "#, 27 | { 28 | use sha2::Digest; 29 | 30 | let mut sum: i32 = 0; 31 | for n in 0..30 { 32 | sum += n; 33 | } 34 | 35 | format!("\"{:x}\"", sha2::Sha256::digest(&sum.to_ne_bytes())) 36 | } 37 | ); 38 | 39 | // Compiles using rustc 40 | const CONST_FOR_LOOP: i32 = inline_rust!({ 41 | let mut sum: i32 = 0; 42 | for n in 0..30 { 43 | sum += n; 44 | } 45 | format!("{}", sum) 46 | }); 47 | ``` -------------------------------------------------------------------------------- /src/cargo.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub(crate) fn try_inline( 4 | target_dir: TargetDir, 5 | storage_dir: StorageDir, 6 | manifest: &str, 7 | code: &str, 8 | ) -> Result { 9 | // cargo init 10 | let output = Command::new("cargo") 11 | .current_dir(&storage_dir) 12 | .arg("init") 13 | .output()?; 14 | 15 | if !output.status.success() { 16 | return Err(InlineRustError::CargoError( 17 | String::from_utf8_lossy(&output.stderr).into_owned(), 18 | )); 19 | } 20 | 21 | // Write code 22 | File::create(storage_dir.join("src").join("main.rs"))?.write_all(code.as_bytes())?; 23 | 24 | // Write manifest 25 | let cargo_manifest = storage_dir.join("Cargo.toml"); 26 | std::fs::write( 27 | &cargo_manifest, 28 | std::fs::read_to_string(&cargo_manifest)?.replace("[dependencies]", &manifest), 29 | )?; 30 | 31 | // cargo run 32 | let output = Command::new("cargo") 33 | .args(&["run", "--target-dir", &format!("{}", target_dir.display())]) 34 | .current_dir(&storage_dir) 35 | .env("RUSTFLAGS", "-Ctarget-cpu=native") 36 | .output()?; 37 | 38 | if !output.status.success() { 39 | return Err(InlineRustError::RuntimeError( 40 | String::from_utf8_lossy(&output.stderr).into_owned(), 41 | )); 42 | } 43 | 44 | Ok(TokenStream::from_str(&String::from_utf8_lossy( 45 | &output.stdout, 46 | ))?) 47 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | sync::atomic::AtomicUsize, 3 | time::SystemTime, 4 | fs::File, 5 | io::Write, 6 | process::Command, 7 | str::FromStr 8 | }; 9 | 10 | use syn::{ 11 | parse::Parser, 12 | punctuated::Punctuated, 13 | spanned::Spanned, 14 | Token 15 | }; 16 | 17 | use proc_macro::TokenStream; 18 | use quote::ToTokens; 19 | use sha2::Digest; 20 | 21 | mod storage; 22 | use storage::{StorageDir, TargetDir}; 23 | 24 | mod cargo; 25 | mod rustc; 26 | 27 | enum InlineRustError { 28 | CargoError(String), 29 | RustcError(String), 30 | RuntimeError(String), 31 | Other(Box), 32 | } 33 | impl From for InlineRustError { 34 | fn from(err: E) -> Self { 35 | InlineRustError::Other(Box::new(err)) 36 | } 37 | } 38 | impl Into 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 | 51 | fn 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 | /// }); 103 | pub fn inline_rust(tokens: TokenStream) -> TokenStream { 104 | let parser = Punctuated::::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 | } 148 | -------------------------------------------------------------------------------- /src/rustc.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub(crate) fn try_inline(storage_dir: StorageDir, code: &str) -> Result { 4 | let input_path = storage_dir.join("inline_rust.rs"); 5 | let executable_path = storage_dir.join("inline_rust"); 6 | 7 | File::create(&input_path)?.write_all(code.as_bytes())?; 8 | 9 | let output = Command::new("rustc") 10 | .args(&[ 11 | "-Ctarget-cpu=native", 12 | &format!("-o{}", executable_path.display()), 13 | &format!("{}", input_path.display()), 14 | ]) 15 | .output()?; 16 | 17 | if !output.status.success() { 18 | return Err(InlineRustError::RustcError( 19 | String::from_utf8_lossy(&output.stderr).into_owned(), 20 | )); 21 | } 22 | 23 | let output = Command::new(executable_path).output()?; 24 | if !output.status.success() { 25 | return Err(InlineRustError::RuntimeError( 26 | String::from_utf8_lossy(&output.stdout).into_owned(), 27 | )); 28 | } 29 | 30 | Ok(TokenStream::from_str(&String::from_utf8_lossy( 31 | &output.stdout, 32 | ))?) 33 | } -------------------------------------------------------------------------------- /src/storage.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | pub(crate) struct TargetDir { 4 | inner: PathBuf, 5 | exec_id: String, 6 | } 7 | impl Drop for TargetDir { 8 | fn drop(&mut self) { 9 | let _ = self.inner.join("debug").read_dir().map(|read_dir| { 10 | read_dir 11 | .filter_map(|entry| entry.ok()) 12 | .filter(|entry| { 13 | entry 14 | .file_type() 15 | .ok() 16 | .map(|file_type| file_type.is_file()) 17 | .unwrap_or(false) 18 | }) 19 | .filter(|entry| { 20 | entry 21 | .path() 22 | .file_stem() 23 | .map(|file_stem| file_stem.to_string_lossy() == self.exec_id) 24 | .unwrap_or(false) 25 | }) 26 | .for_each(|entry| { 27 | let _ = std::fs::remove_file(entry.path()); 28 | }) 29 | }); 30 | } 31 | } 32 | impl AsRef for TargetDir { 33 | fn as_ref(&self) -> &Path { 34 | self.inner.as_ref() 35 | } 36 | } 37 | impl std::ops::Deref for TargetDir { 38 | type Target = PathBuf; 39 | 40 | fn deref(&self) -> &Self::Target { 41 | &self.inner 42 | } 43 | } 44 | impl std::ops::DerefMut for TargetDir { 45 | fn deref_mut(&mut self) -> &mut Self::Target { 46 | &mut self.inner 47 | } 48 | } 49 | 50 | pub(crate) struct StorageDir(PathBuf); 51 | impl StorageDir { 52 | pub(crate) fn create(exec_id: &str) -> Result { 53 | let mut storage_dir = std::env::temp_dir(); 54 | storage_dir.push("inline_rust"); 55 | storage_dir.push(exec_id); 56 | 57 | std::fs::create_dir_all(&storage_dir)?; 58 | 59 | Ok(StorageDir(storage_dir)) 60 | } 61 | 62 | pub(crate) fn target_dir(&self, exec_id: String) -> Result { 63 | let target_dir = self.parent().unwrap().join("target"); 64 | 65 | std::fs::create_dir_all(&target_dir)?; 66 | 67 | Ok(TargetDir { 68 | inner: target_dir, 69 | exec_id 70 | }) 71 | } 72 | } 73 | impl Drop for StorageDir { 74 | fn drop(&mut self) { 75 | let _ = std::fs::remove_dir_all(&self.0); 76 | } 77 | } 78 | impl AsRef for StorageDir { 79 | fn as_ref(&self) -> &Path { 80 | self.0.as_ref() 81 | } 82 | } 83 | impl std::ops::Deref for StorageDir { 84 | type Target = PathBuf; 85 | 86 | fn deref(&self) -> &Self::Target { 87 | &self.0 88 | } 89 | } 90 | impl std::ops::DerefMut for StorageDir { 91 | fn deref_mut(&mut self) -> &mut Self::Target { 92 | &mut self.0 93 | } 94 | } --------------------------------------------------------------------------------