ERR/E0.2: raise / try / catch / onfail + precedence + consumer-aware pipe (parser)

Parser-only second step of the error-handling stream. No sema/codegen.

- token: 4 keywords — `raise`, `try`, `catch`, `onfail`.
- ast: RaiseStmt, TryExpr, CatchExpr {operand, binding?, body, is_match_body},
  OnFailStmt {binding?, body}.
- parser:
  - `try` is a unary prefix (binds tighter than `or`; right-recursive so it
    stacks under `xx`/`@`/etc).
  - `or` is already left-associative (precedence-climbing loop) — no change.
  - `catch` is a postfix with four body shapes (no-binding block / block /
    bare-expr / `== { case }` match-body, the latter reusing parseMatchBody
    with the binding as subject).
  - `raise EXPR;` and `onfail [e] { } | onfail EXPR;` statements; `error`
    parses in expression position so `raise error.X` works; raise rejected
    in expression position and inside an onfail body (in_onfail_body flag).
  - consumer-aware `|>`: pipes the LHS into the head call through a
    try/catch/or wrapper, preserving the wrapper.
- print: printExpr + match-arm printing for round-trips (anyerror!void to
  break the printExpr<->printMatchArms inferred-error-set loop).
- sema/lsp: exhaustive switch arms for the 4 nodes + 4 keyword tokens.
- tests: ~22 inline parser tests (precedence, all catch forms, both
  rejections, pipe cases, round-trip prints incl. match-body).

zig build, zig build test (264), and 254/254 examples green.
This commit is contained in:
agra
2026-05-31 17:07:49 +03:00
parent e88ee66953
commit 1b777dd6ab
6 changed files with 579 additions and 23 deletions

View File

@@ -1130,6 +1130,29 @@ pub const Analyzer = struct {
.defer_stmt => |ds| {
try self.analyzeNode(ds.expr);
},
.raise_stmt => |rs| {
try self.analyzeNode(rs.tag);
},
.try_expr => |te| {
try self.analyzeNode(te.operand);
},
.catch_expr => |ce| {
try self.analyzeNode(ce.operand);
try self.pushScope();
if (ce.binding) |bname| {
try self.addSymbol(bname, .variable, null, node.span);
}
try self.analyzeNode(ce.body);
self.popScope();
},
.onfail_stmt => |os| {
try self.pushScope();
if (os.binding) |bname| {
try self.addSymbol(bname, .variable, null, node.span);
}
try self.analyzeNode(os.body);
self.popScope();
},
.push_stmt => |ps| {
try self.analyzeNode(ps.context_expr);
try self.analyzeNode(ps.body);
@@ -1587,6 +1610,19 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node {
.defer_stmt => |ds| {
if (findNodeAtOffset(ds.expr, offset)) |found| return found;
},
.raise_stmt => |rs| {
if (findNodeAtOffset(rs.tag, offset)) |found| return found;
},
.try_expr => |te| {
if (findNodeAtOffset(te.operand, offset)) |found| return found;
},
.catch_expr => |ce| {
if (findNodeAtOffset(ce.operand, offset)) |found| return found;
if (findNodeAtOffset(ce.body, offset)) |found| return found;
},
.onfail_stmt => |os| {
if (findNodeAtOffset(os.body, offset)) |found| return found;
},
.push_stmt => |ps| {
if (findNodeAtOffset(ps.context_expr, offset)) |found| return found;
if (findNodeAtOffset(ps.body, offset)) |found| return found;