codegen: emit C instead of bytecode — El is now natively compiled

Rewrites codegen.el to produce C source instead of JSON bytecode,
eliminating the ELVM interpreter as a runtime dependency.

- All El values use el_val_t (int64_t) as the universal type; integers
  are stored directly, strings/pointers via uintptr_t cast
- String literals wrapped with EL_STR(), arithmetic works natively
- fn declarations become C functions returning el_val_t
- let bindings become el_val_t local variables
- if/else, while, for all map to native C control flow
- String + String uses el_str_concat(); numeric + uses C +
- strip_outer_parens() prevents double-paren warnings in if/while
- compiler.el updated to describe C output and correct CLI usage

Adds el-compiler/runtime/ with:
- el_runtime.h: declares all builtins using el_val_t
- el_runtime.c: implements I/O, strings, math, list, map, fs, JSON;
  HTTP builtins are stubs (return empty string) pending libcurl

Compile El programs with:
  cc -I<runtime-dir> -o hello hello.c el_runtime.c
This commit is contained in:
Will Anderson
2026-04-29 22:33:27 -05:00
parent 4f3543b068
commit ede087eb04
4 changed files with 948 additions and 518 deletions
+416 -508
View File
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -4,14 +4,14 @@
// This is the bootstrap entry point: compiled once by the Rust el-compiler,
// then self-hosted from that point forward.
//
// The returned JSON is an array of bytecode instruction objects matching
// the serde serialisation format of el-compiler's Bytecode enum.
// The returned string is C source code. Compile the output with:
// cc -o <prog> <prog>.c el_runtime.c
import "lexer.el"
import "parser.el"
import "codegen.el"
// compile full pipeline: source string -> JSON bytecode string
// compile full pipeline: source string -> C source string
fn compile(source: String) -> String {
let tokens: [Map<String, Any>] = lex(source)
let stmts: [Map<String, Any>] = parse(tokens)
@@ -20,23 +20,23 @@ fn compile(source: String) -> String {
// main CLI entry point for self-hosted compilation.
//
// Called by: elvm el-compiler.elc <source.el> <output.elc>
// Called by: elc <source.el> <output.c>
//
// Reads pre-resolved El source from args()[0], compiles it to JSON bytecode,
// and writes the result to args()[1]. The output is raw JSON (accepted by
// both elvm and el exec without an ELVM container header).
// Reads El source from args()[0], compiles it to C source, and writes the
// result to args()[1]. Then run:
// cc -o <prog> <output.c> el_runtime.c
fn main() -> Void {
let argv: [String] = args()
let argc: Int = native_list_len(argv)
if argc < 2 {
println("el-compiler: usage: elvm el-compiler.elc <source.el> <output.elc>")
println("el-compiler: usage: elc <source.el> <output.c>")
exit(1)
}
let src_path: String = native_list_get(argv, 0)
let out_path: String = native_list_get(argv, 1)
let source: String = fs_read(src_path)
let bytecode_json: String = compile(source)
let ok: Bool = fs_write(out_path, bytecode_json)
let c_source: String = compile(source)
let ok: Bool = fs_write(out_path, c_source)
if ok {
exit(0)
} else {