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:
94
crates/nxc-frontend/tests/hir_backend_tests.rs
Normal file
94
crates/nxc-frontend/tests/hir_backend_tests.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
use nxc_frontend::{analyze, emit_c, lower_to_hir, HirExprKind, HirStmtKind, Lexer, Parser};
|
||||
|
||||
fn lower(source: &str) -> nxc_frontend::LoweringResult {
|
||||
let lexed = Lexer::new(source).lex();
|
||||
assert!(lexed.diagnostics.is_empty(), "{:?}", lexed.diagnostics);
|
||||
|
||||
let parsed = Parser::new(lexed.tokens).parse_module();
|
||||
assert!(parsed.diagnostics.is_empty(), "{:?}", parsed.diagnostics);
|
||||
|
||||
let semantic = analyze(&parsed.module);
|
||||
assert!(semantic.diagnostics.is_empty(), "{:?}", semantic.diagnostics);
|
||||
|
||||
lower_to_hir(&semantic.module)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lowers_simple_program_to_hir() {
|
||||
let lowered = lower(
|
||||
"\
|
||||
fn add(a: Int, b: Int) -> Int:
|
||||
let sum = a + b
|
||||
return sum
|
||||
",
|
||||
);
|
||||
|
||||
assert!(lowered.diagnostics.is_empty(), "{:?}", lowered.diagnostics);
|
||||
assert_eq!(lowered.module.functions.len(), 1);
|
||||
let function = &lowered.module.functions[0];
|
||||
assert_eq!(function.params.len(), 2);
|
||||
assert_eq!(function.body.statements.len(), 2);
|
||||
|
||||
let HirStmtKind::Let { value, .. } = &function.body.statements[0].kind else {
|
||||
panic!("expected let statement");
|
||||
};
|
||||
match &value.kind {
|
||||
HirExprKind::Binary { .. } => {}
|
||||
other => panic!("expected lowered binary expression, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emits_c_for_precedence_and_return() {
|
||||
let lowered = lower(
|
||||
"\
|
||||
fn predicate() -> Bool:
|
||||
return 1 + 2 * 3 == 7 || false
|
||||
",
|
||||
);
|
||||
let emitted = emit_c(&lowered.module);
|
||||
assert!(emitted.diagnostics.is_empty(), "{:?}", emitted.diagnostics);
|
||||
|
||||
let code = emitted.code.expect("expected generated C");
|
||||
assert!(code.contains("return (((1LL + (2LL * 3LL)) == 7LL) || false);"));
|
||||
assert!(code.contains("bool nxc_fn_0_predicate(void)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emits_if_else_and_function_calls() {
|
||||
let lowered = lower(
|
||||
"\
|
||||
fn choose(flag: Bool) -> Int:
|
||||
if flag:
|
||||
return answer()
|
||||
else:
|
||||
return 0
|
||||
|
||||
fn answer() -> Int:
|
||||
return 42
|
||||
",
|
||||
);
|
||||
let emitted = emit_c(&lowered.module);
|
||||
assert!(emitted.diagnostics.is_empty(), "{:?}", emitted.diagnostics);
|
||||
|
||||
let code = emitted.code.expect("expected generated C");
|
||||
assert!(code.contains("if (nxc_local_0_flag) {"));
|
||||
assert!(code.contains("return nxc_fn_1_answer();"));
|
||||
assert!(code.contains("else {"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_unsupported_string_binary_ops_in_backend() {
|
||||
let lowered = lower(
|
||||
"\
|
||||
fn main() -> Bool:
|
||||
return \"a\" == \"b\"
|
||||
",
|
||||
);
|
||||
let emitted = emit_c(&lowered.module);
|
||||
assert!(emitted.code.is_none());
|
||||
assert!(emitted
|
||||
.diagnostics
|
||||
.iter()
|
||||
.any(|diag| diag.message.contains("string binary operations are not supported")));
|
||||
}
|
||||
Reference in New Issue
Block a user