extern crate proc_macro;
use c2rust_transpile::RustEdition;
use proc_macro::TokenStream;
use std::path::{Path, PathBuf};
use anyhow::Context;
use rand::Rng;
use c2rust_transpile::{transpile_single_to_buffer, CompileCmd, ReplaceMode, TranspilerConfig};
#[proc_macro]
pub fn c(input: TokenStream) -> TokenStream {
let c_code = input.to_string();
assert!(
c_code.starts_with("r#\""),
"C code must be passed as rust raw string literal starting with r#\""
);
assert!(
c_code.ends_with("\"#"),
"C code must be passed as rust raw string literal"
);
let transplied_code =
transpile_code(&c_code[3..c_code.len() - 2]).expect("transpilation failure");
transplied_code.parse().expect("rust parse output error")
}
fn temp_build_dir() -> std::io::Result<PathBuf> {
let tmp = std::env::temp_dir();
let random: u64 = rand::rng().random();
let build_path = tmp.join("include-c2rust").join(random.to_string());
std::fs::create_dir_all(&build_path)?;
Ok(build_path)
}
fn transpile_code(c_code: &str) -> anyhow::Result<String> {
let build_dir = temp_build_dir().context("unable to make tmp dir")?;
eprintln!("output dir: {}", build_dir.display());
let c_file_path = build_dir.join("code.c");
std::fs::write(&c_file_path, c_code).expect("Unable to write c code to temporary file.");
let transpile_config = make_transpile_config();
let cc_db = compile_commands(&build_dir, &c_file_path);
eprintln!("ccdb {cc_db:?}");
static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
let _guard = LOCK.lock().unwrap();
let (out, _, _) = transpile_single_to_buffer(&transpile_config, c_file_path, &cc_db, &[])
.map_err(|()| std::io::Error::other("transpilation failure"))?;
Ok(out)
}
fn compile_commands(build_dir: &Path, source_file: &Path) -> PathBuf {
let cc_db_file = build_dir.join("compile_commands.json");
let absolute_path = std::fs::canonicalize(source_file)
.unwrap_or_else(|_| panic!("Could not canonicalize {}", source_file.display()));
let compile_commands = [CompileCmd {
directory: PathBuf::from("."),
file: absolute_path.clone(),
arguments: vec![
"clang".to_string(),
absolute_path.to_str().unwrap().to_owned(),
],
command: None,
output: None,
}];
let json_content = serde_json::to_string(&compile_commands).unwrap();
std::fs::write(&cc_db_file, &json_content)
.expect("Failed to write to temporary compile_commands.json");
cc_db_file
}
fn make_transpile_config() -> TranspilerConfig {
TranspilerConfig {
verbose: true,
incremental_relooper: true,
fail_on_multiple: true,
use_c_multiple_info: true,
simplify_structures: true,
panic_on_translator_failure: true,
emit_modules: true, fail_on_error: true,
replace_unsupported_decls: ReplaceMode::None, translate_valist: false, overwrite_existing: true,
reduce_type_annotations: true,
reorganize_definitions: false,
emit_no_std: false,
output_dir: None, disable_refactoring: false,
preserve_unused_functions: true,
dump_untyped_context: false,
dump_typed_context: false,
pretty_typed_context: false,
dump_function_cfgs: false,
json_function_cfgs: false,
dump_cfg_liveness: false,
dump_structures: false,
debug_ast_exporter: false,
filter: None,
debug_relooper_labels: false,
prefix_function_names: None,
translate_asm: false,
use_c_loop_info: false,
enabled_warnings: std::collections::HashSet::new(),
translate_const_macros: false,
translate_fn_macros: false,
log_level: None,
max_rust_edition: RustEdition::Edition2024,
emit_build_files: false,
binaries: vec![],
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compile() {
let rust_translation = transpile_code("int foo() {return 1;}").unwrap();
insta::assert_snapshot!(rust_translation, @r###"
#[unsafe(no_mangle)]
pub unsafe extern "C" fn foo() -> core::ffi::c_int {
unsafe {
return 1 as core::ffi::c_int;
}
}
"###);
}
#[test]
fn test_compile_access_ptr() {
let rust_translation = transpile_code("int access_ptr(int* p) { return *p; }").unwrap();
insta::assert_snapshot!(rust_translation, @r###"
#[unsafe(no_mangle)]
pub unsafe extern "C" fn access_ptr(mut p: *mut core::ffi::c_int) -> core::ffi::c_int {
unsafe {
return *p;
}
}
"###);
}
}