Files
el/lang/el-compiler/src/parser.el
T
will.anderson 5f9cad5908 fix(elc): eliminate OOM in --emit-header by using token-level signature scan
The --emit-header path previously called parse() which builds the entire
program AST in memory before writing the .elh file. For checkout.el (~491
lines with HTML template trees and deep BinOp string-concat chains), this
exhausted memory before the header could be written.

Fix: replace parse() + emit_header() with scan_fn_sigs_el() +
emit_header_from_sigs(). The new path tokenises the source once, then
walks the flat token list skipping over function bodies entirely — peak
memory is O(tokens) instead of O(whole-program AST).

New functions in parser.el:
- scan_type_el: reads a type annotation and returns its El source string
- scan_params_el: reads (name: Type, ...) and returns El params string
- scan_fn_sigs_el: token-level scan that collects El-style fn signatures
  without building any expression AST nodes

New function in compiler.el:
- emit_header_from_sigs: writes .elh from scan_fn_sigs_el output

Self-hosting check: elc compiled with new elc, diff of outputs is
identical (zero difference).

Smoke test: elc --emit-header checkout.el produces correct three-entry
.elh (previously truncated at two entries due to mid-parse OOM).
2026-05-08 08:20:13 -05:00

2468 lines
97 KiB
EmacsLisp

// 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<String, Any>.
//
// The cursor (integer position into the token list) is threaded through every
// parse function. Functions return { "node": <map>, "pos": <int> }.
//
// 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: [Any]) -> [Map<String, Any>]
// -- Token access helpers ------------------------------------------------------
// Tokens is a flat [Any] list: tokens[2*i] = kind, tokens[2*i+1] = value.
// This avoids one ElMap allocation per token (~112B each), saving ~4MB on large
// programs. All callers use these helpers -- only these three need updating.
fn tok_at(tokens: [Any], pos: Int) -> Map<String, Any> {
let kind: String = native_list_get(tokens, pos * 2)
let value: String = native_list_get(tokens, pos * 2 + 1)
{ "kind": kind, "value": value }
}
fn tok_kind(tokens: [Any], pos: Int) -> String {
native_list_get(tokens, pos * 2)
}
fn tok_value(tokens: [Any], pos: Int) -> String {
native_list_get(tokens, pos * 2 + 1)
}
fn expect(tokens: [Any], 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<String, Any>, pos: Int) -> Map<String, Any> {
{ "node": node, "pos": pos }
}
// -- Type annotation parser ----------------------------------------------------
// Skips over a type annotation, returning the new position.
// Types can be: Ident, [Type], Map<K,V>, Type?, Type<Type,...>
fn skip_type(tokens: [Any], 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: [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(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 --------------------------------------------------------
// HTML template parser
//
// HTML templates are written as unquoted HTML in expression position:
// return <div class="x"><h1>{title}</h1></div>
//
// The parser detects an HTML template when parse_primary sees Lt followed
// by a lowercase ident (a known or assumed HTML element name) or `!` (for
// <!doctype html>). It then recursively parses the HTML into an AST.
//
// AST nodes produced:
// { "expr": "HtmlTemplate", "root": child_node }
// { "html": "Element", "tag": "div", "attrs": [...], "children": [...], "self_closing": bool }
// { "html": "Text", "text": "..." }
// { "html": "Interp", "value": expr_node }
// { "html": "Each", "list": expr_node, "item": "name", "body": [...] }
// { "html": "Doctype" }
// { "html": "Raw", "value": expr_node }
fn is_html_tag_name(name: String) -> Bool {
if str_eq(name, "a") { return true }
if str_eq(name, "abbr") { return true }
if str_eq(name, "address") { return true }
if str_eq(name, "area") { return true }
if str_eq(name, "article") { return true }
if str_eq(name, "aside") { return true }
if str_eq(name, "audio") { return true }
if str_eq(name, "b") { return true }
if str_eq(name, "base") { return true }
if str_eq(name, "blockquote") { return true }
if str_eq(name, "body") { return true }
if str_eq(name, "br") { return true }
if str_eq(name, "button") { return true }
if str_eq(name, "canvas") { return true }
if str_eq(name, "caption") { return true }
if str_eq(name, "cite") { return true }
if str_eq(name, "code") { return true }
if str_eq(name, "col") { return true }
if str_eq(name, "colgroup") { return true }
if str_eq(name, "data") { return true }
if str_eq(name, "datalist") { return true }
if str_eq(name, "dd") { return true }
if str_eq(name, "del") { return true }
if str_eq(name, "details") { return true }
if str_eq(name, "dfn") { return true }
if str_eq(name, "dialog") { return true }
if str_eq(name, "div") { return true }
if str_eq(name, "dl") { return true }
if str_eq(name, "dt") { return true }
if str_eq(name, "em") { return true }
if str_eq(name, "embed") { return true }
if str_eq(name, "fieldset") { return true }
if str_eq(name, "figcaption") { return true }
if str_eq(name, "figure") { return true }
if str_eq(name, "footer") { return true }
if str_eq(name, "form") { return true }
if str_eq(name, "h1") { return true }
if str_eq(name, "h2") { return true }
if str_eq(name, "h3") { return true }
if str_eq(name, "h4") { return true }
if str_eq(name, "h5") { return true }
if str_eq(name, "h6") { return true }
if str_eq(name, "head") { return true }
if str_eq(name, "header") { return true }
if str_eq(name, "hr") { return true }
if str_eq(name, "html") { return true }
if str_eq(name, "i") { return true }
if str_eq(name, "iframe") { return true }
if str_eq(name, "img") { return true }
if str_eq(name, "input") { return true }
if str_eq(name, "ins") { return true }
if str_eq(name, "kbd") { return true }
if str_eq(name, "label") { return true }
if str_eq(name, "legend") { return true }
if str_eq(name, "li") { return true }
if str_eq(name, "link") { return true }
if str_eq(name, "main") { return true }
if str_eq(name, "map") { return true }
if str_eq(name, "mark") { return true }
if str_eq(name, "menu") { return true }
if str_eq(name, "meta") { return true }
if str_eq(name, "meter") { return true }
if str_eq(name, "nav") { return true }
if str_eq(name, "noscript") { return true }
if str_eq(name, "object") { return true }
if str_eq(name, "ol") { return true }
if str_eq(name, "optgroup") { return true }
if str_eq(name, "option") { return true }
if str_eq(name, "output") { return true }
if str_eq(name, "p") { return true }
if str_eq(name, "param") { return true }
if str_eq(name, "picture") { return true }
if str_eq(name, "pre") { return true }
if str_eq(name, "progress") { return true }
if str_eq(name, "q") { return true }
if str_eq(name, "rp") { return true }
if str_eq(name, "rt") { return true }
if str_eq(name, "ruby") { return true }
if str_eq(name, "s") { return true }
if str_eq(name, "samp") { return true }
if str_eq(name, "script") { return true }
if str_eq(name, "section") { return true }
if str_eq(name, "select") { return true }
if str_eq(name, "small") { return true }
if str_eq(name, "source") { return true }
if str_eq(name, "span") { return true }
if str_eq(name, "strong") { return true }
if str_eq(name, "style") { return true }
if str_eq(name, "sub") { return true }
if str_eq(name, "summary") { return true }
if str_eq(name, "sup") { return true }
if str_eq(name, "table") { return true }
if str_eq(name, "tbody") { return true }
if str_eq(name, "td") { return true }
if str_eq(name, "template") { return true }
if str_eq(name, "textarea") { return true }
if str_eq(name, "tfoot") { return true }
if str_eq(name, "th") { return true }
if str_eq(name, "thead") { return true }
if str_eq(name, "time") { return true }
if str_eq(name, "title") { return true }
if str_eq(name, "tr") { return true }
if str_eq(name, "track") { return true }
if str_eq(name, "u") { return true }
if str_eq(name, "ul") { return true }
if str_eq(name, "var") { return true }
if str_eq(name, "video") { return true }
if str_eq(name, "wbr") { return true }
false
}
fn is_void_element(name: String) -> Bool {
if str_eq(name, "area") { return true }
if str_eq(name, "base") { return true }
if str_eq(name, "br") { return true }
if str_eq(name, "col") { return true }
if str_eq(name, "embed") { return true }
if str_eq(name, "hr") { return true }
if str_eq(name, "img") { return true }
if str_eq(name, "input") { return true }
if str_eq(name, "link") { return true }
if str_eq(name, "meta") { return true }
if str_eq(name, "param") { return true }
if str_eq(name, "source") { return true }
if str_eq(name, "track") { return true }
if str_eq(name, "wbr") { return true }
false
}
// Collect all tokens as raw text until </tag_name> is encountered.
// Used for <style> and <script> elements to avoid parsing CSS/JS as El.
// Returns { "text": "...", "pos": p_after_closing_tag }
fn parse_raw_text_content(tokens: [Any], pos: Int, tag_name: String) -> Map<String, Any> {
let parts: [String] = native_list_empty()
let p = pos
let running = true
while running {
let k = tok_kind(tokens, p)
if str_eq(k, "Eof") {
let running = false
} else {
if str_eq(k, "Lt") {
let k2 = tok_kind(tokens, p + 1)
if str_eq(k2, "Slash") {
// Check if this is </tag_name>
let close_name = tok_value(tokens, p + 2)
if str_eq(close_name, tag_name) {
// consume </tag_name>
let p = p + 3
let p = expect(tokens, p, "Gt")
let running = false
} else {
let v = tok_value(tokens, p)
let parts = native_list_append(parts, v)
let p = p + 1
}
} else {
let v = tok_value(tokens, p)
let parts = native_list_append(parts, v)
let p = p + 1
}
} else {
let v = tok_value(tokens, p)
let parts = native_list_append(parts, v)
let p = p + 1
}
}
}
{ "text": str_join(parts, ""), "pos": p }
}
// Collect tokens as text content until we hit Lt, LBrace, Eof, or a
// closing-tag marker (Lt Slash). Returns { "text": "...", "pos": p }
fn parse_html_text_tokens(tokens: [Any], pos: Int) -> Map<String, Any> {
let parts: [String] = native_list_empty()
let p = pos
let running = true
while running {
let k = tok_kind(tokens, p)
if str_eq(k, "Eof") {
let running = false
} else {
if str_eq(k, "Lt") {
let running = false
} else {
if str_eq(k, "LBrace") {
let running = false
} else {
// Check for </: Lt already stops us, but Slash alone
// (after consuming whitespace) also stops text.
// Anything else is text content.
let v = tok_value(tokens, p)
let parts = native_list_append(parts, v)
let p = p + 1
}
}
}
}
{ "text": str_join(parts, ""), "pos": p }
}
// Parse an attribute list: (attrname | attrname="val" | attrname={expr})*
// Stops at Gt or Slash (for self-closing />).
fn parse_html_attrs(tokens: [Any], pos: Int) -> Map<String, Any> {
let attrs: [Map<String, Any>] = native_list_empty()
let p = pos
let running = true
while running {
let k = tok_kind(tokens, p)
if str_eq(k, "Gt") {
let running = false
} else {
if str_eq(k, "Slash") {
let running = false
} else {
if str_eq(k, "Eof") {
let running = false
} else {
// Attribute name: could be Ident or keyword used as attr name
let attr_name = tok_value(tokens, p)
let p = p + 1
let k2 = tok_kind(tokens, p)
if str_eq(k2, "Eq") {
let p = p + 1
let k3 = tok_kind(tokens, p)
if str_eq(k3, "Str") {
// static: attr="value"
let attr_val = tok_value(tokens, p)
let p = p + 1
let attrs = native_list_append(attrs, { "name": attr_name, "kind": "static", "value": attr_val })
} else {
if str_eq(k3, "LBrace") {
// dynamic: attr={expr}
let r = parse_expr(tokens, p + 1)
let val_node = r["node"]
let p = r["pos"]
// r result map fully consumed release to free peak heap.
el_release(r)
let p = expect(tokens, p, "RBrace")
let attrs = native_list_append(attrs, { "name": attr_name, "kind": "dynamic", "value": val_node })
} else {
// malformed, skip
}
}
} else {
// boolean attribute
let attrs = native_list_append(attrs, { "name": attr_name, "kind": "bool" })
}
}
}
}
}
{ "attrs": attrs, "pos": p }
}
// Parse the children of an HTML element until we see the closing tag </tag>
// or EOF. Returns { "children": [...], "pos": p_after_closing_tag }
fn parse_html_children(tokens: [Any], pos: Int, parent_tag: String) -> Map<String, Any> {
let children: [Map<String, Any>] = native_list_empty()
let p = pos
let running = true
while running {
let k = tok_kind(tokens, p)
if str_eq(k, "Eof") {
let running = false
} else {
if str_eq(k, "Lt") {
// Check for closing tag: </
let k2 = tok_kind(tokens, p + 1)
if str_eq(k2, "Slash") {
// </tagname> consume and stop
let p = p + 2
// skip tag name
let close_name = tok_value(tokens, p)
let p = p + 1
// consume >
let p = expect(tokens, p, "Gt")
let running = false
} else {
if str_eq(k2, "Not") {
// Possible <!doctype html>
let k3_v = tok_value(tokens, p + 2)
if str_eq(k3_v, "doctype") {
// consume <!doctype html>
let p = p + 2
// skip until >
let scanning = true
while scanning {
let ck = tok_kind(tokens, p)
if str_eq(ck, "Eof") { let scanning = false }
if str_eq(ck, "Gt") {
let p = p + 1
let scanning = false
} else {
let p = p + 1
}
}
let children = native_list_append(children, { "html": "Doctype" })
} else {
let p = p + 1
}
} else {
// nested element
let r = parse_html_element(tokens, p)
let child = r["node"]
let p = r["pos"]
// r result map fully consumed release to free peak heap.
el_release(r)
let children = native_list_append(children, child)
}
}
} else {
if str_eq(k, "LBrace") {
// Interpolation: {expr}, {#each ...}, {#if ...}, {#else}, {/each}, {/if}
// Note: '#' (ASCII 35) is skipped by the lexer, so {#each} lexes as
// LBrace Ident:"each" ... and {#if} lexes as LBrace If ... and
// {#else} lexes as LBrace Else RBrace.
let k2 = tok_kind(tokens, p + 1)
if str_eq(k2, "Slash") {
// {/each} or {/if} end of block, stop
// skip { /
let p = p + 2
// skip the close-tag name token (each, if, etc.)
let p = p + 1
// skip }
let p = expect(tokens, p, "RBrace")
let running = false
} else {
if str_eq(k2, "If") {
// {#if condition} ... {/if}
// Skip { if (2 tokens; '#' was silently skipped by lexer)
let p = p + 2
// Parse condition expression (no block expr)
let prev_no_block: String = state_get("__no_block_expr")
state_set("__no_block_expr", "1")
let r_cond = parse_expr(tokens, p)
state_set("__no_block_expr", prev_no_block)
let cond_expr = r_cond["node"]
let p = r_cond["pos"]
el_release(r_cond)
// consume closing }
let p = expect(tokens, p, "RBrace")
// parse then-children until {#else} or {/if}
let r_then = parse_html_children(tokens, p, "__if_then__")
let then_children = r_then["children"]
let p = r_then["pos"]
el_release(r_then)
// check for {#else} lexed as LBrace Else RBrace
let else_children: [Map<String, Any>] = native_list_empty()
let ck = tok_kind(tokens, p)
if str_eq(ck, "LBrace") {
let ck2 = tok_kind(tokens, p + 1)
if str_eq(ck2, "Else") {
// consume { else }
let p = p + 2
let p = expect(tokens, p, "RBrace")
// parse else-children until {/if}
let r_else = parse_html_children(tokens, p, "__if_else__")
let else_children = r_else["children"]
let p = r_else["pos"]
el_release(r_else)
}
}
let if_node: Map<String, Any> = { "html": "HtmlIf", "cond": cond_expr, "then": then_children, "else": else_children }
let children = native_list_append(children, if_node)
} else {
if str_eq(k2, "Else") {
// {#else} sentinel lexed as LBrace Else RBrace
// Do NOT consume leave position for caller ({#if} handler checks for it)
let running = false
} else {
// Check for {#each list as item} lexed as LBrace Ident:"each" ...
let k2_v = tok_value(tokens, p + 1)
if str_eq(k2_v, "each") {
let p = p + 2
// parse list expr up to "as" keyword
let prev_no_block: String = state_get("__no_block_expr")
state_set("__no_block_expr", "1")
let r_list = parse_expr(tokens, p)
state_set("__no_block_expr", prev_no_block)
let list_expr = r_list["node"]
let p = r_list["pos"]
// r_list result map fully consumed release to free peak heap.
el_release(r_list)
// expect "as"
let p = expect(tokens, p, "As")
// item variable name
let item_name = tok_value(tokens, p)
let p = p + 1
// consume closing }
let p = expect(tokens, p, "RBrace")
// parse body until {/each}
let r_body = parse_html_each_body(tokens, p)
let body_children = r_body["children"]
let p = r_body["pos"]
// r_body result map fully consumed release to free peak heap.
el_release(r_body)
let each_node: Map<String, Any> = { "html": "Each", "list": list_expr, "item": item_name, "body": body_children }
let children = native_list_append(children, each_node)
} else {
// regular {expr} disable map-literal parsing so {fn(a,b)}
// does not trigger the LBracemap path inside parse_primary
let prev_no_block: String = state_get("__no_block_expr")
state_set("__no_block_expr", "1")
let r = parse_expr(tokens, p + 1)
state_set("__no_block_expr", prev_no_block)
let interp_val = r["node"]
let p = r["pos"]
// r result map fully consumed release to free peak heap.
el_release(r)
let p = expect(tokens, p, "RBrace")
// Check if the expr is a call to raw()
let is_raw_call = false
let interp_kind: String = interp_val["expr"]
if str_eq(interp_kind, "Call") {
let fn_node = interp_val["func"]
let fn_kind: String = fn_node["expr"]
if str_eq(fn_kind, "Ident") {
let fn_name_v: String = fn_node["name"]
if str_eq(fn_name_v, "raw") {
let is_raw_call = true
}
}
}
if is_raw_call {
let raw_args = interp_val["args"]
let raw_inner = native_list_get(raw_args, 0)
let children = native_list_append(children, { "html": "Raw", "value": raw_inner })
} else {
let children = native_list_append(children, { "html": "Interp", "value": interp_val })
}
}
}
}
}
} else {
// Text tokens collect run of non-special tokens
let r_text = parse_html_text_tokens(tokens, p)
let text_str: String = r_text["text"]
let p = r_text["pos"]
// r_text result map fully consumed release to free peak heap.
el_release(r_text)
let text_trimmed: String = str_trim(text_str)
if !str_eq(text_trimmed, "") {
let children = native_list_append(children, { "html": "Text", "text": text_trimmed })
}
}
}
}
}
{ "children": children, "pos": p }
}
// Parse body of {#each} until {/each}. Mirrors parse_html_children but
// stops at the {/each} sentinel rather than a closing element tag.
fn parse_html_each_body(tokens: [Any], pos: Int) -> Map<String, Any> {
parse_html_children(tokens, pos, "__each__")
}
// Parse a single HTML element: <tag attrs> children </tag>
// or self-closing: <tag attrs/>
// Pos points to the Lt token.
fn parse_html_element(tokens: [Any], pos: Int) -> Map<String, Any> {
let p = pos
// consume <
let p = expect(tokens, p, "Lt")
// tag name
let tag_name = tok_value(tokens, p)
let p = p + 1
// parse attributes
let r_attrs = parse_html_attrs(tokens, p)
let attrs = r_attrs["attrs"]
let p = r_attrs["pos"]
// r_attrs result map fully consumed release to free peak heap.
el_release(r_attrs)
// check for self-closing /> or void element
let k = tok_kind(tokens, p)
let self_closing = false
if str_eq(k, "Slash") {
// />
let p = p + 1
let p = expect(tokens, p, "Gt")
let self_closing = true
return make_result({ "html": "Element", "tag": tag_name, "attrs": attrs, "children": native_list_empty(), "self_closing": true }, p)
}
// consume >
let p = expect(tokens, p, "Gt")
// void elements have no children, no closing tag
if is_void_element(tag_name) {
return make_result({ "html": "Element", "tag": tag_name, "attrs": attrs, "children": native_list_empty(), "self_closing": true }, p)
}
// raw-text mode for style/script collect content as plain text without parsing CSS/JS as El
if str_eq(tag_name, "style") {
let r_raw = parse_raw_text_content(tokens, p, "style")
let raw_text: String = r_raw["text"]
let p = r_raw["pos"]
el_release(r_raw)
let raw_child: Map<String, Any> = { "html": "Text", "text": raw_text }
let raw_children: [Map<String, Any>] = native_list_empty()
let raw_children = native_list_append(raw_children, raw_child)
return make_result({ "html": "Element", "tag": tag_name, "attrs": attrs, "children": raw_children, "self_closing": false }, p)
}
if str_eq(tag_name, "script") {
let r_raw = parse_raw_text_content(tokens, p, "script")
let raw_text: String = r_raw["text"]
let p = r_raw["pos"]
el_release(r_raw)
let raw_child: Map<String, Any> = { "html": "Text", "text": raw_text }
let raw_children: [Map<String, Any>] = native_list_empty()
let raw_children = native_list_append(raw_children, raw_child)
return make_result({ "html": "Element", "tag": tag_name, "attrs": attrs, "children": raw_children, "self_closing": false }, p)
}
// parse children
let r_children = parse_html_children(tokens, p, tag_name)
let children = r_children["children"]
let p = r_children["pos"]
// r_children result map fully consumed release to free peak heap.
el_release(r_children)
make_result({ "html": "Element", "tag": tag_name, "attrs": attrs, "children": children, "self_closing": false }, p)
}
// Entry point for HTML template parsing.
// Pos points to Lt (or Lt Not for <!doctype>).
// May parse an optional <!doctype html> prefix followed by the root element.
fn parse_html_template(tokens: [Any], pos: Int) -> Map<String, Any> {
let p = pos
// Check for <!doctype html>
let doctype = false
let k = tok_kind(tokens, p)
let k2 = tok_kind(tokens, p + 1)
if str_eq(k, "Lt") {
if str_eq(k2, "Not") {
let k3_v = tok_value(tokens, p + 2)
if str_eq(k3_v, "doctype") {
let doctype = true
// consume <!doctype html>
let p = p + 2
let scanning = true
while scanning {
let ck = tok_kind(tokens, p)
if str_eq(ck, "Eof") { let scanning = false }
if str_eq(ck, "Gt") {
let p = p + 1
let scanning = false
} else {
let p = p + 1
}
}
}
}
}
// Parse root element
let r = parse_html_element(tokens, p)
let root = r["node"]
let p = r["pos"]
// r result map fully consumed release to free peak heap.
el_release(r)
let root_with_doctype = root
if doctype {
let root_with_doctype = { "html": root["html"], "tag": root["tag"], "attrs": root["attrs"], "children": root["children"], "self_closing": root["self_closing"], "doctype": true }
}
make_result({ "expr": "HtmlTemplate", "root": root_with_doctype }, p)
}
fn parse_primary(tokens: [Any], pos: Int) -> Map<String, Any> {
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)
}
// HTML template literal: <tagname ...>...</tagname> or <!doctype html>...
// Detected in value position only; `<` in comparison position is already
// consumed by parse_binop before parse_primary is reached.
if k == "Lt" {
let k2 = tok_kind(tokens, pos + 1)
if str_eq(k2, "Not") {
return parse_html_template(tokens, pos)
}
if str_eq(k2, "Ident") {
let tag_candidate = tok_value(tokens, pos + 1)
if is_html_tag_name(tag_candidate) {
return parse_html_template(tokens, pos)
}
}
}
// 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"]
// r result map fully consumed release to free peak heap.
el_release(r)
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<String, Any>] = 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"]
// r result map fully consumed release to free peak heap.
el_release(r)
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)
}
// Distinguish map literal from interpolation chain.
// A map literal requires { key: value } the second token inside { must be Colon.
// An empty {} is a map literal. Everything else is an interpolation chain.
let first_k: String = tok_kind(tokens, pos + 1)
let second_k: String = tok_kind(tokens, pos + 2)
if str_eq(first_k, "RBrace") {
// Empty map literal {}
return make_result({ "expr": "Map", "pairs": native_list_empty() }, pos + 2)
}
if str_eq(second_k, "Colon") {
// MAP LITERAL: { key: value, ... }
let p = pos + 1
let pairs: [Map<String, Any>] = 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 or Ident 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"]
// r result map fully consumed release to free peak heap.
el_release(r)
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)
}
// INTERPOLATION CHAIN: {expr}, {expr}{expr}, {expr}<html>, etc.
// Build a BinOp(Plus, ...) concatenation chain.
let p = pos
let chain_node: Map<String, Any> = { "expr": "Nil" }
let chain_started = false
let chain_running = true
while chain_running {
let ck: String = tok_kind(tokens, p)
if str_eq(ck, "LBrace") {
let prev_no_block: String = state_get("__no_block_expr")
state_set("__no_block_expr", "1")
let r = parse_expr(tokens, p + 1)
state_set("__no_block_expr", prev_no_block)
let part = r["node"]
let p = r["pos"]
// r result map fully consumed release to free peak heap.
el_release(r)
let p = expect(tokens, p, "RBrace")
if !chain_started {
let chain_node = part
let chain_started = true
} else {
let chain_node: Map<String, Any> = { "expr": "BinOp", "op": "Plus", "left": chain_node, "right": part }
}
} else {
if str_eq(ck, "Lt") {
let ck2: String = tok_kind(tokens, p + 1)
if str_eq(ck2, "Not") {
let r = parse_html_template(tokens, p)
let part = r["node"]
let p = r["pos"]
// r result map fully consumed release to free peak heap.
el_release(r)
if !chain_started {
let chain_node = part
let chain_started = true
} else {
let chain_node: Map<String, Any> = { "expr": "BinOp", "op": "Plus", "left": chain_node, "right": part }
}
} else {
if str_eq(ck2, "Ident") {
let tag_candidate: String = tok_value(tokens, p + 1)
if is_html_tag_name(tag_candidate) {
let r = parse_html_template(tokens, p)
let part = r["node"]
let p = r["pos"]
// r result map fully consumed release to free peak heap.
el_release(r)
if !chain_started {
let chain_node = part
let chain_started = true
} else {
let chain_node: Map<String, Any> = { "expr": "BinOp", "op": "Plus", "left": chain_node, "right": part }
}
} else {
let chain_running = false
}
} else {
let chain_running = false
}
}
} else {
let chain_running = false
}
}
}
return make_result(chain_node, 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
}
// Anonymous function literal (lambda): fn(params) -> RetType { body }
// Used for inline callbacks: dom_listen(el, "click", fn(e: Any) -> Void { ... })
// Produces a Lambda expression node (distinct from a named FnDef statement).
if k == "Fn" {
let p = pos + 1
let r = parse_params(tokens, p)
let params = r["params"]
let p = r["pos"]
// r result map fully consumed release to free peak heap.
el_release(r)
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"]
// r2 result map fully consumed release to free peak heap.
el_release(r2)
return make_result({ "expr": "Lambda", "params": params, "body": body, "ret_type": ret_type }, p)
}
// Unary not
if k == "Not" {
let r = parse_primary(tokens, pos + 1)
let inner = r["node"]
let p = r["pos"]
// r result map fully consumed release to free peak heap.
el_release(r)
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"]
// r result map fully consumed release to free peak heap.
el_release(r)
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: [Any], pos: Int) -> Map<String, Any> {
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"]
// r result map fully consumed release to free peak heap.
el_release(r)
let r2 = parse_block(tokens, p)
let then_stmts = r2["stmts"]
let p = r2["pos"]
// r2 result map fully consumed release to free peak heap.
el_release(r2)
let has_else = false
let else_stmts: [Map<String, Any>] = 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"]
// r3 result map fully consumed release to free peak heap.
el_release(r3)
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"]
// r3 result map fully consumed release to free peak heap.
el_release(r3)
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: [Any], pos: Int) -> Map<String, Any> {
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"]
// r result map fully consumed release to free peak heap.
el_release(r)
let p = expect(tokens, p, "LBrace")
let arms: [Map<String, Any>] = 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"]
// r2 result map fully consumed release to free peak heap.
el_release(r2)
let p = expect(tokens, p, "FatArrow")
let r3 = parse_expr(tokens, p)
let body = r3["node"]
let p = r3["pos"]
// r3 result map fully consumed release to free peak heap.
el_release(r3)
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: [Any], pos: Int) -> Map<String, Any> {
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: [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(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"]
// r result map fully consumed release to free peak heap.
el_release(r)
let r2 = parse_block(tokens, p)
let body = r2["stmts"]
let p = r2["pos"]
// r2 result map fully consumed release to free peak heap.
el_release(r2)
make_result({ "expr": "For", "item": item_name, "list": list_expr, "body": body }, p)
}
fn parse_block(tokens: [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(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"]
// r result map fully consumed release to free peak heap.
el_release(r)
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: [Any], pos: Int) -> Map<String, Any> {
let r = parse_primary(tokens, pos)
let node = r["node"]
let p = r["pos"]
// r result map fully consumed release to free peak heap.
el_release(r)
// Postfix duration literal: `<Int>.<unit>` where <unit> 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<String, Any>] = 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"]
// r2 result map fully consumed release to free peak heap.
el_release(r2)
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"]
// r2 result map fully consumed release to free peak heap.
el_release(r2)
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 }
if kind == "Percent" { 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 }
if kind == "Percent" { return true }
false
}
fn parse_binop(tokens: [Any], pos: Int, min_prec: Int) -> Map<String, Any> {
let r = parse_postfix(tokens, pos)
let left = r["node"]
let p = r["pos"]
// r result map fully consumed release to free peak heap.
el_release(r)
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"]
// r2 result map fully consumed release to free peak heap.
el_release(r2)
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: [Any], pos: Int) -> Map<String, Any> {
parse_binop(tokens, pos, 1)
}
// -- Statement parsing ---------------------------------------------------------
fn parse_stmt(tokens: [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(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"]
// r result map fully consumed release to free peak heap.
el_release(r)
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"]
// r result map fully consumed release to free peak heap.
el_release(r)
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"]
// r result map fully consumed release to free peak heap.
el_release(r)
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"]
// r result map fully consumed release to free peak heap.
el_release(r)
// 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"]
// r2 result map fully consumed release to free peak heap.
el_release(r2)
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<String, Any>] = 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<String, Any>] = 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"]
// r result map fully consumed release to free peak heap.
el_release(r)
let r2 = parse_block(tokens, p)
let body = r2["stmts"]
let p = r2["pos"]
// r2 result map fully consumed release to free peak heap.
el_release(r2)
return make_result({ "stmt": "While", "cond": cond, "body": body }, p)
}
// break statement
if k == "Break" {
return make_result({ "stmt": "Break" }, pos + 1)
}
// continue statement
if k == "Continue" {
return make_result({ "stmt": "Continue" }, pos + 1)
}
// for loop (range or list iteration)
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 start_expr = r["node"]
let p = r["pos"]
// r result map fully consumed release to free peak heap.
el_release(r)
// Check for range operator: .. (exclusive) or ..= (inclusive)
let range_k = tok_kind(tokens, p)
if range_k == "DotDot" {
// exclusive range: for i in start..end
let p = p + 1
let r2 = parse_expr(tokens, p)
let end_expr = r2["node"]
let p = r2["pos"]
el_release(r2)
let r3 = parse_block(tokens, p)
let body = r3["stmts"]
let p = r3["pos"]
el_release(r3)
return make_result({ "stmt": "ForRange", "var": item_name, "start": start_expr, "end": end_expr, "inclusive": false, "body": body }, p)
}
if range_k == "DotDotEq" {
// inclusive range: for i in start..=end
let p = p + 1
let r2 = parse_expr(tokens, p)
let end_expr = r2["node"]
let p = r2["pos"]
el_release(r2)
let r3 = parse_block(tokens, p)
let body = r3["stmts"]
let p = r3["pos"]
el_release(r3)
return make_result({ "stmt": "ForRange", "var": item_name, "start": start_expr, "end": end_expr, "inclusive": true, "body": body }, p)
}
// No range operator: regular for-in (list iteration)
let list_expr = start_expr
let r2 = parse_block(tokens, p)
let body = r2["stmts"]
let p = r2["pos"]
el_release(r2)
return make_result({ "stmt": "For", "item": item_name, "list": list_expr, "body": body }, p)
}
// try/catch statement
// try { body } catch (name: Type) { handler }
// The catch variable name and type are both captured; type is skipped.
if k == "Try" {
let p = pos + 1
let r_try = parse_block(tokens, p)
let try_body = r_try["stmts"]
let p = r_try["pos"]
// r_try result map fully consumed release to free peak heap.
el_release(r_try)
let catch_name = "err"
let k2 = tok_kind(tokens, p)
if str_eq(k2, "Catch") {
let p = p + 1
let p = expect(tokens, p, "LParen")
// catch variable name
let kn = tok_kind(tokens, p)
if str_eq(kn, "Ident") {
let catch_name = tok_value(tokens, p)
let p = p + 1
}
// optional type annotation: : Type
let k3 = tok_kind(tokens, p)
if str_eq(k3, "Colon") {
let p = p + 1
let p = skip_type(tokens, p)
}
let p = expect(tokens, p, "RParen")
let r_catch = parse_block(tokens, p)
let catch_body = r_catch["stmts"]
let p = r_catch["pos"]
// r_catch result map fully consumed release to free peak heap.
el_release(r_catch)
return make_result({ "stmt": "TryCatch", "try_body": try_body, "catch_name": catch_name, "catch_body": catch_body }, p)
}
return make_result({ "stmt": "TryCatch", "try_body": try_body, "catch_name": catch_name, "catch_body": native_list_empty() }, 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
}
// r result map fully consumed release to free peak heap.
el_release(r)
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)
}
// assert <cond_expr> [ , <msg_expr> ]
// The message is optional if the next token after the condition is not a
// Comma, emit an empty string placeholder so the test still works.
if k == "Assert" {
let p: Int = pos + 1
let cond_r = parse_expr(tokens, p)
let cond_node = cond_r["node"]
let p: Int = cond_r["pos"]
el_release(cond_r)
let after_k: String = tok_kind(tokens, p)
if str_eq(after_k, "Comma") {
let p = p + 1
let msg_r = parse_expr(tokens, p)
let msg_node = msg_r["node"]
let p: Int = msg_r["pos"]
el_release(msg_r)
return make_result({ "stmt": "Assert", "cond": cond_node, "msg": msg_node }, p)
}
// No message use empty string placeholder.
return make_result({ "stmt": "Assert", "cond": cond_node, "msg": { "expr": "Str", "value": "" } }, 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"]
// r result map fully consumed release to free peak heap.
el_release(r)
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"]
// r result map fully consumed release to free peak heap.
el_release(r)
make_result({ "stmt": "Expr", "value": val }, p)
}
// -- Top-level parse ------------------------------------------------------------
fn parse(tokens: [Any]) -> [Map<String, Any>] {
// Flat list: 2 entries per token, so divide by 2 for token count.
let total: Int = native_list_len(tokens) / 2
let stmts: [Map<String, Any>] = 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"]
// r result map fully consumed release to free peak heap.
el_release(r)
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
}
// -- Streaming parse helpers ---------------------------------------------------
// parse_one parse exactly one top-level statement at position `pos`.
// Returns { "node": stmt_map, "pos": new_pos }.
// Enables the streaming compiler pipeline (parse one emit C discard).
fn parse_one(tokens: [Any], pos: Int) -> Map<String, Any> {
parse_stmt(tokens, pos)
}
// skip_to_rbrace advance past a balanced { ... } block.
// On entry, pos must point at the LBrace token.
// Returns the position of the token AFTER the matching RBrace.
fn skip_to_rbrace(tokens: [Any], pos: Int) -> Int {
let total: Int = native_list_len(tokens) / 2
let p: Int = pos + 1
let depth: Int = 1
let going: Bool = true
while going {
if p >= total {
let going = false
} else {
let kk: String = tok_kind(tokens, p)
if str_eq(kk, "Eof") {
let going = false
} else {
if str_eq(kk, "LBrace") {
let depth = depth + 1
let p = p + 1
} else {
if str_eq(kk, "RBrace") {
let depth = depth - 1
let p = p + 1
if depth <= 0 {
let going = false
}
} else {
let p = p + 1
}
}
}
}
}
p
}
// is_stmt_start_kind true if `k` is a token kind that can start a new
// top-level statement (used to find safe stopping points during token skips).
fn is_stmt_start_kind(k: String) -> Bool {
if str_eq(k, "Fn") { return true }
if str_eq(k, "Let") { return true }
if str_eq(k, "Extern") { return true }
if str_eq(k, "Cgi") { return true }
if str_eq(k, "Service") { return true }
if str_eq(k, "Type") { return true }
if str_eq(k, "Enum") { return true }
if str_eq(k, "Import") { return true }
if str_eq(k, "From") { return true }
if str_eq(k, "Eof") { return true }
false
}
// skip_expr_to_stmt_boundary skip tokens from `pos` until we reach a
// token that could start a new top-level statement, staying depth-aware
// so that braces inside expressions don't fool us.
fn skip_expr_to_stmt_boundary(tokens: [Any], pos: Int) -> Int {
let total: Int = native_list_len(tokens) / 2
let p: Int = pos
let depth: Int = 0
let going: Bool = true
while going {
if p >= total {
let going = false
} else {
let kk: String = tok_kind(tokens, p)
if str_eq(kk, "Eof") {
let going = false
} else {
if str_eq(kk, "LBrace") {
let depth = depth + 1
let p = p + 1
} else {
if str_eq(kk, "RBrace") {
if depth <= 0 {
let going = false
} else {
let depth = depth - 1
let p = p + 1
}
} else {
if depth == 0 {
if is_stmt_start_kind(kk) {
let going = false
} else {
let p = p + 1
}
} else {
let p = p + 1
}
}
}
}
}
}
p
}
// scan_type_el read a type annotation starting at pos and return its El
// source representation as a string, plus the new position.
// Returns { "el": String, "pos": Int }.
// Handles: Ident, [Type], Map<K,V>, Type?, Type<T,...> (same shapes as skip_type).
fn scan_type_el(tokens: [Any], pos: Int) -> Map<String, Any> {
let k: String = tok_kind(tokens, pos)
// Array type: [Type]
if str_eq(k, "LBracket") {
let p: Int = pos + 1
let inner = scan_type_el(tokens, p)
let inner_str: String = inner["el"]
let p = inner["pos"]
el_release(inner)
let p = expect(tokens, p, "RBracket")
return { "el": "[" + inner_str + "]", "pos": p }
}
// Named type (possibly generic or optional)
if str_eq(k, "Ident") {
let name: String = tok_value(tokens, pos)
let p: Int = pos + 1
let k2: String = tok_kind(tokens, p)
if str_eq(k2, "Lt") {
// Generic params: collect until matching >
let p = p + 1
let depth: Int = 1
let parts: [String] = native_list_empty()
let parts = native_list_append(parts, name + "<")
let running: Bool = true
while running {
let kk: String = tok_kind(tokens, p)
if str_eq(kk, "Eof") {
let running = false
} else {
if str_eq(kk, "Lt") {
let depth = depth + 1
let parts = native_list_append(parts, "<")
let p = p + 1
} else {
if str_eq(kk, "Gt") {
let depth = depth - 1
let p = p + 1
if depth <= 0 {
let parts = native_list_append(parts, ">")
let running = false
} else {
let parts = native_list_append(parts, ">")
}
} else {
if str_eq(kk, "Comma") {
let parts = native_list_append(parts, ", ")
let p = p + 1
} else {
let parts = native_list_append(parts, tok_value(tokens, p))
let p = p + 1
}
}
}
}
}
let k3: String = tok_kind(tokens, p)
if str_eq(k3, "QuestionMark") { let p = p + 1 }
let result: String = str_join(parts, "")
el_release(parts)
return { "el": result, "pos": p }
}
// Optional marker
if str_eq(k2, "QuestionMark") {
return { "el": name + "?", "pos": p + 1 }
}
return { "el": name, "pos": p }
}
// Fallback: unknown token, treat as Any
{ "el": "Any", "pos": pos + 1 }
}
// scan_params_el scan a parameter list `(name: Type, ...)` starting at
// position `pos` (which should point at LParen) and return the El parameter
// declaration string (e.g. "a: String, b: Int") along with the new position.
// Returns { "el": String, "pos": Int }.
// Used by scan_fn_sigs_el for --emit-header without building full AST.
fn scan_params_el(tokens: [Any], pos: Int) -> Map<String, Any> {
let p: Int = expect(tokens, pos, "LParen")
let parts: [String] = native_list_empty()
let going: Bool = true
while going {
let kk: String = tok_kind(tokens, p)
if str_eq(kk, "RParen") {
let going = false
} else {
if str_eq(kk, "Eof") {
let going = false
} else {
let pname: String = tok_value(tokens, p)
let p = p + 1
let p = expect(tokens, p, "Colon")
let tr = scan_type_el(tokens, p)
let ptype: String = tr["el"]
let p = tr["pos"]
el_release(tr)
let parts = native_list_append(parts, pname + ": " + ptype)
let k2: String = tok_kind(tokens, p)
if str_eq(k2, "Comma") {
let p = p + 1
}
}
}
}
let p = expect(tokens, p, "RParen")
let el_str: String = str_join(parts, ", ")
el_release(parts)
{ "el": el_str, "pos": p }
}
// scan_fn_sigs_el lightweight token-level pre-scan for --emit-header.
//
// Like scan_fn_sigs but captures El-style type strings instead of C types.
// Only records fn/extern_fn entries (header generation ignores lets/blocks).
//
// Descriptor shape:
// { "kind": "fn"|"extern_fn", "name": String,
// "params_el": String, <- El param list, e.g. "a: String, b: Int"
// "ret_el": String } <- El return type, e.g. "String" or "Void"
//
// Peak memory: O(tokens) with no expression AST allocation.
fn scan_fn_sigs_el(tokens: [Any]) -> [Map<String, Any>] {
let total: Int = native_list_len(tokens) / 2
let sigs: [Map<String, Any>] = native_list_empty()
let pos: Int = 0
let going: Bool = true
while going {
if pos >= total {
let going = false
} else {
let k: String = tok_kind(tokens, pos)
if str_eq(k, "Eof") {
let going = false
} else {
// --- fn definition ---
if str_eq(k, "Fn") {
let p: Int = pos + 1
let name: String = tok_value(tokens, p)
let p = p + 1
let pr = scan_params_el(tokens, p)
let params_el: String = pr["el"]
let p = pr["pos"]
el_release(pr)
// read return type
let ret_el: String = "Any"
let k2: String = tok_kind(tokens, p)
if str_eq(k2, "Arrow") {
let p = p + 1
let tr = scan_type_el(tokens, p)
let ret_el = tr["el"]
let p = tr["pos"]
el_release(tr)
}
// skip body
let k3: String = tok_kind(tokens, p)
if str_eq(k3, "LBrace") {
let p = skip_to_rbrace(tokens, p)
}
if !str_eq(name, "main") {
let sigs = native_list_append(sigs, {
"kind": "fn",
"name": name,
"params_el": params_el,
"ret_el": ret_el
})
}
let pos = p
} else {
// --- extern fn ---
if str_eq(k, "Extern") {
let p: Int = 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 pr = scan_params_el(tokens, p)
let params_el: String = pr["el"]
let p = pr["pos"]
el_release(pr)
let ret_el: String = "Any"
let k3: String = tok_kind(tokens, p)
if str_eq(k3, "Arrow") {
let p = p + 1
let tr = scan_type_el(tokens, p)
let ret_el = tr["el"]
let p = tr["pos"]
el_release(tr)
}
let sigs = native_list_append(sigs, {
"kind": "extern_fn",
"name": name,
"params_el": params_el,
"ret_el": ret_el
})
let pos = p
} else {
let pos = pos + 1
}
} else {
// Let, Cgi, Service, Import, Type, Enum, From skip to boundary.
let p: Int = pos + 1
let p = skip_expr_to_stmt_boundary(tokens, p)
let pos = p
}}}
}
}
sigs
}
// scan_params_c scan a parameter list `(name: Type, ...)` starting at
// position `pos` (which should point at LParen) and return the C parameter
// declaration string along with the new position.
// Returns { "c": String, "pos": Int }.
// This avoids allocating param-map objects during the pre-scan phase.
fn scan_params_c(tokens: [Any], pos: Int) -> Map<String, Any> {
let p: Int = expect(tokens, pos, "LParen")
let parts: [String] = native_list_empty()
let going: Bool = true
while going {
let kk: String = tok_kind(tokens, p)
if str_eq(kk, "RParen") {
let going = false
} else {
if str_eq(kk, "Eof") {
let going = false
} else {
let pname: String = tok_value(tokens, p)
let p = p + 1
let p = expect(tokens, p, "Colon")
let p = skip_type(tokens, p)
let parts = native_list_append(parts, "el_val_t " + pname)
let k2: String = tok_kind(tokens, p)
if str_eq(k2, "Comma") {
let p = p + 1
}
}
}
}
let p = expect(tokens, p, "RParen")
let c_str: String = str_join(parts, ", ")
// parts list fully consumed release to free peak heap.
el_release(parts)
if str_eq(c_str, "") { let c_str = "void" }
{ "c": c_str, "pos": p }
}
// scan_fn_sigs lightweight token-level pre-scan.
//
// Returns a list of descriptor maps (one per top-level item) without building
// full expression ASTs or param-map objects. Descriptors have these shapes:
//
// fn/extern_fn: { "kind": "fn"|"extern_fn", "name": String,
// "params_c": String, <- C param decl string, e.g. "el_val_t a, el_val_t b"
// "is_main": Bool }
// toplevel_let: { "kind": "toplevel_let", "name": String, "ltype": String }
// cgi_block: { "kind": "cgi_block", "name": String }
// service_block: { "kind": "service_block", "name": String }
//
// Import/TypeDef/EnumDef nodes are skipped (codegen treats them as no-ops).
//
// The scan allocates only small string values per entry, keeping peak RSS low.
fn scan_fn_sigs(tokens: [Any]) -> [Map<String, Any>] {
let total: Int = native_list_len(tokens) / 2
let sigs: [Map<String, Any>] = native_list_empty()
let pos: Int = 0
let going: Bool = true
while going {
if pos >= total {
let going = false
} else {
let k: String = tok_kind(tokens, pos)
if str_eq(k, "Eof") {
let going = false
} else {
// --- fn definition ---
if str_eq(k, "Fn") {
let p: Int = pos + 1
let name: String = tok_value(tokens, p)
let p = p + 1
let r = scan_params_c(tokens, p)
let params_c: String = r["c"]
let p = r["pos"]
// r result map fully consumed release to free peak heap.
el_release(r)
// skip return type
let k2: String = tok_kind(tokens, p)
if str_eq(k2, "Arrow") {
let p = p + 1
let p = skip_type(tokens, p)
}
// skip body
let k3: String = tok_kind(tokens, p)
if str_eq(k3, "LBrace") {
let p = skip_to_rbrace(tokens, p)
}
let is_main: Bool = str_eq(name, "main")
let sigs = native_list_append(sigs, {
"kind": "fn",
"name": name,
"params_c": params_c,
"is_main": is_main
})
let pos = p
} else {
// --- extern fn ---
if str_eq(k, "Extern") {
let p: Int = 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 = scan_params_c(tokens, p)
let params_c: String = r["c"]
let p = r["pos"]
// r result map fully consumed release to free peak heap.
el_release(r)
let k3: String = tok_kind(tokens, p)
if str_eq(k3, "Arrow") {
let p = p + 1
let p = skip_type(tokens, p)
}
let sigs = native_list_append(sigs, {
"kind": "extern_fn",
"name": name,
"params_c": params_c,
"is_main": false
})
let pos = p
} else {
let pos = pos + 1
}
} else {
// --- top-level let ---
if str_eq(k, "Let") {
let p: Int = pos + 1
let name: String = tok_value(tokens, p)
let p = p + 1
let ltype: String = ""
let k2: String = tok_kind(tokens, p)
if str_eq(k2, "Colon") {
let p = p + 1
let kt: String = tok_kind(tokens, p)
if str_eq(kt, "Ident") { let ltype = tok_value(tokens, p) }
let p = skip_type(tokens, p)
}
let p = expect(tokens, p, "Eq")
let p = skip_expr_to_stmt_boundary(tokens, p)
let sigs = native_list_append(sigs, {
"kind": "toplevel_let",
"name": name,
"ltype": ltype
})
let pos = p
} else {
// --- cgi block ---
if str_eq(k, "Cgi") {
let p: Int = pos + 1
let name: String = tok_value(tokens, p)
let p = p + 1
let k2: String = tok_kind(tokens, p)
if str_eq(k2, "LBrace") {
let p = skip_to_rbrace(tokens, p)
}
let sigs = native_list_append(sigs, {
"kind": "cgi_block",
"name": name
})
let pos = p
} else {
// --- service block ---
if str_eq(k, "Service") {
let p: Int = pos + 1
let name: String = tok_value(tokens, p)
let p = p + 1
let k2: String = tok_kind(tokens, p)
if str_eq(k2, "LBrace") {
let p = skip_to_rbrace(tokens, p)
}
let sigs = native_list_append(sigs, {
"kind": "service_block",
"name": name
})
let pos = p
} else {
// Import, Type, Enum, From, or any other token.
// Skip ahead to the next statement boundary.
let p: Int = pos + 1
let p = skip_expr_to_stmt_boundary(tokens, p)
let pos = p
}}}}}
}
}
}
sigs
}