Files
el/el-compiler/src/codegen.el
T
Will Anderson 0fa9e749e1 runtime: engram_*_json accessors, http_set_handler dlsym, codegen int-call
Three changes that turned the runtime into something Engram-the-server
can actually run on top of.

1. engram_*_json accessors. The runtime's engram_get_node/search/scan/
   neighbors/activate return ElList/ElMap; passing those through
   json_stringify hit the type-erasure wall (an ElList* has no header
   that distinguishes it from a string pointer). Added pre-serialized
   sibling builtins:

     engram_get_node_json(id)         -> JSON object
     engram_search_json(query, limit) -> JSON array of node objects
     engram_scan_nodes_json(limit, offset)
     engram_neighbors_json(node_id, max_depth, direction)
     engram_activate_json(query, depth)
     engram_stats_json()

   Each walks the typed C structures and serializes directly, reusing
   the existing engram_emit_node_json / engram_emit_edge_json helpers
   from the snapshot path.

2. http_set_handler now falls back to dlsym(RTLD_DEFAULT, name) when
   the named handler isn't already in the C-level registry. El programs
   that define `fn handle_request(method, path, body) -> String` can
   register themselves just by calling http_set_handler("handle_request").
   No C glue required. Verified live on a real El server.

3. Codegen: extended int-typed dispatch on `+` to handle Calls. New
   helper is_int_call recognizes a known-int-returning builtin set:
   str_len, str_index_of, str_to_int, str_char_code, native_list_len,
   el_list_len, len, json_get_int, json_array_len, engram_node_count,
   engram_edge_count, time_now, time_now_utc, time_diff, time_add,
   time_from_parts, el_abs/max/min, float_to_int. With this,
   `pos + str_len(needle)` compiles to integer arithmetic instead of
   string concat. The earlier limitation noted in the previous commit
   (Ident + Call returning Int) is now closed.

Also: el_to_float / el_from_float moved to el_runtime.h as static
inlines so generated programs can use them. Eliminates the unused
inline definitions that were duplicating in the .c file.

Closure verified: stage1 vs stage2 byte-identical against the new
runtime. dist/platform/elc rebuilt; .prev4 preserved.

Engram server (engram/src/server.el) end-to-end:
  POST /api/nodes ×3 → 3 UUIDs returned
  POST /api/edges ×2 → linkage made
  GET /api/stats → {"node_count":3,"edge_count":2}
  GET /api/search?q=spreading&limit=5 → 1 hit, full node JSON
  POST /api/activate {"query":"Hebbian","depth":3}
    → seed node @ hop 0, strength 0.8
    → 1-hop neighbor @ strength 0.392 (= 0.8 × 0.7 weight × 0.7 decay)
  GET /api/neighbors/<id>?depth=2 → {node, edge, hops} triple
  POST /api/save → {"ok":true,"path":"..."}
  Server stays alive across all routes.

Snapshot save/load on restart still TODO — server starts with 0 nodes
even when a snapshot exists; investigation pending.
2026-04-30 13:44:41 -05:00

876 lines
31 KiB
EmacsLisp

// codegen.el El compiler C source code generator
//
// Input: list of AST statement maps (from parser.el)
// 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
// Escape a C string literal (double-quotes and backslashes).
fn c_escape(s: String) -> String {
let chars: [String] = native_string_chars(s)
let total: Int = native_list_len(chars)
let out = ""
let i = 0
while i < total {
let ch: String = native_list_get(chars, i)
if ch == "\"" {
let out = out + "\\\""
} else {
if ch == "\\" {
let out = out + "\\\\"
} else {
if ch == "\n" {
let out = out + "\\n"
} else {
if ch == "\r" {
let out = out + "\\r"
} else {
if ch == "\t" {
let out = out + "\\t"
} else {
let out = out + ch
}
}
}
}
}
let i = i + 1
}
out
}
fn c_str_lit(s: String) -> String {
"\"" + c_escape(s) + "\""
}
// Type mapping
fn el_type_to_c(type_str: String) -> String {
if type_str == "String" { return "const char*" }
if type_str == "Int" { return "int64_t" }
if type_str == "Bool" { return "int" }
if type_str == "Float" { return "double" }
if type_str == "Void" { return "void" }
if type_str == "void" { return "void" }
"void*"
}
// Code emission
//
// emit_line/emit_blank stream output directly via println.
// This avoids building a large string in memory.
fn emit_line(line: String) -> Void {
println(line)
}
fn emit_blank() -> Void {
println("")
}
// Operator helpers
fn binop_to_c(op: String) -> String {
if op == "Plus" { return "+" }
if op == "Minus" { return "-" }
if op == "Star" { return "*" }
if op == "Slash" { return "/" }
if op == "EqEq" { return "==" }
if op == "NotEq" { return "!=" }
if op == "Lt" { return "<" }
if op == "Gt" { return ">" }
if op == "LtEq" { return "<=" }
if op == "GtEq" { return ">=" }
if op == "And" { return "&&" }
if op == "Or" { return "||" }
op
}
// Expression codegen
//
// cg_expr returns a C expression string (not a statement).
fn cg_expr(expr: Map<String, Any>) -> String {
let kind: String = expr["expr"]
if kind == "Int" {
let v: String = expr["value"]
return v
}
if kind == "Float" {
// Wrap Float literals in el_from_float() so the bit pattern is
// preserved through the el_val_t (int64) slot. Without this,
// implicit doubleint64 conversion in C truncates `0.8` to `0`
// when passed to a builtin that expects el_val_t.
let v: String = expr["value"]
return "el_from_float(" + v + ")"
}
if kind == "Str" {
let v: String = expr["value"]
return "EL_STR(" + c_str_lit(v) + ")"
}
if kind == "Bool" {
let v: String = expr["value"]
if v == "true" { return "1" }
return "0"
}
if kind == "Nil" {
return "EL_NULL"
}
if kind == "Ident" {
let name: String = expr["name"]
return name
}
if kind == "Not" {
let inner = expr["inner"]
let inner_c: String = cg_expr(inner)
return "!" + inner_c
}
if kind == "Neg" {
let inner = expr["inner"]
let inner_c: String = cg_expr(inner)
return "(-" + inner_c + ")"
}
if kind == "BinOp" {
let op: String = expr["op"]
let left = expr["left"]
let right = expr["right"]
let left_c: String = cg_expr(left)
let right_c: String = cg_expr(right)
let left_kind: String = left["expr"]
let right_kind: String = right["expr"]
if op == "Plus" {
// 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 + ")"
}
// 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 + ")"
}
// Type-driven dispatch: if both sides are Idents declared
// with type Int (parameters annotated `: Int` or let bindings
// annotated `: Int`), this is arithmetic, not concat. The
// current-function int-name set is maintained by cg_fn /
// cg_stmt via state_set("__int_names", csv).
if left_kind == "Ident" {
if right_kind == "Ident" {
let lname: String = left["name"]
let rname: String = right["name"]
if is_int_name(lname) {
if is_int_name(rname) {
let op_c: String = binop_to_c(op)
return "(" + left_c + " " + op_c + " " + right_c + ")"
}
}
}
}
// Same dispatch for Ident-Int + Call-to-known-Int-builtin (and the
// mirror). Without this, expressions like `pos + str_len(s)` get
// string-concatenated. is_int_call walks a known-builtin list.
if left_kind == "Ident" {
if right_kind == "Call" {
let lname: String = left["name"]
if is_int_name(lname) {
if is_int_call(right) {
let op_c: String = binop_to_c(op)
return "(" + left_c + " " + op_c + " " + right_c + ")"
}
}
}
}
if right_kind == "Ident" {
if left_kind == "Call" {
let rname: String = right["name"]
if is_int_name(rname) {
if is_int_call(left) {
let op_c: String = binop_to_c(op)
return "(" + left_c + " " + op_c + " " + right_c + ")"
}
}
}
}
if left_kind == "Call" {
if right_kind == "Call" {
if is_int_call(left) {
if is_int_call(right) {
let op_c: String = binop_to_c(op)
return "(" + left_c + " " + op_c + " " + right_c + ")"
}
}
}
return "el_str_concat(" + left_c + ", " + right_c + ")"
}
if right_kind == "Call" {
return "el_str_concat(" + left_c + ", " + right_c + ")"
}
if left_kind == "BinOp" {
let left_op: String = left["op"]
if left_op == "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 without int-typed evidence
// fall back to string concat (the historical heuristic).
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 + ")"
}
if kind == "Call" {
let func = expr["func"]
let args = expr["args"]
let arity: Int = native_list_len(args)
let func_kind: String = func["expr"]
let args_c = ""
let i = 0
while i < arity {
let arg = native_list_get(args, i)
let arg_c: String = cg_expr(arg)
if i > 0 {
let args_c = args_c + ", "
}
let args_c = args_c + arg_c
let i = i + 1
}
if func_kind == "Ident" {
let fn_name: String = func["name"]
return fn_name + "(" + args_c + ")"
}
if func_kind == "Field" {
let obj = func["object"]
let field: String = func["field"]
let obj_c: String = cg_expr(obj)
if arity > 0 {
return field + "(" + obj_c + ", " + args_c + ")"
}
return field + "(" + obj_c + ")"
}
let fn_c: String = cg_expr(func)
return fn_c + "(" + args_c + ")"
}
if kind == "Field" {
let obj = expr["object"]
let field: String = expr["field"]
let obj_c: String = cg_expr(obj)
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 + ")"
}
if kind == "Array" {
let elems = expr["elems"]
let n: Int = native_list_len(elems)
let items = ""
let i = 0
while i < n {
let elem = native_list_get(elems, i)
let elem_c: String = cg_expr(elem)
if i > 0 {
let items = items + ", "
}
let items = items + elem_c
let i = i + 1
}
return "el_list_new(" + native_int_to_str(n) + ", " + items + ")"
}
if kind == "Map" {
let pairs = expr["pairs"]
let n: Int = native_list_len(pairs)
let items = ""
let i = 0
while i < n {
let pair = native_list_get(pairs, i)
let key: String = pair["key"]
let val = pair["value"]
let val_c: String = cg_expr(val)
if i > 0 {
let items = items + ", "
}
let items = items + c_str_lit(key) + ", " + val_c
let i = i + 1
}
return "el_map_new(" + native_int_to_str(n) + ", " + items + ")"
}
if kind == "Try" {
let inner = expr["inner"]
return cg_expr(inner)
}
if kind == "If" {
let cond = expr["cond"]
let cond_c: String = cg_expr(cond)
return "/* if-expr */ ((" + cond_c + ") ? (el_val_t)1 : (el_val_t)0)"
}
"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 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, 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)
// If the binding is annotated `: Int` and val is an Int literal,
// register `name` in the per-function int-name set so that later
// `name + ...` dispatches to arithmetic, not concat.
let ltype: String = stmt["type"]
if str_eq(ltype, "Int") {
add_int_name(name)
}
let vk: String = val["expr"]
if str_eq(vk, "Int") {
add_int_name(name)
}
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 0;")
} else {
let val_c: String = cg_expr(val)
emit_line(indent + "return " + val_c + ";")
}
return declared
}
if kind == "Expr" {
let val = stmt["value"]
let val_kind: String = val["expr"]
if val_kind == "If" {
cg_if_stmt(val, indent, declared)
return declared
}
if val_kind == "For" {
cg_for_stmt(val, indent, declared)
return declared
}
let val_c: String = cg_expr(val)
emit_line(indent + val_c + ";")
return declared
}
if kind == "While" {
let cond = stmt["cond"]
let body = stmt["body"]
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 + " ", declared)
emit_line(indent + "}")
return declared
}
if kind == "For" {
let item: String = stmt["item"]
let list_expr = stmt["list"]
let body = stmt["body"]
cg_for_body(item, list_expr, body, indent, declared)
return declared
}
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.
fn strip_outer_parens(s: String) -> String {
let chars: [String] = native_string_chars(s)
let n: Int = native_list_len(chars)
if n < 2 { return s }
let first: String = native_list_get(chars, 0)
let last: String = native_list_get(chars, n - 1)
if first == "(" {
if last == ")" {
let depth = 1
let i = 1
let balanced = true
while i < n - 1 {
let ch: String = native_list_get(chars, i)
if ch == "(" {
let depth = depth + 1
}
if ch == ")" {
let depth = depth - 1
if depth == 0 {
let balanced = false
let i = n
}
}
let i = i + 1
}
if balanced {
let inner = ""
let j = 1
while j < n - 1 {
let ch: String = native_list_get(chars, j)
let inner = inner + ch
let j = j + 1
}
return inner
}
}
}
s
}
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)
let cond_c = strip_outer_parens(cond_c)
emit_line(indent + "if (" + cond_c + ") {")
cg_stmts(then_stmts, indent + " ", declared)
if has_else {
emit_line(indent + "} else {")
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, declared: [String]) -> Void {
let list_c: String = cg_expr(list_expr)
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 + " ", declared)
emit_line(indent + " }")
emit_line(indent + "}")
}
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, declared)
}
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)
let decl = cg_stmt(stmt, indent, decl)
let i = i + 1
}
decl
}
// Function declaration codegen
fn param_decl(param: Map<String, Any>, idx: Int) -> String {
let name: String = param["name"]
"el_val_t " + name
}
fn params_to_c(params: [Map<String, Any>]) -> String {
let n: Int = native_list_len(params)
if n == 0 { return "void" }
let out = ""
let i = 0
while i < n {
let param = native_list_get(params, i)
let decl: String = param_decl(param, i)
if i > 0 {
let out = out + ", "
}
let out = out + decl
let i = i + 1
}
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
}
// Test whether `name` is currently registered as an Int-typed identifier
// for the function being codegened. The set is maintained as a comma-
// bounded CSV in process state; cg_fn seeds it from typed parameters,
// cg_stmt extends it from typed `let` bindings.
fn is_int_name(name: String) -> Bool {
let csv: String = state_get("__int_names")
if str_eq(csv, "") { return false }
return str_contains(csv, "," + name + ",")
}
// Known runtime builtins that return Int. Used to dispatch arithmetic vs
// string-concat on `+` when one side is a Call. New builtins must be added
// here when they return Int and may participate in arithmetic.
fn is_int_call(call_expr: Map<String, Any>) -> Bool {
let func = call_expr["func"]
let fk: String = func["expr"]
if !str_eq(fk, "Ident") { return false }
let name: String = func["name"]
if str_eq(name, "str_len") { return true }
if str_eq(name, "str_index_of") { return true }
if str_eq(name, "str_to_int") { return true }
if str_eq(name, "str_char_code") { return true }
if str_eq(name, "native_list_len") { return true }
if str_eq(name, "el_list_len") { return true }
if str_eq(name, "len") { return true }
if str_eq(name, "json_get_int") { return true }
if str_eq(name, "json_array_len") { return true }
if str_eq(name, "engram_node_count") { return true }
if str_eq(name, "engram_edge_count") { return true }
if str_eq(name, "time_now") { return true }
if str_eq(name, "time_now_utc") { return true }
if str_eq(name, "time_diff") { return true }
if str_eq(name, "time_add") { return true }
if str_eq(name, "time_from_parts") { return true }
if str_eq(name, "el_abs") { return true }
if str_eq(name, "el_max") { return true }
if str_eq(name, "el_min") { return true }
if str_eq(name, "float_to_int") { return true }
return false
}
fn add_int_name(name: String) -> Bool {
let csv: String = state_get("__int_names")
if str_eq(csv, "") { csv = "," }
let key: String = "," + name + ","
if str_contains(csv, key) { return true }
state_set("__int_names", csv + name + ",")
return true
}
fn build_int_names_for_params(params: [Map<String, Any>]) -> Bool {
state_set("__int_names", ",")
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 ptype: String = param["type"]
if str_eq(ptype, "Int") {
add_int_name(pname)
}
let pi = pi + 1
}
return true
}
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)
// Seed the per-function int-name set so the `+` codegen can dispatch
// arithmetic vs concat on type-annotated identifiers.
build_int_names_for_params(params)
emit_line("el_val_t " + fn_name + "(" + params_c + ") {")
// 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()
}
// Top-level codegen
fn is_fndef(stmt: Map<String, Any>) -> Bool {
let kind: String = stmt["stmt"]
if kind == "FnDef" { return true }
false
}
fn is_top_level_decl(stmt: Map<String, Any>) -> Bool {
let kind: String = stmt["stmt"]
if kind == "TypeDef" { return true }
if kind == "EnumDef" { return true }
if kind == "Import" { return true }
false
}
// Entry point
fn codegen(stmts: [Map<String, Any>], source: String) -> String {
// Preamble
emit_line("#include <stdint.h>")
emit_line("#include <stdlib.h>")
emit_line("#include \"el_runtime.h\"")
emit_blank()
// Forward declarations (skip `main` C provides its own)
let n: Int = native_list_len(stmts)
let i = 0
while i < n {
let stmt = native_list_get(stmts, i)
let kind: String = stmt["stmt"]
if kind == "FnDef" {
let fn_name: String = stmt["name"]
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()
// Function definitions
let i = 0
while i < n {
let stmt = native_list_get(stmts, i)
if is_fndef(stmt) {
cg_fn(stmt)
}
let i = i + 1
}
// 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
} else {
if is_top_level_decl(stmt) {
// skip
} else {
let main_decl = cg_stmt(stmt, " ", main_decl)
}
}
let i = i + 1
}
emit_line(" return 0;")
emit_line("}")
emit_blank()
// Return empty string output was streamed via println
""
}