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:
2026-04-06 17:24:01 +02:00
parent 9304b6bcaa
commit 1e30c05233
9 changed files with 1032 additions and 17 deletions

View File

@@ -18,3 +18,4 @@ path = "src/bin/nexacore.rs"
[dependencies]
nxc-driver = { path = "../nxc-driver" }
nxc-frontend = { path = "../nxc-frontend" }

View File

@@ -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");