feat: add list support to C backend and improve compile workflow

C Backend Lists:
- Add LuxList type (dynamic array with void* boxing)
- Implement all 16 list operations: length, isEmpty, concat, reverse,
  range, take, drop, head, tail, get, map, filter, fold, find, any, all
- Higher-order operations generate inline loops with closure calls
- Fix unique variable names to prevent redefinition errors

Compile Command:
- `lux compile file.lux` now produces a binary (like rustc, go build)
- Add `--emit-c` flag to output C code instead
- Binary name derived from source filename (foo.lux -> ./foo)
- Clean up temp files after compilation

Documentation:
- Create docs/C_BACKEND.md with full strategy documentation
- Document compilation pipeline, runtime types, limitations
- Compare with Koka, Rust, Zig, Go, Nim, OCaml approaches
- Outline future roadmap (evidence passing, Perceus RC)
- Fix misleading doc comment (remove false Perceus claim)
- Update OVERVIEW.md and ROADMAP.md to reflect list completion

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 11:02:26 -05:00
parent d284ee58a8
commit 909dbf7a97
5 changed files with 954 additions and 87 deletions

View File

@@ -140,17 +140,20 @@ fn main() {
handle_pkg_command(&args[2..]);
}
"compile" => {
// Compile to C code
// Compile to native binary
if args.len() < 3 {
eprintln!("Usage: lux compile <file.lux> [-o output.c] [--run]");
eprintln!("Usage: lux compile <file.lux> [-o binary]");
eprintln!(" lux compile <file.lux> --run");
eprintln!(" lux compile <file.lux> --emit-c [-o file.c]");
std::process::exit(1);
}
let run_after = args.iter().any(|a| a == "--run");
let emit_c = args.iter().any(|a| a == "--emit-c");
let output_path = args.iter()
.position(|a| a == "-o")
.and_then(|i| args.get(i + 1))
.map(|s| s.as_str());
compile_to_c(&args[2], output_path, run_after);
compile_to_c(&args[2], output_path, run_after, emit_c);
}
path => {
// Run a file
@@ -169,9 +172,10 @@ fn print_help() {
println!("Usage:");
println!(" lux Start the REPL");
println!(" lux <file.lux> Run a file (interpreter)");
println!(" lux compile <file.lux> Compile to C code (stdout)");
println!(" lux compile <f> -o out.c Compile to C file");
println!(" lux compile <file.lux> Compile to native binary");
println!(" lux compile <f> -o app Compile to binary named 'app'");
println!(" lux compile <f> --run Compile and execute");
println!(" lux compile <f> --emit-c Output C code instead of binary");
println!(" lux fmt <file.lux> Format a file (--check to verify only)");
println!(" lux check <file.lux> Type check without running");
println!(" lux test [pattern] Run tests (optional pattern filter)");
@@ -262,7 +266,7 @@ fn check_file(path: &str) {
println!("{}: OK", path);
}
fn compile_to_c(path: &str, output_path: Option<&str>, run_after: bool) {
fn compile_to_c(path: &str, output_path: Option<&str>, run_after: bool, emit_c: bool) {
use codegen::c_backend::CBackend;
use modules::ModuleLoader;
use std::path::Path;
@@ -308,58 +312,77 @@ fn compile_to_c(path: &str, output_path: Option<&str>, run_after: bool) {
Err(e) => {
eprintln!("C codegen error: {}", e);
eprintln!();
eprintln!("Note: The C backend currently supports:");
eprintln!(" - Integer, Float, Bool, String, Char, Unit literals");
eprintln!(" - Arithmetic and comparison operators");
eprintln!(" - If/then/else conditionals");
eprintln!(" - Let bindings and blocks");
eprintln!(" - Function definitions and calls");
eprintln!(" - Records and enums (basic)");
eprintln!(" - Console.print effect");
eprintln!("Note: The C backend supports functions, closures, ADTs,");
eprintln!("pattern matching, lists, and Console.print.");
eprintln!();
eprintln!("Not yet supported: closures, lists, pattern variable binding,");
eprintln!("other effects, higher-order functions.");
eprintln!("Not yet supported: other effects, some advanced features.");
std::process::exit(1);
}
};
// Determine output
if run_after {
// Write to temp file, compile, and run
let temp_c = std::env::temp_dir().join("lux_output.c");
let temp_bin = std::env::temp_dir().join("lux_output");
if let Err(e) = std::fs::write(&temp_c, &c_code) {
eprintln!("Error writing temp file: {}", e);
std::process::exit(1);
}
// Try to find a C compiler
let cc = std::env::var("CC").unwrap_or_else(|_| "cc".to_string());
let compile_result = Command::new(&cc)
.args(["-O2", "-o"])
.arg(&temp_bin)
.arg(&temp_c)
.output();
match compile_result {
Ok(output) => {
if !output.status.success() {
eprintln!("C compilation failed:");
eprintln!("{}", String::from_utf8_lossy(&output.stderr));
std::process::exit(1);
}
// Handle --emit-c: output C code instead of binary
if emit_c {
if let Some(out_path) = output_path {
if let Err(e) = std::fs::write(out_path, &c_code) {
eprintln!("Error writing file '{}': {}", out_path, e);
std::process::exit(1);
}
Err(e) => {
eprintln!("Failed to run C compiler '{}': {}", cc, e);
eprintln!("Make sure gcc or clang is installed, or set CC environment variable.");
eprintln!("Wrote C code to {}", out_path);
} else {
println!("{}", c_code);
}
return;
}
// Default: compile to native binary
let temp_c = std::env::temp_dir().join("lux_output.c");
// Determine output binary name
let output_bin = if let Some(out) = output_path {
Path::new(out).to_path_buf()
} else {
// Derive from source filename: foo.lux -> ./foo
let stem = file_path.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("a.out");
Path::new(".").join(stem)
};
if let Err(e) = std::fs::write(&temp_c, &c_code) {
eprintln!("Error writing temp file: {}", e);
std::process::exit(1);
}
// Find C compiler
let cc = std::env::var("CC").unwrap_or_else(|_| "cc".to_string());
let compile_result = Command::new(&cc)
.args(["-O2", "-o"])
.arg(&output_bin)
.arg(&temp_c)
.output();
match compile_result {
Ok(output) => {
if !output.status.success() {
eprintln!("C compilation failed:");
eprintln!("{}", String::from_utf8_lossy(&output.stderr));
std::process::exit(1);
}
}
Err(e) => {
eprintln!("Failed to run C compiler '{}': {}", cc, e);
eprintln!("Make sure gcc or clang is installed, or set CC environment variable.");
std::process::exit(1);
}
}
// Clean up temp file
let _ = std::fs::remove_file(&temp_c);
if run_after {
// Run the compiled binary
let run_result = Command::new(&temp_bin).status();
let run_result = Command::new(&output_bin).status();
match run_result {
Ok(status) => {
std::process::exit(status.code().unwrap_or(1));
@@ -369,17 +392,9 @@ fn compile_to_c(path: &str, output_path: Option<&str>, run_after: bool) {
std::process::exit(1);
}
}
} else if let Some(out_path) = output_path {
// Write to specified file
if let Err(e) = std::fs::write(out_path, &c_code) {
eprintln!("Error writing file '{}': {}", out_path, e);
std::process::exit(1);
}
eprintln!("Compiled to {}", out_path);
eprintln!("Compile with: cc -O2 -o output {}", out_path);
} else {
// Print to stdout
println!("{}", c_code);
// Just print where the binary is
eprintln!("Compiled to {}", output_bin.display());
}
}