self-host the el compiler

Today's milestone: dist/platform/elc compiles itself byte-for-byte to
itself (stage1 == stage2 == stage3 verified). The compiler is now a
real binary in the world.

What landed
- Spec rewrite (language.md) to truth — every feature marked
  implemented / planned / not-in-this-language with no fiction.
- C runtime extension: 51 new builtins. JSON parser + accessors,
  time, UUID, env, in-process state K/V, float formatting + math,
  string ops (index_of, split, char_at, char_code, pad_left/right,
  format), list ops (push, push_front, join, range), bool_to_str.
  Runtime grew 631 → 1611 lines, header 171 → 247.
- Codegen fix: transform_implicit_return lifts a function's bare
  trailing expression into an explicit return. Without it, lex(),
  parse(), and every other implicit-return function returned 0/nil
  and the whole pipeline produced empty C output.
- Codegen fix: index expressions dispatch on AST kind. obj["literal"]
  → el_get_field (map), arr[i] → el_list_get (list). Same Index node
  in the parser, two different runtime calls.
- Codegen fix: skip emitting fn main() (collides with C main()) and
  honor parsed return-type annotations so Void functions don't get
  return-wrapped (return println(x) is a C type error).
- Parser: capture return-type identifier from -> Ret annotations.
- Lexer: + vessel keyword, + % operator, + \r escape.
- Runtime fix: el_list_append now allocates a fresh list rather than
  realloc'ing the input. Realloc moved blocks made caller pointers
  dangle, which was inserting garbage values into declared lists and
  causing strcmp segfaults. Persistent allocation eliminates the
  whole class of use-after-free at modest memory cost.

Bootstrap path
- One-shot Python helper translated elc-combined.el to C and
  produced stage1. Helper is disposable; not committed.
- stage1 compiles elc-combined.el → stage2.c which cc compiles to
  stage2; stage2 compiles elc-combined.el → stage3.c. stage2.c and
  stage3.c are byte-identical. Closure proven.
- New elc installed at dist/platform/elc; old broken binary
  preserved as dist/platform/elc.legacy.
- dist/platform/elc.c is the canonical generated source.
- elvm and the bytecode pipeline are no longer on the critical path.

Known gap
- The `+` operator's heuristic dispatch still picks string concat
  when both operands are Idents with no literal anchor. Self-hosting
  works because the compiler source is careful, but `fn add(a:Int,
  b:Int) { a + b }` will not do arithmetic until codegen reads the
  parsed type annotations to dispatch. Fix is wiring; not done here.

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