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 ────────────────────────────────────────────────────────────────── */
|
/* ── JSON ────────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
el_val_t json_get(el_val_t json, el_val_t key);
|
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 ─────────────────────────────────────────────────────────────── */
|
/* ── Process ─────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
void exit_program(el_val_t code);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+254
-139
@@ -1,12 +1,16 @@
|
|||||||
// codegen.el — El compiler C source code generator
|
// codegen.el — El compiler C source code generator
|
||||||
//
|
//
|
||||||
// Input: list of AST statement maps (from parser.el)
|
// 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.
|
// 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().
|
// Functions map directly to C functions; top-level statements become main().
|
||||||
//
|
//
|
||||||
// Entry point: fn codegen(stmts: [Map<String, Any>], source: String) -> String
|
// 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 ────────────────────────────────────────────────────────────
|
// ── String helpers ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -50,9 +54,6 @@ fn c_str_lit(s: String) -> String {
|
|||||||
|
|
||||||
// ── Type mapping ──────────────────────────────────────────────────────────────
|
// ── 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 {
|
fn el_type_to_c(type_str: String) -> String {
|
||||||
if type_str == "String" { return "const char*" }
|
if type_str == "String" { return "const char*" }
|
||||||
if type_str == "Int" { return "int64_t" }
|
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 == "Float" { return "double" }
|
||||||
if type_str == "Void" { return "void" }
|
if type_str == "Void" { return "void" }
|
||||||
if type_str == "void" { return "void" }
|
if type_str == "void" { return "void" }
|
||||||
// Any, Map, list types, unknown → void*
|
|
||||||
"void*"
|
"void*"
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Code buffer ───────────────────────────────────────────────────────────────
|
// ── Code emission ─────────────────────────────────────────────────────────────
|
||||||
//
|
//
|
||||||
// We accumulate output lines into the VM's native instruction buffer
|
// emit_line/emit_blank stream output directly via println.
|
||||||
// (repurposed as a line buffer). Each "instruction" is a line of C text.
|
// This avoids building a large string in memory.
|
||||||
|
|
||||||
fn emit_line(line: String) -> Void {
|
fn emit_line(line: String) -> Void {
|
||||||
native_instr_push(line)
|
println(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_blank() -> Void {
|
fn emit_blank() -> Void {
|
||||||
native_instr_push("")
|
println("")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Operator helpers ──────────────────────────────────────────────────────────
|
// ── Operator helpers ──────────────────────────────────────────────────────────
|
||||||
@@ -95,14 +95,6 @@ fn binop_to_c(op: String) -> String {
|
|||||||
op
|
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 ────────────────────────────────────────────────────────
|
// ── Expression codegen ────────────────────────────────────────────────────────
|
||||||
//
|
//
|
||||||
// cg_expr returns a C expression string (not a statement).
|
// 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" {
|
if kind == "Str" {
|
||||||
let v: String = expr["value"]
|
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) + ")"
|
return "EL_STR(" + c_str_lit(v) + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,37 +150,123 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
let right = expr["right"]
|
let right = expr["right"]
|
||||||
let left_c: String = cg_expr(left)
|
let left_c: String = cg_expr(left)
|
||||||
let right_c: String = cg_expr(right)
|
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" {
|
if op == "Plus" {
|
||||||
// We can't easily detect types here, so we rely on the user
|
// If either side is a string literal, always concat
|
||||||
// calling str_concat() explicitly for strings, or we emit
|
|
||||||
// el_str_concat when either operand looks like a string expression.
|
|
||||||
// Heuristic: if either side is a Str literal, use el_str_concat.
|
|
||||||
let left_kind: String = left["expr"]
|
|
||||||
let right_kind: String = right["expr"]
|
|
||||||
if left_kind == "Str" {
|
if left_kind == "Str" {
|
||||||
return "el_str_concat(" + left_c + ", " + right_c + ")"
|
return "el_str_concat(" + left_c + ", " + right_c + ")"
|
||||||
}
|
}
|
||||||
if right_kind == "Str" {
|
if right_kind == "Str" {
|
||||||
return "el_str_concat(" + left_c + ", " + right_c + ")"
|
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" {
|
if left_kind == "Call" {
|
||||||
return "el_str_concat(" + left_c + ", " + right_c + ")"
|
return "el_str_concat(" + left_c + ", " + right_c + ")"
|
||||||
}
|
}
|
||||||
if right_kind == "Call" {
|
if right_kind == "Call" {
|
||||||
return "el_str_concat(" + left_c + ", " + right_c + ")"
|
return "el_str_concat(" + left_c + ", " + right_c + ")"
|
||||||
}
|
}
|
||||||
// Check if it's a BinOp that already became el_str_concat
|
|
||||||
if left_kind == "BinOp" {
|
if left_kind == "BinOp" {
|
||||||
let left_op: String = left["op"]
|
let left_op: String = left["op"]
|
||||||
if left_op == "Plus" {
|
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 + ")"
|
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)
|
let op_c: String = binop_to_c(op)
|
||||||
return "(" + left_c + " " + op_c + " " + right_c + ")"
|
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 arity: Int = native_list_len(args)
|
||||||
let func_kind: String = func["expr"]
|
let func_kind: String = func["expr"]
|
||||||
|
|
||||||
// Build argument list string
|
|
||||||
let args_c = ""
|
let args_c = ""
|
||||||
let i = 0
|
let i = 0
|
||||||
while i < arity {
|
while i < arity {
|
||||||
@@ -219,7 +295,6 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if func_kind == "Field" {
|
if func_kind == "Field" {
|
||||||
// method-style call: obj.method(args) — pass obj as first arg
|
|
||||||
let obj = func["object"]
|
let obj = func["object"]
|
||||||
let field: String = func["field"]
|
let field: String = func["field"]
|
||||||
let obj_c: String = cg_expr(obj)
|
let obj_c: String = cg_expr(obj)
|
||||||
@@ -229,7 +304,6 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
return field + "(" + obj_c + ")"
|
return field + "(" + obj_c + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic call — emit as a generic pointer call (best effort)
|
|
||||||
let fn_c: String = cg_expr(func)
|
let fn_c: String = cg_expr(func)
|
||||||
return fn_c + "(" + args_c + ")"
|
return fn_c + "(" + args_c + ")"
|
||||||
}
|
}
|
||||||
@@ -238,15 +312,23 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
let obj = expr["object"]
|
let obj = expr["object"]
|
||||||
let field: String = expr["field"]
|
let field: String = expr["field"]
|
||||||
let obj_c: String = cg_expr(obj)
|
let obj_c: String = cg_expr(obj)
|
||||||
// Map field access to a runtime helper
|
|
||||||
return "el_get_field(" + obj_c + ", " + c_str_lit(field) + ")"
|
return "el_get_field(" + obj_c + ", " + c_str_lit(field) + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
if kind == "Index" {
|
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 obj = expr["object"]
|
||||||
let idx = expr["index"]
|
let idx = expr["index"]
|
||||||
let obj_c: String = cg_expr(obj)
|
let obj_c: String = cg_expr(obj)
|
||||||
let idx_c: String = cg_expr(idx)
|
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 + ")"
|
return "el_list_get(" + obj_c + ", " + idx_c + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,7 +350,6 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if kind == "Map" {
|
if kind == "Map" {
|
||||||
// Map literals: emit as el_map_new with key/value pairs
|
|
||||||
let pairs = expr["pairs"]
|
let pairs = expr["pairs"]
|
||||||
let n: Int = native_list_len(pairs)
|
let n: Int = native_list_len(pairs)
|
||||||
let items = ""
|
let items = ""
|
||||||
@@ -288,78 +369,85 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if kind == "Try" {
|
if kind == "Try" {
|
||||||
// ? operator — just pass through the value for now
|
|
||||||
let inner = expr["inner"]
|
let inner = expr["inner"]
|
||||||
return cg_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" {
|
if kind == "If" {
|
||||||
// Emit as a GNU C statement expression ({...}) — widely supported
|
|
||||||
// by GCC/Clang. We emit it inline.
|
|
||||||
let cond = expr["cond"]
|
let cond = expr["cond"]
|
||||||
let then_stmts = expr["then"]
|
|
||||||
let else_stmts = expr["else"]
|
|
||||||
let has_else: Bool = expr["has_else"]
|
|
||||||
let cond_c: String = cg_expr(cond)
|
let cond_c: String = cg_expr(cond)
|
||||||
// Gather then block
|
return "/* if-expr */ ((" + cond_c + ") ? (el_val_t)1 : (el_val_t)0)"
|
||||||
let then_buf = cg_stmts_to_str(then_stmts, " ")
|
|
||||||
let result = "/* if-expr */ ((" + cond_c + ") ? (void*)1 : (void*)0)"
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback
|
"EL_NULL"
|
||||||
"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 ─────────────────────────────────────────────────────────
|
// ── 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"]
|
let kind: String = stmt["stmt"]
|
||||||
|
|
||||||
if kind == "Let" {
|
if kind == "Let" {
|
||||||
let name: String = stmt["name"]
|
let name: String = stmt["name"]
|
||||||
let val = stmt["value"]
|
let val = stmt["value"]
|
||||||
let val_c: String = cg_expr(val)
|
let val_c: String = cg_expr(val)
|
||||||
// All El values are el_val_t (int64_t), which can hold integers directly
|
if list_contains(declared, name) {
|
||||||
// and store pointers (strings, lists) via pointer-int cast.
|
emit_line(indent + name + " = " + val_c + ";")
|
||||||
emit_line(indent + "el_val_t " + name + " = " + val_c + ";")
|
return declared
|
||||||
return
|
} else {
|
||||||
|
emit_line(indent + "el_val_t " + name + " = " + val_c + ";")
|
||||||
|
return native_list_append(declared, name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if kind == "Return" {
|
if kind == "Return" {
|
||||||
let val = stmt["value"]
|
let val = stmt["value"]
|
||||||
let val_kind: String = val["expr"]
|
let val_kind: String = val["expr"]
|
||||||
if val_kind == "Nil" {
|
if val_kind == "Nil" {
|
||||||
emit_line(indent + "return;")
|
emit_line(indent + "return 0;")
|
||||||
} else {
|
} else {
|
||||||
let val_c: String = cg_expr(val)
|
let val_c: String = cg_expr(val)
|
||||||
emit_line(indent + "return " + val_c + ";")
|
emit_line(indent + "return " + val_c + ";")
|
||||||
}
|
}
|
||||||
return
|
return declared
|
||||||
}
|
}
|
||||||
|
|
||||||
if kind == "Expr" {
|
if kind == "Expr" {
|
||||||
let val = stmt["value"]
|
let val = stmt["value"]
|
||||||
let val_kind: String = val["expr"]
|
let val_kind: String = val["expr"]
|
||||||
// Handle if/while/for at statement level specially
|
|
||||||
if val_kind == "If" {
|
if val_kind == "If" {
|
||||||
cg_if_stmt(val, indent)
|
cg_if_stmt(val, indent, declared)
|
||||||
return
|
return declared
|
||||||
}
|
}
|
||||||
if val_kind == "For" {
|
if val_kind == "For" {
|
||||||
cg_for_stmt(val, indent)
|
cg_for_stmt(val, indent, declared)
|
||||||
return
|
return declared
|
||||||
}
|
}
|
||||||
let val_c: String = cg_expr(val)
|
let val_c: String = cg_expr(val)
|
||||||
emit_line(indent + val_c + ";")
|
emit_line(indent + val_c + ";")
|
||||||
return
|
return declared
|
||||||
}
|
}
|
||||||
|
|
||||||
if kind == "While" {
|
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: String = cg_expr(cond)
|
||||||
let cond_c = strip_outer_parens(cond_c)
|
let cond_c = strip_outer_parens(cond_c)
|
||||||
emit_line(indent + "while (" + cond_c + ") {")
|
emit_line(indent + "while (" + cond_c + ") {")
|
||||||
cg_stmts(body, indent + " ")
|
cg_stmts(body, indent + " ", declared)
|
||||||
emit_line(indent + "}")
|
emit_line(indent + "}")
|
||||||
return
|
return declared
|
||||||
}
|
}
|
||||||
|
|
||||||
if kind == "For" {
|
if kind == "For" {
|
||||||
// for item in list { body }
|
|
||||||
let item: String = stmt["item"]
|
let item: String = stmt["item"]
|
||||||
let list_expr = stmt["list"]
|
let list_expr = stmt["list"]
|
||||||
let body = stmt["body"]
|
let body = stmt["body"]
|
||||||
cg_for_body(item, list_expr, body, indent)
|
cg_for_body(item, list_expr, body, indent, declared)
|
||||||
return
|
return declared
|
||||||
}
|
}
|
||||||
|
|
||||||
if kind == "FnDef" {
|
if kind == "FnDef" { return declared }
|
||||||
// Function definitions are handled at the top level — skip here
|
if kind == "TypeDef" { return declared }
|
||||||
// (they would appear as nested fns if El ever supports them,
|
if kind == "EnumDef" { return declared }
|
||||||
// but we emit them top-level in the pass over top-level stmts).
|
if kind == "Import" { return declared }
|
||||||
return
|
declared
|
||||||
}
|
|
||||||
|
|
||||||
if kind == "TypeDef" { return }
|
|
||||||
if kind == "EnumDef" { return }
|
|
||||||
if kind == "Import" { return }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strip a single layer of surrounding parentheses from a C expression string,
|
// Strip a single layer of surrounding parentheses from a C expression string.
|
||||||
// if present. This avoids double-parens in "if ((cond))".
|
|
||||||
fn strip_outer_parens(s: String) -> String {
|
fn strip_outer_parens(s: String) -> String {
|
||||||
let chars: [String] = native_string_chars(s)
|
let chars: [String] = native_string_chars(s)
|
||||||
let n: Int = native_list_len(chars)
|
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)
|
let last: String = native_list_get(chars, n - 1)
|
||||||
if first == "(" {
|
if first == "(" {
|
||||||
if last == ")" {
|
if last == ")" {
|
||||||
// Verify the opening paren matches the closing one (depth check)
|
|
||||||
let depth = 1
|
let depth = 1
|
||||||
let i = 1
|
let i = 1
|
||||||
let balanced = true
|
let balanced = true
|
||||||
@@ -417,13 +497,12 @@ fn strip_outer_parens(s: String) -> String {
|
|||||||
let depth = depth - 1
|
let depth = depth - 1
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
let balanced = false
|
let balanced = false
|
||||||
let i = n // break
|
let i = n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let i = i + 1
|
let i = i + 1
|
||||||
}
|
}
|
||||||
if balanced {
|
if balanced {
|
||||||
// Safe to strip outer parens
|
|
||||||
let inner = ""
|
let inner = ""
|
||||||
let j = 1
|
let j = 1
|
||||||
while j < n - 1 {
|
while j < n - 1 {
|
||||||
@@ -438,72 +517,60 @@ fn strip_outer_parens(s: String) -> String {
|
|||||||
s
|
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 cond = expr["cond"]
|
||||||
let then_stmts = expr["then"]
|
let then_stmts = expr["then"]
|
||||||
let else_stmts = expr["else"]
|
let else_stmts = expr["else"]
|
||||||
let has_else: Bool = expr["has_else"]
|
let has_else: Bool = expr["has_else"]
|
||||||
let cond_c: String = cg_expr(cond)
|
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)
|
let cond_c = strip_outer_parens(cond_c)
|
||||||
emit_line(indent + "if (" + cond_c + ") {")
|
emit_line(indent + "if (" + cond_c + ") {")
|
||||||
cg_stmts(then_stmts, indent + " ")
|
cg_stmts(then_stmts, indent + " ", declared)
|
||||||
if has_else {
|
if has_else {
|
||||||
emit_line(indent + "} else {")
|
emit_line(indent + "} else {")
|
||||||
cg_stmts(else_stmts, indent + " ")
|
cg_stmts(else_stmts, indent + " ", declared)
|
||||||
}
|
}
|
||||||
emit_line(indent + "}")
|
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 list_c: String = cg_expr(list_expr)
|
||||||
let uid0 = native_int_to_str(unique_id())
|
let idx = "_el_i"
|
||||||
let idx = "_el_i_" + uid0
|
let list_tmp = "_el_lst"
|
||||||
let uid1 = native_int_to_str(unique_id())
|
let len_tmp = "_el_len"
|
||||||
let list_tmp = "_el_lst_" + uid1
|
|
||||||
let uid2 = native_int_to_str(unique_id())
|
|
||||||
let len_tmp = "_el_len_" + uid2
|
|
||||||
emit_line(indent + "{")
|
emit_line(indent + "{")
|
||||||
emit_line(indent + " el_val_t " + list_tmp + " = " + list_c + ";")
|
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 + " 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 + " for (el_val_t " + idx + " = 0; " + idx + " < " + len_tmp + "; " + idx + "++) {")
|
||||||
emit_line(indent + " el_val_t " + item + " = el_list_get(" + list_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 + " }")
|
||||||
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 item: String = expr["item"]
|
||||||
let list_expr = expr["list"]
|
let list_expr = expr["list"]
|
||||||
let body = expr["body"]
|
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 n: Int = native_list_len(stmts)
|
||||||
let i = 0
|
let i = 0
|
||||||
|
let decl = declared
|
||||||
while i < n {
|
while i < n {
|
||||||
let stmt = native_list_get(stmts, i)
|
let stmt = native_list_get(stmts, i)
|
||||||
cg_stmt(stmt, indent)
|
let decl = cg_stmt(stmt, indent, decl)
|
||||||
let i = i + 1
|
let i = i + 1
|
||||||
}
|
}
|
||||||
}
|
decl
|
||||||
|
|
||||||
// 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.
|
|
||||||
""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Function declaration codegen ───────────────────────────────────────────────
|
// ── Function declaration codegen ───────────────────────────────────────────────
|
||||||
|
|
||||||
fn param_decl(param: Map<String, Any>, idx: Int) -> String {
|
fn param_decl(param: Map<String, Any>, idx: Int) -> String {
|
||||||
let name: String = param["name"]
|
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
|
"el_val_t " + name
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -524,15 +591,71 @@ fn params_to_c(params: [Map<String, Any>]) -> String {
|
|||||||
out
|
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 {
|
fn cg_fn(stmt: Map<String, Any>) -> Void {
|
||||||
let fn_name: String = stmt["name"]
|
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 params = stmt["params"]
|
||||||
let body = stmt["body"]
|
let body = stmt["body"]
|
||||||
|
let ret_type: String = stmt["ret_type"]
|
||||||
let params_c: String = params_to_c(params)
|
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 + ") {")
|
emit_line("el_val_t " + fn_name + "(" + params_c + ") {")
|
||||||
cg_stmts(body, " ")
|
// Seed declared with parameter names so reassignment works
|
||||||
// Implicit return 0 (el_val_t) if no explicit return
|
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(" return 0;")
|
||||||
emit_line("}")
|
emit_line("}")
|
||||||
emit_blank()
|
emit_blank()
|
||||||
@@ -540,7 +663,6 @@ fn cg_fn(stmt: Map<String, Any>) -> Void {
|
|||||||
|
|
||||||
// ── Top-level codegen ─────────────────────────────────────────────────────────
|
// ── Top-level codegen ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// Collect top-level statements that are NOT FnDefs — these go into main().
|
|
||||||
fn is_fndef(stmt: Map<String, Any>) -> Bool {
|
fn is_fndef(stmt: Map<String, Any>) -> Bool {
|
||||||
let kind: String = stmt["stmt"]
|
let kind: String = stmt["stmt"]
|
||||||
if kind == "FnDef" { return true }
|
if kind == "FnDef" { return true }
|
||||||
@@ -558,15 +680,13 @@ fn is_top_level_decl(stmt: Map<String, Any>) -> Bool {
|
|||||||
// ── Entry point ────────────────────────────────────────────────────────────────
|
// ── Entry point ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn codegen(stmts: [Map<String, Any>], source: String) -> String {
|
fn codegen(stmts: [Map<String, Any>], source: String) -> String {
|
||||||
native_instr_reset()
|
|
||||||
|
|
||||||
// Preamble
|
// Preamble
|
||||||
emit_line("#include <stdint.h>")
|
emit_line("#include <stdint.h>")
|
||||||
emit_line("#include <stdlib.h>")
|
emit_line("#include <stdlib.h>")
|
||||||
emit_line("#include \"el_runtime.h\"")
|
emit_line("#include \"el_runtime.h\"")
|
||||||
emit_blank()
|
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 n: Int = native_list_len(stmts)
|
||||||
let i = 0
|
let i = 0
|
||||||
while i < n {
|
while i < n {
|
||||||
@@ -574,15 +694,17 @@ fn codegen(stmts: [Map<String, Any>], source: String) -> String {
|
|||||||
let kind: String = stmt["stmt"]
|
let kind: String = stmt["stmt"]
|
||||||
if kind == "FnDef" {
|
if kind == "FnDef" {
|
||||||
let fn_name: String = stmt["name"]
|
let fn_name: String = stmt["name"]
|
||||||
let params = stmt["params"]
|
if !str_eq(fn_name, "main") {
|
||||||
let params_c: String = params_to_c(params)
|
let params = stmt["params"]
|
||||||
emit_line("el_val_t " + fn_name + "(" + params_c + ");")
|
let params_c: String = params_to_c(params)
|
||||||
|
emit_line("el_val_t " + fn_name + "(" + params_c + ");")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let i = i + 1
|
let i = i + 1
|
||||||
}
|
}
|
||||||
emit_blank()
|
emit_blank()
|
||||||
|
|
||||||
// Emit function definitions
|
// Function definitions
|
||||||
let i = 0
|
let i = 0
|
||||||
while i < n {
|
while i < n {
|
||||||
let stmt = native_list_get(stmts, i)
|
let stmt = native_list_get(stmts, i)
|
||||||
@@ -592,18 +714,20 @@ fn codegen(stmts: [Map<String, Any>], source: String) -> String {
|
|||||||
let i = i + 1
|
let i = i + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit main() for top-level statements
|
// main()
|
||||||
emit_line("int main(void) {")
|
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
|
let i = 0
|
||||||
while i < n {
|
while i < n {
|
||||||
let stmt = native_list_get(stmts, i)
|
let stmt = native_list_get(stmts, i)
|
||||||
if is_fndef(stmt) {
|
if is_fndef(stmt) {
|
||||||
// skip — already emitted above
|
// skip
|
||||||
} else {
|
} else {
|
||||||
if is_top_level_decl(stmt) {
|
if is_top_level_decl(stmt) {
|
||||||
// skip — compile-time only
|
// skip
|
||||||
} else {
|
} else {
|
||||||
cg_stmt(stmt, " ")
|
let main_decl = cg_stmt(stmt, " ", main_decl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let i = i + 1
|
let i = i + 1
|
||||||
@@ -612,15 +736,6 @@ fn codegen(stmts: [Map<String, Any>], source: String) -> String {
|
|||||||
emit_line("}")
|
emit_line("}")
|
||||||
emit_blank()
|
emit_blank()
|
||||||
|
|
||||||
// Collect all emitted lines and join with newlines
|
// Return empty string — output was streamed via println
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-31
@@ -7,8 +7,8 @@
|
|||||||
//
|
//
|
||||||
// Entry point: fn lex(source: String) -> [Map<String, Any>]
|
// Entry point: fn lex(source: String) -> [Map<String, Any>]
|
||||||
//
|
//
|
||||||
// Uses global char_buf via native_chars_init / native_char_at / native_char_len
|
// Uses native_string_chars to split the source into a chars list,
|
||||||
// to avoid O(N²) cloning of the chars list.
|
// then indexes it with native_list_get — avoids O(N²) string cloning.
|
||||||
|
|
||||||
// ── Character helpers ─────────────────────────────────────────────────────────
|
// ── Character helpers ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -140,15 +140,20 @@ fn keyword_kind(word: String) -> String {
|
|||||||
if word == "target" { return "Target" }
|
if word == "target" { return "Target" }
|
||||||
if word == "true" { return "Bool" }
|
if word == "true" { return "Bool" }
|
||||||
if word == "false" { 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 ──────────────────────────────────────────────────────────────
|
// ── Scan helpers ──────────────────────────────────────────────────────────────
|
||||||
// All scan helpers use the global char_buf (native_char_at / native_char_len).
|
// All scan helpers receive the chars list and total length.
|
||||||
// No chars parameter — avoids O(N²) cloning.
|
|
||||||
|
|
||||||
// scan_digits — advance i while char_buf[i] is a digit, return { "text": ..., "pos": i }
|
// scan_digits — advance i while chars[i] is a digit
|
||||||
fn scan_digits(start: Int, total: Int) -> Map<String, Any> {
|
// Returns { "text": ..., "pos": i }
|
||||||
|
fn scan_digits(chars: [String], start: Int, total: Int) -> Map<String, Any> {
|
||||||
let i = start
|
let i = start
|
||||||
let text = ""
|
let text = ""
|
||||||
let running = true
|
let running = true
|
||||||
@@ -156,7 +161,7 @@ fn scan_digits(start: Int, total: Int) -> Map<String, Any> {
|
|||||||
if i >= total {
|
if i >= total {
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
let ch = native_char_at(i)
|
let ch: String = native_list_get(chars, i)
|
||||||
if is_digit(ch) {
|
if is_digit(ch) {
|
||||||
let text = text + ch
|
let text = text + ch
|
||||||
let i = i + 1
|
let i = i + 1
|
||||||
@@ -168,8 +173,8 @@ fn scan_digits(start: Int, total: Int) -> Map<String, Any> {
|
|||||||
{ "text": text, "pos": i }
|
{ "text": text, "pos": i }
|
||||||
}
|
}
|
||||||
|
|
||||||
// scan_ident — advance i while char_buf[i] is alphanumeric or underscore
|
// scan_ident — advance i while chars[i] is alphanumeric or underscore
|
||||||
fn scan_ident(start: Int, total: Int) -> Map<String, Any> {
|
fn scan_ident(chars: [String], start: Int, total: Int) -> Map<String, Any> {
|
||||||
let i = start
|
let i = start
|
||||||
let text = ""
|
let text = ""
|
||||||
let running = true
|
let running = true
|
||||||
@@ -177,7 +182,7 @@ fn scan_ident(start: Int, total: Int) -> Map<String, Any> {
|
|||||||
if i >= total {
|
if i >= total {
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
let ch = native_char_at(i)
|
let ch: String = native_list_get(chars, i)
|
||||||
if is_alnum_or_underscore(ch) {
|
if is_alnum_or_underscore(ch) {
|
||||||
let text = text + ch
|
let text = text + ch
|
||||||
let i = i + 1
|
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.
|
// scan_string — scan a quoted string literal, handling \" escapes.
|
||||||
// Starts AFTER the opening quote. Returns { "text": content, "pos": i_after_close }
|
// 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 i = start
|
||||||
let text = ""
|
let text = ""
|
||||||
let closed = false
|
|
||||||
let running = true
|
let running = true
|
||||||
while running {
|
while running {
|
||||||
if i >= total {
|
if i >= total {
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
let ch = native_char_at(i)
|
let ch: String = native_list_get(chars, i)
|
||||||
if ch == "\\" {
|
if ch == "\\" {
|
||||||
// escape: peek next char
|
// escape: peek next char
|
||||||
let next_i = i + 1
|
let next_i = i + 1
|
||||||
if next_i < total {
|
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 == "\"" {
|
if next_ch == "\"" {
|
||||||
let text = text + "\""
|
let text = text + "\""
|
||||||
let i = next_i + 1
|
let i = next_i + 1
|
||||||
@@ -218,12 +222,17 @@ fn scan_string(start: Int, total: Int) -> Map<String, Any> {
|
|||||||
let text = text + "\t"
|
let text = text + "\t"
|
||||||
let i = next_i + 1
|
let i = next_i + 1
|
||||||
} else {
|
} else {
|
||||||
if next_ch == "\\" {
|
if next_ch == "r" {
|
||||||
let text = text + "\\"
|
let text = text + "\r"
|
||||||
let i = next_i + 1
|
let i = next_i + 1
|
||||||
} else {
|
} else {
|
||||||
let text = text + next_ch
|
if next_ch == "\\" {
|
||||||
let i = next_i + 1
|
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 {
|
} else {
|
||||||
if ch == "\"" {
|
if ch == "\"" {
|
||||||
let closed = true
|
|
||||||
let i = i + 1
|
let i = i + 1
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
@@ -249,13 +257,13 @@ fn scan_string(start: Int, total: Int) -> Map<String, Any> {
|
|||||||
// ── Main lexer ────────────────────────────────────────────────────────────────
|
// ── Main lexer ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn lex(source: String) -> [Map<String, Any>] {
|
fn lex(source: String) -> [Map<String, Any>] {
|
||||||
native_chars_init(source)
|
let chars: [String] = native_string_chars(source)
|
||||||
let total: Int = native_char_len()
|
let total: Int = native_list_len(chars)
|
||||||
let tokens: [Map<String, Any>] = native_list_empty()
|
let tokens: [Map<String, Any>] = native_list_empty()
|
||||||
let i: Int = 0
|
let i: Int = 0
|
||||||
|
|
||||||
while i < total {
|
while i < total {
|
||||||
let ch: String = native_char_at(i)
|
let ch: String = native_list_get(chars, i)
|
||||||
|
|
||||||
// Skip whitespace
|
// Skip whitespace
|
||||||
if is_whitespace(ch) {
|
if is_whitespace(ch) {
|
||||||
@@ -265,7 +273,7 @@ fn lex(source: String) -> [Map<String, Any>] {
|
|||||||
if ch == "/" {
|
if ch == "/" {
|
||||||
let next_i = i + 1
|
let next_i = i + 1
|
||||||
if next_i < total {
|
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 == "/" {
|
if next_ch == "/" {
|
||||||
// skip to end of line
|
// skip to end of line
|
||||||
let i = i + 2
|
let i = i + 2
|
||||||
@@ -274,7 +282,7 @@ fn lex(source: String) -> [Map<String, Any>] {
|
|||||||
if i >= total {
|
if i >= total {
|
||||||
let running2 = false
|
let running2 = false
|
||||||
} else {
|
} else {
|
||||||
let lch: String = native_char_at(i)
|
let lch: String = native_list_get(chars, i)
|
||||||
if lch == "\n" {
|
if lch == "\n" {
|
||||||
let running2 = false
|
let running2 = false
|
||||||
} else {
|
} else {
|
||||||
@@ -293,7 +301,7 @@ fn lex(source: String) -> [Map<String, Any>] {
|
|||||||
} else {
|
} else {
|
||||||
// String literal
|
// String literal
|
||||||
if ch == "\"" {
|
if ch == "\"" {
|
||||||
let result = scan_string(i + 1, total)
|
let result = scan_string(chars, i + 1, total)
|
||||||
let str_text: String = result["text"]
|
let str_text: String = result["text"]
|
||||||
let new_pos: Int = result["pos"]
|
let new_pos: Int = result["pos"]
|
||||||
let tokens = native_list_append(tokens, make_tok("Str", str_text))
|
let tokens = native_list_append(tokens, make_tok("Str", str_text))
|
||||||
@@ -301,18 +309,18 @@ fn lex(source: String) -> [Map<String, Any>] {
|
|||||||
} else {
|
} else {
|
||||||
// Number literal
|
// Number literal
|
||||||
if is_digit(ch) {
|
if is_digit(ch) {
|
||||||
let result = scan_digits(i, total)
|
let result = scan_digits(chars, i, total)
|
||||||
let num_text: String = result["text"]
|
let num_text: String = result["text"]
|
||||||
let new_pos: Int = result["pos"]
|
let new_pos: Int = result["pos"]
|
||||||
// check for float (dot followed by digit)
|
// check for float (dot followed by digit)
|
||||||
if new_pos < total {
|
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 == "." {
|
if dot_ch == "." {
|
||||||
let after_dot = new_pos + 1
|
let after_dot = new_pos + 1
|
||||||
if after_dot < total {
|
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) {
|
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_text: String = frac_result["text"]
|
||||||
let frac_pos: Int = frac_result["pos"]
|
let frac_pos: Int = frac_result["pos"]
|
||||||
let tokens = native_list_append(tokens, make_tok("Float", num_text + "." + frac_text))
|
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 {
|
} else {
|
||||||
// Identifier or keyword
|
// Identifier or keyword
|
||||||
if is_alpha(ch) || ch == "_" {
|
if is_alpha(ch) || ch == "_" {
|
||||||
let result = scan_ident(i, total)
|
let result = scan_ident(chars, i, total)
|
||||||
let word: String = result["text"]
|
let word: String = result["text"]
|
||||||
let new_pos: Int = result["pos"]
|
let new_pos: Int = result["pos"]
|
||||||
let kw = keyword_kind(word)
|
let kw = keyword_kind(word)
|
||||||
@@ -351,7 +359,7 @@ fn lex(source: String) -> [Map<String, Any>] {
|
|||||||
let peek_i = i + 1
|
let peek_i = i + 1
|
||||||
let peek_ch = ""
|
let peek_ch = ""
|
||||||
if peek_i < total {
|
if peek_i < total {
|
||||||
let peek_ch = native_char_at(peek_i)
|
let peek_ch: String = native_list_get(chars, peek_i)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ch == "=" {
|
if ch == "=" {
|
||||||
@@ -442,6 +450,10 @@ fn lex(source: String) -> [Map<String, Any>] {
|
|||||||
if ch == "*" {
|
if ch == "*" {
|
||||||
let tokens = native_list_append(tokens, make_tok("Star", "*"))
|
let tokens = native_list_append(tokens, make_tok("Star", "*"))
|
||||||
let i = i + 1
|
let i = i + 1
|
||||||
|
} else {
|
||||||
|
if ch == "%" {
|
||||||
|
let tokens = native_list_append(tokens, make_tok("Percent", "%"))
|
||||||
|
let i = i + 1
|
||||||
} else {
|
} else {
|
||||||
if ch == "(" {
|
if ch == "(" {
|
||||||
let tokens = native_list_append(tokens, make_tok("LParen", "("))
|
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
|
// The cursor (integer position into the token list) is threaded through every
|
||||||
// parse function. Functions return { "node": <map>, "pos": <int> }.
|
// parse function. Functions return { "node": <map>, "pos": <int> }.
|
||||||
//
|
//
|
||||||
// The token list is stored in the VM's global token buffer via
|
// The token list is passed as a parameter to all parse functions.
|
||||||
// native_tokens_init / native_token_at / native_token_len. This avoids
|
// native_list_get is used to index into it without cloning.
|
||||||
// O(n²) cloning that occurs when passing the list as a function argument.
|
|
||||||
//
|
//
|
||||||
// Entry point: fn parse(tokens: [Map<String, Any>]) -> [Map<String, Any>]
|
// Entry point: fn parse(tokens: [Map<String, Any>]) -> [Map<String, Any>]
|
||||||
|
|
||||||
// ── Token access helpers ──────────────────────────────────────────────────────
|
// ── Token access helpers ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn tok_at(pos: Int) -> Map<String, Any> {
|
fn tok_at(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||||
native_token_at(pos)
|
native_list_get(tokens, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tok_kind(pos: Int) -> String {
|
fn tok_kind(tokens: [Map<String, Any>], pos: Int) -> String {
|
||||||
let t = native_token_at(pos)
|
let t = native_list_get(tokens, pos)
|
||||||
t["kind"]
|
t["kind"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tok_value(pos: Int) -> String {
|
fn tok_value(tokens: [Map<String, Any>], pos: Int) -> String {
|
||||||
let t = native_token_at(pos)
|
let t = native_list_get(tokens, pos)
|
||||||
t["value"]
|
t["value"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expect(pos: Int, kind: String) -> Int {
|
fn expect(tokens: [Map<String, Any>], pos: Int, kind: String) -> Int {
|
||||||
let k = tok_kind(pos)
|
let k = tok_kind(tokens, pos)
|
||||||
if k == kind {
|
if k == kind {
|
||||||
return pos + 1
|
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.
|
// Skips over a type annotation, returning the new position.
|
||||||
// Types can be: Ident, [Type], Map<K,V>, Type?, Type<Type,...>
|
// Types can be: Ident, [Type], Map<K,V>, Type?, Type<Type,...>
|
||||||
|
|
||||||
fn skip_type(pos: Int) -> Int {
|
fn skip_type(tokens: [Map<String, Any>], pos: Int) -> Int {
|
||||||
let k = tok_kind(pos)
|
let k = tok_kind(tokens, pos)
|
||||||
// Array type: [Type]
|
// Array type: [Type]
|
||||||
if k == "LBracket" {
|
if k == "LBracket" {
|
||||||
let p = pos + 1
|
let p = pos + 1
|
||||||
let p = skip_type(p)
|
let p = skip_type(tokens, p)
|
||||||
let p = expect(p, "RBracket")
|
let p = expect(tokens, p, "RBracket")
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
// Named type (possibly generic)
|
// Named type (possibly generic)
|
||||||
if k == "Ident" {
|
if k == "Ident" {
|
||||||
let p = pos + 1
|
let p = pos + 1
|
||||||
let k2 = tok_kind(p)
|
let k2 = tok_kind(tokens, p)
|
||||||
if k2 == "Lt" {
|
if k2 == "Lt" {
|
||||||
// Generic params: skip until matching >
|
// Generic params: skip until matching >
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
let depth = 1
|
let depth = 1
|
||||||
let running = true
|
let running = true
|
||||||
while running {
|
while running {
|
||||||
let kk = tok_kind(p)
|
let kk = tok_kind(tokens, p)
|
||||||
if kk == "Eof" {
|
if kk == "Eof" {
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} 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" {
|
if k3 == "QuestionMark" {
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
}
|
}
|
||||||
@@ -104,12 +103,12 @@ fn skip_type(pos: Int) -> Int {
|
|||||||
// ── Parameter list ────────────────────────────────────────────────────────────
|
// ── Parameter list ────────────────────────────────────────────────────────────
|
||||||
// Parses (name: Type, name: Type, ...) — returns { "params": [...], "pos": ... }
|
// Parses (name: Type, name: Type, ...) — returns { "params": [...], "pos": ... }
|
||||||
|
|
||||||
fn parse_params(pos: Int) -> Map<String, Any> {
|
fn parse_params(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||||
let p = expect(pos, "LParen")
|
let p = expect(tokens, pos, "LParen")
|
||||||
let params: [Map<String, Any>] = native_list_empty()
|
let params: [Map<String, Any>] = native_list_empty()
|
||||||
let running = true
|
let running = true
|
||||||
while running {
|
while running {
|
||||||
let k = tok_kind(p)
|
let k = tok_kind(tokens, p)
|
||||||
if k == "RParen" {
|
if k == "RParen" {
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
@@ -117,28 +116,28 @@ fn parse_params(pos: Int) -> Map<String, Any> {
|
|||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
// param name
|
// param name
|
||||||
let pname = tok_value(p)
|
let pname = tok_value(tokens, p)
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
let p = expect(p, "Colon")
|
let p = expect(tokens, p, "Colon")
|
||||||
let p = skip_type(p)
|
let p = skip_type(tokens, p)
|
||||||
let param = { "name": pname }
|
let param = { "name": pname }
|
||||||
let params = native_list_append(params, param)
|
let params = native_list_append(params, param)
|
||||||
let k2 = tok_kind(p)
|
let k2 = tok_kind(tokens, p)
|
||||||
if k2 == "Comma" {
|
if k2 == "Comma" {
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let p = expect(p, "RParen")
|
let p = expect(tokens, p, "RParen")
|
||||||
{ "params": params, "pos": p }
|
{ "params": params, "pos": p }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Expression parsing ────────────────────────────────────────────────────────
|
// ── Expression parsing ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn parse_primary(pos: Int) -> Map<String, Any> {
|
fn parse_primary(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||||
let k = tok_kind(pos)
|
let k = tok_kind(tokens, pos)
|
||||||
let v = tok_value(pos)
|
let v = tok_value(tokens, pos)
|
||||||
|
|
||||||
// Integer literal
|
// Integer literal
|
||||||
if k == "Int" {
|
if k == "Int" {
|
||||||
@@ -167,10 +166,10 @@ fn parse_primary(pos: Int) -> Map<String, Any> {
|
|||||||
|
|
||||||
// Grouped expression
|
// Grouped expression
|
||||||
if k == "LParen" {
|
if k == "LParen" {
|
||||||
let r = parse_expr(pos + 1)
|
let r = parse_expr(tokens, pos + 1)
|
||||||
let node = r["node"]
|
let node = r["node"]
|
||||||
let p = r["pos"]
|
let p = r["pos"]
|
||||||
let p = expect(p, "RParen")
|
let p = expect(tokens, p, "RParen")
|
||||||
return make_result(node, p)
|
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 elems: [Map<String, Any>] = native_list_empty()
|
||||||
let running = true
|
let running = true
|
||||||
while running {
|
while running {
|
||||||
let k2 = tok_kind(p)
|
let k2 = tok_kind(tokens, p)
|
||||||
if k2 == "RBracket" {
|
if k2 == "RBracket" {
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
if k2 == "Eof" {
|
if k2 == "Eof" {
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
let r = parse_expr(p)
|
let r = parse_expr(tokens, p)
|
||||||
let elem = r["node"]
|
let elem = r["node"]
|
||||||
let p = r["pos"]
|
let p = r["pos"]
|
||||||
let elems = native_list_append(elems, elem)
|
let elems = native_list_append(elems, elem)
|
||||||
let k3 = tok_kind(p)
|
let k3 = tok_kind(tokens, p)
|
||||||
if k3 == "Comma" {
|
if k3 == "Comma" {
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let p = expect(p, "RBracket")
|
let p = expect(tokens, p, "RBracket")
|
||||||
return make_result({ "expr": "Array", "elems": elems }, p)
|
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 pairs: [Map<String, Any>] = native_list_empty()
|
||||||
let running = true
|
let running = true
|
||||||
while running {
|
while running {
|
||||||
let k2 = tok_kind(p)
|
let k2 = tok_kind(tokens, p)
|
||||||
if k2 == "RBrace" {
|
if k2 == "RBrace" {
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
@@ -216,46 +215,46 @@ fn parse_primary(pos: Int) -> Map<String, Any> {
|
|||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
// key: Str token
|
// key: Str token
|
||||||
let key = tok_value(p)
|
let key = tok_value(tokens, p)
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
let p = expect(p, "Colon")
|
let p = expect(tokens, p, "Colon")
|
||||||
let r = parse_expr(p)
|
let r = parse_expr(tokens, p)
|
||||||
let val_node = r["node"]
|
let val_node = r["node"]
|
||||||
let p = r["pos"]
|
let p = r["pos"]
|
||||||
let pair = { "key": key, "value": val_node }
|
let pair = { "key": key, "value": val_node }
|
||||||
let pairs = native_list_append(pairs, pair)
|
let pairs = native_list_append(pairs, pair)
|
||||||
let k3 = tok_kind(p)
|
let k3 = tok_kind(tokens, p)
|
||||||
if k3 == "Comma" {
|
if k3 == "Comma" {
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let p = expect(p, "RBrace")
|
let p = expect(tokens, p, "RBrace")
|
||||||
return make_result({ "expr": "Map", "pairs": pairs }, p)
|
return make_result({ "expr": "Map", "pairs": pairs }, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if expression
|
// if expression
|
||||||
if k == "If" {
|
if k == "If" {
|
||||||
let r = parse_if(pos)
|
let r = parse_if(tokens, pos)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// match expression
|
// match expression
|
||||||
if k == "Match" {
|
if k == "Match" {
|
||||||
let r = parse_match(pos)
|
let r = parse_match(tokens, pos)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// for expression (used as statement)
|
// for expression (used as statement)
|
||||||
if k == "For" {
|
if k == "For" {
|
||||||
let r = parse_for_expr(pos)
|
let r = parse_for_expr(tokens, pos)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unary not
|
// Unary not
|
||||||
if k == "Not" {
|
if k == "Not" {
|
||||||
let r = parse_primary(pos + 1)
|
let r = parse_primary(tokens, pos + 1)
|
||||||
let inner = r["node"]
|
let inner = r["node"]
|
||||||
let p = r["pos"]
|
let p = r["pos"]
|
||||||
return make_result({ "expr": "Not", "inner": inner }, p)
|
return make_result({ "expr": "Not", "inner": inner }, p)
|
||||||
@@ -263,7 +262,7 @@ fn parse_primary(pos: Int) -> Map<String, Any> {
|
|||||||
|
|
||||||
// Unary minus
|
// Unary minus
|
||||||
if k == "Minus" {
|
if k == "Minus" {
|
||||||
let r = parse_primary(pos + 1)
|
let r = parse_primary(tokens, pos + 1)
|
||||||
let inner = r["node"]
|
let inner = r["node"]
|
||||||
let p = r["pos"]
|
let p = r["pos"]
|
||||||
return make_result({ "expr": "Neg", "inner": inner }, p)
|
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)
|
make_result({ "expr": "Nil" }, pos + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_if(pos: Int) -> Map<String, Any> {
|
fn parse_if(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||||
let p = expect(pos, "If")
|
let p = expect(tokens, pos, "If")
|
||||||
let r = parse_expr(p)
|
let r = parse_expr(tokens, p)
|
||||||
let cond = r["node"]
|
let cond = r["node"]
|
||||||
let p = r["pos"]
|
let p = r["pos"]
|
||||||
let r2 = parse_block(p)
|
let r2 = parse_block(tokens, p)
|
||||||
let then_stmts = r2["stmts"]
|
let then_stmts = r2["stmts"]
|
||||||
let p = r2["pos"]
|
let p = r2["pos"]
|
||||||
let has_else = false
|
let has_else = false
|
||||||
let else_stmts: [Map<String, Any>] = native_list_empty()
|
let else_stmts: [Map<String, Any>] = native_list_empty()
|
||||||
let k2 = tok_kind(p)
|
let k2 = tok_kind(tokens, p)
|
||||||
if k2 == "Else" {
|
if k2 == "Else" {
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
let k3 = tok_kind(p)
|
let k3 = tok_kind(tokens, p)
|
||||||
if k3 == "If" {
|
if k3 == "If" {
|
||||||
// else-if chain: parse as nested if
|
// else-if chain: parse as nested if
|
||||||
let r3 = parse_if(p)
|
let r3 = parse_if(tokens, p)
|
||||||
let nested = r3["node"]
|
let nested = r3["node"]
|
||||||
let p = r3["pos"]
|
let p = r3["pos"]
|
||||||
let else_stmts = native_list_append(else_stmts, { "stmt": "Expr", "value": nested })
|
let else_stmts = native_list_append(else_stmts, { "stmt": "Expr", "value": nested })
|
||||||
let has_else = true
|
let has_else = true
|
||||||
} else {
|
} else {
|
||||||
let r3 = parse_block(p)
|
let r3 = parse_block(tokens, p)
|
||||||
let else_stmts = r3["stmts"]
|
let else_stmts = r3["stmts"]
|
||||||
let p = r3["pos"]
|
let p = r3["pos"]
|
||||||
let has_else = true
|
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)
|
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> {
|
fn parse_match(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||||
let p = expect(pos, "Match")
|
let p = expect(tokens, pos, "Match")
|
||||||
let r = parse_expr(p)
|
let r = parse_expr(tokens, p)
|
||||||
let subject = r["node"]
|
let subject = r["node"]
|
||||||
let p = r["pos"]
|
let p = r["pos"]
|
||||||
let p = expect(p, "LBrace")
|
let p = expect(tokens, p, "LBrace")
|
||||||
let arms: [Map<String, Any>] = native_list_empty()
|
let arms: [Map<String, Any>] = native_list_empty()
|
||||||
let running = true
|
let running = true
|
||||||
while running {
|
while running {
|
||||||
let k = tok_kind(p)
|
let k = tok_kind(tokens, p)
|
||||||
if k == "RBrace" {
|
if k == "RBrace" {
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
@@ -321,131 +320,131 @@ fn parse_match(pos: Int) -> Map<String, Any> {
|
|||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
// parse pattern => body
|
// parse pattern => body
|
||||||
let r2 = parse_pattern(p)
|
let r2 = parse_pattern(tokens, p)
|
||||||
let pattern = r2["node"]
|
let pattern = r2["node"]
|
||||||
let p = r2["pos"]
|
let p = r2["pos"]
|
||||||
let p = expect(p, "FatArrow")
|
let p = expect(tokens, p, "FatArrow")
|
||||||
let r3 = parse_expr(p)
|
let r3 = parse_expr(tokens, p)
|
||||||
let body = r3["node"]
|
let body = r3["node"]
|
||||||
let p = r3["pos"]
|
let p = r3["pos"]
|
||||||
let arm = { "pattern": pattern, "body": body }
|
let arm = { "pattern": pattern, "body": body }
|
||||||
let arms = native_list_append(arms, arm)
|
let arms = native_list_append(arms, arm)
|
||||||
let k2 = tok_kind(p)
|
let k2 = tok_kind(tokens, p)
|
||||||
if k2 == "Comma" {
|
if k2 == "Comma" {
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let p = expect(p, "RBrace")
|
let p = expect(tokens, p, "RBrace")
|
||||||
make_result({ "expr": "Match", "subject": subject, "arms": arms }, p)
|
make_result({ "expr": "Match", "subject": subject, "arms": arms }, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_pattern(pos: Int) -> Map<String, Any> {
|
fn parse_pattern(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||||
let k = tok_kind(pos)
|
let k = tok_kind(tokens, pos)
|
||||||
if k == "Ident" {
|
if k == "Ident" {
|
||||||
let v = tok_value(pos)
|
let v = tok_value(tokens, pos)
|
||||||
if v == "_" {
|
if v == "_" {
|
||||||
return make_result({ "pattern": "Wildcard" }, pos + 1)
|
return make_result({ "pattern": "Wildcard" }, pos + 1)
|
||||||
}
|
}
|
||||||
return make_result({ "pattern": "Binding", "name": v }, pos + 1)
|
return make_result({ "pattern": "Binding", "name": v }, pos + 1)
|
||||||
}
|
}
|
||||||
if k == "Int" {
|
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" {
|
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" {
|
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 _
|
// Wildcard _
|
||||||
make_result({ "pattern": "Wildcard" }, pos + 1)
|
make_result({ "pattern": "Wildcard" }, pos + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_for_expr(pos: Int) -> Map<String, Any> {
|
fn parse_for_expr(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||||
let p = expect(pos, "For")
|
let p = expect(tokens, pos, "For")
|
||||||
let item_name = tok_value(p)
|
let item_name = tok_value(tokens, p)
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
let p = expect(p, "In")
|
let p = expect(tokens, p, "In")
|
||||||
let r = parse_expr(p)
|
let r = parse_expr(tokens, p)
|
||||||
let list_expr = r["node"]
|
let list_expr = r["node"]
|
||||||
let p = r["pos"]
|
let p = r["pos"]
|
||||||
let r2 = parse_block(p)
|
let r2 = parse_block(tokens, p)
|
||||||
let body = r2["stmts"]
|
let body = r2["stmts"]
|
||||||
let p = r2["pos"]
|
let p = r2["pos"]
|
||||||
make_result({ "expr": "For", "item": item_name, "list": list_expr, "body": body }, p)
|
make_result({ "expr": "For", "item": item_name, "list": list_expr, "body": body }, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_block(pos: Int) -> Map<String, Any> {
|
fn parse_block(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||||
let p = expect(pos, "LBrace")
|
let p = expect(tokens, pos, "LBrace")
|
||||||
let stmts: [Map<String, Any>] = native_list_empty()
|
let stmts: [Map<String, Any>] = native_list_empty()
|
||||||
let running = true
|
let running = true
|
||||||
while running {
|
while running {
|
||||||
let k = tok_kind(p)
|
let k = tok_kind(tokens, p)
|
||||||
if k == "RBrace" {
|
if k == "RBrace" {
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
if k == "Eof" {
|
if k == "Eof" {
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
let r = parse_stmt(p)
|
let r = parse_stmt(tokens, p)
|
||||||
let stmt = r["node"]
|
let stmt = r["node"]
|
||||||
let p = r["pos"]
|
let p = r["pos"]
|
||||||
let stmts = native_list_append(stmts, stmt)
|
let stmts = native_list_append(stmts, stmt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let p = expect(p, "RBrace")
|
let p = expect(tokens, p, "RBrace")
|
||||||
{ "stmts": stmts, "pos": p }
|
{ "stmts": stmts, "pos": p }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Postfix expressions (calls, field access, index) ─────────────────────────
|
// ── Postfix expressions (calls, field access, index) ─────────────────────────
|
||||||
|
|
||||||
fn parse_postfix(pos: Int) -> Map<String, Any> {
|
fn parse_postfix(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||||
let r = parse_primary(pos)
|
let r = parse_primary(tokens, pos)
|
||||||
let node = r["node"]
|
let node = r["node"]
|
||||||
let p = r["pos"]
|
let p = r["pos"]
|
||||||
let running = true
|
let running = true
|
||||||
while running {
|
while running {
|
||||||
let k = tok_kind(p)
|
let k = tok_kind(tokens, p)
|
||||||
if k == "LParen" {
|
if k == "LParen" {
|
||||||
// function call
|
// function call
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
let args: [Map<String, Any>] = native_list_empty()
|
let args: [Map<String, Any>] = native_list_empty()
|
||||||
let run2 = true
|
let run2 = true
|
||||||
while run2 {
|
while run2 {
|
||||||
let k2 = tok_kind(p)
|
let k2 = tok_kind(tokens, p)
|
||||||
if k2 == "RParen" {
|
if k2 == "RParen" {
|
||||||
let run2 = false
|
let run2 = false
|
||||||
} else {
|
} else {
|
||||||
if k2 == "Eof" {
|
if k2 == "Eof" {
|
||||||
let run2 = false
|
let run2 = false
|
||||||
} else {
|
} else {
|
||||||
let r2 = parse_expr(p)
|
let r2 = parse_expr(tokens, p)
|
||||||
let arg = r2["node"]
|
let arg = r2["node"]
|
||||||
let p = r2["pos"]
|
let p = r2["pos"]
|
||||||
let args = native_list_append(args, arg)
|
let args = native_list_append(args, arg)
|
||||||
let k3 = tok_kind(p)
|
let k3 = tok_kind(tokens, p)
|
||||||
if k3 == "Comma" {
|
if k3 == "Comma" {
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let p = expect(p, "RParen")
|
let p = expect(tokens, p, "RParen")
|
||||||
let node = { "expr": "Call", "func": node, "args": args }
|
let node = { "expr": "Call", "func": node, "args": args }
|
||||||
} else {
|
} else {
|
||||||
if k == "Dot" {
|
if k == "Dot" {
|
||||||
let field = tok_value(p + 1)
|
let field = tok_value(tokens, p + 1)
|
||||||
let p = p + 2
|
let p = p + 2
|
||||||
let node = { "expr": "Field", "object": node, "field": field }
|
let node = { "expr": "Field", "object": node, "field": field }
|
||||||
} else {
|
} else {
|
||||||
if k == "LBracket" {
|
if k == "LBracket" {
|
||||||
let r2 = parse_expr(p + 1)
|
let r2 = parse_expr(tokens, p + 1)
|
||||||
let idx = r2["node"]
|
let idx = r2["node"]
|
||||||
let p = r2["pos"]
|
let p = r2["pos"]
|
||||||
let p = expect(p, "RBracket")
|
let p = expect(tokens, p, "RBracket")
|
||||||
let node = { "expr": "Index", "object": node, "index": idx }
|
let node = { "expr": "Index", "object": node, "index": idx }
|
||||||
} else {
|
} else {
|
||||||
if k == "QuestionMark" {
|
if k == "QuestionMark" {
|
||||||
@@ -495,18 +494,18 @@ fn is_binop(kind: String) -> Bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_binop(pos: Int, min_prec: Int) -> Map<String, Any> {
|
fn parse_binop(tokens: [Map<String, Any>], pos: Int, min_prec: Int) -> Map<String, Any> {
|
||||||
let r = parse_postfix(pos)
|
let r = parse_postfix(tokens, pos)
|
||||||
let left = r["node"]
|
let left = r["node"]
|
||||||
let p = r["pos"]
|
let p = r["pos"]
|
||||||
let running = true
|
let running = true
|
||||||
while running {
|
while running {
|
||||||
let k = tok_kind(p)
|
let k = tok_kind(tokens, p)
|
||||||
let prec = op_precedence(k)
|
let prec = op_precedence(k)
|
||||||
if is_binop(k) {
|
if is_binop(k) {
|
||||||
if prec >= min_prec {
|
if prec >= min_prec {
|
||||||
let op = k
|
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 right = r2["node"]
|
||||||
let p = r2["pos"]
|
let p = r2["pos"]
|
||||||
let left = { "expr": "BinOp", "op": op, "left": left, "right": right }
|
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)
|
make_result(left, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_expr(pos: Int) -> Map<String, Any> {
|
fn parse_expr(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||||
parse_binop(pos, 1)
|
parse_binop(tokens, pos, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Statement parsing ─────────────────────────────────────────────────────────
|
// ── Statement parsing ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn parse_stmt(pos: Int) -> Map<String, Any> {
|
fn parse_stmt(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||||
let k = tok_kind(pos)
|
let k = tok_kind(tokens, pos)
|
||||||
|
|
||||||
// let binding
|
// let binding
|
||||||
if k == "Let" {
|
if k == "Let" {
|
||||||
let p = pos + 1
|
let p = pos + 1
|
||||||
let name = tok_value(p)
|
let name = tok_value(tokens, p)
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
let k2 = tok_kind(p)
|
let k2 = tok_kind(tokens, p)
|
||||||
// optional type annotation: name: Type
|
// optional type annotation: name: Type
|
||||||
if k2 == "Colon" {
|
if k2 == "Colon" {
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
let p = skip_type(p)
|
let p = skip_type(tokens, p)
|
||||||
}
|
}
|
||||||
let p = expect(p, "Eq")
|
let p = expect(tokens, p, "Eq")
|
||||||
let r = parse_expr(p)
|
let r = parse_expr(tokens, p)
|
||||||
let val = r["node"]
|
let val = r["node"]
|
||||||
let p = r["pos"]
|
let p = r["pos"]
|
||||||
return make_result({ "stmt": "Let", "name": name, "value": val }, p)
|
return make_result({ "stmt": "Let", "name": name, "value": val }, p)
|
||||||
@@ -550,14 +549,14 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
|
|||||||
// return statement
|
// return statement
|
||||||
if k == "Return" {
|
if k == "Return" {
|
||||||
let p = pos + 1
|
let p = pos + 1
|
||||||
let k2 = tok_kind(p)
|
let k2 = tok_kind(tokens, p)
|
||||||
if k2 == "RBrace" {
|
if k2 == "RBrace" {
|
||||||
return make_result({ "stmt": "Return", "value": { "expr": "Nil" } }, p)
|
return make_result({ "stmt": "Return", "value": { "expr": "Nil" } }, p)
|
||||||
}
|
}
|
||||||
if k2 == "Eof" {
|
if k2 == "Eof" {
|
||||||
return make_result({ "stmt": "Return", "value": { "expr": "Nil" } }, p)
|
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 val = r["node"]
|
||||||
let p = r["pos"]
|
let p = r["pos"]
|
||||||
return make_result({ "stmt": "Return", "value": val }, p)
|
return make_result({ "stmt": "Return", "value": val }, p)
|
||||||
@@ -566,89 +565,96 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
|
|||||||
// fn definition
|
// fn definition
|
||||||
if k == "Fn" {
|
if k == "Fn" {
|
||||||
let p = pos + 1
|
let p = pos + 1
|
||||||
let name = tok_value(p)
|
let name = tok_value(tokens, p)
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
let r = parse_params(p)
|
let r = parse_params(tokens, p)
|
||||||
let params = r["params"]
|
let params = r["params"]
|
||||||
let p = r["pos"]
|
let p = r["pos"]
|
||||||
// return type annotation: -> Type
|
// return type annotation: -> Type. Capture the leading identifier
|
||||||
let k2 = tok_kind(p)
|
// 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" {
|
if k2 == "Arrow" {
|
||||||
let p = p + 1
|
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 body = r2["stmts"]
|
||||||
let p = r2["pos"]
|
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
|
// type definition
|
||||||
if k == "Type" {
|
if k == "Type" {
|
||||||
let p = pos + 1
|
let p = pos + 1
|
||||||
let name = tok_value(p)
|
let name = tok_value(tokens, p)
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
let p = expect(p, "LBrace")
|
let p = expect(tokens, p, "LBrace")
|
||||||
let fields: [Map<String, Any>] = native_list_empty()
|
let fields: [Map<String, Any>] = native_list_empty()
|
||||||
let running = true
|
let running = true
|
||||||
while running {
|
while running {
|
||||||
let k2 = tok_kind(p)
|
let k2 = tok_kind(tokens, p)
|
||||||
if k2 == "RBrace" {
|
if k2 == "RBrace" {
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
if k2 == "Eof" {
|
if k2 == "Eof" {
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
let fname = tok_value(p)
|
let fname = tok_value(tokens, p)
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
let p = expect(p, "Colon")
|
let p = expect(tokens, p, "Colon")
|
||||||
let p = skip_type(p)
|
let p = skip_type(tokens, p)
|
||||||
let fields = native_list_append(fields, { "name": fname })
|
let fields = native_list_append(fields, { "name": fname })
|
||||||
let k3 = tok_kind(p)
|
let k3 = tok_kind(tokens, p)
|
||||||
if k3 == "Comma" {
|
if k3 == "Comma" {
|
||||||
let p = p + 1
|
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)
|
return make_result({ "stmt": "TypeDef", "name": name, "fields": fields }, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// enum definition
|
// enum definition
|
||||||
if k == "Enum" {
|
if k == "Enum" {
|
||||||
let p = pos + 1
|
let p = pos + 1
|
||||||
let name = tok_value(p)
|
let name = tok_value(tokens, p)
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
let p = expect(p, "LBrace")
|
let p = expect(tokens, p, "LBrace")
|
||||||
let variants: [Map<String, Any>] = native_list_empty()
|
let variants: [Map<String, Any>] = native_list_empty()
|
||||||
let running = true
|
let running = true
|
||||||
while running {
|
while running {
|
||||||
let k2 = tok_kind(p)
|
let k2 = tok_kind(tokens, p)
|
||||||
if k2 == "RBrace" {
|
if k2 == "RBrace" {
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
if k2 == "Eof" {
|
if k2 == "Eof" {
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
let vname = tok_value(p)
|
let vname = tok_value(tokens, p)
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
let variants = native_list_append(variants, { "name": vname })
|
let variants = native_list_append(variants, { "name": vname })
|
||||||
let k3 = tok_kind(p)
|
let k3 = tok_kind(tokens, p)
|
||||||
if k3 == "Comma" {
|
if k3 == "Comma" {
|
||||||
let p = p + 1
|
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)
|
return make_result({ "stmt": "EnumDef", "name": name, "variants": variants }, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// import statement
|
// import statement
|
||||||
if k == "Import" {
|
if k == "Import" {
|
||||||
let p = pos + 1
|
let p = pos + 1
|
||||||
let path = tok_value(p)
|
let path = tok_value(tokens, p)
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
return make_result({ "stmt": "Import", "path": path }, p)
|
return make_result({ "stmt": "Import", "path": path }, p)
|
||||||
}
|
}
|
||||||
@@ -656,20 +662,20 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
|
|||||||
// from ... import { ... }
|
// from ... import { ... }
|
||||||
if k == "From" {
|
if k == "From" {
|
||||||
let p = pos + 1
|
let p = pos + 1
|
||||||
let module_name = tok_value(p)
|
let module_name = tok_value(tokens, p)
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
// skip "import" keyword
|
// skip "import" keyword
|
||||||
let k2 = tok_kind(p)
|
let k2 = tok_kind(tokens, p)
|
||||||
if k2 == "Import" {
|
if k2 == "Import" {
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
}
|
}
|
||||||
// skip { Name, ... }
|
// skip { Name, ... }
|
||||||
let k3 = tok_kind(p)
|
let k3 = tok_kind(tokens, p)
|
||||||
if k3 == "LBrace" {
|
if k3 == "LBrace" {
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
let running = true
|
let running = true
|
||||||
while running {
|
while running {
|
||||||
let k4 = tok_kind(p)
|
let k4 = tok_kind(tokens, p)
|
||||||
if k4 == "RBrace" {
|
if k4 == "RBrace" {
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
@@ -677,14 +683,14 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
|
|||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
let k5 = tok_kind(p)
|
let k5 = tok_kind(tokens, p)
|
||||||
if k5 == "Comma" {
|
if k5 == "Comma" {
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let p = expect(p, "RBrace")
|
let p = expect(tokens, p, "RBrace")
|
||||||
}
|
}
|
||||||
return make_result({ "stmt": "Import", "path": module_name }, p)
|
return make_result({ "stmt": "Import", "path": module_name }, p)
|
||||||
}
|
}
|
||||||
@@ -692,10 +698,10 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
|
|||||||
// while loop
|
// while loop
|
||||||
if k == "While" {
|
if k == "While" {
|
||||||
let p = pos + 1
|
let p = pos + 1
|
||||||
let r = parse_expr(p)
|
let r = parse_expr(tokens, p)
|
||||||
let cond = r["node"]
|
let cond = r["node"]
|
||||||
let p = r["pos"]
|
let p = r["pos"]
|
||||||
let r2 = parse_block(p)
|
let r2 = parse_block(tokens, p)
|
||||||
let body = r2["stmts"]
|
let body = r2["stmts"]
|
||||||
let p = r2["pos"]
|
let p = r2["pos"]
|
||||||
return make_result({ "stmt": "While", "cond": cond, "body": body }, p)
|
return make_result({ "stmt": "While", "cond": cond, "body": body }, p)
|
||||||
@@ -704,13 +710,13 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
|
|||||||
// for loop
|
// for loop
|
||||||
if k == "For" {
|
if k == "For" {
|
||||||
let p = pos + 1
|
let p = pos + 1
|
||||||
let item_name = tok_value(p)
|
let item_name = tok_value(tokens, p)
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
let p = expect(p, "In")
|
let p = expect(tokens, p, "In")
|
||||||
let r = parse_expr(p)
|
let r = parse_expr(tokens, p)
|
||||||
let list_expr = r["node"]
|
let list_expr = r["node"]
|
||||||
let p = r["pos"]
|
let p = r["pos"]
|
||||||
let r2 = parse_block(p)
|
let r2 = parse_block(tokens, p)
|
||||||
let body = r2["stmts"]
|
let body = r2["stmts"]
|
||||||
let p = r2["pos"]
|
let p = r2["pos"]
|
||||||
return make_result({ "stmt": "For", "item": item_name, "list": list_expr, "body": body }, p)
|
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
|
let p = pos + 1
|
||||||
// skip decorator name
|
// skip decorator name
|
||||||
let p = p + 1
|
let p = p + 1
|
||||||
return parse_stmt(p)
|
return parse_stmt(tokens, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// bare expression or if/match statement
|
// bare expression or if/match statement
|
||||||
let r = parse_expr(pos)
|
let r = parse_expr(tokens, pos)
|
||||||
let val = r["node"]
|
let val = r["node"]
|
||||||
let p = r["pos"]
|
let p = r["pos"]
|
||||||
make_result({ "stmt": "Expr", "value": val }, p)
|
make_result({ "stmt": "Expr", "value": val }, p)
|
||||||
@@ -734,9 +740,7 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
|
|||||||
// ── Top-level parse ────────────────────────────────────────────────────────────
|
// ── Top-level parse ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn parse(tokens: [Map<String, Any>]) -> [Map<String, Any>] {
|
fn parse(tokens: [Map<String, Any>]) -> [Map<String, Any>] {
|
||||||
// Store tokens in global buffer to avoid O(n²) cloning on every recursive call.
|
let total: Int = native_list_len(tokens)
|
||||||
native_tokens_init(tokens)
|
|
||||||
let total: Int = native_token_len()
|
|
||||||
let stmts: [Map<String, Any>] = native_list_empty()
|
let stmts: [Map<String, Any>] = native_list_empty()
|
||||||
let pos: Int = 0
|
let pos: Int = 0
|
||||||
let running = true
|
let running = true
|
||||||
@@ -744,11 +748,11 @@ fn parse(tokens: [Map<String, Any>]) -> [Map<String, Any>] {
|
|||||||
if pos >= total {
|
if pos >= total {
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
let k = tok_kind(pos)
|
let k = tok_kind(tokens, pos)
|
||||||
if k == "Eof" {
|
if k == "Eof" {
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
let r = parse_stmt(pos)
|
let r = parse_stmt(tokens, pos)
|
||||||
let stmt = r["node"]
|
let stmt = r["node"]
|
||||||
let new_pos: Int = r["pos"]
|
let new_pos: Int = r["pos"]
|
||||||
let stmts = native_list_append(stmts, stmt)
|
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