use std::fs;
use std::process;
use clap::Parser as _;
use trueno_ptx_debug::bugs::BugRegistry;
use trueno_ptx_debug::cli::{
exit_code_for_parse_error, version_string, AnalyzeArgs, Cli, Command, GenFkrArgs,
};
use trueno_ptx_debug::falsification::FalsificationRegistry;
use trueno_ptx_debug::output::{generate_fkr_tests, generate_html_report, AnalysisResult};
use trueno_ptx_debug::parser::Parser;
fn main() {
let cli = match Cli::try_parse() {
Ok(cli) => cli,
Err(err) => {
let _ = err.print();
process::exit(exit_code_for_parse_error(&err));
}
};
let result = match cli.command {
Command::Analyze(args) => cmd_analyze(args),
Command::GenFkr(args) => cmd_gen_fkr(args),
Command::Version => {
print!("{}", version_string());
Ok(())
}
};
if let Err(e) = result {
eprintln!("Error: {}", e);
process::exit(1);
}
}
fn print_json_report(
result: &AnalysisResult,
report: &trueno_ptx_debug::falsification::FalsificationReport,
) {
println!("{{");
println!(" \"module\": \"{}\",", result.module_name);
println!(" \"score\": {:.1},", result.falsification_score);
println!(" \"confidence\": {:.2},", result.confidence);
println!(" \"earned_points\": {},", report.earned_points);
println!(" \"total_points\": {},", report.total_points);
println!(
" \"critical_bugs_absent\": {}",
report.critical_bugs_absent()
);
println!("}}");
}
fn print_text_report(
result: &AnalysisResult,
report: &trueno_ptx_debug::falsification::FalsificationReport,
) {
println!("PTX Analysis Report: {}", result.module_name);
println!("=========================================");
println!("Score: {:.1}/100", result.falsification_score);
println!("Confidence: {:.1}%", result.confidence * 100.0);
println!("Points: {}/{}", report.earned_points, report.total_points);
println!();
let failed = report.failed_tests();
if failed.is_empty() {
println!("All tests passed!");
} else {
println!("Failed tests ({}):", failed.len());
for (id, category, desc, _result) in failed {
println!(" {} [{}]: {}", id, category, desc);
}
}
}
fn exit_for_score(
report: &trueno_ptx_debug::falsification::FalsificationReport,
score: f64,
min_score: f64,
) {
if report.has_critical_bugs() {
process::exit(3);
} else if score < min_score {
process::exit(2);
} else if score < 90.0 {
process::exit(1);
}
}
fn cmd_analyze(opts: AnalyzeArgs) -> Result<(), String> {
let result = analyze_ptx_file(&opts.file)?;
if opts.json {
print_json_report(&result, &result.falsification_report);
} else {
print_text_report(&result, &result.falsification_report);
}
if let Some(html_path) = opts.html {
let html = generate_html_report(&result);
fs::write(&html_path, html).map_err(|e| format!("Failed to write {}: {}", html_path, e))?;
println!("\nHTML report written to: {}", html_path);
}
exit_for_score(
&result.falsification_report,
result.falsification_score,
opts.min_score,
);
Ok(())
}
fn analyze_ptx_file(file_path: &str) -> Result<AnalysisResult, String> {
let ptx_source = fs::read_to_string(file_path)
.map_err(|e| format!("Failed to read {}: {}", file_path, e))?;
let mut parser = Parser::new(&ptx_source).map_err(|e| format!("Parse error: {}", e))?;
let module = parser.parse().map_err(|e| format!("Parse error: {}", e))?;
let module_name = std::path::Path::new(file_path)
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("unknown")
.to_string();
let registry = FalsificationRegistry::new();
let report = registry.evaluate(&module);
let bugs = BugRegistry::new();
Ok(AnalysisResult::new(&module_name, report, bugs))
}
fn write_or_print(content: &str, output_path: Option<String>, label: &str) -> Result<(), String> {
match output_path {
Some(path) => {
fs::write(&path, content).map_err(|e| format!("Failed to write {}: {}", path, e))?;
println!("{} written to: {}", label, path);
}
None => println!("{}", content),
}
Ok(())
}
fn cmd_gen_fkr(opts: GenFkrArgs) -> Result<(), String> {
let result = analyze_ptx_file(&opts.file)?;
let fkr_tests = generate_fkr_tests(&result);
write_or_print(&fkr_tests, opts.output, "FKR tests")
}