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
Vendored Executable
BIN
View File
Binary file not shown.
+2241
View File
File diff suppressed because it is too large Load Diff
Vendored Executable
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
+139
View File
@@ -98,11 +98,150 @@ 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);
el_val_t json_parse(el_val_t s);
el_val_t json_stringify(el_val_t v);
el_val_t json_get_string(el_val_t json_str, el_val_t key);
el_val_t json_get_int(el_val_t json_str, el_val_t key);
el_val_t json_get_float(el_val_t json_str, el_val_t key);
el_val_t json_get_bool(el_val_t json_str, el_val_t key);
el_val_t json_get_raw(el_val_t json_str, el_val_t key);
el_val_t json_set(el_val_t json_str, el_val_t key, el_val_t value);
el_val_t json_array_len(el_val_t json_str);
/* ── Time ────────────────────────────────────────────────────────────────── */
el_val_t time_now(void);
el_val_t time_now_utc(void);
el_val_t time_format(el_val_t ts, el_val_t fmt);
el_val_t time_to_parts(el_val_t ts);
el_val_t time_from_parts(el_val_t secs, el_val_t ns, el_val_t tz);
el_val_t time_add(el_val_t ts, el_val_t n, el_val_t unit);
el_val_t time_diff(el_val_t ts1, el_val_t ts2, el_val_t unit);
/* ── UUID ────────────────────────────────────────────────────────────────── */
el_val_t uuid_new(void);
el_val_t uuid_v4(void);
/* ── Environment ─────────────────────────────────────────────────────────── */
el_val_t env(el_val_t key);
/* ── In-process state K/V ────────────────────────────────────────────────── */
el_val_t state_set(el_val_t key, el_val_t value);
el_val_t state_get(el_val_t key);
el_val_t state_del(el_val_t key);
el_val_t state_keys(void);
/* ── Float formatting ────────────────────────────────────────────────────── */
el_val_t float_to_str(el_val_t f);
el_val_t int_to_float(el_val_t n);
el_val_t float_to_int(el_val_t f);
el_val_t format_float(el_val_t f, el_val_t decimals);
el_val_t decimal_round(el_val_t f, el_val_t decimals);
el_val_t str_to_float(el_val_t s);
/* ── Math (Float-aware) ──────────────────────────────────────────────────── */
el_val_t math_sqrt(el_val_t f);
el_val_t math_log(el_val_t f);
el_val_t math_ln(el_val_t f);
el_val_t math_sin(el_val_t f);
el_val_t math_cos(el_val_t f);
el_val_t math_pi(void);
/* ── String additions ────────────────────────────────────────────────────── */
el_val_t str_index_of(el_val_t s, el_val_t sub);
el_val_t str_split(el_val_t s, el_val_t sep);
el_val_t str_char_at(el_val_t s, el_val_t i);
el_val_t str_char_code(el_val_t s, el_val_t i);
el_val_t str_pad_left(el_val_t s, el_val_t width, el_val_t pad);
el_val_t str_pad_right(el_val_t s, el_val_t width, el_val_t pad);
el_val_t str_format(el_val_t template, el_val_t data);
el_val_t str_lower(el_val_t s);
el_val_t str_upper(el_val_t s);
/* ── List additions ──────────────────────────────────────────────────────── */
el_val_t list_push(el_val_t list, el_val_t elem);
el_val_t list_push_front(el_val_t list, el_val_t elem);
el_val_t list_join(el_val_t list, el_val_t sep);
el_val_t list_range(el_val_t start, el_val_t end);
/* ── Bool helpers ────────────────────────────────────────────────────────── */
el_val_t bool_to_str(el_val_t b);
/* ── Process ─────────────────────────────────────────────────────────────── */
void exit_program(el_val_t code);
/* ── CGI identity ─────────────────────────────────────────────────────────────
* Called at the start of main() in CGI programs (those with a `cgi {}` block).
* Records the program's DHARMA identity before any other code executes. */
void el_cgi_init(el_val_t name, el_val_t dharma_id, el_val_t principal,
el_val_t network, el_val_t engram);
/* ── DHARMA network builtins ─────────────────────────────────────────────────
* Available to CGI programs (declared with a `cgi {}` block).
* Stubs print descriptive output and return empty/zero values.
* Full implementations are linked from the DHARMA runtime at deploy time. */
el_val_t dharma_connect(el_val_t cgi_id);
el_val_t dharma_send(el_val_t channel, el_val_t content);
el_val_t dharma_activate(el_val_t query);
void dharma_emit(el_val_t event_type, el_val_t payload);
el_val_t dharma_field(el_val_t event_type);
void dharma_strengthen(el_val_t cgi_id, el_val_t weight);
el_val_t dharma_relationship(el_val_t cgi_id);
el_val_t dharma_peers(void);
/* ── Engram local graph primitives ───────────────────────────────────────────
* Operate on the CGI's local Engram knowledge graph.
* `engram_activate` queries the local graph only; `dharma_activate` is
* network-wide across all connected CGI graphs. */
el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience);
el_val_t engram_activate(el_val_t query, el_val_t depth);
void engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t relation);
void engram_strengthen(el_val_t node_id);
/* ── args() ─────────────────────────────────────────────────────────────────
* Provides access to command-line arguments passed to the program.
* Populated by el_runtime_init_args() before main() runs. */
el_val_t args(void);
void el_runtime_init_args(int argc, char** argv);
/* ── Native VM builtin aliases (for compiled El source) ─────────────────────
* These match the El VM's native_* builtins so that El source compiled
* to C can call the same names without modification. */
el_val_t native_list_get(el_val_t list, el_val_t index);
el_val_t native_list_len(el_val_t list);
el_val_t native_list_append(el_val_t list, el_val_t elem);
el_val_t native_list_empty(void);
el_val_t native_string_chars(el_val_t s);
el_val_t native_int_to_str(el_val_t n);
/* ── Method-call shorthand aliases ──────────────────────────────────────────
* The El method-call convention `obj.method(args)` compiles to
* `method(obj, args)`. These aliases expose the runtime functions under
* the short names that result from method calls in El source.
*
* Example: `myList.append(x)` `append(myList, x)` (calls this alias)
* `myList.len()` `len(myList)` (calls this alias) */
el_val_t append(el_val_t list, el_val_t elem); /* el_list_append */
el_val_t len(el_val_t list); /* el_list_len */
el_val_t get(el_val_t list, el_val_t index); /* el_list_get */
el_val_t map_get(el_val_t map, el_val_t key); /* el_map_get */
el_val_t map_set(el_val_t map, el_val_t key, el_val_t value); /* el_map_set */
#ifdef __cplusplus
}
#endif
+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
""
}
+44 -31
View File
@@ -7,8 +7,8 @@
//
// Entry point: fn lex(source: String) -> [Map<String, Any>]
//
// Uses global char_buf via native_chars_init / native_char_at / native_char_len
// to avoid O(N²) cloning of the chars list.
// Uses native_string_chars to split the source into a chars list,
// then indexes it with native_list_get avoids O(N²) string cloning.
// Character helpers
@@ -140,15 +140,20 @@ fn keyword_kind(word: String) -> String {
if word == "target" { return "Target" }
if word == "true" { return "Bool" }
if word == "false" { return "Bool" }
if word == "cgi" { return "Cgi" }
if word == "manager" { return "Manager" }
if word == "engine" { return "Engine" }
if word == "accessor" { return "Accessor" }
if word == "vessel" { return "Vessel" }
""
}
// Scan helpers
// All scan helpers use the global char_buf (native_char_at / native_char_len).
// No chars parameter avoids O(N²) cloning.
// All scan helpers receive the chars list and total length.
// scan_digits advance i while char_buf[i] is a digit, return { "text": ..., "pos": i }
fn scan_digits(start: Int, total: Int) -> Map<String, Any> {
// scan_digits advance i while chars[i] is a digit
// Returns { "text": ..., "pos": i }
fn scan_digits(chars: [String], start: Int, total: Int) -> Map<String, Any> {
let i = start
let text = ""
let running = true
@@ -156,7 +161,7 @@ fn scan_digits(start: Int, total: Int) -> Map<String, Any> {
if i >= total {
let running = false
} else {
let ch = native_char_at(i)
let ch: String = native_list_get(chars, i)
if is_digit(ch) {
let text = text + ch
let i = i + 1
@@ -168,8 +173,8 @@ fn scan_digits(start: Int, total: Int) -> Map<String, Any> {
{ "text": text, "pos": i }
}
// scan_ident advance i while char_buf[i] is alphanumeric or underscore
fn scan_ident(start: Int, total: Int) -> Map<String, Any> {
// scan_ident advance i while chars[i] is alphanumeric or underscore
fn scan_ident(chars: [String], start: Int, total: Int) -> Map<String, Any> {
let i = start
let text = ""
let running = true
@@ -177,7 +182,7 @@ fn scan_ident(start: Int, total: Int) -> Map<String, Any> {
if i >= total {
let running = false
} else {
let ch = native_char_at(i)
let ch: String = native_list_get(chars, i)
if is_alnum_or_underscore(ch) {
let text = text + ch
let i = i + 1
@@ -191,21 +196,20 @@ fn scan_ident(start: Int, total: Int) -> Map<String, Any> {
// scan_string scan a quoted string literal, handling \" escapes.
// Starts AFTER the opening quote. Returns { "text": content, "pos": i_after_close }
fn scan_string(start: Int, total: Int) -> Map<String, Any> {
fn scan_string(chars: [String], start: Int, total: Int) -> Map<String, Any> {
let i = start
let text = ""
let closed = false
let running = true
while running {
if i >= total {
let running = false
} else {
let ch = native_char_at(i)
let ch: String = native_list_get(chars, i)
if ch == "\\" {
// escape: peek next char
let next_i = i + 1
if next_i < total {
let next_ch = native_char_at(next_i)
let next_ch: String = native_list_get(chars, next_i)
if next_ch == "\"" {
let text = text + "\""
let i = next_i + 1
@@ -218,12 +222,17 @@ fn scan_string(start: Int, total: Int) -> Map<String, Any> {
let text = text + "\t"
let i = next_i + 1
} else {
if next_ch == "\\" {
let text = text + "\\"
if next_ch == "r" {
let text = text + "\r"
let i = next_i + 1
} else {
let text = text + next_ch
let i = next_i + 1
if next_ch == "\\" {
let text = text + "\\"
let i = next_i + 1
} else {
let text = text + next_ch
let i = next_i + 1
}
}
}
}
@@ -233,7 +242,6 @@ fn scan_string(start: Int, total: Int) -> Map<String, Any> {
}
} else {
if ch == "\"" {
let closed = true
let i = i + 1
let running = false
} else {
@@ -249,13 +257,13 @@ fn scan_string(start: Int, total: Int) -> Map<String, Any> {
// Main lexer
fn lex(source: String) -> [Map<String, Any>] {
native_chars_init(source)
let total: Int = native_char_len()
let chars: [String] = native_string_chars(source)
let total: Int = native_list_len(chars)
let tokens: [Map<String, Any>] = native_list_empty()
let i: Int = 0
while i < total {
let ch: String = native_char_at(i)
let ch: String = native_list_get(chars, i)
// Skip whitespace
if is_whitespace(ch) {
@@ -265,7 +273,7 @@ fn lex(source: String) -> [Map<String, Any>] {
if ch == "/" {
let next_i = i + 1
if next_i < total {
let next_ch: String = native_char_at(next_i)
let next_ch: String = native_list_get(chars, next_i)
if next_ch == "/" {
// skip to end of line
let i = i + 2
@@ -274,7 +282,7 @@ fn lex(source: String) -> [Map<String, Any>] {
if i >= total {
let running2 = false
} else {
let lch: String = native_char_at(i)
let lch: String = native_list_get(chars, i)
if lch == "\n" {
let running2 = false
} else {
@@ -293,7 +301,7 @@ fn lex(source: String) -> [Map<String, Any>] {
} else {
// String literal
if ch == "\"" {
let result = scan_string(i + 1, total)
let result = scan_string(chars, i + 1, total)
let str_text: String = result["text"]
let new_pos: Int = result["pos"]
let tokens = native_list_append(tokens, make_tok("Str", str_text))
@@ -301,18 +309,18 @@ fn lex(source: String) -> [Map<String, Any>] {
} else {
// Number literal
if is_digit(ch) {
let result = scan_digits(i, total)
let result = scan_digits(chars, i, total)
let num_text: String = result["text"]
let new_pos: Int = result["pos"]
// check for float (dot followed by digit)
if new_pos < total {
let dot_ch: String = native_char_at(new_pos)
let dot_ch: String = native_list_get(chars, new_pos)
if dot_ch == "." {
let after_dot = new_pos + 1
if after_dot < total {
let after_dot_ch: String = native_char_at(after_dot)
let after_dot_ch: String = native_list_get(chars, after_dot)
if is_digit(after_dot_ch) {
let frac_result = scan_digits(after_dot, total)
let frac_result = scan_digits(chars, after_dot, total)
let frac_text: String = frac_result["text"]
let frac_pos: Int = frac_result["pos"]
let tokens = native_list_append(tokens, make_tok("Float", num_text + "." + frac_text))
@@ -336,7 +344,7 @@ fn lex(source: String) -> [Map<String, Any>] {
} else {
// Identifier or keyword
if is_alpha(ch) || ch == "_" {
let result = scan_ident(i, total)
let result = scan_ident(chars, i, total)
let word: String = result["text"]
let new_pos: Int = result["pos"]
let kw = keyword_kind(word)
@@ -351,7 +359,7 @@ fn lex(source: String) -> [Map<String, Any>] {
let peek_i = i + 1
let peek_ch = ""
if peek_i < total {
let peek_ch = native_char_at(peek_i)
let peek_ch: String = native_list_get(chars, peek_i)
}
if ch == "=" {
@@ -442,6 +450,10 @@ fn lex(source: String) -> [Map<String, Any>] {
if ch == "*" {
let tokens = native_list_append(tokens, make_tok("Star", "*"))
let i = i + 1
} else {
if ch == "%" {
let tokens = native_list_append(tokens, make_tok("Percent", "%"))
let i = i + 1
} else {
if ch == "(" {
let tokens = native_list_append(tokens, make_tok("LParen", "("))
@@ -501,6 +513,7 @@ fn lex(source: String) -> [Map<String, Any>] {
}
}
}
}
}
}
}
+151 -147
View File
@@ -6,30 +6,29 @@
// The cursor (integer position into the token list) is threaded through every
// parse function. Functions return { "node": <map>, "pos": <int> }.
//
// The token list is stored in the VM's global token buffer via
// native_tokens_init / native_token_at / native_token_len. This avoids
// O(n²) cloning that occurs when passing the list as a function argument.
// The token list is passed as a parameter to all parse functions.
// native_list_get is used to index into it without cloning.
//
// Entry point: fn parse(tokens: [Map<String, Any>]) -> [Map<String, Any>]
// Token access helpers
fn tok_at(pos: Int) -> Map<String, Any> {
native_token_at(pos)
fn tok_at(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
native_list_get(tokens, pos)
}
fn tok_kind(pos: Int) -> String {
let t = native_token_at(pos)
fn tok_kind(tokens: [Map<String, Any>], pos: Int) -> String {
let t = native_list_get(tokens, pos)
t["kind"]
}
fn tok_value(pos: Int) -> String {
let t = native_token_at(pos)
fn tok_value(tokens: [Map<String, Any>], pos: Int) -> String {
let t = native_list_get(tokens, pos)
t["value"]
}
fn expect(pos: Int, kind: String) -> Int {
let k = tok_kind(pos)
fn expect(tokens: [Map<String, Any>], pos: Int, kind: String) -> Int {
let k = tok_kind(tokens, pos)
if k == kind {
return pos + 1
}
@@ -47,26 +46,26 @@ fn make_result(node: Map<String, Any>, pos: Int) -> Map<String, Any> {
// Skips over a type annotation, returning the new position.
// Types can be: Ident, [Type], Map<K,V>, Type?, Type<Type,...>
fn skip_type(pos: Int) -> Int {
let k = tok_kind(pos)
fn skip_type(tokens: [Map<String, Any>], pos: Int) -> Int {
let k = tok_kind(tokens, pos)
// Array type: [Type]
if k == "LBracket" {
let p = pos + 1
let p = skip_type(p)
let p = expect(p, "RBracket")
let p = skip_type(tokens, p)
let p = expect(tokens, p, "RBracket")
return p
}
// Named type (possibly generic)
if k == "Ident" {
let p = pos + 1
let k2 = tok_kind(p)
let k2 = tok_kind(tokens, p)
if k2 == "Lt" {
// Generic params: skip until matching >
let p = p + 1
let depth = 1
let running = true
while running {
let kk = tok_kind(p)
let kk = tok_kind(tokens, p)
if kk == "Eof" {
let running = false
} else {
@@ -86,7 +85,7 @@ fn skip_type(pos: Int) -> Int {
}
}
}
let k3 = tok_kind(p)
let k3 = tok_kind(tokens, p)
if k3 == "QuestionMark" {
let p = p + 1
}
@@ -104,12 +103,12 @@ fn skip_type(pos: Int) -> Int {
// Parameter list
// Parses (name: Type, name: Type, ...) returns { "params": [...], "pos": ... }
fn parse_params(pos: Int) -> Map<String, Any> {
let p = expect(pos, "LParen")
fn parse_params(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let p = expect(tokens, pos, "LParen")
let params: [Map<String, Any>] = native_list_empty()
let running = true
while running {
let k = tok_kind(p)
let k = tok_kind(tokens, p)
if k == "RParen" {
let running = false
} else {
@@ -117,28 +116,28 @@ fn parse_params(pos: Int) -> Map<String, Any> {
let running = false
} else {
// param name
let pname = tok_value(p)
let pname = tok_value(tokens, p)
let p = p + 1
let p = expect(p, "Colon")
let p = skip_type(p)
let p = expect(tokens, p, "Colon")
let p = skip_type(tokens, p)
let param = { "name": pname }
let params = native_list_append(params, param)
let k2 = tok_kind(p)
let k2 = tok_kind(tokens, p)
if k2 == "Comma" {
let p = p + 1
}
}
}
}
let p = expect(p, "RParen")
let p = expect(tokens, p, "RParen")
{ "params": params, "pos": p }
}
// Expression parsing
fn parse_primary(pos: Int) -> Map<String, Any> {
let k = tok_kind(pos)
let v = tok_value(pos)
fn parse_primary(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let k = tok_kind(tokens, pos)
let v = tok_value(tokens, pos)
// Integer literal
if k == "Int" {
@@ -167,10 +166,10 @@ fn parse_primary(pos: Int) -> Map<String, Any> {
// Grouped expression
if k == "LParen" {
let r = parse_expr(pos + 1)
let r = parse_expr(tokens, pos + 1)
let node = r["node"]
let p = r["pos"]
let p = expect(p, "RParen")
let p = expect(tokens, p, "RParen")
return make_result(node, p)
}
@@ -180,25 +179,25 @@ fn parse_primary(pos: Int) -> Map<String, Any> {
let elems: [Map<String, Any>] = native_list_empty()
let running = true
while running {
let k2 = tok_kind(p)
let k2 = tok_kind(tokens, p)
if k2 == "RBracket" {
let running = false
} else {
if k2 == "Eof" {
let running = false
} else {
let r = parse_expr(p)
let r = parse_expr(tokens, p)
let elem = r["node"]
let p = r["pos"]
let elems = native_list_append(elems, elem)
let k3 = tok_kind(p)
let k3 = tok_kind(tokens, p)
if k3 == "Comma" {
let p = p + 1
}
}
}
}
let p = expect(p, "RBracket")
let p = expect(tokens, p, "RBracket")
return make_result({ "expr": "Array", "elems": elems }, p)
}
@@ -208,7 +207,7 @@ fn parse_primary(pos: Int) -> Map<String, Any> {
let pairs: [Map<String, Any>] = native_list_empty()
let running = true
while running {
let k2 = tok_kind(p)
let k2 = tok_kind(tokens, p)
if k2 == "RBrace" {
let running = false
} else {
@@ -216,46 +215,46 @@ fn parse_primary(pos: Int) -> Map<String, Any> {
let running = false
} else {
// key: Str token
let key = tok_value(p)
let key = tok_value(tokens, p)
let p = p + 1
let p = expect(p, "Colon")
let r = parse_expr(p)
let p = expect(tokens, p, "Colon")
let r = parse_expr(tokens, p)
let val_node = r["node"]
let p = r["pos"]
let pair = { "key": key, "value": val_node }
let pairs = native_list_append(pairs, pair)
let k3 = tok_kind(p)
let k3 = tok_kind(tokens, p)
if k3 == "Comma" {
let p = p + 1
}
}
}
}
let p = expect(p, "RBrace")
let p = expect(tokens, p, "RBrace")
return make_result({ "expr": "Map", "pairs": pairs }, p)
}
// if expression
if k == "If" {
let r = parse_if(pos)
let r = parse_if(tokens, pos)
return r
}
// match expression
if k == "Match" {
let r = parse_match(pos)
let r = parse_match(tokens, pos)
return r
}
// for expression (used as statement)
if k == "For" {
let r = parse_for_expr(pos)
let r = parse_for_expr(tokens, pos)
return r
}
// Unary not
if k == "Not" {
let r = parse_primary(pos + 1)
let r = parse_primary(tokens, pos + 1)
let inner = r["node"]
let p = r["pos"]
return make_result({ "expr": "Not", "inner": inner }, p)
@@ -263,7 +262,7 @@ fn parse_primary(pos: Int) -> Map<String, Any> {
// Unary minus
if k == "Minus" {
let r = parse_primary(pos + 1)
let r = parse_primary(tokens, pos + 1)
let inner = r["node"]
let p = r["pos"]
return make_result({ "expr": "Neg", "inner": inner }, p)
@@ -273,29 +272,29 @@ fn parse_primary(pos: Int) -> Map<String, Any> {
make_result({ "expr": "Nil" }, pos + 1)
}
fn parse_if(pos: Int) -> Map<String, Any> {
let p = expect(pos, "If")
let r = parse_expr(p)
fn parse_if(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let p = expect(tokens, pos, "If")
let r = parse_expr(tokens, p)
let cond = r["node"]
let p = r["pos"]
let r2 = parse_block(p)
let r2 = parse_block(tokens, p)
let then_stmts = r2["stmts"]
let p = r2["pos"]
let has_else = false
let else_stmts: [Map<String, Any>] = native_list_empty()
let k2 = tok_kind(p)
let k2 = tok_kind(tokens, p)
if k2 == "Else" {
let p = p + 1
let k3 = tok_kind(p)
let k3 = tok_kind(tokens, p)
if k3 == "If" {
// else-if chain: parse as nested if
let r3 = parse_if(p)
let r3 = parse_if(tokens, p)
let nested = r3["node"]
let p = r3["pos"]
let else_stmts = native_list_append(else_stmts, { "stmt": "Expr", "value": nested })
let has_else = true
} else {
let r3 = parse_block(p)
let r3 = parse_block(tokens, p)
let else_stmts = r3["stmts"]
let p = r3["pos"]
let has_else = true
@@ -304,16 +303,16 @@ fn parse_if(pos: Int) -> Map<String, Any> {
make_result({ "expr": "If", "cond": cond, "then": then_stmts, "else": else_stmts, "has_else": has_else }, p)
}
fn parse_match(pos: Int) -> Map<String, Any> {
let p = expect(pos, "Match")
let r = parse_expr(p)
fn parse_match(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let p = expect(tokens, pos, "Match")
let r = parse_expr(tokens, p)
let subject = r["node"]
let p = r["pos"]
let p = expect(p, "LBrace")
let p = expect(tokens, p, "LBrace")
let arms: [Map<String, Any>] = native_list_empty()
let running = true
while running {
let k = tok_kind(p)
let k = tok_kind(tokens, p)
if k == "RBrace" {
let running = false
} else {
@@ -321,131 +320,131 @@ fn parse_match(pos: Int) -> Map<String, Any> {
let running = false
} else {
// parse pattern => body
let r2 = parse_pattern(p)
let r2 = parse_pattern(tokens, p)
let pattern = r2["node"]
let p = r2["pos"]
let p = expect(p, "FatArrow")
let r3 = parse_expr(p)
let p = expect(tokens, p, "FatArrow")
let r3 = parse_expr(tokens, p)
let body = r3["node"]
let p = r3["pos"]
let arm = { "pattern": pattern, "body": body }
let arms = native_list_append(arms, arm)
let k2 = tok_kind(p)
let k2 = tok_kind(tokens, p)
if k2 == "Comma" {
let p = p + 1
}
}
}
}
let p = expect(p, "RBrace")
let p = expect(tokens, p, "RBrace")
make_result({ "expr": "Match", "subject": subject, "arms": arms }, p)
}
fn parse_pattern(pos: Int) -> Map<String, Any> {
let k = tok_kind(pos)
fn parse_pattern(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let k = tok_kind(tokens, pos)
if k == "Ident" {
let v = tok_value(pos)
let v = tok_value(tokens, pos)
if v == "_" {
return make_result({ "pattern": "Wildcard" }, pos + 1)
}
return make_result({ "pattern": "Binding", "name": v }, pos + 1)
}
if k == "Int" {
return make_result({ "pattern": "LitInt", "value": tok_value(pos) }, pos + 1)
return make_result({ "pattern": "LitInt", "value": tok_value(tokens, pos) }, pos + 1)
}
if k == "Str" {
return make_result({ "pattern": "LitStr", "value": tok_value(pos) }, pos + 1)
return make_result({ "pattern": "LitStr", "value": tok_value(tokens, pos) }, pos + 1)
}
if k == "Bool" {
return make_result({ "pattern": "LitBool", "value": tok_value(pos) }, pos + 1)
return make_result({ "pattern": "LitBool", "value": tok_value(tokens, pos) }, pos + 1)
}
// Wildcard _
make_result({ "pattern": "Wildcard" }, pos + 1)
}
fn parse_for_expr(pos: Int) -> Map<String, Any> {
let p = expect(pos, "For")
let item_name = tok_value(p)
fn parse_for_expr(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let p = expect(tokens, pos, "For")
let item_name = tok_value(tokens, p)
let p = p + 1
let p = expect(p, "In")
let r = parse_expr(p)
let p = expect(tokens, p, "In")
let r = parse_expr(tokens, p)
let list_expr = r["node"]
let p = r["pos"]
let r2 = parse_block(p)
let r2 = parse_block(tokens, p)
let body = r2["stmts"]
let p = r2["pos"]
make_result({ "expr": "For", "item": item_name, "list": list_expr, "body": body }, p)
}
fn parse_block(pos: Int) -> Map<String, Any> {
let p = expect(pos, "LBrace")
fn parse_block(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let p = expect(tokens, pos, "LBrace")
let stmts: [Map<String, Any>] = native_list_empty()
let running = true
while running {
let k = tok_kind(p)
let k = tok_kind(tokens, p)
if k == "RBrace" {
let running = false
} else {
if k == "Eof" {
let running = false
} else {
let r = parse_stmt(p)
let r = parse_stmt(tokens, p)
let stmt = r["node"]
let p = r["pos"]
let stmts = native_list_append(stmts, stmt)
}
}
}
let p = expect(p, "RBrace")
let p = expect(tokens, p, "RBrace")
{ "stmts": stmts, "pos": p }
}
// Postfix expressions (calls, field access, index)
fn parse_postfix(pos: Int) -> Map<String, Any> {
let r = parse_primary(pos)
fn parse_postfix(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let r = parse_primary(tokens, pos)
let node = r["node"]
let p = r["pos"]
let running = true
while running {
let k = tok_kind(p)
let k = tok_kind(tokens, p)
if k == "LParen" {
// function call
let p = p + 1
let args: [Map<String, Any>] = native_list_empty()
let run2 = true
while run2 {
let k2 = tok_kind(p)
let k2 = tok_kind(tokens, p)
if k2 == "RParen" {
let run2 = false
} else {
if k2 == "Eof" {
let run2 = false
} else {
let r2 = parse_expr(p)
let r2 = parse_expr(tokens, p)
let arg = r2["node"]
let p = r2["pos"]
let args = native_list_append(args, arg)
let k3 = tok_kind(p)
let k3 = tok_kind(tokens, p)
if k3 == "Comma" {
let p = p + 1
}
}
}
}
let p = expect(p, "RParen")
let p = expect(tokens, p, "RParen")
let node = { "expr": "Call", "func": node, "args": args }
} else {
if k == "Dot" {
let field = tok_value(p + 1)
let field = tok_value(tokens, p + 1)
let p = p + 2
let node = { "expr": "Field", "object": node, "field": field }
} else {
if k == "LBracket" {
let r2 = parse_expr(p + 1)
let r2 = parse_expr(tokens, p + 1)
let idx = r2["node"]
let p = r2["pos"]
let p = expect(p, "RBracket")
let p = expect(tokens, p, "RBracket")
let node = { "expr": "Index", "object": node, "index": idx }
} else {
if k == "QuestionMark" {
@@ -495,18 +494,18 @@ fn is_binop(kind: String) -> Bool {
false
}
fn parse_binop(pos: Int, min_prec: Int) -> Map<String, Any> {
let r = parse_postfix(pos)
fn parse_binop(tokens: [Map<String, Any>], pos: Int, min_prec: Int) -> Map<String, Any> {
let r = parse_postfix(tokens, pos)
let left = r["node"]
let p = r["pos"]
let running = true
while running {
let k = tok_kind(p)
let k = tok_kind(tokens, p)
let prec = op_precedence(k)
if is_binop(k) {
if prec >= min_prec {
let op = k
let r2 = parse_binop(p + 1, prec + 1)
let r2 = parse_binop(tokens, p + 1, prec + 1)
let right = r2["node"]
let p = r2["pos"]
let left = { "expr": "BinOp", "op": op, "left": left, "right": right }
@@ -520,28 +519,28 @@ fn parse_binop(pos: Int, min_prec: Int) -> Map<String, Any> {
make_result(left, p)
}
fn parse_expr(pos: Int) -> Map<String, Any> {
parse_binop(pos, 1)
fn parse_expr(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
parse_binop(tokens, pos, 1)
}
// Statement parsing
fn parse_stmt(pos: Int) -> Map<String, Any> {
let k = tok_kind(pos)
fn parse_stmt(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let k = tok_kind(tokens, pos)
// let binding
if k == "Let" {
let p = pos + 1
let name = tok_value(p)
let name = tok_value(tokens, p)
let p = p + 1
let k2 = tok_kind(p)
let k2 = tok_kind(tokens, p)
// optional type annotation: name: Type
if k2 == "Colon" {
let p = p + 1
let p = skip_type(p)
let p = skip_type(tokens, p)
}
let p = expect(p, "Eq")
let r = parse_expr(p)
let p = expect(tokens, p, "Eq")
let r = parse_expr(tokens, p)
let val = r["node"]
let p = r["pos"]
return make_result({ "stmt": "Let", "name": name, "value": val }, p)
@@ -550,14 +549,14 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
// return statement
if k == "Return" {
let p = pos + 1
let k2 = tok_kind(p)
let k2 = tok_kind(tokens, p)
if k2 == "RBrace" {
return make_result({ "stmt": "Return", "value": { "expr": "Nil" } }, p)
}
if k2 == "Eof" {
return make_result({ "stmt": "Return", "value": { "expr": "Nil" } }, p)
}
let r = parse_expr(p)
let r = parse_expr(tokens, p)
let val = r["node"]
let p = r["pos"]
return make_result({ "stmt": "Return", "value": val }, p)
@@ -566,89 +565,96 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
// fn definition
if k == "Fn" {
let p = pos + 1
let name = tok_value(p)
let name = tok_value(tokens, p)
let p = p + 1
let r = parse_params(p)
let r = parse_params(tokens, p)
let params = r["params"]
let p = r["pos"]
// return type annotation: -> Type
let k2 = tok_kind(p)
// return type annotation: -> Type. Capture the leading identifier
// so codegen can distinguish Void-returning functions from value-
// returning ones. Anything not "Void" is treated as a value type.
let ret_type = ""
let k2 = tok_kind(tokens, p)
if k2 == "Arrow" {
let p = p + 1
let p = skip_type(p)
let kt = tok_kind(tokens, p)
if kt == "Ident" {
let ret_type = tok_value(tokens, p)
}
let p = skip_type(tokens, p)
}
let r2 = parse_block(p)
let r2 = parse_block(tokens, p)
let body = r2["stmts"]
let p = r2["pos"]
return make_result({ "stmt": "FnDef", "name": name, "params": params, "body": body }, p)
return make_result({ "stmt": "FnDef", "name": name, "params": params, "body": body, "ret_type": ret_type }, p)
}
// type definition
if k == "Type" {
let p = pos + 1
let name = tok_value(p)
let name = tok_value(tokens, p)
let p = p + 1
let p = expect(p, "LBrace")
let p = expect(tokens, p, "LBrace")
let fields: [Map<String, Any>] = native_list_empty()
let running = true
while running {
let k2 = tok_kind(p)
let k2 = tok_kind(tokens, p)
if k2 == "RBrace" {
let running = false
} else {
if k2 == "Eof" {
let running = false
} else {
let fname = tok_value(p)
let fname = tok_value(tokens, p)
let p = p + 1
let p = expect(p, "Colon")
let p = skip_type(p)
let p = expect(tokens, p, "Colon")
let p = skip_type(tokens, p)
let fields = native_list_append(fields, { "name": fname })
let k3 = tok_kind(p)
let k3 = tok_kind(tokens, p)
if k3 == "Comma" {
let p = p + 1
}
}
}
}
let p = expect(p, "RBrace")
let p = expect(tokens, p, "RBrace")
return make_result({ "stmt": "TypeDef", "name": name, "fields": fields }, p)
}
// enum definition
if k == "Enum" {
let p = pos + 1
let name = tok_value(p)
let name = tok_value(tokens, p)
let p = p + 1
let p = expect(p, "LBrace")
let p = expect(tokens, p, "LBrace")
let variants: [Map<String, Any>] = native_list_empty()
let running = true
while running {
let k2 = tok_kind(p)
let k2 = tok_kind(tokens, p)
if k2 == "RBrace" {
let running = false
} else {
if k2 == "Eof" {
let running = false
} else {
let vname = tok_value(p)
let vname = tok_value(tokens, p)
let p = p + 1
let variants = native_list_append(variants, { "name": vname })
let k3 = tok_kind(p)
let k3 = tok_kind(tokens, p)
if k3 == "Comma" {
let p = p + 1
}
}
}
}
let p = expect(p, "RBrace")
let p = expect(tokens, p, "RBrace")
return make_result({ "stmt": "EnumDef", "name": name, "variants": variants }, p)
}
// import statement
if k == "Import" {
let p = pos + 1
let path = tok_value(p)
let path = tok_value(tokens, p)
let p = p + 1
return make_result({ "stmt": "Import", "path": path }, p)
}
@@ -656,20 +662,20 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
// from ... import { ... }
if k == "From" {
let p = pos + 1
let module_name = tok_value(p)
let module_name = tok_value(tokens, p)
let p = p + 1
// skip "import" keyword
let k2 = tok_kind(p)
let k2 = tok_kind(tokens, p)
if k2 == "Import" {
let p = p + 1
}
// skip { Name, ... }
let k3 = tok_kind(p)
let k3 = tok_kind(tokens, p)
if k3 == "LBrace" {
let p = p + 1
let running = true
while running {
let k4 = tok_kind(p)
let k4 = tok_kind(tokens, p)
if k4 == "RBrace" {
let running = false
} else {
@@ -677,14 +683,14 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
let running = false
} else {
let p = p + 1
let k5 = tok_kind(p)
let k5 = tok_kind(tokens, p)
if k5 == "Comma" {
let p = p + 1
}
}
}
}
let p = expect(p, "RBrace")
let p = expect(tokens, p, "RBrace")
}
return make_result({ "stmt": "Import", "path": module_name }, p)
}
@@ -692,10 +698,10 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
// while loop
if k == "While" {
let p = pos + 1
let r = parse_expr(p)
let r = parse_expr(tokens, p)
let cond = r["node"]
let p = r["pos"]
let r2 = parse_block(p)
let r2 = parse_block(tokens, p)
let body = r2["stmts"]
let p = r2["pos"]
return make_result({ "stmt": "While", "cond": cond, "body": body }, p)
@@ -704,13 +710,13 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
// for loop
if k == "For" {
let p = pos + 1
let item_name = tok_value(p)
let item_name = tok_value(tokens, p)
let p = p + 1
let p = expect(p, "In")
let r = parse_expr(p)
let p = expect(tokens, p, "In")
let r = parse_expr(tokens, p)
let list_expr = r["node"]
let p = r["pos"]
let r2 = parse_block(p)
let r2 = parse_block(tokens, p)
let body = r2["stmts"]
let p = r2["pos"]
return make_result({ "stmt": "For", "item": item_name, "list": list_expr, "body": body }, p)
@@ -721,11 +727,11 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
let p = pos + 1
// skip decorator name
let p = p + 1
return parse_stmt(p)
return parse_stmt(tokens, p)
}
// bare expression or if/match statement
let r = parse_expr(pos)
let r = parse_expr(tokens, pos)
let val = r["node"]
let p = r["pos"]
make_result({ "stmt": "Expr", "value": val }, p)
@@ -734,9 +740,7 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
// Top-level parse
fn parse(tokens: [Map<String, Any>]) -> [Map<String, Any>] {
// Store tokens in global buffer to avoid O(n²) cloning on every recursive call.
native_tokens_init(tokens)
let total: Int = native_token_len()
let total: Int = native_list_len(tokens)
let stmts: [Map<String, Any>] = native_list_empty()
let pos: Int = 0
let running = true
@@ -744,11 +748,11 @@ fn parse(tokens: [Map<String, Any>]) -> [Map<String, Any>] {
if pos >= total {
let running = false
} else {
let k = tok_kind(pos)
let k = tok_kind(tokens, pos)
if k == "Eof" {
let running = false
} else {
let r = parse_stmt(pos)
let r = parse_stmt(tokens, pos)
let stmt = r["node"]
let new_pos: Int = r["pos"]
let stmts = native_list_append(stmts, stmt)
+8
View File
@@ -0,0 +1,8 @@
import "el-compiler/src/compiler.el"
// compile() streams C source to stdout via println.
// We read source from args()[0] and compile it.
let _argv: [String] = args()
let src_path: String = native_list_get(_argv, 0)
let source: String = fs_read(src_path)
compile(source)
+2081
View File
File diff suppressed because it is too large Load Diff
+757 -628
View File
File diff suppressed because it is too large Load Diff