feat: add C code generation backend and build command to CLI
Add C backend to nxc-frontend: - Implement CEmitter with HIR-to-C translation - Add emit_c() function for code generation - Support function prototypes, statements, expressions, and control flow - Generate C11-compatible code with proper type mapping - Add main() wrapper generation for entry points Extend driver crate with build pipeline: - Add emit_c_from_frontend() to generate C source from HIR - Add write_c_file() to write generated
This commit is contained in:
@@ -18,3 +18,4 @@ path = "src/bin/nexacore.rs"
|
||||
|
||||
[dependencies]
|
||||
nxc-driver = { path = "../nxc-driver" }
|
||||
nxc-frontend = { path = "../nxc-frontend" }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::process::ExitCode;
|
||||
|
||||
@@ -20,7 +21,7 @@ fn run() -> Result<(), String> {
|
||||
};
|
||||
|
||||
match command.as_str() {
|
||||
"check" | "build" => {
|
||||
"check" => {
|
||||
let Some(path) = args.next() else {
|
||||
return Err(format!("usage: {} {command} <file.nx>", executable_name()));
|
||||
};
|
||||
@@ -44,6 +45,44 @@ fn run() -> Result<(), String> {
|
||||
println!("structs: {}", summary.structs);
|
||||
Ok(())
|
||||
}
|
||||
"emit-c" => {
|
||||
let Some(path) = args.next() else {
|
||||
return Err(format!("usage: {} emit-c <file.nx> -o <out.c>", executable_name()));
|
||||
};
|
||||
let out_path = parse_output_path(args.collect(), "emit-c", "out.c")?;
|
||||
let output = nxc_driver::check_file(Path::new(&path)).map_err(format_driver_error)?;
|
||||
if output.has_errors() {
|
||||
eprintln!("{}", output.render_diagnostics());
|
||||
return Err(format!(
|
||||
"emit-c failed with {} diagnostic(s)",
|
||||
output.diagnostics.len()
|
||||
));
|
||||
}
|
||||
|
||||
let c_source = nxc_driver::emit_c_from_frontend(&output)
|
||||
.map_err(|diagnostics| render_backend_diagnostics(&output, diagnostics))?;
|
||||
fs::write(&out_path, c_source).map_err(|err| format!("io error: {err}"))?;
|
||||
println!("wrote {}", out_path.display());
|
||||
Ok(())
|
||||
}
|
||||
"build" => {
|
||||
let Some(path) = args.next() else {
|
||||
return Err(format!("usage: {} build <file.nx> -o <binary>", executable_name()));
|
||||
};
|
||||
let out_path = parse_output_path(args.collect(), "build", "a.out")?;
|
||||
let output = nxc_driver::check_file(Path::new(&path)).map_err(format_driver_error)?;
|
||||
if output.has_errors() {
|
||||
eprintln!("{}", output.render_diagnostics());
|
||||
return Err(format!(
|
||||
"build failed with {} diagnostic(s)",
|
||||
output.diagnostics.len()
|
||||
));
|
||||
}
|
||||
|
||||
nxc_driver::build_binary(&output, &out_path).map_err(format_driver_error)?;
|
||||
println!("built {}", out_path.display());
|
||||
Ok(())
|
||||
}
|
||||
"run" => Err("runtime execution is not implemented yet".to_string()),
|
||||
"new" => Err("project scaffolding is not implemented yet".to_string()),
|
||||
"test" => Err("test runner is not implemented yet".to_string()),
|
||||
@@ -68,15 +107,61 @@ fn executable_name() -> String {
|
||||
fn format_driver_error(error: nxc_driver::DriverError) -> String {
|
||||
match error {
|
||||
nxc_driver::DriverError::Io(io) => format!("io error: {io}"),
|
||||
nxc_driver::DriverError::ToolNotFound { tool_names } => format!(
|
||||
"no supported C compiler found in PATH; tried: {}",
|
||||
tool_names.join(", ")
|
||||
),
|
||||
nxc_driver::DriverError::BuildFailed {
|
||||
tool,
|
||||
status,
|
||||
stderr,
|
||||
} => format!(
|
||||
"build step `{tool}` failed{}{}",
|
||||
status
|
||||
.map(|code| format!(" with exit code {code}"))
|
||||
.unwrap_or_default(),
|
||||
if stderr.trim().is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(":\n{stderr}")
|
||||
}
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_backend_diagnostics(
|
||||
output: &nxc_driver::FrontendOutput,
|
||||
diagnostics: Vec<nxc_frontend::Diagnostic>,
|
||||
) -> String {
|
||||
diagnostics
|
||||
.iter()
|
||||
.map(|diagnostic| diagnostic.render(&output.path.display().to_string(), &output.source))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n\n")
|
||||
}
|
||||
|
||||
fn parse_output_path(args: Vec<String>, command: &str, default_name: &str) -> Result<std::path::PathBuf, String> {
|
||||
if args.is_empty() {
|
||||
return Ok(std::path::PathBuf::from(default_name));
|
||||
}
|
||||
|
||||
if args.len() == 2 && args[0] == "-o" {
|
||||
return Ok(std::path::PathBuf::from(&args[1]));
|
||||
}
|
||||
|
||||
Err(format!(
|
||||
"usage: {} {command} <file.nx> -o <output>",
|
||||
executable_name()
|
||||
))
|
||||
}
|
||||
|
||||
fn print_help() {
|
||||
let name = executable_name();
|
||||
println!("NexaCore CLI");
|
||||
println!("usage:");
|
||||
println!(" {name} check <file.nx>");
|
||||
println!(" {name} build <file.nx>");
|
||||
println!(" {name} emit-c <file.nx> -o <out.c>");
|
||||
println!(" {name} build <file.nx> -o <binary>");
|
||||
println!(" {name} run <file.nx>");
|
||||
println!(" {name} new <name>");
|
||||
println!(" {name} test");
|
||||
|
||||
Reference in New Issue
Block a user