Something went wrong. Try again.
A general purpose .. programming language written in zig ijadux2.tngl.sh/Rinnescript
zig language
Something went wrong. Try again.
12 kB · 519 lines
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520// 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) -> primaryvar 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();}