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:
BIN
Binary file not shown.
Vendored
+2241
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+757
-628
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user