// selfhost/parser.rg — recursive-descent parser over lexer.rg tokens. // // Tokens are [type,line,col,text] arrays produced by lex(src). Keywords are // ordinary identifier tokens matched by text. AST nodes are maps with a // "kind" field; syntax errors raise panic() and are caught with try(). // // Grammar subset: // program := (fn_decl | stmt)* // fn_decl := ['pub'] 'fn' NAME '(' (NAME ':' type)* ')' [':' type] block // type := NAME ['[' NAME ']'] // stmt := const/var decl | if | while | for-in | return | break // | continue | expr_stmt (semicolons optional) // exprs := assign -> or -> and -> equality -> comparison -> term // -> factor -> unary -> postfix(call/index/member) -> primary var g_toks = []; var g_pos: int = 0; fn pk() [str] { return g_toks[g_pos]; } fn tkind() str { return pk()[0]; } fn ttext() str { return pk()[3]; } fn adv() [str] { const t = g_toks[g_pos]; g_pos = g_pos + 1; return t; } fn at_kind(k: str) bool { return tkind() == k; } fn at_text(x: str) bool { return ttext() == x; } fn at_op(x: str) bool { return tkind() == "op" and ttext() == x; } fn eat_op(x: str) bool { if at_op(x) { g_pos = g_pos + 1; return true; } return false; } fn expect_op(x: str) { if !at_op(x) { panic("parser: expected '" + x + "' but found '" + ttext() + "'"); } g_pos = g_pos + 1; } fn expect_ident() str { if tkind() != "ident" { panic("parser: expected identifier but found '" + ttext() + "'"); } return adv()[3]; } fn mk(kind: str) { // NOTE: .{} is an EMPTY ARRAY in RinneScript (it is the format-tuple // syntax), so nodes must be built with map_new(). var n = map_new(); n["kind"] = kind; return n; } // ---- types ---- fn parse_type() { // Consume a type syntactically: NAME or [NAME]. Types are not needed by // the evaluator, so no node is produced. if eat_op("[") { _skip_ident(); expect_op("]"); return; } _skip_ident(); } fn _skip_ident() { if tkind() != "ident" { panic("parser: expected type name but found '" + ttext() + "'"); } g_pos = g_pos + 1; } // ---- program / declarations ---- fn parse_program() { var stmts = []; while !at_kind("eof") { stmts = push(stmts, parse_stmt_top()); } var p = mk("program"); p["stmts"] = stmts; return p; } fn parse_stmt_top() { if at_text("pub") and g_toks[g_pos + 1][3] == "fn" { g_pos = g_pos + 1; // consume 'pub' return parse_fn(true); } if at_text("fn") { return parse_fn(false); } return parse_stmt(); } fn parse_fn(is_pub: bool) { g_pos = g_pos + 1; // consume 'fn' const name = expect_ident(); expect_op("("); var params = []; while !at_op(")") { const pname = expect_ident(); params = push(params, pname); if eat_op(":") { parse_type(); } if !eat_op(",") { break; } } expect_op(")"); // Optional return type — note the concrete syntax has NO colon here // (`fn f(x: int) int { ... }`), so a bare identifier follows ')'. // Optional return-type annotation: NAME or [NAME]. if tkind() == "ident" { _skip_ident(); } else if eat_op("[") { _skip_ident(); expect_op("]"); } const body = parse_block(); var f = mk("fn"); f["name"] = name; f["params"] = params; f["body"] = body; f["is_pub"] = is_pub; return f; } // ---- statements ---- fn parse_block() { expect_op("{"); var stmts = []; while !at_op("}") { if at_kind("eof") { panic("parser: unexpected end of input inside block"); } stmts = push(stmts, parse_stmt()); } expect_op("}"); var b = mk("block"); b["stmts"] = stmts; return b; } fn parse_stmt() { // Declarations. if at_text("const") or at_text("var") { const kw = ttext(); g_pos = g_pos + 1; const name = expect_ident(); // Optional type annotation: NAME or [NAME]. if eat_op(":") { if at_op("[") { g_pos = g_pos + 1; _skip_ident(); expect_op("]"); } else { _skip_ident(); } } var d = mk("var"); d["name"] = name; d["is_const"] = kw == "const"; if eat_op("=") { d["value"] = parse_expr(); } else { var nv = mk("null"); d["value"] = nv; } eat_op(";"); return d; } // Control flow. if at_text("if") { return parse_if(); } if at_text("while") { g_pos = g_pos + 1; var w = mk("while"); w["cond"] = parse_expr(); w["body"] = parse_block(); return w; } if at_text("for") { g_pos = g_pos + 1; const vname = expect_ident(); expect_op("|"); var f = mk("for"); f["var"] = vname; f["iter"] = parse_expr(); f["body"] = parse_block(); return f; } if at_text("return") { g_pos = g_pos + 1; var r = mk("return"); if !at_op(";") and !at_op("}") and !at_kind("eof") { r["value"] = parse_expr(); } else { r["value"] = mk("null"); } eat_op(";"); return r; } if at_text("break") { g_pos = g_pos + 1; eat_op(";"); return mk("break"); } if at_text("continue") { g_pos = g_pos + 1; eat_op(";"); return mk("continue"); } // Expression statement. var e = mk("expr"); e["e"] = parse_expr(); eat_op(";"); return e; } fn parse_if() { g_pos = g_pos + 1; // consume 'if' var n = mk("if"); n["cond"] = parse_expr(); n["then"] = parse_block(); if at_text("else") { g_pos = g_pos + 1; if at_text("if") { n["els"] = parse_if(); } else { n["els"] = parse_block(); } } return n; } // ---- expressions ---- fn parse_expr() { return parse_assign(); } fn parse_assign() { const left = parse_or(); if at_op("=") { const k = left["kind"]; if k != "ident" and k != "index" and k != "member" { panic("parser: invalid assignment target"); } g_pos = g_pos + 1; // '=' var a = mk("assign"); a["target"] = left; a["value"] = parse_assign(); // right-associative return a; } return left; } fn bin_node(op: str, l: map, r: map) { var n = mk("bin"); n["op"] = op; n["l"] = l; n["r"] = r; return n; } fn parse_or() { var l = parse_and(); while at_op("||") or (tkind() == "ident" and ttext() == "or") { g_pos = g_pos + 1; l = bin_node("or", l, parse_and()); } return l; } fn parse_and() { var l = parse_equality(); while at_op("&&") or (tkind() == "ident" and ttext() == "and") { g_pos = g_pos + 1; l = bin_node("and", l, parse_equality()); } return l; } fn parse_equality() { var l = parse_comparison(); while at_op("==") or at_op("!=") { const op = ttext(); g_pos = g_pos + 1; l = bin_node(op, l, parse_comparison()); } return l; } fn parse_comparison() { var l = parse_term(); while at_op("<") or at_op("<=") or at_op(">") or at_op(">=") { const op = ttext(); g_pos = g_pos + 1; l = bin_node(op, l, parse_term()); } return l; } fn parse_term() { var l = parse_factor(); while at_op("+") or at_op("-") { const op = ttext(); g_pos = g_pos + 1; l = bin_node(op, l, parse_factor()); } return l; } fn parse_factor() { var l = parse_unary(); while at_op("*") or at_op("/") or at_op("%") { const op = ttext(); g_pos = g_pos + 1; l = bin_node(op, l, parse_unary()); } return l; } fn parse_unary() { if at_op("-") { g_pos = g_pos + 1; var u = mk("un"); u["op"] = "-"; u["e"] = parse_unary(); return u; } if at_op("!") { g_pos = g_pos + 1; var u2 = mk("un"); u2["op"] = "!"; u2["e"] = parse_unary(); return u2; } if at_text("not") and tkind() == "ident" { g_pos = g_pos + 1; var u3 = mk("un"); u3["op"] = "not"; u3["e"] = parse_unary(); return u3; } return parse_postfix(); } fn parse_postfix() { var e = parse_primary(); while true { if at_op("(") { g_pos = g_pos + 1; var args = []; while !at_op(")") { args = push(args, parse_expr()); if !eat_op(",") { break; } } expect_op(")"); var c = mk("call"); c["callee"] = e; c["args"] = args; e = c; continue; } if at_op("[") { g_pos = g_pos + 1; const idx = parse_expr(); expect_op("]"); var ix = mk("index"); ix["obj"] = e; ix["idx"] = idx; e = ix; continue; } if at_op(".") { g_pos = g_pos + 1; const field = expect_ident(); var m = mk("member"); m["obj"] = e; m["name"] = field; e = m; continue; } break; } return e; } fn parse_primary() { const t = pk(); if t[0] == "int" or t[0] == "float" { g_pos = g_pos + 1; var n = mk(t[0]); n["text"] = t[3]; return n; } if t[0] == "string" { g_pos = g_pos + 1; var s = mk("str"); s["value"] = t[3]; return s; } if at_op("(") { g_pos = g_pos + 1; const e = parse_expr(); expect_op(")"); return e; } if at_op("[") { g_pos = g_pos + 1; var items = []; while !at_op("]") { items = push(items, parse_expr()); if !eat_op(",") { break; } } expect_op("]"); var arr = mk("array"); arr["items"] = items; return arr; } if at_op(".") and g_toks[g_pos + 1][3] == "{" { g_pos = g_pos + 2; // '.{' // Empty .{} is the format-tuple (compiles to a zero-element array on // the host); IDENT = ... begins map pairs; anything else is an // expression bundle like .{line} — also an array. if at_op("}") { g_pos = g_pos + 1; var e0 = mk("array"); e0["items"] = []; return e0; } const looks_pair = tkind() == "ident" and g_toks[g_pos + 1][0] == "op" and g_toks[g_pos + 1][3] == "="; if looks_pair { var pairs = []; while !at_op("}") { const key = expect_ident(); expect_op("="); const val = parse_expr(); pairs = push(pairs, [key, val]); if !eat_op(",") { break; } } expect_op("}"); var mp = mk("map"); mp["pairs"] = pairs; return mp; } var items = []; while !at_op("}") { items = push(items, parse_expr()); if !eat_op(",") { break; } } expect_op("}"); var arr2 = mk("array"); arr2["items"] = items; return arr2; } if tkind() == "ident" { const name = adv()[3]; if name == "true" or name == "false" { var b = mk("bool"); b["value"] = name == "true"; return b; } if name == "null" { return mk("null"); } var id = mk("ident"); id["name"] = name; return id; } panic("parser: unexpected token '" + ttext() + "' at pos " + str(g_pos) + " prev '" + g_toks[g_pos - 1][3] + "'"); } // Entry point: lex+parse a source string into an AST. Panics carry syntax // errors; wrap with try() at the call site. fn parse(src: str) { g_toks = lex(src); g_pos = 0; return parse_program(); }