From ede087eb046c7eca3e3573cfc441e25df279040e Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Wed, 29 Apr 2026 22:33:27 -0500 Subject: [PATCH] =?UTF-8?q?codegen:=20emit=20C=20instead=20of=20bytecode?= =?UTF-8?q?=20=E2=80=94=20El=20is=20now=20natively=20compiled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 -o hello hello.c el_runtime.c --- el-compiler/runtime/el_runtime.c | 414 ++++++++++++++ el-compiler/runtime/el_runtime.h | 108 ++++ el-compiler/src/codegen.el | 924 ++++++++++++++----------------- el-compiler/src/compiler.el | 20 +- 4 files changed, 948 insertions(+), 518 deletions(-) create mode 100644 el-compiler/runtime/el_runtime.c create mode 100644 el-compiler/runtime/el_runtime.h diff --git a/el-compiler/runtime/el_runtime.c b/el-compiler/runtime/el_runtime.c new file mode 100644 index 0000000..b570426 --- /dev/null +++ b/el-compiler/runtime/el_runtime.c @@ -0,0 +1,414 @@ +/* + * el_runtime.c — El language C runtime implementation + * + * All functions use el_val_t (= int64_t) as the universal value type. + * Strings are transported as their pointer address cast to int64_t. + * On any 64-bit system sizeof(pointer) <= sizeof(int64_t), so this is safe. + * + * Compile with: + * cc -std=c11 -I -o .c el_runtime.c + */ + +#include "el_runtime.h" + +#include +#include +#include +#include +#include +#include + +/* ── Internal allocators ─────────────────────────────────────────────────── */ + +static char* el_strdup(const char* s) { + if (!s) return strdup(""); + return strdup(s); +} + +static char* el_strbuf(size_t n) { + char* p = malloc(n + 1); + if (!p) { fputs("el_runtime: out of memory\n", stderr); exit(1); } + p[0] = '\0'; + return p; +} + +/* Wrap an allocated C string as el_val_t */ +static el_val_t el_wrap_str(char* s) { + return EL_STR(s); +} + +/* ── I/O ──────────────────────────────────────────────────────────────────── */ + +void println(el_val_t s) { + const char* str = EL_CSTR(s); + if (str) puts(str); + else puts(""); +} + +void print(el_val_t s) { + const char* str = EL_CSTR(s); + if (str) fputs(str, stdout); +} + +el_val_t readline(void) { + char buf[4096]; + if (!fgets(buf, sizeof(buf), stdin)) return el_wrap_str(el_strdup("")); + size_t len = strlen(buf); + if (len > 0 && buf[len - 1] == '\n') buf[len - 1] = '\0'; + return el_wrap_str(el_strdup(buf)); +} + +/* ── String builtins ─────────────────────────────────────────────────────── */ + +el_val_t el_str_concat(el_val_t av, el_val_t bv) { + const char* a = EL_CSTR(av); + const char* b = EL_CSTR(bv); + if (!a) a = ""; + if (!b) b = ""; + size_t la = strlen(a); + size_t lb = strlen(b); + char* out = el_strbuf(la + lb); + memcpy(out, a, la); + memcpy(out + la, b, lb); + out[la + lb] = '\0'; + return el_wrap_str(out); +} + +el_val_t str_eq(el_val_t av, el_val_t bv) { + const char* a = EL_CSTR(av); + const char* b = EL_CSTR(bv); + if (!a || !b) return (el_val_t)(a == b); + return (el_val_t)(strcmp(a, b) == 0); +} + +el_val_t str_starts_with(el_val_t sv, el_val_t prefv) { + const char* s = EL_CSTR(sv); + const char* prefix = EL_CSTR(prefv); + if (!s || !prefix) return 0; + size_t lp = strlen(prefix); + return (el_val_t)(strncmp(s, prefix, lp) == 0); +} + +el_val_t str_ends_with(el_val_t sv, el_val_t sufv) { + const char* s = EL_CSTR(sv); + const char* suffix = EL_CSTR(sufv); + if (!s || !suffix) return 0; + size_t ls = strlen(s); + size_t lsuf = strlen(suffix); + if (lsuf > ls) return 0; + return (el_val_t)(strcmp(s + ls - lsuf, suffix) == 0); +} + +el_val_t str_len(el_val_t sv) { + const char* s = EL_CSTR(sv); + if (!s) return 0; + return (el_val_t)strlen(s); +} + +el_val_t str_concat(el_val_t a, el_val_t b) { + return el_str_concat(a, b); +} + +el_val_t int_to_str(el_val_t n) { + char buf[32]; + snprintf(buf, sizeof(buf), "%lld", (long long)n); + return el_wrap_str(el_strdup(buf)); +} + +el_val_t str_to_int(el_val_t sv) { + const char* s = EL_CSTR(sv); + if (!s) return 0; + return (el_val_t)atoll(s); +} + +el_val_t str_slice(el_val_t sv, el_val_t start, el_val_t end) { + const char* s = EL_CSTR(sv); + if (!s) return el_wrap_str(el_strdup("")); + int64_t len = (int64_t)strlen(s); + if (start < 0) start = 0; + if (end > len) end = len; + if (start >= end) return el_wrap_str(el_strdup("")); + int64_t sz = end - start; + char* out = el_strbuf((size_t)sz); + memcpy(out, s + start, (size_t)sz); + out[sz] = '\0'; + return el_wrap_str(out); +} + +el_val_t str_contains(el_val_t sv, el_val_t subv) { + const char* s = EL_CSTR(sv); + const char* sub = EL_CSTR(subv); + if (!s || !sub) return 0; + return (el_val_t)(strstr(s, sub) != NULL); +} + +el_val_t str_replace(el_val_t sv, el_val_t fromv, el_val_t tov) { + const char* s = EL_CSTR(sv); + const char* from = EL_CSTR(fromv); + const char* to = EL_CSTR(tov); + if (!s || !from || !to) return el_wrap_str(el_strdup(s ? s : "")); + size_t ls = strlen(s); + size_t lf = strlen(from); + size_t lt = strlen(to); + if (lf == 0) return el_wrap_str(el_strdup(s)); + size_t count = 0; + const char* p = s; + while ((p = strstr(p, from)) != NULL) { count++; p += lf; } + size_t out_sz = ls + count * lt + 1; + char* out = el_strbuf(out_sz); + char* dst = out; + p = s; + const char* found; + while ((found = strstr(p, from)) != NULL) { + size_t chunk = (size_t)(found - p); + memcpy(dst, p, chunk); dst += chunk; + memcpy(dst, to, lt); dst += lt; + p = found + lf; + } + strcpy(dst, p); + return el_wrap_str(out); +} + +el_val_t str_to_upper(el_val_t sv) { + const char* s = EL_CSTR(sv); + if (!s) return el_wrap_str(el_strdup("")); + size_t n = strlen(s); + char* out = el_strbuf(n); + for (size_t i = 0; i < n; i++) out[i] = (char)toupper((unsigned char)s[i]); + out[n] = '\0'; + return el_wrap_str(out); +} + +el_val_t str_to_lower(el_val_t sv) { + const char* s = EL_CSTR(sv); + if (!s) return el_wrap_str(el_strdup("")); + size_t n = strlen(s); + char* out = el_strbuf(n); + for (size_t i = 0; i < n; i++) out[i] = (char)tolower((unsigned char)s[i]); + out[n] = '\0'; + return el_wrap_str(out); +} + +el_val_t str_trim(el_val_t sv) { + const char* s = EL_CSTR(sv); + if (!s) return el_wrap_str(el_strdup("")); + while (*s && isspace((unsigned char)*s)) s++; + size_t n = strlen(s); + while (n > 0 && isspace((unsigned char)s[n - 1])) n--; + char* out = el_strbuf(n); + memcpy(out, s, n); + out[n] = '\0'; + return el_wrap_str(out); +} + +/* ── Math ────────────────────────────────────────────────────────────────── */ + +el_val_t el_abs(el_val_t n) { return n < 0 ? -n : n; } +el_val_t el_max(el_val_t a, el_val_t b) { return a > b ? a : b; } +el_val_t el_min(el_val_t a, el_val_t b) { return a < b ? a : b; } + +/* ── List ────────────────────────────────────────────────────────────────── */ +/* + * Dynamic array header: + * int64_t capacity + * int64_t length + * el_val_t elems[] + */ + +typedef struct { + int64_t capacity; + int64_t length; + el_val_t elems[1]; +} ElList; + +static ElList* list_alloc(int64_t cap) { + ElList* lst = malloc(sizeof(ElList) + (size_t)(cap > 1 ? cap - 1 : 0) * sizeof(el_val_t)); + if (!lst) { fputs("el_runtime: out of memory\n", stderr); exit(1); } + lst->capacity = cap; + lst->length = 0; + return lst; +} + +el_val_t el_list_empty(void) { + return EL_STR(list_alloc(4)); +} + +el_val_t el_list_new(el_val_t count, ...) { + ElList* lst = list_alloc(count > 0 ? count : 4); + va_list ap; + va_start(ap, count); + for (int64_t i = 0; i < count; i++) { + lst->elems[i] = va_arg(ap, el_val_t); + } + va_end(ap); + lst->length = count; + return EL_STR(lst); +} + +el_val_t el_list_len(el_val_t listv) { + ElList* lst = (ElList*)(uintptr_t)listv; + if (!lst) return 0; + return lst->length; +} + +el_val_t el_list_get(el_val_t listv, el_val_t index) { + ElList* lst = (ElList*)(uintptr_t)listv; + if (!lst) return 0; + if (index < 0 || index >= lst->length) return 0; + return lst->elems[index]; +} + +el_val_t el_list_append(el_val_t listv, el_val_t elem) { + ElList* lst = (ElList*)(uintptr_t)listv; + if (!lst) { lst = list_alloc(4); } + if (lst->length >= lst->capacity) { + int64_t new_cap = lst->capacity * 2; + lst = realloc(lst, sizeof(ElList) + (size_t)(new_cap - 1) * sizeof(el_val_t)); + if (!lst) { fputs("el_runtime: out of memory\n", stderr); exit(1); } + lst->capacity = new_cap; + } + lst->elems[lst->length++] = elem; + return EL_STR(lst); +} + +/* ── Map ─────────────────────────────────────────────────────────────────── */ + +typedef struct { + int64_t count; + el_val_t* keys; + el_val_t* values; +} ElMap; + +el_val_t el_map_new(el_val_t pair_count, ...) { + ElMap* m = malloc(sizeof(ElMap)); + if (!m) { fputs("el_runtime: out of memory\n", stderr); exit(1); } + m->count = pair_count; + m->keys = malloc(sizeof(el_val_t) * (size_t)(pair_count > 0 ? pair_count : 1)); + m->values = malloc(sizeof(el_val_t) * (size_t)(pair_count > 0 ? pair_count : 1)); + va_list ap; + va_start(ap, pair_count); + for (int64_t i = 0; i < pair_count; i++) { + m->keys[i] = va_arg(ap, el_val_t); + m->values[i] = va_arg(ap, el_val_t); + } + va_end(ap); + return EL_STR(m); +} + +static ElMap* as_map(el_val_t v) { return (ElMap*)(uintptr_t)v; } + +el_val_t el_map_get(el_val_t mapv, el_val_t keyv) { + ElMap* m = as_map(mapv); + const char* key = EL_CSTR(keyv); + if (!m || !key) return 0; + for (int64_t i = 0; i < m->count; i++) { + const char* k = EL_CSTR(m->keys[i]); + if (k && strcmp(k, key) == 0) return m->values[i]; + } + return 0; +} + +el_val_t el_get_field(el_val_t mapv, el_val_t keyv) { + return el_map_get(mapv, keyv); +} + +el_val_t el_map_set(el_val_t mapv, el_val_t keyv, el_val_t value) { + ElMap* m = as_map(mapv); + const char* key = EL_CSTR(keyv); + if (!m) return 0; + for (int64_t i = 0; i < m->count; i++) { + const char* k = EL_CSTR(m->keys[i]); + if (k && strcmp(k, key) == 0) { m->values[i] = value; return mapv; } + } + int64_t nc = m->count + 1; + m->keys = realloc(m->keys, sizeof(el_val_t) * (size_t)nc); + m->values = realloc(m->values, sizeof(el_val_t) * (size_t)nc); + m->keys[m->count] = keyv; + m->values[m->count] = value; + m->count = nc; + return mapv; +} + +/* ── HTTP stubs ──────────────────────────────────────────────────────────── */ + +el_val_t http_get(el_val_t url) { + (void)url; + return el_wrap_str(el_strdup("")); +} + +el_val_t http_post(el_val_t url, el_val_t body) { + (void)url; (void)body; + return el_wrap_str(el_strdup("")); +} + +void http_serve(el_val_t port, el_val_t handler) { + (void)port; (void)handler; +} + +/* ── Filesystem ──────────────────────────────────────────────────────────── */ + +el_val_t fs_read(el_val_t pathv) { + const char* path = EL_CSTR(pathv); + if (!path) return el_wrap_str(el_strdup("")); + FILE* f = fopen(path, "rb"); + if (!f) return el_wrap_str(el_strdup("")); + fseek(f, 0, SEEK_END); + long sz = ftell(f); + rewind(f); + char* buf = el_strbuf((size_t)sz); + size_t got = fread(buf, 1, (size_t)sz, f); + buf[got] = '\0'; + fclose(f); + return el_wrap_str(buf); +} + +el_val_t fs_write(el_val_t pathv, el_val_t contentv) { + const char* path = EL_CSTR(pathv); + const char* content = EL_CSTR(contentv); + if (!path || !content) return 0; + FILE* f = fopen(path, "wb"); + if (!f) return 0; + size_t n = strlen(content); + size_t written = fwrite(content, 1, n, f); + fclose(f); + return written == n ? 1 : 0; +} + +/* ── JSON ────────────────────────────────────────────────────────────────── */ + +el_val_t json_get(el_val_t jsonv, el_val_t keyv) { + const char* json = EL_CSTR(jsonv); + const char* key = EL_CSTR(keyv); + if (!json || !key) return el_wrap_str(el_strdup("")); + size_t klen = strlen(key); + char* pattern = el_strbuf(klen + 4); + snprintf(pattern, klen + 5, "\"%s\":", key); + const char* p = strstr(json, pattern); + free(pattern); + if (!p) return el_wrap_str(el_strdup("")); + p += strlen(key) + 3; /* skip "key": */ + while (*p == ' ' || *p == '\t' || *p == '\n') p++; + if (*p == '"') { + p++; + const char* start = p; + while (*p && !(*p == '"' && *(p-1) != '\\')) p++; + size_t len = (size_t)(p - start); + char* out = el_strbuf(len); + memcpy(out, start, len); + out[len] = '\0'; + return el_wrap_str(out); + } + const char* start = p; + while (*p && *p != ',' && *p != '}' && *p != ']' && *p != '\n') p++; + size_t len = (size_t)(p - start); + char* out = el_strbuf(len); + memcpy(out, start, len); + out[len] = '\0'; + return el_wrap_str(out); +} + +/* ── Process ─────────────────────────────────────────────────────────────── */ + +void exit_program(el_val_t code) { + exit((int)code); +} diff --git a/el-compiler/runtime/el_runtime.h b/el-compiler/runtime/el_runtime.h new file mode 100644 index 0000000..51d409c --- /dev/null +++ b/el-compiler/runtime/el_runtime.h @@ -0,0 +1,108 @@ +/* + * el_runtime.h — El language C runtime header + * + * Declares all built-in functions available to compiled El programs. + * Include this in every generated .c file. + * + * Value model: + * All El values are represented as el_val_t (= int64_t). + * On 64-bit systems a pointer fits in int64_t. + * String values are cast: (el_val_t)(uintptr_t)"hello" + * Integer values are stored directly. + * This lets arithmetic work naturally while still passing strings around. + * + * Type conventions (El -> C): + * String -> el_val_t (holds const char* via uintptr_t cast) + * Int -> el_val_t + * Bool -> el_val_t (0 = false, nonzero = true) + * Any -> el_val_t + * Void -> void + * + * Macros for convenience: + * EL_STR(s) cast string literal to el_val_t + * EL_CSTR(v) cast el_val_t back to const char* + * EL_INT(v) identity — el_val_t is already int64_t + */ + +#pragma once + +#include +#include + +typedef int64_t el_val_t; + +#define EL_STR(s) ((el_val_t)(uintptr_t)(s)) +#define EL_CSTR(v) ((const char*)(uintptr_t)(v)) +#define EL_INT(v) (v) +#define EL_NULL ((el_val_t)0) + +#ifdef __cplusplus +extern "C" { +#endif + +/* ── I/O ──────────────────────────────────────────────────────────────────── */ + +void println(el_val_t s); +void print(el_val_t s); +el_val_t readline(void); + +/* ── String builtins ─────────────────────────────────────────────────────── */ + +el_val_t el_str_concat(el_val_t a, el_val_t b); +el_val_t str_eq(el_val_t a, el_val_t b); +el_val_t str_starts_with(el_val_t s, el_val_t prefix); +el_val_t str_ends_with(el_val_t s, el_val_t suffix); +el_val_t str_len(el_val_t s); +el_val_t str_concat(el_val_t a, el_val_t b); +el_val_t int_to_str(el_val_t n); +el_val_t str_to_int(el_val_t s); +el_val_t str_slice(el_val_t s, el_val_t start, el_val_t end); +el_val_t str_contains(el_val_t s, el_val_t sub); +el_val_t str_replace(el_val_t s, el_val_t from, el_val_t to); +el_val_t str_to_upper(el_val_t s); +el_val_t str_to_lower(el_val_t s); +el_val_t str_trim(el_val_t s); + +/* ── Math ────────────────────────────────────────────────────────────────── */ + +el_val_t el_abs(el_val_t n); +el_val_t el_max(el_val_t a, el_val_t b); +el_val_t el_min(el_val_t a, el_val_t b); + +/* ── List ────────────────────────────────────────────────────────────────── */ + +el_val_t el_list_new(el_val_t count, ...); +el_val_t el_list_len(el_val_t list); +el_val_t el_list_get(el_val_t list, el_val_t index); +el_val_t el_list_append(el_val_t list, el_val_t elem); +el_val_t el_list_empty(void); + +/* ── Map ─────────────────────────────────────────────────────────────────── */ + +el_val_t el_map_new(el_val_t pair_count, ...); +el_val_t el_get_field(el_val_t map, el_val_t key); +el_val_t el_map_get(el_val_t map, el_val_t key); +el_val_t el_map_set(el_val_t map, el_val_t key, el_val_t value); + +/* ── HTTP ─────────────────────────────────────────────────────────────────── */ + +el_val_t http_get(el_val_t url); +el_val_t http_post(el_val_t url, el_val_t body); +void http_serve(el_val_t port, el_val_t handler); + +/* ── Filesystem ──────────────────────────────────────────────────────────── */ + +el_val_t fs_read(el_val_t path); +el_val_t fs_write(el_val_t path, el_val_t content); + +/* ── JSON ────────────────────────────────────────────────────────────────── */ + +el_val_t json_get(el_val_t json, el_val_t key); + +/* ── Process ─────────────────────────────────────────────────────────────── */ + +void exit_program(el_val_t code); + +#ifdef __cplusplus +} +#endif diff --git a/el-compiler/src/codegen.el b/el-compiler/src/codegen.el index ee3b7e0..41ca592 100644 --- a/el-compiler/src/codegen.el +++ b/el-compiler/src/codegen.el @@ -1,22 +1,17 @@ -// codegen.el — el self-hosting bytecode code generator +// codegen.el — El compiler C source code generator // // Input: list of AST statement maps (from parser.el) -// Output: JSON string encoding an array of bytecode instructions +// Output: C source string // -// Bytecode JSON format matches the Rust serde output exactly. -// See the el-compiler/src/bytecode.rs for the canonical enum. +// 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], source: String) -> String -// -// Performance: instruction accumulation uses the VM-native global buffer -// (native_instr_push / native_instr_len / native_instr_patch / native_instr_all) -// instead of passing a growing list through function arguments. This avoids O(n²) -// cloning and keeps compilation time linear in the number of instructions. -// ── JSON helpers ────────────────────────────────────────────────────────────── +// ── String helpers ──────────────────────────────────────────────────────────── -// Escape a string for JSON embedding. -fn json_escape(s: String) -> String { +// 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 = "" @@ -49,670 +44,583 @@ fn json_escape(s: String) -> String { out } -fn json_str(s: String) -> String { - "\"" + json_escape(s) + "\"" +fn c_str_lit(s: String) -> String { + "\"" + c_escape(s) + "\"" } -fn json_int(n: Int) -> String { - native_int_to_str(n) +// ── 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" } + if type_str == "Bool" { return "int" } + 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*" } -fn json_bool(b: Bool) -> String { - if b { return "true" } - "false" -} - -// ── Codegen state ───────────────────────────────────────────────────────────── +// ── Code buffer ─────────────────────────────────────────────────────────────── // -// The instruction accumulator is stored in the VM's global instr_buf. -// native_instr_push(s) — append JSON instruction string -// native_instr_len() — current instruction count -// native_instr_get(i) — get instruction at index i -// native_instr_patch(i,s) — replace instruction at index i -// native_instr_all() — return full list as [String] +// We accumulate output lines into the VM's native instruction buffer +// (repurposed as a line buffer). Each "instruction" is a line of C text. -// ── Instruction emitters ────────────────────────────────────────────────────── - -fn emit_halt() -> Void { - native_instr_push("\"Halt\"") +fn emit_line(line: String) -> Void { + native_instr_push(line) } -fn emit_pop() -> Void { - native_instr_push("\"Pop\"") +fn emit_blank() -> Void { + native_instr_push("") } -fn emit_return() -> Void { - native_instr_push("\"Return\"") +// ── 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 } -fn emit_add() -> Void { - native_instr_push("\"Add\"") -} +// ── Unique label generator ───────────────────────────────────────────────── -fn emit_sub() -> Void { - native_instr_push("\"Sub\"") -} - -fn emit_mul() -> Void { - native_instr_push("\"Mul\"") -} - -fn emit_div() -> Void { - native_instr_push("\"Div\"") -} - -fn emit_eq() -> Void { - native_instr_push("\"Eq\"") -} - -fn emit_not_eq() -> Void { - native_instr_push("\"NotEq\"") -} - -fn emit_lt() -> Void { - native_instr_push("\"Lt\"") -} - -fn emit_gt() -> Void { - native_instr_push("\"Gt\"") -} - -fn emit_lt_eq() -> Void { - native_instr_push("\"LtEq\"") -} - -fn emit_gt_eq() -> Void { - native_instr_push("\"GtEq\"") -} - -fn emit_and() -> Void { - native_instr_push("\"And\"") -} - -fn emit_or() -> Void { - native_instr_push("\"Or\"") -} - -fn emit_not() -> Void { - native_instr_push("\"Not\"") -} - -fn emit_get_index() -> Void { - native_instr_push("\"GetIndex\"") -} - -fn emit_push_nil() -> Void { - native_instr_push("{\"Push\":\"Nil\"}") -} - -fn emit_push_int(n: Int) -> Void { - native_instr_push("{\"Push\":{\"Int\":" + json_int(n) + "}}") -} - -fn emit_push_bool(b: Bool) -> Void { - native_instr_push("{\"Push\":{\"Bool\":" + json_bool(b) + "}}") -} - -fn emit_push_str(s: String) -> Void { - native_instr_push("{\"Push\":{\"Str\":" + json_str(s) + "}}") -} - -fn emit_load(name: String) -> Void { - native_instr_push("{\"LoadLocal\":" + json_str(name) + "}") -} - -fn emit_store(name: String) -> Void { - native_instr_push("{\"StoreLocal\":" + json_str(name) + "}") -} - -fn emit_call(name: String, arity: Int) -> Void { - native_instr_push("{\"Call\":{\"name\":" + json_str(name) + ",\"arity\":" + json_int(arity) + "}}") -} - -fn emit_get_field(field: String) -> Void { - native_instr_push("{\"GetField\":" + json_str(field) + "}") -} - -fn emit_build_map(n: Int) -> Void { - native_instr_push("{\"BuildMap\":" + json_int(n) + "}") -} - -fn emit_build_list(n: Int) -> Void { - native_instr_push("{\"BuildList\":" + json_int(n) + "}") -} - -// Emit a placeholder Jump and return its index (for later patching). -fn emit_jump_placeholder() -> Int { - let idx: Int = native_instr_len() - native_instr_push("{\"Jump\":0}") - idx -} - -fn emit_jump_if_not_placeholder() -> Int { - let idx: Int = native_instr_len() - native_instr_push("{\"JumpIfNot\":0}") - idx -} - -fn emit_jump_to(dest: Int) -> Void { - let here: Int = native_instr_len() - let offset: Int = dest - (here + 1) - native_instr_push("{\"Jump\":" + json_int(offset) + "}") -} - -// Patch a previously-emitted placeholder jump instruction. -fn patch_instr(idx: Int, dest: Int) -> Void { - let offset: Int = dest - (idx + 1) - let original: String = native_instr_get(idx) - if native_string_contains(original, "JumpIfNot") { - native_instr_patch(idx, "{\"JumpIfNot\":" + json_int(offset) + "}") - } else { - if native_string_contains(original, "JumpIf") { - native_instr_patch(idx, "{\"JumpIf\":" + json_int(offset) + "}") - } else { - native_instr_patch(idx, "{\"Jump\":" + json_int(offset) + "}") - } - } +// 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). -fn cg_expr(expr: Map) -> Void { +fn cg_expr(expr: Map) -> String { let kind: String = expr["expr"] if kind == "Int" { let v: String = expr["value"] - let n: Int = native_str_to_int(v) - emit_push_int(n) - return + return v } if kind == "Float" { let v: String = expr["value"] - emit_push_str(v) - return + return v } if kind == "Str" { let v: String = expr["value"] - emit_push_str(v) - return + // String literals are wrapped in EL_STR() to convert const char* to el_val_t + return "EL_STR(" + c_str_lit(v) + ")" } if kind == "Bool" { let v: String = expr["value"] - if v == "true" { - emit_push_bool(true) - } else { - emit_push_bool(false) - } - return + if v == "true" { return "1" } + return "0" } if kind == "Nil" { - emit_push_nil() - return + return "EL_NULL" } if kind == "Ident" { let name: String = expr["name"] - emit_load(name) - return + return name } if kind == "Not" { let inner = expr["inner"] - cg_expr(inner) - emit_not() - return + let inner_c: String = cg_expr(inner) + return "!" + inner_c } if kind == "Neg" { - // unary minus: push 0, push inner, sub - emit_push_int(0) let inner = expr["inner"] - cg_expr(inner) - emit_sub() - return + 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"] - cg_expr(left) - cg_expr(right) - if op == "Plus" { emit_add() } - if op == "Minus" { emit_sub() } - if op == "Star" { emit_mul() } - if op == "Slash" { emit_div() } - if op == "EqEq" { emit_eq() } - if op == "NotEq" { emit_not_eq() } - if op == "Lt" { emit_lt() } - if op == "Gt" { emit_gt() } - if op == "LtEq" { emit_lt_eq() } - if op == "GtEq" { emit_gt_eq() } - if op == "And" { emit_and() } - if op == "Or" { emit_or() } - return + 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 + 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 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 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 + ")" + } + } + } + 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) - // push args left-to-right + let func_kind: String = func["expr"] + + // Build argument list string + let args_c = "" let i = 0 while i < arity { let arg = native_list_get(args, i) - cg_expr(arg) + 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 } - // get function name from func expr - let func_kind: String = func["expr"] + if func_kind == "Ident" { let fn_name: String = func["name"] - emit_call(fn_name, arity) - return + return fn_name + "(" + args_c + ")" } + if func_kind == "Field" { + // method-style call: obj.method(args) — pass obj as first arg let obj = func["object"] let field: String = func["field"] - cg_expr(obj) - emit_call(field, arity + 1) - return + let obj_c: String = cg_expr(obj) + if arity > 0 { + return field + "(" + obj_c + ", " + args_c + ")" + } + return field + "(" + obj_c + ")" } - emit_call("__dynamic__", arity) - return + + // Dynamic call — emit as a generic pointer call (best effort) + let fn_c: String = cg_expr(func) + return fn_c + "(" + args_c + ")" } if kind == "Field" { let obj = expr["object"] let field: String = expr["field"] - cg_expr(obj) - emit_get_field(field) - return + 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" { let obj = expr["object"] let idx = expr["index"] - cg_expr(obj) - cg_expr(idx) - emit_get_index() - return + let obj_c: String = cg_expr(obj) + let idx_c: String = cg_expr(idx) + 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) - cg_expr(elem) + let elem_c: String = cg_expr(elem) + if i > 0 { + let items = items + ", " + } + let items = items + elem_c let i = i + 1 } - emit_build_list(n) - return + return "el_list_new(" + native_int_to_str(n) + ", " + items + ")" } 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 = "" let i = 0 while i < n { let pair = native_list_get(pairs, i) let key: String = pair["key"] let val = pair["value"] - emit_push_str(key) - cg_expr(val) + 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 } - emit_build_map(n) - return + return "el_map_new(" + native_int_to_str(n) + ", " + items + ")" } + 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"] - // cond - cg_expr(cond) - // JumpIfNot placeholder - let jump_false_idx: Int = emit_jump_if_not_placeholder() - // then body - cg_stmts(then_stmts) - if has_else { - // jump over else - let jump_end_idx: Int = emit_jump_placeholder() - // patch jump_false to here - let else_start: Int = native_instr_len() - patch_instr(jump_false_idx, else_start) - // else body - cg_stmts(else_stmts) - // patch jump_end to here - let after_else: Int = native_instr_len() - patch_instr(jump_end_idx, after_else) - } else { - let after_then: Int = native_instr_len() - patch_instr(jump_false_idx, after_then) - } - return - } - - if kind == "Match" { - let subject = expr["subject"] - let arms = expr["arms"] - let n_arms: Int = native_list_len(arms) - cg_expr(subject) - // store subject in temp var - emit_store("__match_subj__") - let end_jump_idxs: [Int] = native_list_empty() - let i = 0 - while i < n_arms { - let arm = native_list_get(arms, i) - let pattern = arm["pattern"] - let body = arm["body"] - let pat_kind: String = pattern["pattern"] - if pat_kind == "Wildcard" { - // always matches — just emit body - cg_expr(body) - let jidx: Int = emit_jump_placeholder() - let end_jump_idxs = native_list_append(end_jump_idxs, jidx) - } else { - if pat_kind == "Binding" { - let bind_name: String = pattern["name"] - emit_load("__match_subj__") - emit_store(bind_name) - cg_expr(body) - let jidx: Int = emit_jump_placeholder() - let end_jump_idxs = native_list_append(end_jump_idxs, jidx) - } else { - // literal pattern: compare subject to literal - emit_load("__match_subj__") - if pat_kind == "LitInt" { - let v: String = pattern["value"] - let n: Int = native_str_to_int(v) - emit_push_int(n) - } else { - if pat_kind == "LitStr" { - let v: String = pattern["value"] - emit_push_str(v) - } else { - if pat_kind == "LitBool" { - let v: String = pattern["value"] - if v == "true" { - emit_push_bool(true) - } else { - emit_push_bool(false) - } - } else { - emit_push_nil() - } - } - } - emit_eq() - let no_match_idx: Int = emit_jump_if_not_placeholder() - cg_expr(body) - let jidx: Int = emit_jump_placeholder() - let end_jump_idxs = native_list_append(end_jump_idxs, jidx) - let next_arm: Int = native_instr_len() - patch_instr(no_match_idx, next_arm) - } - } - let i = i + 1 - } - // default: push nil - emit_push_nil() - let end_pos: Int = native_instr_len() - // patch all end jumps - let n_end: Int = native_list_len(end_jump_idxs) - let j = 0 - while j < n_end { - let jidx: Int = native_list_get(end_jump_idxs, j) - patch_instr(jidx, end_pos) - let j = j + 1 - } - return - } - - if kind == "For" { - // for item in list { body } - let item: String = expr["item"] - let list_expr = expr["list"] - let body = expr["body"] - // emit list, store it - cg_expr(list_expr) - emit_store("__for_list__") - // compute length - emit_load("__for_list__") - emit_call("native_list_len", 1) - emit_store("__for_len__") - // init counter - emit_push_int(0) - emit_store("__for_i__") - // loop start - let loop_start: Int = native_instr_len() - // condition: __for_i__ < __for_len__ - emit_load("__for_i__") - emit_load("__for_len__") - emit_lt() - let to_done_idx: Int = emit_jump_if_not_placeholder() - // get current element - emit_load("__for_list__") - emit_load("__for_i__") - emit_get_index() - emit_store(item) - // body - cg_stmts(body) - // increment counter - emit_load("__for_i__") - emit_push_int(1) - emit_add() - emit_store("__for_i__") - // jump back - emit_jump_to(loop_start) - // patch done - let done_pos: Int = native_instr_len() - patch_instr(to_done_idx, done_pos) - return - } - - if kind == "Try" { - // Just emit the inner expression - let inner = expr["inner"] - cg_expr(inner) - return + 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 } // Fallback - emit_push_nil() + "NULL" } // ── Statement codegen ───────────────────────────────────────────────────────── +// +// cg_stmt emits C lines for a statement, using the given indentation prefix. -// cg_stmt_tail — emit a statement in tail position (last stmt of fn body). -// For Expr statements, the result is left on the stack (not popped). -// For all other statement kinds, we delegate to cg_stmt and push Nil. -fn cg_stmt_tail(stmt: Map) -> Void { - let kind: String = stmt["stmt"] - - if kind == "Expr" { - let val = stmt["value"] - // Emit the expression — leave result on stack (tail position). - cg_expr(val) - return - } - - if kind == "Return" { - // Explicit return — same as cg_stmt - let val = stmt["value"] - cg_expr(val) - emit_return() - return - } - - // All other statement kinds (Let, FnDef, While, etc.) don't produce - // a natural return value — fall through to cg_stmt then push Nil. - cg_stmt(stmt) - emit_push_nil() -} - -// cg_stmts_body — emit function body statements. -// All statements except the last use cg_stmt (pops results). -// The last statement uses cg_stmt_tail (leaves value on stack). -fn cg_stmts_body(stmts: [Map]) -> Void { - let n: Int = native_list_len(stmts) - if n == 0 { - // Empty body — push Nil as return value. - emit_push_nil() - return - } - let i = 0 - while i < n { - let stmt = native_list_get(stmts, i) - let is_last: Bool = (i == n - 1) - if is_last { - cg_stmt_tail(stmt) - } else { - cg_stmt(stmt) - } - let i = i + 1 - } -} - -fn cg_stmt(stmt: Map) -> Void { +fn cg_stmt(stmt: Map, indent: String) -> Void { let kind: String = stmt["stmt"] if kind == "Let" { let name: String = stmt["name"] let val = stmt["value"] - cg_expr(val) - emit_store(name) + 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 kind == "Return" { let val = stmt["value"] - cg_expr(val) - emit_return() - return - } - - if kind == "FnDef" { - let fn_name: String = stmt["name"] - let params = stmt["params"] - let body = stmt["body"] - let n_params: Int = native_list_len(params) - // emit a Jump to skip over the function body - let skip_jump_idx: Int = emit_jump_placeholder() - // function body entry: store params in reverse order (caller pushes L→R, so pop R→L) - let pi = n_params - 1 - while pi >= 0 { - let param = native_list_get(params, pi) - let pname: String = param["name"] - emit_store(pname) - let pi = pi - 1 + let val_kind: String = val["expr"] + if val_kind == "Nil" { + emit_line(indent + "return;") + } else { + let val_c: String = cg_expr(val) + emit_line(indent + "return " + val_c + ";") } - // emit body statements using tail-aware emit (last stmt leaves value on stack) - cg_stmts_body(body) - // return the top-of-stack value - emit_return() - // patch skip jump - let after_fn: Int = native_instr_len() - patch_instr(skip_jump_idx, after_fn) - // register function entry point - let entry_ip: Int = skip_jump_idx + 1 - emit_push_int(entry_ip) - emit_store("__fn_" + fn_name) - return - } - - if kind == "While" { - let cond = stmt["cond"] - let body = stmt["body"] - let loop_start: Int = native_instr_len() - cg_expr(cond) - let to_done_idx: Int = emit_jump_if_not_placeholder() - cg_stmts(body) - emit_jump_to(loop_start) - let done_pos: Int = native_instr_len() - patch_instr(to_done_idx, done_pos) - return - } - - if kind == "For" { - // Desugar for-loop as expression codegen - let item: String = stmt["item"] - let list_expr = stmt["list"] - let body = stmt["body"] - let for_expr = { "expr": "For", "item": item, "list": list_expr, "body": body } - cg_expr(for_expr) return } if kind == "Expr" { let val = stmt["value"] let val_kind: String = val["expr"] - cg_expr(val) - // Discard result unless it's a control-flow expression that doesn't push a value - if val_kind == "For" { + // Handle if/while/for at statement level specially + if val_kind == "If" { + cg_if_stmt(val, indent) return } - emit_pop() + if val_kind == "For" { + cg_for_stmt(val, indent) + return + } + let val_c: String = cg_expr(val) + emit_line(indent + val_c + ";") return } - if kind == "TypeDef" { - // compile-time only; no runtime code + 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 + " ") + emit_line(indent + "}") return } - if kind == "EnumDef" { - // compile-time only; no runtime code + 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 } - if kind == "Import" { - // handled at a higher level; skip + 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 } } -fn cg_stmts(stmts: [Map]) -> Void { +// Strip a single layer of surrounding parentheses from a C expression string, +// if present. This avoids double-parens in "if ((cond))". +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 == ")" { + // Verify the opening paren matches the closing one (depth check) + 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 // break + } + } + let i = i + 1 + } + if balanced { + // Safe to strip outer parens + 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, indent: 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 + " ") + if has_else { + emit_line(indent + "} else {") + cg_stmts(else_stmts, indent + " ") + } + emit_line(indent + "}") +} + +fn cg_for_body(item: String, list_expr: Map, body: [Map], indent: 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 + 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 + " ") + emit_line(indent + " }") + emit_line(indent + "}") +} + +fn cg_for_stmt(expr: Map, indent: String) -> Void { + let item: String = expr["item"] + let list_expr = expr["list"] + let body = expr["body"] + cg_for_body(item, list_expr, body, indent) +} + +fn cg_stmts(stmts: [Map], indent: String) -> Void { let n: Int = native_list_len(stmts) let i = 0 while i < n { let stmt = native_list_get(stmts, i) - cg_stmt(stmt) + cg_stmt(stmt, indent) let i = i + 1 } } -// ── JSON serialisation ──────────────────────────────────────────────────────── +// 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], indent: String) -> String { + // Not implemented for inline use; returns empty — if-exprs as statements + // are handled by cg_if_stmt instead. + "" +} -fn instrs_to_json(instrs: [String]) -> String { - let n: Int = native_list_len(instrs) - let out = "[" +// ── Function declaration codegen ─────────────────────────────────────────────── + +fn param_decl(param: Map, 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 +} + +fn params_to_c(params: [Map]) -> String { + let n: Int = native_list_len(params) + if n == 0 { return "void" } + let out = "" let i = 0 while i < n { - let instr: String = native_list_get(instrs, i) + let param = native_list_get(params, i) + let decl: String = param_decl(param, i) if i > 0 { - let out = out + "," + let out = out + ", " } - let out = out + instr + let out = out + decl let i = i + 1 } - out + "]" + out } -// ── Entry point ─────────────────────────────────────────────────────────────── +fn cg_fn(stmt: Map) -> Void { + let fn_name: String = stmt["name"] + let params = stmt["params"] + let body = stmt["body"] + 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 + emit_line(" return 0;") + emit_line("}") + emit_blank() +} + +// ── Top-level codegen ───────────────────────────────────────────────────────── + +// Collect top-level statements that are NOT FnDefs — these go into main(). +fn is_fndef(stmt: Map) -> Bool { + let kind: String = stmt["stmt"] + if kind == "FnDef" { return true } + false +} + +fn is_top_level_decl(stmt: Map) -> 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], source: String) -> String { native_instr_reset() - cg_stmts(stmts) - emit_halt() - let instrs: [String] = native_instr_all() - instrs_to_json(instrs) + + // Preamble + emit_line("#include ") + emit_line("#include ") + emit_line("#include \"el_runtime.h\"") + emit_blank() + + // Emit forward declarations for all user-defined functions + 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"] + 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 + let i = 0 + while i < n { + let stmt = native_list_get(stmts, i) + if is_fndef(stmt) { + cg_fn(stmt) + } + let i = i + 1 + } + + // Emit main() for top-level statements + emit_line("int main(void) {") + let i = 0 + while i < n { + let stmt = native_list_get(stmts, i) + if is_fndef(stmt) { + // skip — already emitted above + } else { + if is_top_level_decl(stmt) { + // skip — compile-time only + } else { + cg_stmt(stmt, " ") + } + } + let i = i + 1 + } + emit_line(" return 0;") + 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 } diff --git a/el-compiler/src/compiler.el b/el-compiler/src/compiler.el index cbb227d..6b080d2 100644 --- a/el-compiler/src/compiler.el +++ b/el-compiler/src/compiler.el @@ -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 .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] = lex(source) let stmts: [Map] = 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 +// Called by: elc // -// 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 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 ") + println("el-compiler: usage: elc ") 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 {