use clap::{Parser, ValueEnum};
use log::LevelFilter;
use regex::Regex;
use std::{ffi::OsStr, fs, path::PathBuf};
use c2rust_transpile::{Diagnostic, ReplaceMode, TranspilerConfig};
#[derive(Debug, Parser)]
#[clap(
name = "transpile",
author = "- The C2Rust Project Developers <c2rust@immunant.com>
- Eric Mertens <emertens@galois.com>
- Alec Theriault <atheriault@galois.com>",
version,
about = "Translate C code to equivalent Rust code",
long_about = None,
trailing_var_arg = true)]
struct Args {
#[clap(long)]
prefix_function_names: Option<String>,
#[clap(long)]
dump_untyped_clang_ast: bool,
#[clap(long)]
dump_typed_clang_ast: bool,
#[clap(long)]
pretty_typed_clang_ast: bool,
#[clap(long)]
debug_ast_exporter: bool,
#[clap(long)]
emit_c_decl_map: bool,
#[clap(short = 'v', long)]
verbose: bool,
#[clap(long, value_enum, default_value_t)]
translate_const_macros: TranslateMacros,
#[clap(long, value_enum, default_value_t)]
translate_fn_macros: TranslateMacros,
#[clap(long)]
no_incremental_relooper: bool,
#[clap(long)]
no_simplify_structures: bool,
#[clap(long)]
ignore_c_loop_info: bool,
#[clap(long)]
ignore_c_multiple_info: bool,
#[clap(long = "ddump-function-cfgs")]
dump_function_cfgs: bool,
#[clap(long)]
json_function_cfgs: bool,
#[clap(long = "ddump-cfgs-liveness", requires = "dump-function-cfgs")]
dump_cfgs_liveness: bool,
#[clap(long = "ddump-structures")]
dump_structures: bool,
#[clap(long = "ddebug-labels")]
debug_labels: bool,
#[clap(parse(from_os_str), multiple_values = true)]
compile_commands: Vec<PathBuf>,
#[clap(long, value_enum, default_value_t = InvalidCodes::CompileError)]
invalid_code: InvalidCodes,
#[clap(long)]
emit_modules: bool,
#[clap(short = 'e', long)]
emit_build_files: bool,
#[clap(long)]
c2rust_dir: Option<PathBuf>,
#[clap(short = 'o', long, value_name = "DIR")]
output_dir: Option<PathBuf>,
#[clap(short = 'f', long)]
filter: Option<Regex>,
#[clap(long)]
fail_on_error: bool,
#[clap(short = 'b', long = "binary", multiple = true, number_of_values = 1)]
binary: Option<Vec<String>>,
#[clap(long)]
overwrite_existing: bool,
#[clap(long)]
reduce_type_annotations: bool,
#[clap(short = 'r', long)]
reorganize_definitions: bool,
#[clap(multiple = true, last(true))]
extra_clang_args: Vec<String>,
#[clap(short = 'W')]
warn: Option<Diagnostic>,
#[clap(long)]
emit_no_std: bool,
#[clap(long)]
disable_rustfmt: bool,
#[clap(long)]
disable_refactoring: bool,
#[clap(long)]
preserve_unused_functions: bool,
#[clap(long, default_value_t = LevelFilter::Warn)]
log_level: LevelFilter,
#[clap(long)]
fail_on_multiple: bool,
#[clap(long, short = 'x')]
cross_checks: bool,
#[clap(long, short = 'X', multiple = true)]
cross_check_config: Vec<String>,
#[clap(long, value_enum, default_value_t)]
cross_check_backend: CrossCheckBackend,
}
#[derive(Default, Debug, PartialEq, Eq, ValueEnum, Clone)]
pub enum TranslateMacros {
None,
#[default]
Conservative,
Experimental,
}
impl From<TranslateMacros> for c2rust_transpile::TranslateMacros {
fn from(this: TranslateMacros) -> Self {
match this {
TranslateMacros::None => c2rust_transpile::TranslateMacros::None,
TranslateMacros::Conservative => c2rust_transpile::TranslateMacros::Conservative,
TranslateMacros::Experimental => c2rust_transpile::TranslateMacros::Experimental,
}
}
}
#[derive(Default, Debug, ValueEnum, Clone)]
pub enum CrossCheckBackend {
DynamicDlsym,
#[default]
ZstdLogging,
LibclevrbufSys,
LibfakechecksSys,
}
impl From<CrossCheckBackend> for String {
fn from(x: CrossCheckBackend) -> String {
let s = match x {
CrossCheckBackend::DynamicDlsym => "dynamic-dlsym",
CrossCheckBackend::ZstdLogging => "zstd-logging",
CrossCheckBackend::LibclevrbufSys => "libclevrbuf-sys",
CrossCheckBackend::LibfakechecksSys => "libfakechecks-sys",
};
s.to_string()
}
}
#[derive(Debug, PartialEq, Eq, ValueEnum, Clone)]
#[clap(rename_all = "snake_case")]
enum InvalidCodes {
Panic,
CompileError,
}
fn main() {
let args = Args::parse();
let mut tcfg = TranspilerConfig {
dump_untyped_context: args.dump_untyped_clang_ast,
dump_typed_context: args.dump_typed_clang_ast,
pretty_typed_context: args.pretty_typed_clang_ast,
dump_function_cfgs: args.dump_function_cfgs,
json_function_cfgs: args.json_function_cfgs,
dump_cfg_liveness: args.dump_cfgs_liveness,
dump_structures: args.dump_structures,
debug_ast_exporter: args.debug_ast_exporter,
emit_c_decl_map: args.emit_c_decl_map,
verbose: args.verbose,
incremental_relooper: !args.no_incremental_relooper,
fail_on_error: args.fail_on_error,
fail_on_multiple: args.fail_on_multiple,
filter: args.filter,
debug_relooper_labels: args.debug_labels,
cross_checks: args.cross_checks,
cross_check_backend: args.cross_check_backend.into(),
cross_check_configs: args.cross_check_config,
prefix_function_names: args.prefix_function_names,
translate_asm: true,
translate_valist: true,
translate_const_macros: args.translate_const_macros.into(),
translate_fn_macros: args.translate_fn_macros.into(),
disable_rustfmt: args.disable_rustfmt,
disable_refactoring: args.disable_refactoring,
preserve_unused_functions: args.preserve_unused_functions,
use_c_loop_info: !args.ignore_c_loop_info,
use_c_multiple_info: !args.ignore_c_multiple_info,
simplify_structures: !args.no_simplify_structures,
overwrite_existing: args.overwrite_existing,
reduce_type_annotations: args.reduce_type_annotations,
reorganize_definitions: args.reorganize_definitions,
emit_modules: args.emit_modules,
emit_build_files: args.emit_build_files,
c2rust_dir: args.c2rust_dir,
output_dir: args.output_dir,
binaries: args.binary.unwrap_or_default(),
panic_on_translator_failure: args.invalid_code == InvalidCodes::Panic,
replace_unsupported_decls: ReplaceMode::Extern,
emit_no_std: args.emit_no_std,
enabled_warnings: args.warn.into_iter().collect(),
log_level: args.log_level,
};
if !tcfg.binaries.is_empty() {
tcfg.emit_build_files = true
};
if tcfg.emit_build_files {
tcfg.emit_modules = true
};
let mut temp_compile_commands_dir = None;
let compile_commands = if args
.compile_commands
.iter()
.any(|path| path.extension() == Some(OsStr::new("json")))
{
if args.compile_commands.len() != 1 {
panic!("Compile commands JSON and multiple sources provided.
Exactly one compile_commands.json file should be provided, or a list of source files, but not both.");
}
let cc_json_path = &args.compile_commands[0];
match fs::canonicalize(cc_json_path) {
Ok(canonical_path) => canonical_path,
Err(e) => panic!(
"Failed to canonicalize path {}: {:?}",
cc_json_path.display(),
e
),
}
} else {
let (temp_dir, temp_path) =
c2rust_transpile::create_temp_compile_commands(&args.compile_commands);
temp_compile_commands_dir = Some(temp_dir);
temp_path
};
let extra_args = args
.extra_clang_args
.iter()
.map(AsRef::as_ref)
.collect::<Vec<_>>();
c2rust_transpile::transpile(tcfg, &compile_commands, &extra_args);
if let Some(temp) = temp_compile_commands_dir {
temp.close()
.expect("Failed to remove temporary compile_commands.json");
}
}