fix: move top-level let initialization into main() in C backend

Top-level let bindings with function calls (e.g., `let result = factorial(10)`)
were emitted as static initializers, which is invalid C since function calls
aren't compile-time constants. Now globals are declared with zero-init and
initialized inside main() before any run expressions execute.

Also fixes validate.sh to use exit codes instead of grep for cargo check/build.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 02:31:49 -05:00
parent 3a2376cd49
commit 89741b4a32
2 changed files with 22 additions and 8 deletions

View File

@@ -30,7 +30,7 @@ fail() { printf "${RED}FAIL${NC} %s\n" "${1:-}"; FAILED=$((FAILED + 1)); }
# --- Rust checks --- # --- Rust checks ---
step "cargo check" step "cargo check"
if nix develop --command cargo check 2>&1 | grep -q "Finished"; then ok; else fail; fi if nix develop --command cargo check 2>/dev/null; then ok; else fail; fi
step "cargo test" step "cargo test"
OUTPUT=$(nix develop --command cargo test 2>&1 || true) OUTPUT=$(nix develop --command cargo test 2>&1 || true)
@@ -39,7 +39,7 @@ if echo "$RESULT" | grep -q "0 failed"; then ok "$RESULT"; else fail "$RESULT";
# --- Build release binary --- # --- Build release binary ---
step "cargo build --release" step "cargo build --release"
if nix develop --command cargo build --release 2>&1 | grep -q "Finished"; then ok; else fail; fi if nix develop --command cargo build --release 2>/dev/null; then ok; else fail; fi
# --- Package tests --- # --- Package tests ---
for pkg in path frontmatter xml rss markdown; do for pkg in path frontmatter xml rss markdown; do

View File

@@ -279,7 +279,7 @@ impl CBackend {
Declaration::Let(let_decl) => { Declaration::Let(let_decl) => {
// Skip run expressions - they're handled in the main wrapper // Skip run expressions - they're handled in the main wrapper
if !matches!(&let_decl.value, Expr::Run { .. }) { if !matches!(&let_decl.value, Expr::Run { .. }) {
self.emit_global_let(&let_decl.name, &let_decl.value)?; self.emit_global_let(&let_decl.name)?;
} }
} }
_ => {} _ => {}
@@ -5561,9 +5561,9 @@ impl CBackend {
} }
} }
fn emit_global_let(&mut self, name: &Ident, value: &Expr) -> Result<(), CGenError> { fn emit_global_let(&mut self, name: &Ident) -> Result<(), CGenError> {
let val = self.emit_expr(value)?; // Declare global variable without initializer (initialized in main)
self.writeln(&format!("static LuxInt {} = {};", name.name, val)); self.writeln(&format!("static LuxInt {} = 0;", name.name));
self.writeln(""); self.writeln("");
Ok(()) Ok(())
} }
@@ -5574,12 +5574,16 @@ impl CBackend {
matches!(d, Declaration::Function(f) if f.name.name == "main") matches!(d, Declaration::Function(f) if f.name.name == "main")
}); });
// Check for top-level run expressions // Check for top-level run expressions or let bindings
let has_run = program.declarations.iter().any(|d| { let has_run = program.declarations.iter().any(|d| {
matches!(d, Declaration::Let(let_decl) if matches!(&let_decl.value, Expr::Run { .. })) matches!(d, Declaration::Let(let_decl) if matches!(&let_decl.value, Expr::Run { .. }))
}); });
if has_main || has_run { let has_global_lets = program.declarations.iter().any(|d| {
matches!(d, Declaration::Let(let_decl) if !matches!(&let_decl.value, Expr::Run { .. }))
});
if has_main || has_run || has_global_lets {
self.writeln("int main(int argc, char** argv) {"); self.writeln("int main(int argc, char** argv) {");
self.indent += 1; self.indent += 1;
@@ -5588,6 +5592,16 @@ impl CBackend {
self.writeln("lux_argv = argv;"); self.writeln("lux_argv = argv;");
self.writeln(""); self.writeln("");
// Initialize top-level let bindings (non-run) inside main
for decl in &program.declarations {
if let Declaration::Let(let_decl) = decl {
if !matches!(&let_decl.value, Expr::Run { .. }) {
let val = self.emit_expr(&let_decl.value)?;
self.writeln(&format!("{} = {};", let_decl.name.name, val));
}
}
}
// Execute top-level let bindings with run expressions // Execute top-level let bindings with run expressions
// Track if main was already called via a run expression // Track if main was already called via a run expression
let mut main_called_via_run = false; let mut main_called_via_run = false;