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:
@@ -1,10 +1,12 @@
|
||||
use std::fmt::Write;
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use nxc_frontend::{
|
||||
analyze, has_errors, Diagnostic, Item, LexResult, Lexer, Module, ParseResult, Parser, Token,
|
||||
TypedModule,
|
||||
analyze, emit_c, has_errors, lower_to_hir, BackendResult, Diagnostic, HirModule, Item,
|
||||
LexResult, Lexer, Module, ParseResult, Parser, Token, TypedModule,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -14,12 +16,21 @@ pub struct FrontendOutput {
|
||||
pub tokens: Vec<Token>,
|
||||
pub module: Module,
|
||||
pub typed_module: Option<TypedModule>,
|
||||
pub hir_module: Option<HirModule>,
|
||||
pub diagnostics: Vec<Diagnostic>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DriverError {
|
||||
Io(std::io::Error),
|
||||
ToolNotFound {
|
||||
tool_names: Vec<String>,
|
||||
},
|
||||
BuildFailed {
|
||||
tool: String,
|
||||
status: Option<i32>,
|
||||
stderr: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for DriverError {
|
||||
@@ -89,11 +100,21 @@ pub fn check_source(path: PathBuf, source: String) -> FrontendOutput {
|
||||
|
||||
lexer_diagnostics.extend(parser_diagnostics);
|
||||
let mut typed_module = None;
|
||||
let mut hir_module = None;
|
||||
|
||||
if !has_errors(&lexer_diagnostics) {
|
||||
let semantic = analyze(&module);
|
||||
lexer_diagnostics.extend(semantic.diagnostics);
|
||||
typed_module = Some(semantic.module);
|
||||
if !has_errors(&lexer_diagnostics) {
|
||||
let lowering = lower_to_hir(&semantic.module);
|
||||
lexer_diagnostics.extend(lowering.diagnostics);
|
||||
if !has_errors(&lexer_diagnostics) {
|
||||
hir_module = Some(lowering.module.clone());
|
||||
}
|
||||
typed_module = Some(semantic.module);
|
||||
} else {
|
||||
typed_module = Some(semantic.module);
|
||||
}
|
||||
}
|
||||
|
||||
FrontendOutput {
|
||||
@@ -102,6 +123,96 @@ pub fn check_source(path: PathBuf, source: String) -> FrontendOutput {
|
||||
tokens,
|
||||
module,
|
||||
typed_module,
|
||||
hir_module,
|
||||
diagnostics: lexer_diagnostics,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_c_from_frontend(frontend: &FrontendOutput) -> Result<String, Vec<Diagnostic>> {
|
||||
let Some(hir_module) = &frontend.hir_module else {
|
||||
return Err(vec![Diagnostic::error(
|
||||
"HIR is unavailable; run `check` diagnostics first",
|
||||
frontend.module.span,
|
||||
)]);
|
||||
};
|
||||
|
||||
let BackendResult { code, diagnostics } = emit_c(hir_module);
|
||||
if diagnostics.is_empty() {
|
||||
Ok(code.unwrap_or_default())
|
||||
} else {
|
||||
Err(diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_c_file(frontend: &FrontendOutput, out_path: impl AsRef<Path>) -> Result<(), DriverError> {
|
||||
let out_path = out_path.as_ref();
|
||||
let code = emit_c_from_frontend(frontend)
|
||||
.map_err(|diagnostics| DriverError::BuildFailed {
|
||||
tool: "c-backend".to_string(),
|
||||
status: None,
|
||||
stderr: diagnostics
|
||||
.iter()
|
||||
.map(|diagnostic| diagnostic.render(&frontend.path.display().to_string(), &frontend.source))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n\n"),
|
||||
})?;
|
||||
|
||||
fs::write(out_path, code)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn build_binary(frontend: &FrontendOutput, out_path: impl AsRef<Path>) -> Result<(), DriverError> {
|
||||
let compiler = find_c_compiler().ok_or_else(|| DriverError::ToolNotFound {
|
||||
tool_names: vec!["cc".to_string(), "clang".to_string(), "gcc".to_string()],
|
||||
})?;
|
||||
|
||||
let c_source = emit_c_from_frontend(frontend)
|
||||
.map_err(|diagnostics| DriverError::BuildFailed {
|
||||
tool: "c-backend".to_string(),
|
||||
status: None,
|
||||
stderr: diagnostics
|
||||
.iter()
|
||||
.map(|diagnostic| diagnostic.render(&frontend.path.display().to_string(), &frontend.source))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n\n"),
|
||||
})?;
|
||||
|
||||
let mut temp_path = std::env::temp_dir();
|
||||
let stamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_nanos();
|
||||
temp_path.push(format!("nexacore-build-{stamp}.c"));
|
||||
|
||||
fs::write(&temp_path, c_source)?;
|
||||
|
||||
let output = Command::new(&compiler)
|
||||
.arg(&temp_path)
|
||||
.arg("-std=c11")
|
||||
.arg("-O2")
|
||||
.arg("-o")
|
||||
.arg(out_path.as_ref())
|
||||
.output();
|
||||
|
||||
let _ = fs::remove_file(&temp_path);
|
||||
|
||||
let output = output?;
|
||||
if output.status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(DriverError::BuildFailed {
|
||||
tool: compiler,
|
||||
status: output.status.code(),
|
||||
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn find_c_compiler() -> Option<String> {
|
||||
for tool in ["cc", "clang", "gcc"] {
|
||||
if Command::new(tool).arg("--version").output().is_ok() {
|
||||
return Some(tool.to_string());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user