// parser.el — el self-hosting recursive descent parser // // Consumes the token list produced by lexer.el and builds a list of AST // statement maps. Each statement and expression is a Map. // // The cursor (integer position into the token list) is threaded through every // parse function. Functions return { "node": , "pos": }. // // 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]) -> [Map] // ── Token access helpers ────────────────────────────────────────────────────── fn tok_at(tokens: [Map], pos: Int) -> Map { native_list_get(tokens, pos) } fn tok_kind(tokens: [Map], pos: Int) -> String { let t = native_list_get(tokens, pos) t["kind"] } fn tok_value(tokens: [Map], pos: Int) -> String { let t = native_list_get(tokens, pos) t["value"] } fn expect(tokens: [Map], pos: Int, kind: String) -> Int { let k = tok_kind(tokens, pos) if k == kind { return pos + 1 } // On mismatch just advance; error recovery is best-effort pos + 1 } // ── Result helpers ──────────────────────────────────────────────────────────── fn make_result(node: Map, pos: Int) -> Map { { "node": node, "pos": pos } } // ── Type annotation parser ──────────────────────────────────────────────────── // Skips over a type annotation, returning the new position. // Types can be: Ident, [Type], Map, Type?, Type fn skip_type(tokens: [Map], pos: Int) -> Int { let k = tok_kind(tokens, pos) // Array type: [Type] if k == "LBracket" { let p = pos + 1 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(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(tokens, p) if kk == "Eof" { let running = false } else { if kk == "Lt" { let depth = depth + 1 let p = p + 1 } else { if kk == "Gt" { let depth = depth - 1 let p = p + 1 if depth <= 0 { let running = false } } else { let p = p + 1 } } } } let k3 = tok_kind(tokens, p) if k3 == "QuestionMark" { let p = p + 1 } return p } // Optional marker if k2 == "QuestionMark" { return p + 1 } return p } pos + 1 } // ── Parameter list ──────────────────────────────────────────────────────────── // Parses (name: Type, name: Type, ...) — returns { "params": [...], "pos": ... } fn parse_params(tokens: [Map], pos: Int) -> Map { let p = expect(tokens, pos, "LParen") let params: [Map] = native_list_empty() let running = true while running { let k = tok_kind(tokens, p) if k == "RParen" { let running = false } else { if k == "Eof" { let running = false } else { // param name let pname = tok_value(tokens, p) let p = p + 1 let p = expect(tokens, p, "Colon") // Capture the leading type identifier so codegen can dispatch // arithmetic vs string-concat on `+` based on declared types. let ptype = "" let kt = tok_kind(tokens, p) if kt == "Ident" { let ptype = tok_value(tokens, p) } let p = skip_type(tokens, p) let param = { "name": pname, "type": ptype } let params = native_list_append(params, param) let k2 = tok_kind(tokens, p) if k2 == "Comma" { let p = p + 1 } } } } let p = expect(tokens, p, "RParen") { "params": params, "pos": p } } // ── Expression parsing ──────────────────────────────────────────────────────── fn parse_primary(tokens: [Map], pos: Int) -> Map { let k = tok_kind(tokens, pos) let v = tok_value(tokens, pos) // Integer literal if k == "Int" { return make_result({ "expr": "Int", "value": v }, pos + 1) } // Float literal if k == "Float" { return make_result({ "expr": "Float", "value": v }, pos + 1) } // String literal if k == "Str" { return make_result({ "expr": "Str", "value": v }, pos + 1) } // Bool literal if k == "Bool" { return make_result({ "expr": "Bool", "value": v }, pos + 1) } // Identifier if k == "Ident" { return make_result({ "expr": "Ident", "name": v }, pos + 1) } // Grouped expression if k == "LParen" { let r = parse_expr(tokens, pos + 1) let node = r["node"] let p = r["pos"] let p = expect(tokens, p, "RParen") return make_result(node, p) } // Array literal: [e1, e2, ...] if k == "LBracket" { let p = pos + 1 let elems: [Map] = native_list_empty() let running = true while running { let k2 = tok_kind(tokens, p) if k2 == "RBracket" { let running = false } else { if k2 == "Eof" { let running = false } else { 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(tokens, p) if k3 == "Comma" { let p = p + 1 } } } } let p = expect(tokens, p, "RBracket") return make_result({ "expr": "Array", "elems": elems }, p) } // Map literal: { "key": val, ... } // // Suppression: when parse_if / parse_while / parse_for / parse_match // are parsing a head expression, they set __no_block_expr=1 so a stray // `{` here doesn't get gobbled as a Map literal — it belongs to the // following block. Without this, `if a || b { ... }` could mis-parse // (the `||` recursion lands at `{` and tries to read the if-body as a // Map, then loops forever when keys don't match `Str: expr`). if k == "LBrace" { let no_block: String = state_get("__no_block_expr") if str_eq(no_block, "1") { // Fall through to fallback — caller will see `{` and treat it // as the start of the block they're expecting. return make_result({ "expr": "Nil" }, pos) } let p = pos + 1 let pairs: [Map] = native_list_empty() let running = true while running { let k2 = tok_kind(tokens, p) if k2 == "RBrace" { let running = false } else { if k2 == "Eof" { let running = false } else { // key: Str token let key = tok_value(tokens, p) let new_p: Int = p + 1 let new_p = expect(tokens, new_p, "Colon") let r = parse_expr(tokens, new_p) let val_node = r["node"] let new_p = r["pos"] let pair = { "key": key, "value": val_node } let pairs = native_list_append(pairs, pair) let k3 = tok_kind(tokens, new_p) if k3 == "Comma" { let new_p = new_p + 1 } // Non-progress guard: malformed map content can leave // parse_expr returning the same pos. Force advance. if new_p <= p { let p = p + 1 } else { let p = new_p } } } } let p = expect(tokens, p, "RBrace") return make_result({ "expr": "Map", "pairs": pairs }, p) } // if expression if k == "If" { let r = parse_if(tokens, pos) return r } // match expression if k == "Match" { let r = parse_match(tokens, pos) return r } // for expression (used as statement) if k == "For" { let r = parse_for_expr(tokens, pos) return r } // Unary not if k == "Not" { let r = parse_primary(tokens, pos + 1) let inner = r["node"] let p = r["pos"] return make_result({ "expr": "Not", "inner": inner }, p) } // Unary minus if k == "Minus" { let r = parse_primary(tokens, pos + 1) let inner = r["node"] let p = r["pos"] return make_result({ "expr": "Neg", "inner": inner }, p) } // Soft keywords usable as identifiers in expression position. The lexer // turns words like `target`, `to`, `via`, `deploy`, etc. into dedicated // token kinds for the deploy/retry DSLs, but they're also valid as // parameter names and local variables. When one of these appears in // expression position (where only an Ident makes sense), treat it as // an Ident carrying the original text — otherwise references to a // parameter named `target` compile to EL_NULL. if k == "Target" { return make_result({ "expr": "Ident", "name": v }, pos + 1) } if k == "To" { return make_result({ "expr": "Ident", "name": v }, pos + 1) } if k == "Via" { return make_result({ "expr": "Ident", "name": v }, pos + 1) } if k == "Deploy" { return make_result({ "expr": "Ident", "name": v }, pos + 1) } if k == "Reason" { return make_result({ "expr": "Ident", "name": v }, pos + 1) } if k == "Times" { return make_result({ "expr": "Ident", "name": v }, pos + 1) } if k == "Fallback" { return make_result({ "expr": "Ident", "name": v }, pos + 1) } if k == "Retry" { return make_result({ "expr": "Ident", "name": v }, pos + 1) } if k == "Parallel" { return make_result({ "expr": "Ident", "name": v }, pos + 1) } if k == "Trace" { return make_result({ "expr": "Ident", "name": v }, pos + 1) } if k == "Requires" { return make_result({ "expr": "Ident", "name": v }, pos + 1) } if k == "Where" { return make_result({ "expr": "Ident", "name": v }, pos + 1) } if k == "As" { return make_result({ "expr": "Ident", "name": v }, pos + 1) } if k == "With" { return make_result({ "expr": "Ident", "name": v }, pos + 1) } if k == "Manager" { return make_result({ "expr": "Ident", "name": v }, pos + 1) } if k == "Engine" { return make_result({ "expr": "Ident", "name": v }, pos + 1) } if k == "Accessor" { return make_result({ "expr": "Ident", "name": v }, pos + 1) } if k == "Vessel" { return make_result({ "expr": "Ident", "name": v }, pos + 1) } // Fallback: skip unknown token make_result({ "expr": "Nil" }, pos + 1) } fn parse_if(tokens: [Map], pos: Int) -> Map { let p = expect(tokens, pos, "If") // Suppress Map-literal parsing in the cond so a stray `{` (the start // of the then-block) isn't gobbled as a Map. let prev_no_block: String = state_get("__no_block_expr") state_set("__no_block_expr", "1") let r = parse_expr(tokens, p) state_set("__no_block_expr", prev_no_block) let cond = r["node"] let p = r["pos"] let r2 = parse_block(tokens, p) let then_stmts = r2["stmts"] let p = r2["pos"] let has_else = false let else_stmts: [Map] = native_list_empty() let k2 = tok_kind(tokens, p) if k2 == "Else" { let p = p + 1 let k3 = tok_kind(tokens, p) if k3 == "If" { // else-if chain: parse as nested if 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(tokens, p) let else_stmts = r3["stmts"] let p = r3["pos"] let has_else = true } } make_result({ "expr": "If", "cond": cond, "then": then_stmts, "else": else_stmts, "has_else": has_else }, p) } fn parse_match(tokens: [Map], pos: Int) -> Map { let p = expect(tokens, pos, "Match") let prev_no_block: String = state_get("__no_block_expr") state_set("__no_block_expr", "1") let r = parse_expr(tokens, p) state_set("__no_block_expr", prev_no_block) let subject = r["node"] let p = r["pos"] let p = expect(tokens, p, "LBrace") let arms: [Map] = native_list_empty() let running = true while running { let k = tok_kind(tokens, p) if k == "RBrace" { let running = false } else { if k == "Eof" { let running = false } else { // parse pattern => body let r2 = parse_pattern(tokens, p) let pattern = r2["node"] let p = r2["pos"] 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(tokens, p) if k2 == "Comma" { let p = p + 1 } } } } let p = expect(tokens, p, "RBrace") make_result({ "expr": "Match", "subject": subject, "arms": arms }, p) } fn parse_pattern(tokens: [Map], pos: Int) -> Map { let k = tok_kind(tokens, pos) if k == "Ident" { let v = tok_value(tokens, pos) if v == "_" { return make_result({ "pattern": "Wildcard" }, pos + 1) } // Check for Enum::Variant pattern (Color::Red, Status::Ok, etc.) // Lexed as: Ident ColonColon Ident let next_k = tok_kind(tokens, pos + 1) if next_k == "ColonColon" { let variant_name = tok_value(tokens, pos + 2) return make_result({ "pattern": "Variant", "enum_name": v, "variant": variant_name }, pos + 3) } return make_result({ "pattern": "Binding", "name": v }, pos + 1) } if k == "Int" { return make_result({ "pattern": "LitInt", "value": tok_value(tokens, pos) }, pos + 1) } if k == "Str" { return make_result({ "pattern": "LitStr", "value": tok_value(tokens, pos) }, pos + 1) } if k == "Bool" { return make_result({ "pattern": "LitBool", "value": tok_value(tokens, pos) }, pos + 1) } // Wildcard _ make_result({ "pattern": "Wildcard" }, pos + 1) } fn parse_for_expr(tokens: [Map], pos: Int) -> Map { let p = expect(tokens, pos, "For") let item_name = tok_value(tokens, p) let p = p + 1 let p = expect(tokens, p, "In") let prev_no_block: String = state_get("__no_block_expr") state_set("__no_block_expr", "1") let r = parse_expr(tokens, p) state_set("__no_block_expr", prev_no_block) let list_expr = r["node"] let p = r["pos"] 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(tokens: [Map], pos: Int) -> Map { let p = expect(tokens, pos, "LBrace") let stmts: [Map] = native_list_empty() let running = true while running { let k = tok_kind(tokens, p) if k == "RBrace" { let running = false } else { if k == "Eof" { let running = false } else { let r = parse_stmt(tokens, p) let stmt = r["node"] let new_p: Int = r["pos"] let stmts = native_list_append(stmts, stmt) // Non-progress guard: a malformed input (e.g. `||` that // dragged the parser into Map-literal mode partway through // the if-body) can leave parse_stmt returning the same pos. // Force advance so the outer compile doesn't hang. if new_p <= p { let p = p + 1 } else { let p = new_p } } } } let p = expect(tokens, p, "RBrace") { "stmts": stmts, "pos": p } } // ── Postfix expressions (calls, field access, index) ───────────────────────── // is_duration_unit — recognise the postfix unit suffix on a numeric literal. // Used by parse_postfix to detect `30.seconds`-shape time literals before // falling back to the generic `obj.field` field-access lowering. Singular // and plural forms map to the same nanosecond multiplier; codegen does the // arithmetic at compile time. fn is_duration_unit(name: String) -> Bool { if name == "nanos" { return true } if name == "nano" { return true } if name == "millis" { return true } if name == "milli" { return true } if name == "millisecond" { return true } if name == "milliseconds" { return true } if name == "second" { return true } if name == "seconds" { return true } if name == "minute" { return true } if name == "minutes" { return true } if name == "hour" { return true } if name == "hours" { return true } if name == "day" { return true } if name == "days" { return true } false } fn parse_postfix(tokens: [Map], pos: Int) -> Map { let r = parse_primary(tokens, pos) let node = r["node"] let p = r["pos"] // Postfix duration literal: `.` where is one of // nanos | millis | seconds | minutes | hours | days (each with an // optional plural). We recognise this before the generic Dot-as-field // path so `30.seconds` lowers to a DurationLit AST node carrying the // count and the unit, not a field access on an Int. let primary_kind: String = node["expr"] if primary_kind == "Int" { let dot_kind = tok_kind(tokens, p) if dot_kind == "Dot" { let unit_kind = tok_kind(tokens, p + 1) if unit_kind == "Ident" { let unit_name = tok_value(tokens, p + 1) if is_duration_unit(unit_name) { let count_str: String = node["value"] let node = { "expr": "DurationLit", "count": count_str, "unit": unit_name } let p = p + 2 } } } } let running = true while running { let k = tok_kind(tokens, p) if k == "LParen" { // function call let p = p + 1 let args: [Map] = native_list_empty() let run2 = true while run2 { let k2 = tok_kind(tokens, p) if k2 == "RParen" { let run2 = false } else { if k2 == "Eof" { let run2 = false } else { 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(tokens, p) if k3 == "Comma" { let p = p + 1 } } } } let p = expect(tokens, p, "RParen") let node = { "expr": "Call", "func": node, "args": args } } else { if k == "Dot" { 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(tokens, p + 1) let idx = r2["node"] let p = r2["pos"] let p = expect(tokens, p, "RBracket") let node = { "expr": "Index", "object": node, "index": idx } } else { if k == "QuestionMark" { let p = p + 1 let node = { "expr": "Try", "inner": node } } else { let running = false } } } } } make_result(node, p) } // ── Binary expression precedence climbing ──────────────────────────────────── fn op_precedence(kind: String) -> Int { if kind == "Or" { return 1 } if kind == "And" { return 2 } if kind == "EqEq" { return 3 } if kind == "NotEq" { return 3 } if kind == "Lt" { return 4 } if kind == "Gt" { return 4 } if kind == "LtEq" { return 4 } if kind == "GtEq" { return 4 } if kind == "Plus" { return 5 } if kind == "Minus" { return 5 } if kind == "Star" { return 6 } if kind == "Slash" { return 6 } 0 } fn is_binop(kind: String) -> Bool { if kind == "Or" { return true } if kind == "And" { return true } if kind == "EqEq" { return true } if kind == "NotEq" { return true } if kind == "Lt" { return true } if kind == "Gt" { return true } if kind == "LtEq" { return true } if kind == "GtEq" { return true } if kind == "Plus" { return true } if kind == "Minus" { return true } if kind == "Star" { return true } if kind == "Slash" { return true } false } fn parse_binop(tokens: [Map], pos: Int, min_prec: Int) -> Map { let r = parse_postfix(tokens, pos) let left = r["node"] let p = r["pos"] let running = true while running { 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(tokens, p + 1, prec + 1) let right = r2["node"] let p = r2["pos"] let left = { "expr": "BinOp", "op": op, "left": left, "right": right } } else { let running = false } } else { let running = false } } make_result(left, p) } fn parse_expr(tokens: [Map], pos: Int) -> Map { parse_binop(tokens, pos, 1) } // ── Statement parsing ───────────────────────────────────────────────────────── fn parse_stmt(tokens: [Map], pos: Int) -> Map { let k = tok_kind(tokens, pos) // let binding if k == "Let" { let p = pos + 1 let name = tok_value(tokens, p) let p = p + 1 let ltype = "" let k2 = tok_kind(tokens, p) // optional type annotation: name: Type — capture the leading // identifier so codegen can dispatch arithmetic vs concat on // `+` between two typed Idents. if k2 == "Colon" { let p = p + 1 let kt = tok_kind(tokens, p) if kt == "Ident" { let ltype = tok_value(tokens, p) } let p = skip_type(tokens, 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, "type": ltype }, p) } // return statement if k == "Return" { let p = pos + 1 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(tokens, p) let val = r["node"] let p = r["pos"] return make_result({ "stmt": "Return", "value": val }, p) } // extern fn declaration (no body — forward declaration for separate compilation) if k == "Extern" { let p = pos + 1 let k2: String = tok_kind(tokens, p) if str_eq(k2, "Fn") { let p = p + 1 let name: String = tok_value(tokens, p) let p = p + 1 let r = parse_params(tokens, p) let params = r["params"] let p = r["pos"] let ret_type = "" let k3: String = tok_kind(tokens, p) if str_eq(k3, "Arrow") { let p = p + 1 let kt: String = tok_kind(tokens, p) if str_eq(kt, "Ident") { let ret_type = tok_value(tokens, p) } let p = skip_type(tokens, p) } return make_result({ "stmt": "ExternFn", "name": name, "params": params, "ret_type": ret_type }, p) } } // fn definition if k == "Fn" { let p = pos + 1 let name = tok_value(tokens, p) let p = p + 1 let r = parse_params(tokens, p) let params = r["params"] let p = r["pos"] // 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 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(tokens, p) let body = r2["stmts"] let p = r2["pos"] return make_result({ "stmt": "FnDef", "name": name, "params": params, "body": body, "ret_type": ret_type }, p) } // type definition: `type Name = { field: Type, ... }` // The `=` between the name and the brace is optional in the spec but // present in practice. Skip it if present before consuming the LBrace. if k == "Type" { let p = pos + 1 let name = tok_value(tokens, p) let p = p + 1 // Consume optional `=` before the opening brace let pk = tok_kind(tokens, p) if pk == "Eq" { let p = p + 1 } let p = expect(tokens, p, "LBrace") let fields: [Map] = native_list_empty() let running = true while running { let k2 = tok_kind(tokens, p) if k2 == "RBrace" { let running = false } else { if k2 == "Eof" { let running = false } else { let fname = tok_value(tokens, p) let p = p + 1 let p = expect(tokens, p, "Colon") let p = skip_type(tokens, p) let fields = native_list_append(fields, { "name": fname }) let k3 = tok_kind(tokens, p) if k3 == "Comma" { let p = p + 1 } } } } 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(tokens, p) let p = p + 1 let p = expect(tokens, p, "LBrace") let variants: [Map] = native_list_empty() let running = true while running { let k2 = tok_kind(tokens, p) if k2 == "RBrace" { let running = false } else { if k2 == "Eof" { let running = false } else { let vname = tok_value(tokens, p) let p = p + 1 let variants = native_list_append(variants, { "name": vname }) let k3 = tok_kind(tokens, p) if k3 == "Comma" { let p = p + 1 } } } } 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(tokens, p) let p = p + 1 return make_result({ "stmt": "Import", "path": path }, p) } // from ... import { ... } if k == "From" { let p = pos + 1 let module_name = tok_value(tokens, p) let p = p + 1 // skip "import" keyword let k2 = tok_kind(tokens, p) if k2 == "Import" { let p = p + 1 } // skip { Name, ... } let k3 = tok_kind(tokens, p) if k3 == "LBrace" { let p = p + 1 let running = true while running { let k4 = tok_kind(tokens, p) if k4 == "RBrace" { let running = false } else { if k4 == "Eof" { let running = false } else { let p = p + 1 let k5 = tok_kind(tokens, p) if k5 == "Comma" { let p = p + 1 } } } } let p = expect(tokens, p, "RBrace") } return make_result({ "stmt": "Import", "path": module_name }, p) } // while loop if k == "While" { let p = pos + 1 let prev_no_block: String = state_get("__no_block_expr") state_set("__no_block_expr", "1") let r = parse_expr(tokens, p) state_set("__no_block_expr", prev_no_block) let cond = r["node"] let p = r["pos"] let r2 = parse_block(tokens, p) let body = r2["stmts"] let p = r2["pos"] return make_result({ "stmt": "While", "cond": cond, "body": body }, p) } // for loop if k == "For" { let p = pos + 1 let item_name = tok_value(tokens, p) let p = p + 1 let p = expect(tokens, p, "In") let prev_no_block: String = state_get("__no_block_expr") state_set("__no_block_expr", "1") let r = parse_expr(tokens, p) state_set("__no_block_expr", prev_no_block) let list_expr = r["node"] let p = r["pos"] 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) } // @decorator — capture decorator name and attach to following stmt if k == "At" { let p = pos + 1 let dec_name = tok_value(tokens, p) let p = p + 1 let r = parse_stmt(tokens, p) let inner = r["node"] let p2 = r["pos"] let inner_kind: String = inner["stmt"] if str_eq(inner_kind, "FnDef") { let with_dec = { "stmt": "FnDef", "name": inner["name"], "params": inner["params"], "body": inner["body"], "ret_type": inner["ret_type"], "decorator": dec_name } return make_result(with_dec, p2) } return r } // cgi block: cgi "name" { field: "val", ... } if k == "Cgi" { let p = pos + 1 let name = tok_value(tokens, p) let p = p + 1 let p = expect(tokens, p, "LBrace") let dharma_id = "" let principal = "" let network = "" let engram = "" let has_dharma_id = false let has_principal = false let has_network = false let has_engram = false let running = true while running { let k2 = tok_kind(tokens, p) if k2 == "RBrace" { let running = false } else { if k2 == "Eof" { let running = false } else { let fname = tok_value(tokens, p) let p = p + 1 let p = expect(tokens, p, "Colon") let fval = tok_value(tokens, p) let p = p + 1 if str_eq(fname, "dharma_id") { let dharma_id = fval let has_dharma_id = true } if str_eq(fname, "principal") { let principal = fval let has_principal = true } if str_eq(fname, "network") { let network = fval let has_network = true } if str_eq(fname, "engram") { let engram = fval let has_engram = true } let k3 = tok_kind(tokens, p) if k3 == "Comma" { let p = p + 1 } } } } let p = expect(tokens, p, "RBrace") return make_result({ "stmt": "CgiBlock", "name": name, "dharma_id": dharma_id, "principal": principal, "network": network, "engram": engram, "has_dharma_id": has_dharma_id, "has_principal": has_principal, "has_network": has_network, "has_engram": has_engram }, p) } // service block: service "name" { sponsor: "...", domain: "...", ... } // // A `service` declaration restricts the program's capabilities at // compile time: services CANNOT call self-formation primitives // (llm_call_agentic, llm_register_tool, dharma_emit, dharma_field, // mindlink-creation). Codegen enforces this with #error directives. if k == "Service" { let p = pos + 1 let name = tok_value(tokens, p) let p = p + 1 let p = expect(tokens, p, "LBrace") let sponsor = "" let domain = "" let running = true while running { let k2 = tok_kind(tokens, p) if k2 == "RBrace" { let running = false } else { if k2 == "Eof" { let running = false } else { let fname = tok_value(tokens, p) let p = p + 1 let p = expect(tokens, p, "Colon") let fval = tok_value(tokens, p) let p = p + 1 if str_eq(fname, "sponsor") { let sponsor = fval } if str_eq(fname, "domain") { let domain = fval } let k3 = tok_kind(tokens, p) if k3 == "Comma" { let p = p + 1 } } } } let p = expect(tokens, p, "RBrace") return make_result({ "stmt": "ServiceBlock", "name": name, "sponsor": sponsor, "domain": domain }, p) } // Bare reassignment: `name = expr`. Handled BEFORE the expression // fallback so we don't drop the assign on the floor and emit three // orphan expressions (the original silent-miscompile bug). El's `let` // already permits redeclaration, so this only applies when the parser // sees an Ident followed directly by `=`. `==` is a separate kind // (EqEq) so there's no ambiguity. if k == "Ident" { let k2 = tok_kind(tokens, pos + 1) if k2 == "Eq" { let name = tok_value(tokens, pos) let p = pos + 2 let r = parse_expr(tokens, p) let val = r["node"] let p = r["pos"] return make_result({ "stmt": "Assign", "name": name, "value": val }, p) } } // bare expression or if/match statement let r = parse_expr(tokens, pos) let val = r["node"] let p = r["pos"] make_result({ "stmt": "Expr", "value": val }, p) } // ── Top-level parse ──────────────────────────────────────────────────────────── fn parse(tokens: [Map]) -> [Map] { let total: Int = native_list_len(tokens) let stmts: [Map] = native_list_empty() let pos: Int = 0 let running = true while running { if pos >= total { let running = false } else { let k = tok_kind(tokens, pos) if k == "Eof" { let running = false } else { let r = parse_stmt(tokens, pos) let stmt = r["node"] let new_pos: Int = r["pos"] let stmts = native_list_append(stmts, stmt) // Guard against infinite loops — if pos didn't advance, force it if new_pos <= pos { let pos = pos + 1 } else { let pos = new_pos } } } } stmts }