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:
+151
-147
@@ -6,30 +6,29 @@
|
||||
// The cursor (integer position into the token list) is threaded through every
|
||||
// parse function. Functions return { "node": <map>, "pos": <int> }.
|
||||
//
|
||||
// The token list is stored in the VM's global token buffer via
|
||||
// native_tokens_init / native_token_at / native_token_len. This avoids
|
||||
// O(n²) cloning that occurs when passing the list as a function argument.
|
||||
// The token list is passed as a parameter to all parse functions.
|
||||
// native_list_get is used to index into it without cloning.
|
||||
//
|
||||
// Entry point: fn parse(tokens: [Map<String, Any>]) -> [Map<String, Any>]
|
||||
|
||||
// ── Token access helpers ──────────────────────────────────────────────────────
|
||||
|
||||
fn tok_at(pos: Int) -> Map<String, Any> {
|
||||
native_token_at(pos)
|
||||
fn tok_at(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||
native_list_get(tokens, pos)
|
||||
}
|
||||
|
||||
fn tok_kind(pos: Int) -> String {
|
||||
let t = native_token_at(pos)
|
||||
fn tok_kind(tokens: [Map<String, Any>], pos: Int) -> String {
|
||||
let t = native_list_get(tokens, pos)
|
||||
t["kind"]
|
||||
}
|
||||
|
||||
fn tok_value(pos: Int) -> String {
|
||||
let t = native_token_at(pos)
|
||||
fn tok_value(tokens: [Map<String, Any>], pos: Int) -> String {
|
||||
let t = native_list_get(tokens, pos)
|
||||
t["value"]
|
||||
}
|
||||
|
||||
fn expect(pos: Int, kind: String) -> Int {
|
||||
let k = tok_kind(pos)
|
||||
fn expect(tokens: [Map<String, Any>], pos: Int, kind: String) -> Int {
|
||||
let k = tok_kind(tokens, pos)
|
||||
if k == kind {
|
||||
return pos + 1
|
||||
}
|
||||
@@ -47,26 +46,26 @@ fn make_result(node: Map<String, Any>, pos: Int) -> Map<String, Any> {
|
||||
// Skips over a type annotation, returning the new position.
|
||||
// Types can be: Ident, [Type], Map<K,V>, Type?, Type<Type,...>
|
||||
|
||||
fn skip_type(pos: Int) -> Int {
|
||||
let k = tok_kind(pos)
|
||||
fn skip_type(tokens: [Map<String, Any>], pos: Int) -> Int {
|
||||
let k = tok_kind(tokens, pos)
|
||||
// Array type: [Type]
|
||||
if k == "LBracket" {
|
||||
let p = pos + 1
|
||||
let p = skip_type(p)
|
||||
let p = expect(p, "RBracket")
|
||||
let p = skip_type(tokens, p)
|
||||
let p = expect(tokens, p, "RBracket")
|
||||
return p
|
||||
}
|
||||
// Named type (possibly generic)
|
||||
if k == "Ident" {
|
||||
let p = pos + 1
|
||||
let k2 = tok_kind(p)
|
||||
let k2 = tok_kind(tokens, p)
|
||||
if k2 == "Lt" {
|
||||
// Generic params: skip until matching >
|
||||
let p = p + 1
|
||||
let depth = 1
|
||||
let running = true
|
||||
while running {
|
||||
let kk = tok_kind(p)
|
||||
let kk = tok_kind(tokens, p)
|
||||
if kk == "Eof" {
|
||||
let running = false
|
||||
} else {
|
||||
@@ -86,7 +85,7 @@ fn skip_type(pos: Int) -> Int {
|
||||
}
|
||||
}
|
||||
}
|
||||
let k3 = tok_kind(p)
|
||||
let k3 = tok_kind(tokens, p)
|
||||
if k3 == "QuestionMark" {
|
||||
let p = p + 1
|
||||
}
|
||||
@@ -104,12 +103,12 @@ fn skip_type(pos: Int) -> Int {
|
||||
// ── Parameter list ────────────────────────────────────────────────────────────
|
||||
// Parses (name: Type, name: Type, ...) — returns { "params": [...], "pos": ... }
|
||||
|
||||
fn parse_params(pos: Int) -> Map<String, Any> {
|
||||
let p = expect(pos, "LParen")
|
||||
fn parse_params(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||
let p = expect(tokens, pos, "LParen")
|
||||
let params: [Map<String, Any>] = native_list_empty()
|
||||
let running = true
|
||||
while running {
|
||||
let k = tok_kind(p)
|
||||
let k = tok_kind(tokens, p)
|
||||
if k == "RParen" {
|
||||
let running = false
|
||||
} else {
|
||||
@@ -117,28 +116,28 @@ fn parse_params(pos: Int) -> Map<String, Any> {
|
||||
let running = false
|
||||
} else {
|
||||
// param name
|
||||
let pname = tok_value(p)
|
||||
let pname = tok_value(tokens, p)
|
||||
let p = p + 1
|
||||
let p = expect(p, "Colon")
|
||||
let p = skip_type(p)
|
||||
let p = expect(tokens, p, "Colon")
|
||||
let p = skip_type(tokens, p)
|
||||
let param = { "name": pname }
|
||||
let params = native_list_append(params, param)
|
||||
let k2 = tok_kind(p)
|
||||
let k2 = tok_kind(tokens, p)
|
||||
if k2 == "Comma" {
|
||||
let p = p + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let p = expect(p, "RParen")
|
||||
let p = expect(tokens, p, "RParen")
|
||||
{ "params": params, "pos": p }
|
||||
}
|
||||
|
||||
// ── Expression parsing ────────────────────────────────────────────────────────
|
||||
|
||||
fn parse_primary(pos: Int) -> Map<String, Any> {
|
||||
let k = tok_kind(pos)
|
||||
let v = tok_value(pos)
|
||||
fn parse_primary(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||
let k = tok_kind(tokens, pos)
|
||||
let v = tok_value(tokens, pos)
|
||||
|
||||
// Integer literal
|
||||
if k == "Int" {
|
||||
@@ -167,10 +166,10 @@ fn parse_primary(pos: Int) -> Map<String, Any> {
|
||||
|
||||
// Grouped expression
|
||||
if k == "LParen" {
|
||||
let r = parse_expr(pos + 1)
|
||||
let r = parse_expr(tokens, pos + 1)
|
||||
let node = r["node"]
|
||||
let p = r["pos"]
|
||||
let p = expect(p, "RParen")
|
||||
let p = expect(tokens, p, "RParen")
|
||||
return make_result(node, p)
|
||||
}
|
||||
|
||||
@@ -180,25 +179,25 @@ fn parse_primary(pos: Int) -> Map<String, Any> {
|
||||
let elems: [Map<String, Any>] = native_list_empty()
|
||||
let running = true
|
||||
while running {
|
||||
let k2 = tok_kind(p)
|
||||
let k2 = tok_kind(tokens, p)
|
||||
if k2 == "RBracket" {
|
||||
let running = false
|
||||
} else {
|
||||
if k2 == "Eof" {
|
||||
let running = false
|
||||
} else {
|
||||
let r = parse_expr(p)
|
||||
let r = parse_expr(tokens, p)
|
||||
let elem = r["node"]
|
||||
let p = r["pos"]
|
||||
let elems = native_list_append(elems, elem)
|
||||
let k3 = tok_kind(p)
|
||||
let k3 = tok_kind(tokens, p)
|
||||
if k3 == "Comma" {
|
||||
let p = p + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let p = expect(p, "RBracket")
|
||||
let p = expect(tokens, p, "RBracket")
|
||||
return make_result({ "expr": "Array", "elems": elems }, p)
|
||||
}
|
||||
|
||||
@@ -208,7 +207,7 @@ fn parse_primary(pos: Int) -> Map<String, Any> {
|
||||
let pairs: [Map<String, Any>] = native_list_empty()
|
||||
let running = true
|
||||
while running {
|
||||
let k2 = tok_kind(p)
|
||||
let k2 = tok_kind(tokens, p)
|
||||
if k2 == "RBrace" {
|
||||
let running = false
|
||||
} else {
|
||||
@@ -216,46 +215,46 @@ fn parse_primary(pos: Int) -> Map<String, Any> {
|
||||
let running = false
|
||||
} else {
|
||||
// key: Str token
|
||||
let key = tok_value(p)
|
||||
let key = tok_value(tokens, p)
|
||||
let p = p + 1
|
||||
let p = expect(p, "Colon")
|
||||
let r = parse_expr(p)
|
||||
let p = expect(tokens, p, "Colon")
|
||||
let r = parse_expr(tokens, p)
|
||||
let val_node = r["node"]
|
||||
let p = r["pos"]
|
||||
let pair = { "key": key, "value": val_node }
|
||||
let pairs = native_list_append(pairs, pair)
|
||||
let k3 = tok_kind(p)
|
||||
let k3 = tok_kind(tokens, p)
|
||||
if k3 == "Comma" {
|
||||
let p = p + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let p = expect(p, "RBrace")
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
return make_result({ "expr": "Map", "pairs": pairs }, p)
|
||||
}
|
||||
|
||||
// if expression
|
||||
if k == "If" {
|
||||
let r = parse_if(pos)
|
||||
let r = parse_if(tokens, pos)
|
||||
return r
|
||||
}
|
||||
|
||||
// match expression
|
||||
if k == "Match" {
|
||||
let r = parse_match(pos)
|
||||
let r = parse_match(tokens, pos)
|
||||
return r
|
||||
}
|
||||
|
||||
// for expression (used as statement)
|
||||
if k == "For" {
|
||||
let r = parse_for_expr(pos)
|
||||
let r = parse_for_expr(tokens, pos)
|
||||
return r
|
||||
}
|
||||
|
||||
// Unary not
|
||||
if k == "Not" {
|
||||
let r = parse_primary(pos + 1)
|
||||
let r = parse_primary(tokens, pos + 1)
|
||||
let inner = r["node"]
|
||||
let p = r["pos"]
|
||||
return make_result({ "expr": "Not", "inner": inner }, p)
|
||||
@@ -263,7 +262,7 @@ fn parse_primary(pos: Int) -> Map<String, Any> {
|
||||
|
||||
// Unary minus
|
||||
if k == "Minus" {
|
||||
let r = parse_primary(pos + 1)
|
||||
let r = parse_primary(tokens, pos + 1)
|
||||
let inner = r["node"]
|
||||
let p = r["pos"]
|
||||
return make_result({ "expr": "Neg", "inner": inner }, p)
|
||||
@@ -273,29 +272,29 @@ fn parse_primary(pos: Int) -> Map<String, Any> {
|
||||
make_result({ "expr": "Nil" }, pos + 1)
|
||||
}
|
||||
|
||||
fn parse_if(pos: Int) -> Map<String, Any> {
|
||||
let p = expect(pos, "If")
|
||||
let r = parse_expr(p)
|
||||
fn parse_if(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||
let p = expect(tokens, pos, "If")
|
||||
let r = parse_expr(tokens, p)
|
||||
let cond = r["node"]
|
||||
let p = r["pos"]
|
||||
let r2 = parse_block(p)
|
||||
let r2 = parse_block(tokens, p)
|
||||
let then_stmts = r2["stmts"]
|
||||
let p = r2["pos"]
|
||||
let has_else = false
|
||||
let else_stmts: [Map<String, Any>] = native_list_empty()
|
||||
let k2 = tok_kind(p)
|
||||
let k2 = tok_kind(tokens, p)
|
||||
if k2 == "Else" {
|
||||
let p = p + 1
|
||||
let k3 = tok_kind(p)
|
||||
let k3 = tok_kind(tokens, p)
|
||||
if k3 == "If" {
|
||||
// else-if chain: parse as nested if
|
||||
let r3 = parse_if(p)
|
||||
let r3 = parse_if(tokens, p)
|
||||
let nested = r3["node"]
|
||||
let p = r3["pos"]
|
||||
let else_stmts = native_list_append(else_stmts, { "stmt": "Expr", "value": nested })
|
||||
let has_else = true
|
||||
} else {
|
||||
let r3 = parse_block(p)
|
||||
let r3 = parse_block(tokens, p)
|
||||
let else_stmts = r3["stmts"]
|
||||
let p = r3["pos"]
|
||||
let has_else = true
|
||||
@@ -304,16 +303,16 @@ fn parse_if(pos: Int) -> Map<String, Any> {
|
||||
make_result({ "expr": "If", "cond": cond, "then": then_stmts, "else": else_stmts, "has_else": has_else }, p)
|
||||
}
|
||||
|
||||
fn parse_match(pos: Int) -> Map<String, Any> {
|
||||
let p = expect(pos, "Match")
|
||||
let r = parse_expr(p)
|
||||
fn parse_match(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||
let p = expect(tokens, pos, "Match")
|
||||
let r = parse_expr(tokens, p)
|
||||
let subject = r["node"]
|
||||
let p = r["pos"]
|
||||
let p = expect(p, "LBrace")
|
||||
let p = expect(tokens, p, "LBrace")
|
||||
let arms: [Map<String, Any>] = native_list_empty()
|
||||
let running = true
|
||||
while running {
|
||||
let k = tok_kind(p)
|
||||
let k = tok_kind(tokens, p)
|
||||
if k == "RBrace" {
|
||||
let running = false
|
||||
} else {
|
||||
@@ -321,131 +320,131 @@ fn parse_match(pos: Int) -> Map<String, Any> {
|
||||
let running = false
|
||||
} else {
|
||||
// parse pattern => body
|
||||
let r2 = parse_pattern(p)
|
||||
let r2 = parse_pattern(tokens, p)
|
||||
let pattern = r2["node"]
|
||||
let p = r2["pos"]
|
||||
let p = expect(p, "FatArrow")
|
||||
let r3 = parse_expr(p)
|
||||
let p = expect(tokens, p, "FatArrow")
|
||||
let r3 = parse_expr(tokens, p)
|
||||
let body = r3["node"]
|
||||
let p = r3["pos"]
|
||||
let arm = { "pattern": pattern, "body": body }
|
||||
let arms = native_list_append(arms, arm)
|
||||
let k2 = tok_kind(p)
|
||||
let k2 = tok_kind(tokens, p)
|
||||
if k2 == "Comma" {
|
||||
let p = p + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let p = expect(p, "RBrace")
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
make_result({ "expr": "Match", "subject": subject, "arms": arms }, p)
|
||||
}
|
||||
|
||||
fn parse_pattern(pos: Int) -> Map<String, Any> {
|
||||
let k = tok_kind(pos)
|
||||
fn parse_pattern(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||
let k = tok_kind(tokens, pos)
|
||||
if k == "Ident" {
|
||||
let v = tok_value(pos)
|
||||
let v = tok_value(tokens, pos)
|
||||
if v == "_" {
|
||||
return make_result({ "pattern": "Wildcard" }, pos + 1)
|
||||
}
|
||||
return make_result({ "pattern": "Binding", "name": v }, pos + 1)
|
||||
}
|
||||
if k == "Int" {
|
||||
return make_result({ "pattern": "LitInt", "value": tok_value(pos) }, pos + 1)
|
||||
return make_result({ "pattern": "LitInt", "value": tok_value(tokens, pos) }, pos + 1)
|
||||
}
|
||||
if k == "Str" {
|
||||
return make_result({ "pattern": "LitStr", "value": tok_value(pos) }, pos + 1)
|
||||
return make_result({ "pattern": "LitStr", "value": tok_value(tokens, pos) }, pos + 1)
|
||||
}
|
||||
if k == "Bool" {
|
||||
return make_result({ "pattern": "LitBool", "value": tok_value(pos) }, pos + 1)
|
||||
return make_result({ "pattern": "LitBool", "value": tok_value(tokens, pos) }, pos + 1)
|
||||
}
|
||||
// Wildcard _
|
||||
make_result({ "pattern": "Wildcard" }, pos + 1)
|
||||
}
|
||||
|
||||
fn parse_for_expr(pos: Int) -> Map<String, Any> {
|
||||
let p = expect(pos, "For")
|
||||
let item_name = tok_value(p)
|
||||
fn parse_for_expr(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||
let p = expect(tokens, pos, "For")
|
||||
let item_name = tok_value(tokens, p)
|
||||
let p = p + 1
|
||||
let p = expect(p, "In")
|
||||
let r = parse_expr(p)
|
||||
let p = expect(tokens, p, "In")
|
||||
let r = parse_expr(tokens, p)
|
||||
let list_expr = r["node"]
|
||||
let p = r["pos"]
|
||||
let r2 = parse_block(p)
|
||||
let r2 = parse_block(tokens, p)
|
||||
let body = r2["stmts"]
|
||||
let p = r2["pos"]
|
||||
make_result({ "expr": "For", "item": item_name, "list": list_expr, "body": body }, p)
|
||||
}
|
||||
|
||||
fn parse_block(pos: Int) -> Map<String, Any> {
|
||||
let p = expect(pos, "LBrace")
|
||||
fn parse_block(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||
let p = expect(tokens, pos, "LBrace")
|
||||
let stmts: [Map<String, Any>] = native_list_empty()
|
||||
let running = true
|
||||
while running {
|
||||
let k = tok_kind(p)
|
||||
let k = tok_kind(tokens, p)
|
||||
if k == "RBrace" {
|
||||
let running = false
|
||||
} else {
|
||||
if k == "Eof" {
|
||||
let running = false
|
||||
} else {
|
||||
let r = parse_stmt(p)
|
||||
let r = parse_stmt(tokens, p)
|
||||
let stmt = r["node"]
|
||||
let p = r["pos"]
|
||||
let stmts = native_list_append(stmts, stmt)
|
||||
}
|
||||
}
|
||||
}
|
||||
let p = expect(p, "RBrace")
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
{ "stmts": stmts, "pos": p }
|
||||
}
|
||||
|
||||
// ── Postfix expressions (calls, field access, index) ─────────────────────────
|
||||
|
||||
fn parse_postfix(pos: Int) -> Map<String, Any> {
|
||||
let r = parse_primary(pos)
|
||||
fn parse_postfix(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||
let r = parse_primary(tokens, pos)
|
||||
let node = r["node"]
|
||||
let p = r["pos"]
|
||||
let running = true
|
||||
while running {
|
||||
let k = tok_kind(p)
|
||||
let k = tok_kind(tokens, p)
|
||||
if k == "LParen" {
|
||||
// function call
|
||||
let p = p + 1
|
||||
let args: [Map<String, Any>] = native_list_empty()
|
||||
let run2 = true
|
||||
while run2 {
|
||||
let k2 = tok_kind(p)
|
||||
let k2 = tok_kind(tokens, p)
|
||||
if k2 == "RParen" {
|
||||
let run2 = false
|
||||
} else {
|
||||
if k2 == "Eof" {
|
||||
let run2 = false
|
||||
} else {
|
||||
let r2 = parse_expr(p)
|
||||
let r2 = parse_expr(tokens, p)
|
||||
let arg = r2["node"]
|
||||
let p = r2["pos"]
|
||||
let args = native_list_append(args, arg)
|
||||
let k3 = tok_kind(p)
|
||||
let k3 = tok_kind(tokens, p)
|
||||
if k3 == "Comma" {
|
||||
let p = p + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let p = expect(p, "RParen")
|
||||
let p = expect(tokens, p, "RParen")
|
||||
let node = { "expr": "Call", "func": node, "args": args }
|
||||
} else {
|
||||
if k == "Dot" {
|
||||
let field = tok_value(p + 1)
|
||||
let field = tok_value(tokens, p + 1)
|
||||
let p = p + 2
|
||||
let node = { "expr": "Field", "object": node, "field": field }
|
||||
} else {
|
||||
if k == "LBracket" {
|
||||
let r2 = parse_expr(p + 1)
|
||||
let r2 = parse_expr(tokens, p + 1)
|
||||
let idx = r2["node"]
|
||||
let p = r2["pos"]
|
||||
let p = expect(p, "RBracket")
|
||||
let p = expect(tokens, p, "RBracket")
|
||||
let node = { "expr": "Index", "object": node, "index": idx }
|
||||
} else {
|
||||
if k == "QuestionMark" {
|
||||
@@ -495,18 +494,18 @@ fn is_binop(kind: String) -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn parse_binop(pos: Int, min_prec: Int) -> Map<String, Any> {
|
||||
let r = parse_postfix(pos)
|
||||
fn parse_binop(tokens: [Map<String, Any>], pos: Int, min_prec: Int) -> Map<String, Any> {
|
||||
let r = parse_postfix(tokens, pos)
|
||||
let left = r["node"]
|
||||
let p = r["pos"]
|
||||
let running = true
|
||||
while running {
|
||||
let k = tok_kind(p)
|
||||
let k = tok_kind(tokens, p)
|
||||
let prec = op_precedence(k)
|
||||
if is_binop(k) {
|
||||
if prec >= min_prec {
|
||||
let op = k
|
||||
let r2 = parse_binop(p + 1, prec + 1)
|
||||
let r2 = parse_binop(tokens, p + 1, prec + 1)
|
||||
let right = r2["node"]
|
||||
let p = r2["pos"]
|
||||
let left = { "expr": "BinOp", "op": op, "left": left, "right": right }
|
||||
@@ -520,28 +519,28 @@ fn parse_binop(pos: Int, min_prec: Int) -> Map<String, Any> {
|
||||
make_result(left, p)
|
||||
}
|
||||
|
||||
fn parse_expr(pos: Int) -> Map<String, Any> {
|
||||
parse_binop(pos, 1)
|
||||
fn parse_expr(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||
parse_binop(tokens, pos, 1)
|
||||
}
|
||||
|
||||
// ── Statement parsing ─────────────────────────────────────────────────────────
|
||||
|
||||
fn parse_stmt(pos: Int) -> Map<String, Any> {
|
||||
let k = tok_kind(pos)
|
||||
fn parse_stmt(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
||||
let k = tok_kind(tokens, pos)
|
||||
|
||||
// let binding
|
||||
if k == "Let" {
|
||||
let p = pos + 1
|
||||
let name = tok_value(p)
|
||||
let name = tok_value(tokens, p)
|
||||
let p = p + 1
|
||||
let k2 = tok_kind(p)
|
||||
let k2 = tok_kind(tokens, p)
|
||||
// optional type annotation: name: Type
|
||||
if k2 == "Colon" {
|
||||
let p = p + 1
|
||||
let p = skip_type(p)
|
||||
let p = skip_type(tokens, p)
|
||||
}
|
||||
let p = expect(p, "Eq")
|
||||
let r = parse_expr(p)
|
||||
let p = expect(tokens, p, "Eq")
|
||||
let r = parse_expr(tokens, p)
|
||||
let val = r["node"]
|
||||
let p = r["pos"]
|
||||
return make_result({ "stmt": "Let", "name": name, "value": val }, p)
|
||||
@@ -550,14 +549,14 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
|
||||
// return statement
|
||||
if k == "Return" {
|
||||
let p = pos + 1
|
||||
let k2 = tok_kind(p)
|
||||
let k2 = tok_kind(tokens, p)
|
||||
if k2 == "RBrace" {
|
||||
return make_result({ "stmt": "Return", "value": { "expr": "Nil" } }, p)
|
||||
}
|
||||
if k2 == "Eof" {
|
||||
return make_result({ "stmt": "Return", "value": { "expr": "Nil" } }, p)
|
||||
}
|
||||
let r = parse_expr(p)
|
||||
let r = parse_expr(tokens, p)
|
||||
let val = r["node"]
|
||||
let p = r["pos"]
|
||||
return make_result({ "stmt": "Return", "value": val }, p)
|
||||
@@ -566,89 +565,96 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
|
||||
// fn definition
|
||||
if k == "Fn" {
|
||||
let p = pos + 1
|
||||
let name = tok_value(p)
|
||||
let name = tok_value(tokens, p)
|
||||
let p = p + 1
|
||||
let r = parse_params(p)
|
||||
let r = parse_params(tokens, p)
|
||||
let params = r["params"]
|
||||
let p = r["pos"]
|
||||
// return type annotation: -> Type
|
||||
let k2 = tok_kind(p)
|
||||
// return type annotation: -> Type. Capture the leading identifier
|
||||
// so codegen can distinguish Void-returning functions from value-
|
||||
// returning ones. Anything not "Void" is treated as a value type.
|
||||
let ret_type = ""
|
||||
let k2 = tok_kind(tokens, p)
|
||||
if k2 == "Arrow" {
|
||||
let p = p + 1
|
||||
let p = skip_type(p)
|
||||
let kt = tok_kind(tokens, p)
|
||||
if kt == "Ident" {
|
||||
let ret_type = tok_value(tokens, p)
|
||||
}
|
||||
let p = skip_type(tokens, p)
|
||||
}
|
||||
let r2 = parse_block(p)
|
||||
let r2 = parse_block(tokens, p)
|
||||
let body = r2["stmts"]
|
||||
let p = r2["pos"]
|
||||
return make_result({ "stmt": "FnDef", "name": name, "params": params, "body": body }, p)
|
||||
return make_result({ "stmt": "FnDef", "name": name, "params": params, "body": body, "ret_type": ret_type }, p)
|
||||
}
|
||||
|
||||
// type definition
|
||||
if k == "Type" {
|
||||
let p = pos + 1
|
||||
let name = tok_value(p)
|
||||
let name = tok_value(tokens, p)
|
||||
let p = p + 1
|
||||
let p = expect(p, "LBrace")
|
||||
let p = expect(tokens, p, "LBrace")
|
||||
let fields: [Map<String, Any>] = native_list_empty()
|
||||
let running = true
|
||||
while running {
|
||||
let k2 = tok_kind(p)
|
||||
let k2 = tok_kind(tokens, p)
|
||||
if k2 == "RBrace" {
|
||||
let running = false
|
||||
} else {
|
||||
if k2 == "Eof" {
|
||||
let running = false
|
||||
} else {
|
||||
let fname = tok_value(p)
|
||||
let fname = tok_value(tokens, p)
|
||||
let p = p + 1
|
||||
let p = expect(p, "Colon")
|
||||
let p = skip_type(p)
|
||||
let p = expect(tokens, p, "Colon")
|
||||
let p = skip_type(tokens, p)
|
||||
let fields = native_list_append(fields, { "name": fname })
|
||||
let k3 = tok_kind(p)
|
||||
let k3 = tok_kind(tokens, p)
|
||||
if k3 == "Comma" {
|
||||
let p = p + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let p = expect(p, "RBrace")
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
return make_result({ "stmt": "TypeDef", "name": name, "fields": fields }, p)
|
||||
}
|
||||
|
||||
// enum definition
|
||||
if k == "Enum" {
|
||||
let p = pos + 1
|
||||
let name = tok_value(p)
|
||||
let name = tok_value(tokens, p)
|
||||
let p = p + 1
|
||||
let p = expect(p, "LBrace")
|
||||
let p = expect(tokens, p, "LBrace")
|
||||
let variants: [Map<String, Any>] = native_list_empty()
|
||||
let running = true
|
||||
while running {
|
||||
let k2 = tok_kind(p)
|
||||
let k2 = tok_kind(tokens, p)
|
||||
if k2 == "RBrace" {
|
||||
let running = false
|
||||
} else {
|
||||
if k2 == "Eof" {
|
||||
let running = false
|
||||
} else {
|
||||
let vname = tok_value(p)
|
||||
let vname = tok_value(tokens, p)
|
||||
let p = p + 1
|
||||
let variants = native_list_append(variants, { "name": vname })
|
||||
let k3 = tok_kind(p)
|
||||
let k3 = tok_kind(tokens, p)
|
||||
if k3 == "Comma" {
|
||||
let p = p + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let p = expect(p, "RBrace")
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
return make_result({ "stmt": "EnumDef", "name": name, "variants": variants }, p)
|
||||
}
|
||||
|
||||
// import statement
|
||||
if k == "Import" {
|
||||
let p = pos + 1
|
||||
let path = tok_value(p)
|
||||
let path = tok_value(tokens, p)
|
||||
let p = p + 1
|
||||
return make_result({ "stmt": "Import", "path": path }, p)
|
||||
}
|
||||
@@ -656,20 +662,20 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
|
||||
// from ... import { ... }
|
||||
if k == "From" {
|
||||
let p = pos + 1
|
||||
let module_name = tok_value(p)
|
||||
let module_name = tok_value(tokens, p)
|
||||
let p = p + 1
|
||||
// skip "import" keyword
|
||||
let k2 = tok_kind(p)
|
||||
let k2 = tok_kind(tokens, p)
|
||||
if k2 == "Import" {
|
||||
let p = p + 1
|
||||
}
|
||||
// skip { Name, ... }
|
||||
let k3 = tok_kind(p)
|
||||
let k3 = tok_kind(tokens, p)
|
||||
if k3 == "LBrace" {
|
||||
let p = p + 1
|
||||
let running = true
|
||||
while running {
|
||||
let k4 = tok_kind(p)
|
||||
let k4 = tok_kind(tokens, p)
|
||||
if k4 == "RBrace" {
|
||||
let running = false
|
||||
} else {
|
||||
@@ -677,14 +683,14 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
|
||||
let running = false
|
||||
} else {
|
||||
let p = p + 1
|
||||
let k5 = tok_kind(p)
|
||||
let k5 = tok_kind(tokens, p)
|
||||
if k5 == "Comma" {
|
||||
let p = p + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let p = expect(p, "RBrace")
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
}
|
||||
return make_result({ "stmt": "Import", "path": module_name }, p)
|
||||
}
|
||||
@@ -692,10 +698,10 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
|
||||
// while loop
|
||||
if k == "While" {
|
||||
let p = pos + 1
|
||||
let r = parse_expr(p)
|
||||
let r = parse_expr(tokens, p)
|
||||
let cond = r["node"]
|
||||
let p = r["pos"]
|
||||
let r2 = parse_block(p)
|
||||
let r2 = parse_block(tokens, p)
|
||||
let body = r2["stmts"]
|
||||
let p = r2["pos"]
|
||||
return make_result({ "stmt": "While", "cond": cond, "body": body }, p)
|
||||
@@ -704,13 +710,13 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
|
||||
// for loop
|
||||
if k == "For" {
|
||||
let p = pos + 1
|
||||
let item_name = tok_value(p)
|
||||
let item_name = tok_value(tokens, p)
|
||||
let p = p + 1
|
||||
let p = expect(p, "In")
|
||||
let r = parse_expr(p)
|
||||
let p = expect(tokens, p, "In")
|
||||
let r = parse_expr(tokens, p)
|
||||
let list_expr = r["node"]
|
||||
let p = r["pos"]
|
||||
let r2 = parse_block(p)
|
||||
let r2 = parse_block(tokens, p)
|
||||
let body = r2["stmts"]
|
||||
let p = r2["pos"]
|
||||
return make_result({ "stmt": "For", "item": item_name, "list": list_expr, "body": body }, p)
|
||||
@@ -721,11 +727,11 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
|
||||
let p = pos + 1
|
||||
// skip decorator name
|
||||
let p = p + 1
|
||||
return parse_stmt(p)
|
||||
return parse_stmt(tokens, p)
|
||||
}
|
||||
|
||||
// bare expression or if/match statement
|
||||
let r = parse_expr(pos)
|
||||
let r = parse_expr(tokens, pos)
|
||||
let val = r["node"]
|
||||
let p = r["pos"]
|
||||
make_result({ "stmt": "Expr", "value": val }, p)
|
||||
@@ -734,9 +740,7 @@ fn parse_stmt(pos: Int) -> Map<String, Any> {
|
||||
// ── Top-level parse ────────────────────────────────────────────────────────────
|
||||
|
||||
fn parse(tokens: [Map<String, Any>]) -> [Map<String, Any>] {
|
||||
// Store tokens in global buffer to avoid O(n²) cloning on every recursive call.
|
||||
native_tokens_init(tokens)
|
||||
let total: Int = native_token_len()
|
||||
let total: Int = native_list_len(tokens)
|
||||
let stmts: [Map<String, Any>] = native_list_empty()
|
||||
let pos: Int = 0
|
||||
let running = true
|
||||
@@ -744,11 +748,11 @@ fn parse(tokens: [Map<String, Any>]) -> [Map<String, Any>] {
|
||||
if pos >= total {
|
||||
let running = false
|
||||
} else {
|
||||
let k = tok_kind(pos)
|
||||
let k = tok_kind(tokens, pos)
|
||||
if k == "Eof" {
|
||||
let running = false
|
||||
} else {
|
||||
let r = parse_stmt(pos)
|
||||
let r = parse_stmt(tokens, pos)
|
||||
let stmt = r["node"]
|
||||
let new_pos: Int = r["pos"]
|
||||
let stmts = native_list_append(stmts, stmt)
|
||||
|
||||
Reference in New Issue
Block a user