1extern crate proc_macro;
2
3use c2rust_transpile::RustEdition;
4use proc_macro::TokenStream;
5use std::path::{Path, PathBuf};
6
7use anyhow::Context;
8use rand::Rng;
9
10use c2rust_transpile::{transpile_single_to_buffer, CompileCmd, ReplaceMode, TranspilerConfig};
11
12#[proc_macro]
13pub fn c(input: TokenStream) -> TokenStream {
14 let c_code = input.to_string();
15
16 assert!(
20 c_code.starts_with("r#\""),
21 "C code must be passed as rust raw string literal starting with r#\""
22 );
23 assert!(
24 c_code.ends_with("\"#"),
25 "C code must be passed as rust raw string literal"
26 );
27
28 let transplied_code =
29 transpile_code(&c_code[3..c_code.len() - 2]).expect("transpilation failure");
30
31 transplied_code.parse().expect("rust parse output error")
32}
33
34fn temp_build_dir() -> std::io::Result<PathBuf> {
35 let tmp = std::env::temp_dir();
36 let random: u64 = rand::rng().random();
37
38 let build_path = tmp.join("include-c2rust").join(random.to_string());
39 std::fs::create_dir_all(&build_path)?;
40 Ok(build_path)
41}
42
43fn transpile_code(c_code: &str) -> anyhow::Result<String> {
44 let build_dir = temp_build_dir().context("unable to make tmp dir")?;
45 eprintln!("output dir: {}", build_dir.display());
46
47 let c_file_path = build_dir.join("code.c");
48 std::fs::write(&c_file_path, c_code).expect("Unable to write c code to temporary file.");
49 let transpile_config = make_transpile_config();
50 let cc_db = compile_commands(&build_dir, &c_file_path);
51 eprintln!("ccdb {cc_db:?}");
52
53 static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
54 let _guard = LOCK.lock().unwrap();
55 let (out, _, _) = transpile_single_to_buffer(&transpile_config, c_file_path, &cc_db, &[])
65 .map_err(|()| std::io::Error::other("transpilation failure"))?;
66
67 Ok(out)
68}
69
70fn compile_commands(build_dir: &Path, source_file: &Path) -> PathBuf {
71 let cc_db_file = build_dir.join("compile_commands.json");
72
73 let absolute_path = std::fs::canonicalize(source_file)
74 .unwrap_or_else(|_| panic!("Could not canonicalize {}", source_file.display()));
75
76 let compile_commands = [CompileCmd {
77 directory: PathBuf::from("."),
78 file: absolute_path.clone(),
79 arguments: vec![
80 "clang".to_string(),
81 absolute_path.to_str().unwrap().to_owned(),
82 ],
83 command: None,
84 output: None,
85 }];
86
87 let json_content = serde_json::to_string(&compile_commands).unwrap();
88 std::fs::write(&cc_db_file, &json_content)
89 .expect("Failed to write to temporary compile_commands.json");
90
91 cc_db_file
92}
93
94fn make_transpile_config() -> TranspilerConfig {
95 TranspilerConfig {
98 verbose: true,
99 incremental_relooper: true,
100 fail_on_multiple: true,
101 use_c_multiple_info: true,
102 simplify_structures: true,
103 panic_on_translator_failure: true,
104 emit_modules: true, fail_on_error: true,
106 replace_unsupported_decls: ReplaceMode::None, translate_valist: false, overwrite_existing: true,
109 reduce_type_annotations: true,
110 reorganize_definitions: false,
111 emit_no_std: false,
112 output_dir: None, disable_refactoring: false,
114 preserve_unused_functions: true,
115 dump_untyped_context: false,
117 dump_typed_context: false,
118 pretty_typed_context: false,
119 dump_function_cfgs: false,
120 json_function_cfgs: false,
121 dump_cfg_liveness: false,
122 dump_structures: false,
123 debug_ast_exporter: false,
124 filter: None,
125 debug_relooper_labels: false,
126 prefix_function_names: None,
127 translate_asm: false,
128 use_c_loop_info: false,
129 enabled_warnings: std::collections::HashSet::new(),
130 translate_const_macros: false,
131 translate_fn_macros: false,
132 log_level: None,
133 max_rust_edition: RustEdition::Edition2024,
134 emit_build_files: false,
135 binaries: vec![],
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn test_compile() {
145 let rust_translation = transpile_code("int foo() {return 1;}").unwrap();
146
147 insta::assert_snapshot!(rust_translation, @r###"
148 #[unsafe(no_mangle)]
149 pub unsafe extern "C" fn foo() -> core::ffi::c_int {
150 unsafe {
151 return 1 as core::ffi::c_int;
152 }
153 }
154 "###);
155 }
156
157 #[test]
158 fn test_compile_access_ptr() {
159 let rust_translation = transpile_code("int access_ptr(int* p) { return *p; }").unwrap();
160
161 insta::assert_snapshot!(rust_translation, @r###"
162 #[unsafe(no_mangle)]
163 pub unsafe extern "C" fn access_ptr(mut p: *mut core::ffi::c_int) -> core::ffi::c_int {
164 unsafe {
165 return *p;
166 }
167 }
168 "###);
169 }
170}