self-host the el compiler

Today's milestone: dist/platform/elc compiles itself byte-for-byte to
itself (stage1 == stage2 == stage3 verified). The compiler is now a
real binary in the world.

What landed
- Spec rewrite (language.md) to truth — every feature marked
  implemented / planned / not-in-this-language with no fiction.
- C runtime extension: 51 new builtins. JSON parser + accessors,
  time, UUID, env, in-process state K/V, float formatting + math,
  string ops (index_of, split, char_at, char_code, pad_left/right,
  format), list ops (push, push_front, join, range), bool_to_str.
  Runtime grew 631 → 1611 lines, header 171 → 247.
- Codegen fix: transform_implicit_return lifts a function's bare
  trailing expression into an explicit return. Without it, lex(),
  parse(), and every other implicit-return function returned 0/nil
  and the whole pipeline produced empty C output.
- Codegen fix: index expressions dispatch on AST kind. obj["literal"]
  → el_get_field (map), arr[i] → el_list_get (list). Same Index node
  in the parser, two different runtime calls.
- Codegen fix: skip emitting fn main() (collides with C main()) and
  honor parsed return-type annotations so Void functions don't get
  return-wrapped (return println(x) is a C type error).
- Parser: capture return-type identifier from -> Ret annotations.
- Lexer: + vessel keyword, + % operator, + \r escape.
- Runtime fix: el_list_append now allocates a fresh list rather than
  realloc'ing the input. Realloc moved blocks made caller pointers
  dangle, which was inserting garbage values into declared lists and
  causing strcmp segfaults. Persistent allocation eliminates the
  whole class of use-after-free at modest memory cost.

Bootstrap path
- One-shot Python helper translated elc-combined.el to C and
  produced stage1. Helper is disposable; not committed.
- stage1 compiles elc-combined.el → stage2.c which cc compiles to
  stage2; stage2 compiles elc-combined.el → stage3.c. stage2.c and
  stage3.c are byte-identical. Closure proven.
- New elc installed at dist/platform/elc; old broken binary
  preserved as dist/platform/elc.legacy.
- dist/platform/elc.c is the canonical generated source.
- elvm and the bytecode pipeline are no longer on the critical path.

Known gap
- The `+` operator's heuristic dispatch still picks string concat
  when both operands are Idents with no literal anchor. Self-hosting
  works because the compiler source is careful, but `fn add(a:Int,
  b:Int) { a + b }` will not do arithmetic until codegen reads the
  parsed type annotations to dispatch. Fix is wiring; not done here.

Tested
- tiny / lextest / whiletest / map+field / array build all run.
- cgi-studio (1037 lines real El) compiles to C cleanly. Link fails
  only because runtime is missing fs_list, json_encode, llm_*; those
  are scheduled batches.
- Three-stage closure (stage1 vs stage2 vs stage3) byte-identical.
This commit is contained in:
Will Anderson
2026-04-30 13:10:29 -05:00
parent e7a49ebc34
commit 5c05ce9b99
11 changed files with 6890 additions and 954 deletions
+254 -139
View File
@@ -1,12 +1,16 @@
// codegen.el El compiler C source code generator
//
// Input: list of AST statement maps (from parser.el)
// Output: C source string
// Output: C source printed to stdout (streamed, one line at a time)
//
// Each El program compiles to a single .c file that #includes el_runtime.h.
// Functions map directly to C functions; top-level statements become main().
//
// Entry point: fn codegen(stmts: [Map<String, Any>], source: String) -> String
// Returns "" output goes to stdout via println().
//
// Streaming output avoids O(n²) string concatenation: each emitted line is
// printed immediately rather than appended to a growing string.
// String helpers
@@ -50,9 +54,6 @@ fn c_str_lit(s: String) -> String {
// Type mapping
// Map El type annotation strings to C types.
// type_str is whatever appeared after ":" in El source we only recognise
// the core types; everything else falls back to void*.
fn el_type_to_c(type_str: String) -> String {
if type_str == "String" { return "const char*" }
if type_str == "Int" { return "int64_t" }
@@ -60,21 +61,20 @@ fn el_type_to_c(type_str: String) -> String {
if type_str == "Float" { return "double" }
if type_str == "Void" { return "void" }
if type_str == "void" { return "void" }
// Any, Map, list types, unknown void*
"void*"
}
// Code buffer
// Code emission
//
// We accumulate output lines into the VM's native instruction buffer
// (repurposed as a line buffer). Each "instruction" is a line of C text.
// emit_line/emit_blank stream output directly via println.
// This avoids building a large string in memory.
fn emit_line(line: String) -> Void {
native_instr_push(line)
println(line)
}
fn emit_blank() -> Void {
native_instr_push("")
println("")
}
// Operator helpers
@@ -95,14 +95,6 @@ fn binop_to_c(op: String) -> String {
op
}
// Unique label generator
// We use the native instruction counter (length before emitting) as a unique
// monotone id so that nested if/while labels don't collide.
fn unique_id() -> Int {
native_instr_len()
}
// Expression codegen
//
// cg_expr returns a C expression string (not a statement).
@@ -122,7 +114,6 @@ fn cg_expr(expr: Map<String, Any>) -> String {
if kind == "Str" {
let v: String = expr["value"]
// String literals are wrapped in EL_STR() to convert const char* to el_val_t
return "EL_STR(" + c_str_lit(v) + ")"
}
@@ -159,37 +150,123 @@ fn cg_expr(expr: Map<String, Any>) -> String {
let right = expr["right"]
let left_c: String = cg_expr(left)
let right_c: String = cg_expr(right)
// String concatenation: El uses + for strings map to el_str_concat
let left_kind: String = left["expr"]
let right_kind: String = right["expr"]
if op == "Plus" {
// We can't easily detect types here, so we rely on the user
// calling str_concat() explicitly for strings, or we emit
// el_str_concat when either operand looks like a string expression.
// Heuristic: if either side is a Str literal, use el_str_concat.
let left_kind: String = left["expr"]
let right_kind: String = right["expr"]
// If either side is a string literal, always concat
if left_kind == "Str" {
return "el_str_concat(" + left_c + ", " + right_c + ")"
}
if right_kind == "Str" {
return "el_str_concat(" + left_c + ", " + right_c + ")"
}
// Check if it's a call to something that returns string
// If either side is an integer literal, this is arithmetic (not string concat)
if left_kind == "Int" {
let op_c: String = binop_to_c(op)
return "(" + left_c + " " + op_c + " " + right_c + ")"
}
if right_kind == "Int" {
let op_c: String = binop_to_c(op)
return "(" + left_c + " " + op_c + " " + right_c + ")"
}
if left_kind == "Call" {
return "el_str_concat(" + left_c + ", " + right_c + ")"
}
if right_kind == "Call" {
return "el_str_concat(" + left_c + ", " + right_c + ")"
}
// Check if it's a BinOp that already became el_str_concat
if left_kind == "BinOp" {
let left_op: String = left["op"]
if left_op == "Plus" {
// If nested plus and outer is string context, keep as el_str_concat
// For simplicity emit el_str_concat for any nested plus
return "el_str_concat(" + left_c + ", " + right_c + ")"
}
}
if right_kind == "BinOp" {
let right_op: String = right["op"]
if right_op == "Plus" {
return "el_str_concat(" + left_c + ", " + right_c + ")"
}
}
// Ident + Ident or Ident + unknown assume string concat
// (This is the ambiguous case: El uses + for both string and integer ops)
if left_kind == "Ident" {
return "el_str_concat(" + left_c + ", " + right_c + ")"
}
if right_kind == "Ident" {
return "el_str_concat(" + left_c + ", " + right_c + ")"
}
}
// String equality: use str_eq() when either side is a string literal or ident.
// Use plain == when comparing integer literals.
if op == "EqEq" {
// Integer literal on either side arithmetic comparison
if left_kind == "Int" {
return "(" + left_c + " == " + right_c + ")"
}
if right_kind == "Int" {
return "(" + left_c + " == " + right_c + ")"
}
if left_kind == "Bool" {
return "(" + left_c + " == " + right_c + ")"
}
if right_kind == "Bool" {
return "(" + left_c + " == " + right_c + ")"
}
if left_kind == "Str" {
return "str_eq(" + left_c + ", " + right_c + ")"
}
if right_kind == "Str" {
return "str_eq(" + left_c + ", " + right_c + ")"
}
if left_kind == "Ident" {
return "str_eq(" + left_c + ", " + right_c + ")"
}
if right_kind == "Ident" {
return "str_eq(" + left_c + ", " + right_c + ")"
}
if left_kind == "Call" {
return "str_eq(" + left_c + ", " + right_c + ")"
}
if right_kind == "Call" {
return "str_eq(" + left_c + ", " + right_c + ")"
}
}
if op == "NotEq" {
if left_kind == "Int" {
return "(" + left_c + " != " + right_c + ")"
}
if right_kind == "Int" {
return "(" + left_c + " != " + right_c + ")"
}
if left_kind == "Bool" {
return "(" + left_c + " != " + right_c + ")"
}
if right_kind == "Bool" {
return "(" + left_c + " != " + right_c + ")"
}
if left_kind == "Str" {
return "!str_eq(" + left_c + ", " + right_c + ")"
}
if right_kind == "Str" {
return "!str_eq(" + left_c + ", " + right_c + ")"
}
if left_kind == "Ident" {
return "!str_eq(" + left_c + ", " + right_c + ")"
}
if right_kind == "Ident" {
return "!str_eq(" + left_c + ", " + right_c + ")"
}
if left_kind == "Call" {
return "!str_eq(" + left_c + ", " + right_c + ")"
}
if right_kind == "Call" {
return "!str_eq(" + left_c + ", " + right_c + ")"
}
}
let op_c: String = binop_to_c(op)
return "(" + left_c + " " + op_c + " " + right_c + ")"
}
@@ -200,7 +277,6 @@ fn cg_expr(expr: Map<String, Any>) -> String {
let arity: Int = native_list_len(args)
let func_kind: String = func["expr"]
// Build argument list string
let args_c = ""
let i = 0
while i < arity {
@@ -219,7 +295,6 @@ fn cg_expr(expr: Map<String, Any>) -> String {
}
if func_kind == "Field" {
// method-style call: obj.method(args) pass obj as first arg
let obj = func["object"]
let field: String = func["field"]
let obj_c: String = cg_expr(obj)
@@ -229,7 +304,6 @@ fn cg_expr(expr: Map<String, Any>) -> String {
return field + "(" + obj_c + ")"
}
// Dynamic call emit as a generic pointer call (best effort)
let fn_c: String = cg_expr(func)
return fn_c + "(" + args_c + ")"
}
@@ -238,15 +312,23 @@ fn cg_expr(expr: Map<String, Any>) -> String {
let obj = expr["object"]
let field: String = expr["field"]
let obj_c: String = cg_expr(obj)
// Map field access to a runtime helper
return "el_get_field(" + obj_c + ", " + c_str_lit(field) + ")"
}
if kind == "Index" {
// El programs use `t["field"]` for map access and `arr[i]` for
// list access. The parser emits the same Index node for both.
// Dispatch at codegen time on the index expression kind: string-
// literal index map field access (`el_get_field`); anything
// else list element access (`el_list_get`).
let obj = expr["object"]
let idx = expr["index"]
let obj_c: String = cg_expr(obj)
let idx_c: String = cg_expr(idx)
let idx_kind: String = idx["expr"]
if str_eq(idx_kind, "Str") {
return "el_get_field(" + obj_c + ", " + idx_c + ")"
}
return "el_list_get(" + obj_c + ", " + idx_c + ")"
}
@@ -268,7 +350,6 @@ fn cg_expr(expr: Map<String, Any>) -> String {
}
if kind == "Map" {
// Map literals: emit as el_map_new with key/value pairs
let pairs = expr["pairs"]
let n: Int = native_list_len(pairs)
let items = ""
@@ -288,78 +369,85 @@ fn cg_expr(expr: Map<String, Any>) -> String {
}
if kind == "Try" {
// ? operator just pass through the value for now
let inner = expr["inner"]
return cg_expr(inner)
}
// If expression used as expression emit a ternary where possible,
// or fall through to inline if-expression via a statement block.
// For simplicity we handle this at statement level; as an expression
// we emit a temporary variable approach is complex emit NULL for now
// and rely on statement-level handling.
if kind == "If" {
// Emit as a GNU C statement expression ({...}) widely supported
// by GCC/Clang. We emit it inline.
let cond = expr["cond"]
let then_stmts = expr["then"]
let else_stmts = expr["else"]
let has_else: Bool = expr["has_else"]
let cond_c: String = cg_expr(cond)
// Gather then block
let then_buf = cg_stmts_to_str(then_stmts, " ")
let result = "/* if-expr */ ((" + cond_c + ") ? (void*)1 : (void*)0)"
return result
return "/* if-expr */ ((" + cond_c + ") ? (el_val_t)1 : (el_val_t)0)"
}
// Fallback
"NULL"
"EL_NULL"
}
// Variable scope tracking
//
// El allows `let x = expr` to both declare and reassign x in the same scope.
// C doesn't allow redeclaring the same name in the same block.
// We track declared names in a list and emit `x = expr` (no type prefix)
// when x is already declared. The declared list is passed through all
// statement emitters.
fn list_contains(lst: [String], s: String) -> Bool {
let n: Int = native_list_len(lst)
let i = 0
while i < n {
let item: String = native_list_get(lst, i)
if item == s { return true }
let i = i + 1
}
false
}
// Statement codegen
//
// cg_stmt emits C lines for a statement, using the given indentation prefix.
// cg_stmt emits C lines via println. declared is a list of already-declared
// variable names in the current C scope; returns updated declared list.
fn cg_stmt(stmt: Map<String, Any>, indent: String) -> Void {
fn cg_stmt(stmt: Map<String, Any>, indent: String, declared: [String]) -> [String] {
let kind: String = stmt["stmt"]
if kind == "Let" {
let name: String = stmt["name"]
let val = stmt["value"]
let val_c: String = cg_expr(val)
// All El values are el_val_t (int64_t), which can hold integers directly
// and store pointers (strings, lists) via pointer-int cast.
emit_line(indent + "el_val_t " + name + " = " + val_c + ";")
return
if list_contains(declared, name) {
emit_line(indent + name + " = " + val_c + ";")
return declared
} else {
emit_line(indent + "el_val_t " + name + " = " + val_c + ";")
return native_list_append(declared, name)
}
}
if kind == "Return" {
let val = stmt["value"]
let val_kind: String = val["expr"]
if val_kind == "Nil" {
emit_line(indent + "return;")
emit_line(indent + "return 0;")
} else {
let val_c: String = cg_expr(val)
emit_line(indent + "return " + val_c + ";")
}
return
return declared
}
if kind == "Expr" {
let val = stmt["value"]
let val_kind: String = val["expr"]
// Handle if/while/for at statement level specially
if val_kind == "If" {
cg_if_stmt(val, indent)
return
cg_if_stmt(val, indent, declared)
return declared
}
if val_kind == "For" {
cg_for_stmt(val, indent)
return
cg_for_stmt(val, indent, declared)
return declared
}
let val_c: String = cg_expr(val)
emit_line(indent + val_c + ";")
return
return declared
}
if kind == "While" {
@@ -368,34 +456,27 @@ fn cg_stmt(stmt: Map<String, Any>, indent: String) -> Void {
let cond_c: String = cg_expr(cond)
let cond_c = strip_outer_parens(cond_c)
emit_line(indent + "while (" + cond_c + ") {")
cg_stmts(body, indent + " ")
cg_stmts(body, indent + " ", declared)
emit_line(indent + "}")
return
return declared
}
if kind == "For" {
// for item in list { body }
let item: String = stmt["item"]
let list_expr = stmt["list"]
let body = stmt["body"]
cg_for_body(item, list_expr, body, indent)
return
cg_for_body(item, list_expr, body, indent, declared)
return declared
}
if kind == "FnDef" {
// Function definitions are handled at the top level skip here
// (they would appear as nested fns if El ever supports them,
// but we emit them top-level in the pass over top-level stmts).
return
}
if kind == "TypeDef" { return }
if kind == "EnumDef" { return }
if kind == "Import" { return }
if kind == "FnDef" { return declared }
if kind == "TypeDef" { return declared }
if kind == "EnumDef" { return declared }
if kind == "Import" { return declared }
declared
}
// Strip a single layer of surrounding parentheses from a C expression string,
// if present. This avoids double-parens in "if ((cond))".
// Strip a single layer of surrounding parentheses from a C expression string.
fn strip_outer_parens(s: String) -> String {
let chars: [String] = native_string_chars(s)
let n: Int = native_list_len(chars)
@@ -404,7 +485,6 @@ fn strip_outer_parens(s: String) -> String {
let last: String = native_list_get(chars, n - 1)
if first == "(" {
if last == ")" {
// Verify the opening paren matches the closing one (depth check)
let depth = 1
let i = 1
let balanced = true
@@ -417,13 +497,12 @@ fn strip_outer_parens(s: String) -> String {
let depth = depth - 1
if depth == 0 {
let balanced = false
let i = n // break
let i = n
}
}
let i = i + 1
}
if balanced {
// Safe to strip outer parens
let inner = ""
let j = 1
while j < n - 1 {
@@ -438,72 +517,60 @@ fn strip_outer_parens(s: String) -> String {
s
}
fn cg_if_stmt(expr: Map<String, Any>, indent: String) -> Void {
fn cg_if_stmt(expr: Map<String, Any>, indent: String, declared: [String]) -> Void {
let cond = expr["cond"]
let then_stmts = expr["then"]
let else_stmts = expr["else"]
let has_else: Bool = expr["has_else"]
let cond_c: String = cg_expr(cond)
// Strip outer parens to avoid double-parens warning from BinOp wrapping
let cond_c = strip_outer_parens(cond_c)
emit_line(indent + "if (" + cond_c + ") {")
cg_stmts(then_stmts, indent + " ")
cg_stmts(then_stmts, indent + " ", declared)
if has_else {
emit_line(indent + "} else {")
cg_stmts(else_stmts, indent + " ")
cg_stmts(else_stmts, indent + " ", declared)
}
emit_line(indent + "}")
}
fn cg_for_body(item: String, list_expr: Map<String, Any>, body: [Map<String, Any>], indent: String) -> Void {
fn cg_for_body(item: String, list_expr: Map<String, Any>, body: [Map<String, Any>], indent: String, declared: [String]) -> Void {
let list_c: String = cg_expr(list_expr)
let uid0 = native_int_to_str(unique_id())
let idx = "_el_i_" + uid0
let uid1 = native_int_to_str(unique_id())
let list_tmp = "_el_lst_" + uid1
let uid2 = native_int_to_str(unique_id())
let len_tmp = "_el_len_" + uid2
let idx = "_el_i"
let list_tmp = "_el_lst"
let len_tmp = "_el_len"
emit_line(indent + "{")
emit_line(indent + " el_val_t " + list_tmp + " = " + list_c + ";")
emit_line(indent + " el_val_t " + len_tmp + " = el_list_len(" + list_tmp + ");")
emit_line(indent + " for (el_val_t " + idx + " = 0; " + idx + " < " + len_tmp + "; " + idx + "++) {")
emit_line(indent + " el_val_t " + item + " = el_list_get(" + list_tmp + ", " + idx + ");")
cg_stmts(body, indent + " ")
cg_stmts(body, indent + " ", declared)
emit_line(indent + " }")
emit_line(indent + "}")
}
fn cg_for_stmt(expr: Map<String, Any>, indent: String) -> Void {
fn cg_for_stmt(expr: Map<String, Any>, indent: String, declared: [String]) -> Void {
let item: String = expr["item"]
let list_expr = expr["list"]
let body = expr["body"]
cg_for_body(item, list_expr, body, indent)
cg_for_body(item, list_expr, body, indent, declared)
}
fn cg_stmts(stmts: [Map<String, Any>], indent: String) -> Void {
fn cg_stmts(stmts: [Map<String, Any>], indent: String, declared: [String]) -> [String] {
let n: Int = native_list_len(stmts)
let i = 0
let decl = declared
while i < n {
let stmt = native_list_get(stmts, i)
cg_stmt(stmt, indent)
let decl = cg_stmt(stmt, indent, decl)
let i = i + 1
}
}
// cg_stmts_to_str emit statements, return accumulated lines as a single string.
// Used for if-expression body collection (not the main output path).
fn cg_stmts_to_str(stmts: [Map<String, Any>], indent: String) -> String {
// Not implemented for inline use; returns empty — if-exprs as statements
// are handled by cg_if_stmt instead.
""
decl
}
// Function declaration codegen
fn param_decl(param: Map<String, Any>, idx: Int) -> String {
let name: String = param["name"]
// All El parameters are el_val_t the universal value type that can hold
// integers directly and pointers (strings, lists, maps) via pointer-int cast.
"el_val_t " + name
}
@@ -524,15 +591,71 @@ fn params_to_c(params: [Map<String, Any>]) -> String {
out
}
// Transform a function body so that an implicit-return final expression
// becomes an explicit Return. El allows the last expression in a function
// body to be the return value (e.g. `fn lex(s) { ... tokens }` returns
// `tokens`). Without this transform, the codegen emits the bare expression
// and falls through to the trailing `return 0;`, losing the value.
//
// Rules: a body ending in a bare Expr whose inner expr is NOT a control-
// flow construct (If/For) is rewritten so that final Expr becomes a
// Return statement carrying the same value. Bodies whose final statement
// is already a Return, While, For, or a non-value-producing form pass
// through unchanged.
fn transform_implicit_return(body: [Map<String, Any>]) -> [Map<String, Any>] {
let n: Int = native_list_len(body)
if n == 0 { return body }
let last: Map<String, Any> = native_list_get(body, n - 1)
let last_kind: String = last["stmt"]
if last_kind == "Expr" {
let val = last["value"]
let val_kind: String = val["expr"]
// Skip control-flow expressions used as statements
if val_kind == "If" { return body }
if val_kind == "For" { return body }
// Replace the last bare Expr with a Return carrying the same value
let new_body: [Map<String, Any>] = native_list_empty()
let i = 0
while i < n - 1 {
let new_body = native_list_append(new_body, native_list_get(body, i))
let i = i + 1
}
let return_stmt: Map<String, Any> = { "stmt": "Return", "value": val }
let new_body = native_list_append(new_body, return_stmt)
return new_body
}
body
}
fn cg_fn(stmt: Map<String, Any>) -> Void {
let fn_name: String = stmt["name"]
// Skip El's `fn main()` C provides its own main() for top-level stmts
// and a duplicate `el_val_t main(void)` would collide with it.
if fn_name == "main" { return }
let params = stmt["params"]
let body = stmt["body"]
let ret_type: String = stmt["ret_type"]
let params_c: String = params_to_c(params)
// All El functions return el_val_t for uniformity.
emit_line("el_val_t " + fn_name + "(" + params_c + ") {")
cg_stmts(body, " ")
// Implicit return 0 (el_val_t) if no explicit return
// Seed declared with parameter names so reassignment works
let decl = native_list_empty()
let np: Int = native_list_len(params)
let pi = 0
while pi < np {
let param = native_list_get(params, pi)
let pname: String = param["name"]
let decl = native_list_append(decl, pname)
let pi = pi + 1
}
// Lift the final bare expression into an explicit return so implicit
// returns ("fn lex(s) { ... tokens }") actually return their value.
// Void-returning functions skip this wrapping `println(x)` in
// `return ` is a C type error.
let body_xformed = body
if !str_eq(ret_type, "Void") {
let body_xformed = transform_implicit_return(body)
}
cg_stmts(body_xformed, " ", decl)
emit_line(" return 0;")
emit_line("}")
emit_blank()
@@ -540,7 +663,6 @@ fn cg_fn(stmt: Map<String, Any>) -> Void {
// Top-level codegen
// Collect top-level statements that are NOT FnDefs these go into main().
fn is_fndef(stmt: Map<String, Any>) -> Bool {
let kind: String = stmt["stmt"]
if kind == "FnDef" { return true }
@@ -558,15 +680,13 @@ fn is_top_level_decl(stmt: Map<String, Any>) -> Bool {
// Entry point
fn codegen(stmts: [Map<String, Any>], source: String) -> String {
native_instr_reset()
// Preamble
emit_line("#include <stdint.h>")
emit_line("#include <stdlib.h>")
emit_line("#include \"el_runtime.h\"")
emit_blank()
// Emit forward declarations for all user-defined functions
// Forward declarations (skip `main` C provides its own)
let n: Int = native_list_len(stmts)
let i = 0
while i < n {
@@ -574,15 +694,17 @@ fn codegen(stmts: [Map<String, Any>], source: String) -> String {
let kind: String = stmt["stmt"]
if kind == "FnDef" {
let fn_name: String = stmt["name"]
let params = stmt["params"]
let params_c: String = params_to_c(params)
emit_line("el_val_t " + fn_name + "(" + params_c + ");")
if !str_eq(fn_name, "main") {
let params = stmt["params"]
let params_c: String = params_to_c(params)
emit_line("el_val_t " + fn_name + "(" + params_c + ");")
}
}
let i = i + 1
}
emit_blank()
// Emit function definitions
// Function definitions
let i = 0
while i < n {
let stmt = native_list_get(stmts, i)
@@ -592,18 +714,20 @@ fn codegen(stmts: [Map<String, Any>], source: String) -> String {
let i = i + 1
}
// Emit main() for top-level statements
emit_line("int main(void) {")
// main()
emit_line("int main(int argc, char** argv) {")
emit_line(" el_runtime_init_args(argc, argv);")
let main_decl = native_list_empty()
let i = 0
while i < n {
let stmt = native_list_get(stmts, i)
if is_fndef(stmt) {
// skip already emitted above
// skip
} else {
if is_top_level_decl(stmt) {
// skip compile-time only
// skip
} else {
cg_stmt(stmt, " ")
let main_decl = cg_stmt(stmt, " ", main_decl)
}
}
let i = i + 1
@@ -612,15 +736,6 @@ fn codegen(stmts: [Map<String, Any>], source: String) -> String {
emit_line("}")
emit_blank()
// Collect all emitted lines and join with newlines
let lines: [String] = native_instr_all()
let total: Int = native_list_len(lines)
let out = ""
let j = 0
while j < total {
let line: String = native_list_get(lines, j)
let out = out + line + "\n"
let j = j + 1
}
out
// Return empty string output was streamed via println
""
}