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:
363
src/parser.zig
363
src/parser.zig
@@ -28,6 +28,10 @@ pub const Parser = struct {
|
||||
/// while parsing a `for` range bound so `for 0..N (i)` reads `N` as the
|
||||
/// end and leaves `(i)` for the cursor rather than parsing `N(i)`.
|
||||
suppress_call: bool = false,
|
||||
/// When true (set while parsing an `onfail` body), a `raise` statement is
|
||||
/// rejected — an error during cleanup has no propagation target. E1.7
|
||||
/// extends this to the full {try, return, break, continue} set.
|
||||
in_onfail_body: bool = false,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, source: [:0]const u8) Parser {
|
||||
var lexer = Lexer.init(source);
|
||||
@@ -2011,6 +2015,42 @@ pub const Parser = struct {
|
||||
return try self.createNode(start, .{ .defer_stmt = .{ .expr = deferred } });
|
||||
}
|
||||
|
||||
// Raise statement: raise <expr>;
|
||||
if (self.current.tag == .kw_raise) {
|
||||
const start = self.current.loc.start;
|
||||
if (self.in_onfail_body) {
|
||||
return self.fail("`raise` is not allowed inside an `onfail` body — an error during cleanup has no propagation target");
|
||||
}
|
||||
self.advance();
|
||||
const tag_expr = try self.parseExpr();
|
||||
try self.expect(.semicolon);
|
||||
return try self.createNode(start, .{ .raise_stmt = .{ .tag = tag_expr } });
|
||||
}
|
||||
|
||||
// Onfail statement: onfail { body } | onfail e { body } | onfail <expr>;
|
||||
// A binding is present only when an identifier is immediately followed
|
||||
// by `{`; otherwise the text after `onfail` is the (no-binding) body.
|
||||
if (self.current.tag == .kw_onfail) {
|
||||
const start = self.current.loc.start;
|
||||
self.advance();
|
||||
var binding: ?[]const u8 = null;
|
||||
if (self.current.tag == .identifier and self.peekNext() == .l_brace) {
|
||||
binding = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
}
|
||||
const saved_onfail = self.in_onfail_body;
|
||||
self.in_onfail_body = true;
|
||||
defer self.in_onfail_body = saved_onfail;
|
||||
const body: *Node = if (self.current.tag == .l_brace)
|
||||
try self.parseBlock()
|
||||
else blk: {
|
||||
const e = try self.parseExpr();
|
||||
try self.expect(.semicolon);
|
||||
break :blk e;
|
||||
};
|
||||
return try self.createNode(start, .{ .onfail_stmt = .{ .binding = binding, .body = body } });
|
||||
}
|
||||
|
||||
// Break statement: break;
|
||||
if (self.current.tag == .kw_break) {
|
||||
const start = self.current.loc.start;
|
||||
@@ -2146,23 +2186,37 @@ pub const Parser = struct {
|
||||
var lhs = initial_lhs;
|
||||
|
||||
while (true) {
|
||||
// Pipe operator: desugar a |> f(args) → f(a, args), a |> f → f(a)
|
||||
// Pipe operator: desugar a |> f(args) → f(a, args), a |> f → f(a).
|
||||
// Consumer-aware: the piped LHS goes into the RHS's HEAD call,
|
||||
// looking THROUGH a `try` prefix / `catch` postfix / `or` fallback
|
||||
// and leaving the wrapper intact:
|
||||
// a |> try f(x) → try f(a, x)
|
||||
// a |> f(x) catch e {...} → f(a, x) catch e {...}
|
||||
// a |> f(x) or default → f(a, x) or default (only f gets a)
|
||||
if (self.current.tag == .pipe_arrow and Prec.pipe >= min_prec) {
|
||||
self.advance();
|
||||
// Parse the RHS as a full call expression (higher precedence)
|
||||
const rhs = try self.parseBinary(Prec.pipe + 1);
|
||||
// Desugar based on RHS shape
|
||||
if (rhs.data == .call) {
|
||||
// a |> f(args) → f(a, args...)
|
||||
// Walk through error-handling wrappers to the head call node.
|
||||
var head = rhs;
|
||||
const head_call: ?*Node = while (true) {
|
||||
switch (head.data) {
|
||||
.call => break head,
|
||||
.try_expr => head = head.data.try_expr.operand,
|
||||
.catch_expr => head = head.data.catch_expr.operand,
|
||||
.binary_op => |bo| {
|
||||
if (bo.op == .or_op) head = bo.lhs else break null;
|
||||
},
|
||||
else => break null,
|
||||
}
|
||||
};
|
||||
if (head_call) |cn| {
|
||||
// a |> ...f(args)... → ...f(a, args)... (wrapper preserved;
|
||||
// mutating the head call in place updates the wrapper).
|
||||
var new_args = std.ArrayList(*Node).empty;
|
||||
try new_args.append(self.allocator, lhs);
|
||||
for (rhs.data.call.args) |arg| {
|
||||
try new_args.append(self.allocator, arg);
|
||||
}
|
||||
lhs = try self.createNode(lhs.span.start, .{ .call = .{
|
||||
.callee = rhs.data.call.callee,
|
||||
.args = try new_args.toOwnedSlice(self.allocator),
|
||||
} });
|
||||
for (cn.data.call.args) |arg| try new_args.append(self.allocator, arg);
|
||||
cn.data.call.args = try new_args.toOwnedSlice(self.allocator);
|
||||
lhs = rhs;
|
||||
} else {
|
||||
// a |> f → f(a)
|
||||
const args = try self.allocator.alloc(*Node, 1);
|
||||
@@ -2252,6 +2306,16 @@ pub const Parser = struct {
|
||||
const operand = try self.parseUnary();
|
||||
return try self.createNode(start, .{ .unary_op = .{ .op = .address_of, .operand = operand } });
|
||||
}
|
||||
// `try X` — failable-attempt prefix. Joins the unary tier (binds
|
||||
// tighter than any binary op incl. `or`); right-recursive so prefixes
|
||||
// stack by adjacency (`xx try foo()` = `xx (try foo())`). Failability
|
||||
// of the operand is a sema check (E1.4), not a parse-time restriction.
|
||||
if (self.current.tag == .kw_try) {
|
||||
const start = self.current.loc.start;
|
||||
self.advance();
|
||||
const operand = try self.parseUnary();
|
||||
return try self.createNode(start, .{ .try_expr = .{ .operand = operand } });
|
||||
}
|
||||
// cast(Type) expr — prefix operator with type parameter
|
||||
if (self.current.tag == .identifier and std.mem.eql(u8, self.tokenSlice(self.current), "cast")) {
|
||||
const saved_lexer = self.lexer;
|
||||
@@ -2403,6 +2467,38 @@ pub const Parser = struct {
|
||||
// Only if it's not != (bang_equal would have been lexed as a single token)
|
||||
self.advance();
|
||||
expr = try self.createNode(expr.span.start, .{ .force_unwrap = .{ .operand = expr } });
|
||||
} else if (self.current.tag == .kw_catch) {
|
||||
// `X catch [binding] BODY` — postfix failure handler.
|
||||
// Four shapes, disambiguated by peeking after `catch`:
|
||||
// catch { block } — no binding (braces required)
|
||||
// catch e { block } — binding + block body
|
||||
// catch e == { case ... } — binding + match body (sugar)
|
||||
// catch e EXPR — binding + bare-expression body
|
||||
self.advance(); // consume 'catch'
|
||||
var binding: ?[]const u8 = null;
|
||||
if (self.current.tag == .identifier) {
|
||||
binding = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
}
|
||||
var is_match_body = false;
|
||||
const body: *Node = if (self.current.tag == .l_brace)
|
||||
try self.parseBlock()
|
||||
else if (binding != null and self.current.tag == .equal_equal) blk: {
|
||||
const m_start = self.current.loc.start;
|
||||
self.advance(); // consume '=='
|
||||
is_match_body = true;
|
||||
const subject = try self.createNode(m_start, .{ .identifier = .{ .name = binding.? } });
|
||||
break :blk try self.parseMatchBody(subject, m_start);
|
||||
} else if (binding != null)
|
||||
try self.parseExpr()
|
||||
else
|
||||
return self.fail("`catch` without a binding requires a braced body: `catch { ... }`");
|
||||
expr = try self.createNode(expr.span.start, .{ .catch_expr = .{
|
||||
.operand = expr,
|
||||
.binding = binding,
|
||||
.body = body,
|
||||
.is_match_body = is_match_body,
|
||||
} });
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -2661,6 +2757,14 @@ pub const Parser = struct {
|
||||
.hash_jni_env => {
|
||||
return try self.parseJniEnvBlock();
|
||||
},
|
||||
// `error` in expression position is the head of a tag literal
|
||||
// `error.X` (parsed as a field access); sema (E1) gives it meaning.
|
||||
.kw_error => {
|
||||
self.advance();
|
||||
return try self.createNode(start, .{ .identifier = .{ .name = "error" } });
|
||||
},
|
||||
.kw_raise => return self.fail("`raise` is a statement and cannot appear in expression position"),
|
||||
.kw_onfail => return self.fail("`onfail` is a statement and cannot appear in expression position"),
|
||||
else => {
|
||||
return self.fail("unexpected token in expression");
|
||||
},
|
||||
@@ -3997,3 +4101,238 @@ test "round-trip print: bare inferred and named error types" {
|
||||
try std.testing.expectEqualStrings("!ParseErr", aw.writer.toArrayList().items);
|
||||
}
|
||||
}
|
||||
|
||||
// ── ERR step E0.2 — raise / try / catch / onfail + precedence + pipe ──
|
||||
|
||||
/// Parse `src` (a single `f :: () { ... }` decl) and return its body's first
|
||||
/// statement node.
|
||||
fn e02FirstStmt(alloc: std.mem.Allocator, src: [:0]const u8) anyerror!*Node {
|
||||
var parser = Parser.init(alloc, src);
|
||||
const root = try parser.parse();
|
||||
return root.data.root.decls[0].data.fn_decl.body.data.block.stmts[0];
|
||||
}
|
||||
|
||||
/// Parse `src` (a `f :: () { v := EXPR; }` decl) and return the EXPR node.
|
||||
fn e02FirstValue(alloc: std.mem.Allocator, src: [:0]const u8) anyerror!*Node {
|
||||
const stmt = try e02FirstStmt(alloc, src);
|
||||
return stmt.data.var_decl.value.?;
|
||||
}
|
||||
|
||||
fn e02ExpectPrints(alloc: std.mem.Allocator, node: *const Node, expected: []const u8) !void {
|
||||
var aw = std.Io.Writer.Allocating.init(alloc);
|
||||
try print.printNode(node, &aw.writer);
|
||||
try std.testing.expectEqualStrings(expected, aw.writer.toArrayList().items);
|
||||
}
|
||||
|
||||
test "E0.2 try binds tighter than or: try foo() or try boo()" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const v = try e02FirstValue(arena.allocator(), "f :: () { v := try foo() or try boo(); }");
|
||||
try std.testing.expect(v.data == .binary_op);
|
||||
try std.testing.expect(v.data.binary_op.op == .or_op);
|
||||
try std.testing.expect(v.data.binary_op.lhs.data == .try_expr);
|
||||
try std.testing.expect(v.data.binary_op.rhs.data == .try_expr);
|
||||
}
|
||||
|
||||
test "E0.2 or is left-associative: a or b or c => (a or b) or c" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const v = try e02FirstValue(arena.allocator(), "f :: () { v := try a() or try b() or try c(); }");
|
||||
try std.testing.expect(v.data == .binary_op and v.data.binary_op.op == .or_op);
|
||||
// LHS is the nested (a or b); RHS is the final operand.
|
||||
try std.testing.expect(v.data.binary_op.lhs.data == .binary_op);
|
||||
try std.testing.expect(v.data.binary_op.lhs.data.binary_op.op == .or_op);
|
||||
try std.testing.expect(v.data.binary_op.rhs.data == .try_expr);
|
||||
}
|
||||
|
||||
test "E0.2 try prefix stacks under xx: xx try foo()" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const v = try e02FirstValue(arena.allocator(), "f :: () { v := xx try foo(); }");
|
||||
try std.testing.expect(v.data == .unary_op and v.data.unary_op.op == .xx);
|
||||
try std.testing.expect(v.data.unary_op.operand.data == .try_expr);
|
||||
}
|
||||
|
||||
test "E0.2 catch no binding, braced body" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const v = try e02FirstValue(arena.allocator(), "f :: () { v := foo() catch { }; }");
|
||||
try std.testing.expect(v.data == .catch_expr);
|
||||
try std.testing.expect(v.data.catch_expr.binding == null);
|
||||
try std.testing.expect(v.data.catch_expr.is_match_body == false);
|
||||
try std.testing.expect(v.data.catch_expr.body.data == .block);
|
||||
}
|
||||
|
||||
test "E0.2 catch with binding, block body" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const v = try e02FirstValue(arena.allocator(), "f :: () { v := foo() catch e { bar(); }; }");
|
||||
try std.testing.expect(v.data == .catch_expr);
|
||||
try std.testing.expectEqualStrings("e", v.data.catch_expr.binding.?);
|
||||
try std.testing.expect(v.data.catch_expr.body.data == .block);
|
||||
}
|
||||
|
||||
test "E0.2 catch with binding, bare-expression body" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const v = try e02FirstValue(arena.allocator(), "f :: () { v := foo() catch e bar(); }");
|
||||
try std.testing.expect(v.data == .catch_expr);
|
||||
try std.testing.expectEqualStrings("e", v.data.catch_expr.binding.?);
|
||||
try std.testing.expect(v.data.catch_expr.is_match_body == false);
|
||||
try std.testing.expect(v.data.catch_expr.body.data == .call);
|
||||
}
|
||||
|
||||
test "E0.2 catch match-body desugars to match_expr over the binding" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const v = try e02FirstValue(arena.allocator(), "f :: () { v := foo() catch e == { case .Empty: 0; else: 1; }; }");
|
||||
try std.testing.expect(v.data == .catch_expr);
|
||||
try std.testing.expect(v.data.catch_expr.is_match_body);
|
||||
try std.testing.expect(v.data.catch_expr.body.data == .match_expr);
|
||||
try std.testing.expectEqual(@as(usize, 2), v.data.catch_expr.body.data.match_expr.arms.len);
|
||||
// subject is the binding identifier
|
||||
try std.testing.expect(v.data.catch_expr.body.data.match_expr.subject.data == .identifier);
|
||||
try std.testing.expectEqualStrings("e", v.data.catch_expr.body.data.match_expr.subject.data.identifier.name);
|
||||
}
|
||||
|
||||
test "E0.2 catch over a parenthesized or-chain" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const v = try e02FirstValue(arena.allocator(), "f :: () { v := (try foo() or try boo()) catch e { }; }");
|
||||
try std.testing.expect(v.data == .catch_expr);
|
||||
try std.testing.expect(v.data.catch_expr.operand.data == .binary_op);
|
||||
try std.testing.expect(v.data.catch_expr.operand.data.binary_op.op == .or_op);
|
||||
}
|
||||
|
||||
test "E0.2 catch without binding and unbraced body is rejected" {
|
||||
// No binding (the token after `catch` is not an identifier) and no braces:
|
||||
// the no-binding form requires a braced body.
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var parser = Parser.init(arena.allocator(), "f :: () { v := foo() catch 42; }");
|
||||
try std.testing.expectError(error.ParseError, parser.parse());
|
||||
}
|
||||
|
||||
test "E0.2 raise error.X parses as raise_stmt over a field access" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const s = try e02FirstStmt(arena.allocator(), "f :: () { raise error.BadDigit; }");
|
||||
try std.testing.expect(s.data == .raise_stmt);
|
||||
try std.testing.expect(s.data.raise_stmt.tag.data == .field_access);
|
||||
try std.testing.expectEqualStrings("BadDigit", s.data.raise_stmt.tag.data.field_access.field);
|
||||
const obj = s.data.raise_stmt.tag.data.field_access.object;
|
||||
try std.testing.expect(obj.data == .identifier);
|
||||
try std.testing.expectEqualStrings("error", obj.data.identifier.name);
|
||||
}
|
||||
|
||||
test "E0.2 raise variable form" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const s = try e02FirstStmt(arena.allocator(), "f :: () { raise e; }");
|
||||
try std.testing.expect(s.data == .raise_stmt);
|
||||
try std.testing.expect(s.data.raise_stmt.tag.data == .identifier);
|
||||
}
|
||||
|
||||
test "E0.2 raise rejected in expression position" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var parser = Parser.init(arena.allocator(), "f :: () { x := 1 + raise error.X; }");
|
||||
try std.testing.expectError(error.ParseError, parser.parse());
|
||||
}
|
||||
|
||||
test "E0.2 raise rejected inside an onfail body" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var parser = Parser.init(arena.allocator(), "f :: () { onfail { raise error.X; } }");
|
||||
try std.testing.expectError(error.ParseError, parser.parse());
|
||||
}
|
||||
|
||||
test "E0.2 onfail with binding and block body" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const s = try e02FirstStmt(arena.allocator(), "f :: () { onfail e { close(h); } }");
|
||||
try std.testing.expect(s.data == .onfail_stmt);
|
||||
try std.testing.expectEqualStrings("e", s.data.onfail_stmt.binding.?);
|
||||
try std.testing.expect(s.data.onfail_stmt.body.data == .block);
|
||||
}
|
||||
|
||||
test "E0.2 onfail no-binding block vs bare-expression body" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const block_body = try e02FirstStmt(arena.allocator(), "f :: () { onfail { close(h); } }");
|
||||
try std.testing.expect(block_body.data == .onfail_stmt);
|
||||
try std.testing.expect(block_body.data.onfail_stmt.binding == null);
|
||||
try std.testing.expect(block_body.data.onfail_stmt.body.data == .block);
|
||||
|
||||
const expr_body = try e02FirstStmt(arena.allocator(), "f :: () { onfail close(h); }");
|
||||
try std.testing.expect(expr_body.data == .onfail_stmt);
|
||||
try std.testing.expect(expr_body.data.onfail_stmt.binding == null);
|
||||
try std.testing.expect(expr_body.data.onfail_stmt.body.data == .call);
|
||||
}
|
||||
|
||||
test "E0.2 consumer-aware pipe: x |> try f() inserts x into the head call" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const v = try e02FirstValue(arena.allocator(), "f :: () { v := x |> try f(); }");
|
||||
try std.testing.expect(v.data == .try_expr);
|
||||
const call = v.data.try_expr.operand;
|
||||
try std.testing.expect(call.data == .call);
|
||||
try std.testing.expectEqual(@as(usize, 1), call.data.call.args.len);
|
||||
try std.testing.expect(call.data.call.args[0].data == .identifier);
|
||||
try std.testing.expectEqualStrings("x", call.data.call.args[0].data.identifier.name);
|
||||
}
|
||||
|
||||
test "E0.2 consumer-aware pipe: x |> f() catch e { } preserves the catch" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const v = try e02FirstValue(arena.allocator(), "f :: () { v := x |> g() catch e { }; }");
|
||||
try std.testing.expect(v.data == .catch_expr);
|
||||
try std.testing.expectEqualStrings("e", v.data.catch_expr.binding.?);
|
||||
try std.testing.expect(v.data.catch_expr.operand.data == .call);
|
||||
try std.testing.expectEqual(@as(usize, 1), v.data.catch_expr.operand.data.call.args.len);
|
||||
}
|
||||
|
||||
test "E0.2 consumer-aware pipe: x |> f() or d feeds only the head call" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const v = try e02FirstValue(arena.allocator(), "f :: () { v := x |> g() or d; }");
|
||||
try std.testing.expect(v.data == .binary_op and v.data.binary_op.op == .or_op);
|
||||
// LHS (head call g) receives x; RHS fallback `d` is untouched.
|
||||
try std.testing.expect(v.data.binary_op.lhs.data == .call);
|
||||
try std.testing.expectEqual(@as(usize, 1), v.data.binary_op.lhs.data.call.args.len);
|
||||
try std.testing.expect(v.data.binary_op.rhs.data == .identifier);
|
||||
try std.testing.expectEqualStrings("d", v.data.binary_op.rhs.data.identifier.name);
|
||||
}
|
||||
|
||||
test "E0.2 plain pipe still works: x |> f(a) => f(x, a)" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const v = try e02FirstValue(arena.allocator(), "f :: () { v := x |> g(a); }");
|
||||
try std.testing.expect(v.data == .call);
|
||||
try std.testing.expectEqual(@as(usize, 2), v.data.call.args.len);
|
||||
try std.testing.expectEqualStrings("x", v.data.call.args[0].data.identifier.name);
|
||||
try std.testing.expectEqualStrings("a", v.data.call.args[1].data.identifier.name);
|
||||
}
|
||||
|
||||
test "E0.2 round-trip print: try / or precedence / raise / catch / onfail" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const a = arena.allocator();
|
||||
try e02ExpectPrints(a, try e02FirstValue(a, "f :: () { v := try foo(); }"), "try foo()");
|
||||
try e02ExpectPrints(a, try e02FirstValue(a, "f :: () { v := try foo() or try boo(); }"), "try foo() or try boo()");
|
||||
try e02ExpectPrints(a, try e02FirstStmt(a, "f :: () { raise error.BadDigit; }"), "raise error.BadDigit");
|
||||
try e02ExpectPrints(a, try e02FirstStmt(a, "f :: () { raise e; }"), "raise e");
|
||||
try e02ExpectPrints(a, try e02FirstValue(a, "f :: () { v := foo() catch e bar(); }"), "foo() catch e bar()");
|
||||
try e02ExpectPrints(a, try e02FirstValue(a, "f :: () { v := foo() catch e { bar(); }; }"), "foo() catch e { bar(); }");
|
||||
try e02ExpectPrints(a, try e02FirstValue(a, "f :: () { v := foo() catch { bar(); }; }"), "foo() catch { bar(); }");
|
||||
try e02ExpectPrints(a, try e02FirstStmt(a, "f :: () { onfail close(h); }"), "onfail close(h)");
|
||||
try e02ExpectPrints(a, try e02FirstStmt(a, "f :: () { onfail e { close(h); } }"), "onfail e { close(h); }");
|
||||
}
|
||||
|
||||
test "E0.2 round-trip print: catch match-body form" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const a = arena.allocator();
|
||||
const v = try e02FirstValue(a, "f :: () { v := foo() catch e == { case .Empty: 0; else: 1; }; }");
|
||||
try e02ExpectPrints(a, v, "foo() catch e == { case .Empty: 0; else: 1; }");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user