diff options
| author | Veikka Tuominen <git@vexu.eu> | 2021-02-17 22:11:26 +0200 |
|---|---|---|
| committer | Veikka Tuominen <git@vexu.eu> | 2021-02-17 22:11:26 +0200 |
| commit | 7ca53bdfaab59e61c38d0bedb6b16739904f7519 (patch) | |
| tree | b7678de14625868401c95001a77e697cb8fffa66 /src | |
| parent | 3717bedb4e3198fe2ded167b41f9b0441e817b9c (diff) | |
| download | zig-7ca53bdfaab59e61c38d0bedb6b16739904f7519.tar.gz zig-7ca53bdfaab59e61c38d0bedb6b16739904f7519.zip | |
translate-c: improve switch translation
Diffstat (limited to 'src')
| -rw-r--r-- | src/clang.zig | 6 | ||||
| -rw-r--r-- | src/translate_c.zig | 359 | ||||
| -rw-r--r-- | src/translate_c/ast.zig | 111 |
3 files changed, 257 insertions, 219 deletions
diff --git a/src/clang.zig b/src/clang.zig index fbb955205b..5adb858b90 100644 --- a/src/clang.zig +++ b/src/clang.zig @@ -273,12 +273,12 @@ pub const CompoundAssignOperator = opaque { pub const CompoundStmt = opaque { pub const body_begin = ZigClangCompoundStmt_body_begin; - extern fn ZigClangCompoundStmt_body_begin(*const CompoundStmt) const_body_iterator; + extern fn ZigClangCompoundStmt_body_begin(*const CompoundStmt) ConstBodyIterator; pub const body_end = ZigClangCompoundStmt_body_end; - extern fn ZigClangCompoundStmt_body_end(*const CompoundStmt) const_body_iterator; + extern fn ZigClangCompoundStmt_body_end(*const CompoundStmt) ConstBodyIterator; - pub const const_body_iterator = [*]const *Stmt; + pub const ConstBodyIterator = [*]const *Stmt; }; pub const ConditionalOperator = opaque {}; diff --git a/src/translate_c.zig b/src/translate_c.zig index 5ac60bffae..47c601677e 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -31,7 +31,6 @@ const Scope = struct { parent: ?*Scope, const Id = enum { - @"switch", block, root, condition, @@ -39,17 +38,6 @@ const Scope = struct { do_loop, }; - /// Represents an in-progress Node.Switch. This struct is stack-allocated. - /// When it is deinitialized, it produces an Node.Switch which is allocated - /// into the main arena. - const Switch = struct { - base: Scope, - pending_block: Block, - cases: std.ArrayList(Node), - switch_label: ?[]const u8, - default_label: ?[]const u8, - }; - /// Used for the scope of condition expressions, for example `if (cond)`. /// The block is lazily initialised because it is only needed for rare /// cases of comma operators being used. @@ -230,7 +218,7 @@ const Scope = struct { return switch (scope.id) { .root => return name, .block => @fieldParentPtr(Block, "base", scope).getAlias(name), - .@"switch", .loop, .do_loop, .condition => scope.parent.?.getAlias(name), + .loop, .do_loop, .condition => scope.parent.?.getAlias(name), }; } @@ -238,7 +226,7 @@ const Scope = struct { return switch (scope.id) { .root => @fieldParentPtr(Root, "base", scope).contains(name), .block => @fieldParentPtr(Block, "base", scope).contains(name), - .@"switch", .loop, .do_loop, .condition => scope.parent.?.contains(name), + .loop, .do_loop, .condition => scope.parent.?.contains(name), }; } @@ -247,24 +235,12 @@ const Scope = struct { while (true) { switch (scope.id) { .root => unreachable, - .@"switch" => return scope, .loop, .do_loop => return scope, else => scope = scope.parent.?, } } } - fn getSwitch(inner: *Scope) *Scope.Switch { - var scope = inner; - while (true) { - switch (scope.id) { - .root => unreachable, - .@"switch" => return @fieldParentPtr(Switch, "base", scope), - else => scope = scope.parent.?, - } - } - } - /// Appends a node to the first block scope if inside a function, or to the root tree if not. fn appendNode(inner: *Scope, node: Node) !void { var scope = inner; @@ -570,7 +546,7 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void { } const casted_body = @ptrCast(*const clang.CompoundStmt, body_stmt); - transCompoundStmtInline(c, &block_scope.base, casted_body, &block_scope) catch |err| switch (err) { + transCompoundStmtInline(c, casted_body, &block_scope) catch |err| switch (err) { error.OutOfMemory => |e| return e, error.UnsupportedTranslation, error.UnsupportedType, @@ -583,24 +559,10 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void { }; // add return statement if the function didn't have one blk: { - if (fn_ty.getNoReturnAttr()) break :blk; - if (isCVoid(return_qt)) break :blk; - - if (block_scope.statements.items.len > 0) { - var last = block_scope.statements.items[block_scope.statements.items.len - 1]; - while (true) { - switch (last.tag()) { - .block => { - const block = last.castTag(.block).?; - if (block.data.stmts.len == 0) break; - - last = block.data.stmts[block.data.stmts.len - 1]; - }, - // no extra return needed - .@"return", .return_void => break :blk, - else => break, - } - } + const maybe_body = try block_scope.complete(c); + if (fn_ty.getNoReturnAttr() or isCVoid(return_qt) or maybe_body.isNoreturn(false)) { + proto_node.data.body = maybe_body; + break :blk; } const rhs = transZeroInitExpr(c, scope, fn_decl_loc, return_qt.getTypePtr()) catch |err| switch (err) { @@ -616,9 +578,9 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void { }; const ret = try Tag.@"return".create(c.arena, rhs); try block_scope.statements.append(ret); + proto_node.data.body = try block_scope.complete(c); } - proto_node.data.body = try block_scope.complete(c); return addTopLevelDecl(c, fn_name, Node.initPayload(&proto_node.base)); } @@ -1079,7 +1041,7 @@ fn transStmt( return Tag.empty_block.init(); }, .ContinueStmtClass => return Tag.@"continue".init(), - .BreakStmtClass => return transBreak(c, scope), + .BreakStmtClass => return Tag.@"break".init(), .ForStmtClass => return transForLoop(c, scope, @ptrCast(*const clang.ForStmt, stmt)), .FloatingLiteralClass => return transFloatingLiteral(c, scope, @ptrCast(*const clang.FloatingLiteral, stmt), result_used), .ConditionalOperatorClass => { @@ -1089,8 +1051,9 @@ fn transStmt( return transBinaryConditionalOperator(c, scope, @ptrCast(*const clang.BinaryConditionalOperator, stmt), result_used); }, .SwitchStmtClass => return transSwitch(c, scope, @ptrCast(*const clang.SwitchStmt, stmt)), - .CaseStmtClass => return transCase(c, scope, @ptrCast(*const clang.CaseStmt, stmt)), - .DefaultStmtClass => return transDefault(c, scope, @ptrCast(*const clang.DefaultStmt, stmt)), + .CaseStmtClass, .DefaultStmtClass => { + return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "TODO complex switch", .{}); + }, .ConstantExprClass => return transConstantExpr(c, scope, @ptrCast(*const clang.Expr, stmt), result_used), .PredefinedExprClass => return transPredefinedExpr(c, scope, @ptrCast(*const clang.PredefinedExpr, stmt), result_used), .CharacterLiteralClass => return transCharLiteral(c, scope, @ptrCast(*const clang.CharacterLiteral, stmt), result_used, .with_as), @@ -1107,13 +1070,7 @@ fn transStmt( return maybeSuppressResult(c, scope, result_used, expr); }, else => { - return fail( - c, - error.UnsupportedTranslation, - stmt.getBeginLoc(), - "TODO implement translation of stmt class {s}", - .{@tagName(sc)}, - ); + return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "TODO implement translation of stmt class {s}", .{@tagName(sc)}); }, } } @@ -1255,14 +1212,13 @@ fn transBinaryOperator( fn transCompoundStmtInline( c: *Context, - parent_scope: *Scope, stmt: *const clang.CompoundStmt, block: *Scope.Block, ) TransError!void { var it = stmt.body_begin(); const end_it = stmt.body_end(); while (it != end_it) : (it += 1) { - const result = try transStmt(c, parent_scope, it[0], .unused); + const result = try transStmt(c, &block.base, it[0], .unused); if (result.tag() == .declaration) continue; try block.statements.append(result); } @@ -1271,7 +1227,7 @@ fn transCompoundStmtInline( fn transCompoundStmt(c: *Context, scope: *Scope, stmt: *const clang.CompoundStmt) TransError!Node { var block_scope = try Scope.Block.init(c, scope, false); defer block_scope.deinit(); - try transCompoundStmtInline(c, &block_scope.base, stmt, &block_scope); + try transCompoundStmtInline(c, stmt, &block_scope); return try block_scope.complete(c); } @@ -2162,7 +2118,7 @@ fn transDoWhileLoop( defer cond_scope.deinit(); const cond = try transBoolExpr(c, &cond_scope.base, @ptrCast(*const clang.Expr, stmt.getCond()), .used); const if_not_break = switch (cond.tag()) { - .false_literal => try Tag.@"break".create(c.arena, null), + .false_literal => Tag.@"break".init(), .true_literal => { const body_node = try transStmt(c, scope, stmt.getBody(), .unused); return Tag.while_true.create(c.arena, body_node); @@ -2263,133 +2219,189 @@ fn transSwitch( }; defer cond_scope.deinit(); const switch_expr = try transExpr(c, &cond_scope.base, stmt.getCond(), .used); - const switch_node = try c.arena.create(ast.Payload.Switch); - switch_node.* = .{ - .base = .{ .tag = .@"switch" }, - .data = .{ - .cond = switch_expr, - .cases = undefined, // set later - }, - }; - - var switch_scope = Scope.Switch{ - .base = .{ - .id = .@"switch", - .parent = scope, - }, - .cases = std.ArrayList(Node).init(c.gpa), - .pending_block = undefined, - .default_label = null, - .switch_label = null, - }; - defer switch_scope.cases.deinit(); - // tmp block that all statements will go before being picked up by a case or default - var block_scope = try Scope.Block.init(c, &switch_scope.base, false); - defer block_scope.deinit(); - - // Note that we do not defer a deinit here; the switch_scope.pending_block field - // has its own memory management. This resource is freed inside `transCase` and - // then the final pending_block is freed at the bottom of this function with - // pending_block.deinit(). - switch_scope.pending_block = try Scope.Block.init(c, scope, false); - try switch_scope.pending_block.statements.append(Node.initPayload(&switch_node.base)); + var cases = std.ArrayList(Node).init(c.gpa); + defer cases.deinit(); + var has_default = false; + + const body = stmt.getBody(); + assert(body.getStmtClass() == .CompoundStmtClass); + const compound_stmt = @ptrCast(*const clang.CompoundStmt, body); + var it = compound_stmt.body_begin(); + const end_it = compound_stmt.body_end(); + // Iterate over switch body and collect all cases. + // Fallthrough is handled by duplicating statements. + while (it != end_it) : (it += 1) { + switch (it[0].getStmtClass()) { + .CaseStmtClass => { + var items = std.ArrayList(Node).init(c.gpa); + defer items.deinit(); + const sub = try transCaseStmt(c, scope, it[0], &items); + const res = try transSwitchProngStmt(c, scope, sub, it, end_it); + + if (items.items.len == 0) { + has_default = true; + const switch_else = try Tag.switch_else.create(c.arena, res); + try cases.append(switch_else); + } else { + const switch_prong = try Tag.switch_prong.create(c.arena, .{ + .cases = try c.arena.dupe(Node, items.items), + .cond = res, + }); + try cases.append(switch_prong); + } + }, + .DefaultStmtClass => { + has_default = true; + const default_stmt = @ptrCast(*const clang.DefaultStmt, it[0]); + + var sub = default_stmt.getSubStmt(); + while (true) switch (sub.getStmtClass()) { + .CaseStmtClass => sub = @ptrCast(*const clang.CaseStmt, sub).getSubStmt(), + .DefaultStmtClass => sub = @ptrCast(*const clang.DefaultStmt, sub).getSubStmt(), + else => break, + }; - const last = try transStmt(c, &block_scope.base, stmt.getBody(), .unused); + const res = try transSwitchProngStmt(c, scope, sub, it, end_it); - // take all pending statements - const last_block_stmts = last.castTag(.block).?.data.stmts; - try switch_scope.pending_block.statements.ensureCapacity( - switch_scope.pending_block.statements.items.len + last_block_stmts.len, - ); - for (last_block_stmts) |n| { - switch_scope.pending_block.statements.appendAssumeCapacity(n); + const switch_else = try Tag.switch_else.create(c.arena, res); + try cases.append(switch_else); + }, + else => {}, // collected in transSwitchProngStmt + } } - if (switch_scope.default_label == null) { - switch_scope.switch_label = try block_scope.makeMangledName(c, "switch"); - } - if (switch_scope.switch_label) |l| { - switch_scope.pending_block.label = l; + if (!has_default) { + const else_prong = try Tag.switch_else.create(c.arena, Tag.@"break".init()); + try cases.append(else_prong); } - if (switch_scope.default_label == null) { - const else_prong = try Tag.switch_else.create( - c.arena, - try Tag.@"break".create(c.arena, switch_scope.switch_label.?), - ); - try switch_scope.cases.append(else_prong); - } - - switch_node.data.cases = try c.arena.dupe(Node, switch_scope.cases.items); - const result_node = try switch_scope.pending_block.complete(c); - switch_scope.pending_block.deinit(); - return result_node; -} - -fn transCase( - c: *Context, - scope: *Scope, - stmt: *const clang.CaseStmt, -) TransError!Node { - const block_scope = try scope.findBlockScope(c); - const switch_scope = scope.getSwitch(); - const label = try block_scope.makeMangledName(c, "case"); - - const expr = if (stmt.getRHS()) |rhs| blk: { - const lhs_node = try transExpr(c, scope, stmt.getLHS(), .used); - const rhs_node = try transExpr(c, scope, rhs, .used); - - break :blk try Tag.ellipsis3.create(c.arena, .{ .lhs = lhs_node, .rhs = rhs_node }); - } else - try transExpr(c, scope, stmt.getLHS(), .used); - const switch_prong = try Tag.switch_prong.create(c.arena, .{ - .lhs = expr, - .rhs = try Tag.@"break".create(c.arena, label), + return Tag.@"switch".create(c.arena, .{ + .cond = switch_expr, + .cases = try c.arena.dupe(Node, cases.items), }); - try switch_scope.cases.append(switch_prong); +} - switch_scope.pending_block.label = label; +/// Collects all items for this case, returns the first statement after the labels. +/// If items ends up empty, the prong should be translated as an else. +fn transCaseStmt(c: *Context, scope: *Scope, stmt: *const clang.Stmt, items: *std.ArrayList(Node)) TransError!*const clang.Stmt { + var sub = stmt; + var seen_default = false; + while (true) { + switch (sub.getStmtClass()) { + .DefaultStmtClass => { + seen_default = true; + items.items.len = 0; + const default_stmt = @ptrCast(*const clang.DefaultStmt, sub); + sub = default_stmt.getSubStmt(); + }, + .CaseStmtClass => { + const case_stmt = @ptrCast(*const clang.CaseStmt, sub); - // take all pending statements - try switch_scope.pending_block.statements.appendSlice(block_scope.statements.items); - block_scope.statements.shrinkAndFree(0); + if (seen_default) { + items.items.len = 0; + sub = case_stmt.getSubStmt(); + continue; + } - const pending_node = try switch_scope.pending_block.complete(c); - switch_scope.pending_block.deinit(); - switch_scope.pending_block = try Scope.Block.init(c, scope, false); + const expr = if (case_stmt.getRHS()) |rhs| blk: { + const lhs_node = try transExprCoercing(c, scope, case_stmt.getLHS(), .used); + const rhs_node = try transExprCoercing(c, scope, rhs, .used); - try switch_scope.pending_block.statements.append(pending_node); + break :blk try Tag.ellipsis3.create(c.arena, .{ .lhs = lhs_node, .rhs = rhs_node }); + } else + try transExprCoercing(c, scope, case_stmt.getLHS(), .used); - return transStmt(c, scope, stmt.getSubStmt(), .unused); + try items.append(expr); + sub = case_stmt.getSubStmt(); + }, + else => return sub, + } + } } -fn transDefault( +/// Collects all statements seen by this case into a block. +/// Avoids creating a block if the first statement is a break or return. +fn transSwitchProngStmt( c: *Context, scope: *Scope, - stmt: *const clang.DefaultStmt, + stmt: *const clang.Stmt, + parent_it: clang.CompoundStmt.ConstBodyIterator, + parent_end_it: clang.CompoundStmt.ConstBodyIterator, ) TransError!Node { - const block_scope = try scope.findBlockScope(c); - const switch_scope = scope.getSwitch(); - switch_scope.default_label = try block_scope.makeMangledName(c, "default"); - - const else_prong = try Tag.switch_else.create( - c.arena, - try Tag.@"break".create(c.arena, switch_scope.default_label.?), - ); - try switch_scope.cases.append(else_prong); - switch_scope.pending_block.label = switch_scope.default_label.?; - - // take all pending statements - try switch_scope.pending_block.statements.appendSlice(block_scope.statements.items); - block_scope.statements.shrinkAndFree(0); + switch (stmt.getStmtClass()) { + .BreakStmtClass => return Tag.empty_block.init(), + .ReturnStmtClass => return transStmt(c, scope, stmt, .unused), + .CaseStmtClass, .DefaultStmtClass => unreachable, + else => { + var block_scope = try Scope.Block.init(c, scope, false); + defer block_scope.deinit(); - const pending_node = try switch_scope.pending_block.complete(c); - switch_scope.pending_block.deinit(); - switch_scope.pending_block = try Scope.Block.init(c, scope, false); - try switch_scope.pending_block.statements.append(pending_node); + // we do not need to translate `stmt` since it is the first stmt of `parent_it` + try transSwitchProngStmtInline(c, &block_scope, parent_it, parent_end_it); + return try block_scope.complete(c); + }, + } +} - return transStmt(c, scope, stmt.getSubStmt(), .unused); +/// Collects all statements seen by this case into a block. +fn transSwitchProngStmtInline( + c: *Context, + block: *Scope.Block, + start_it: clang.CompoundStmt.ConstBodyIterator, + end_it: clang.CompoundStmt.ConstBodyIterator, +) TransError!void { + var it = start_it; + while (it != end_it) : (it += 1) { + switch (it[0].getStmtClass()) { + .ReturnStmtClass => { + const result = try transStmt(c, &block.base, it[0], .unused); + try block.statements.append(result); + return; + }, + .BreakStmtClass => return, + .CaseStmtClass => { + var sub = @ptrCast(*const clang.CaseStmt, it[0]).getSubStmt(); + while (true) switch (sub.getStmtClass()) { + .CaseStmtClass => sub = @ptrCast(*const clang.CaseStmt, sub).getSubStmt(), + .DefaultStmtClass => sub = @ptrCast(*const clang.DefaultStmt, sub).getSubStmt(), + else => break, + }; + const result = try transStmt(c, &block.base, sub, .unused); + assert(result.tag() != .declaration); + try block.statements.append(result); + }, + .DefaultStmtClass => { + var sub = @ptrCast(*const clang.DefaultStmt, it[0]).getSubStmt(); + while (true) switch (sub.getStmtClass()) { + .CaseStmtClass => sub = @ptrCast(*const clang.CaseStmt, sub).getSubStmt(), + .DefaultStmtClass => sub = @ptrCast(*const clang.DefaultStmt, sub).getSubStmt(), + else => break, + }; + const result = try transStmt(c, &block.base, sub, .unused); + assert(result.tag() != .declaration); + try block.statements.append(result); + }, + .CompoundStmtClass => { + const compound_stmt = @ptrCast(*const clang.CompoundStmt, it[0]); + var child_block = try Scope.Block.init(c, &block.base, false); + defer child_block.deinit(); + + try transCompoundStmtInline(c, compound_stmt, &child_block); + const result = try child_block.complete(c); + try block.statements.append(result); + if (result.isNoreturn(true)) { + return; + } + }, + else => { + const result = try transStmt(c, &block.base, it[0], .unused); + if (result.tag() == .declaration) continue; + try block.statements.append(result); + }, + } + } + return; } fn transConstantExpr(c: *Context, scope: *Scope, expr: *const clang.Expr, used: ResultUsed) TransError!Node { @@ -3025,19 +3037,6 @@ fn transCPtrCast( } } -fn transBreak(c: *Context, scope: *Scope) TransError!Node { - const break_scope = scope.getBreakableScope(); - const label_text: ?[]const u8 = if (break_scope.id == .@"switch") blk: { - const swtch = @fieldParentPtr(Scope.Switch, "base", break_scope); - const block_scope = try scope.findBlockScope(c); - swtch.switch_label = try block_scope.makeMangledName(c, "switch"); - break :blk swtch.switch_label; - } else - null; - - return Tag.@"break".create(c.arena, label_text); -} - fn transFloatingLiteral(c: *Context, scope: *Scope, stmt: *const clang.FloatingLiteral, used: ResultUsed) TransError!Node { // TODO use something more accurate var dbl = stmt.getValueAsApproximateDouble(); diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig index 25cbe1bf3f..2306d1c36f 100644 --- a/src/translate_c/ast.zig +++ b/src/translate_c/ast.zig @@ -30,6 +30,7 @@ pub const Node = extern union { noreturn_type, @"anytype", @"continue", + @"break", /// pub usingnamespace @import("std").c.builtins; usingnamespace_builtins, // After this, the tag requires a payload. @@ -48,9 +49,8 @@ pub const Node = extern union { @"switch", /// else => operand, switch_else, - /// lhs => rhs, + /// items => body, switch_prong, - @"break", break_val, @"return", field_access, @@ -219,6 +219,7 @@ pub const Node = extern union { .noreturn_type, .@"anytype", .@"continue", + .@"break", => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"), .std_mem_zeroes, @@ -294,7 +295,6 @@ pub const Node = extern union { .int_to_ptr, .array_cat, .ellipsis3, - .switch_prong, .assign, .align_cast, .array_access, @@ -312,8 +312,7 @@ pub const Node = extern union { => Payload.Value, .@"if" => Payload.If, .@"while" => Payload.While, - .@"switch", .array_init => Payload.Switch, - .@"break" => Payload.Break, + .@"switch", .array_init,.switch_prong => Payload.Switch, .break_val => Payload.BreakVal, .call => Payload.Call, .var_decl => Payload.VarDecl, @@ -377,6 +376,37 @@ pub const Node = extern union { std.debug.assert(@enumToInt(payload.tag) >= Tag.no_payload_count); return .{ .ptr_otherwise = payload }; } + + pub fn isNoreturn(node: Node, break_counts: bool) bool { + switch (node.tag()) { + .block => { + const block_node = node.castTag(.block).?; + if (block_node.data.stmts.len == 0) return false; + + const last = block_node.data.stmts[block_node.data.stmts.len - 1]; + return last.isNoreturn(break_counts); + }, + .@"switch" => { + const switch_node = node.castTag(.@"switch").?; + + for (switch_node.data.cases) |case| { + const body = if (case.castTag(.switch_else)) |some| + some.data + else if (case.castTag(.switch_prong)) |some| + some.data.cond + else unreachable; + + if (!body.isNoreturn(break_counts)) return false; + } + return true; + }, + .@"return", .return_void => return true, + .break_val, .@"break" => if (break_counts) return true, + else => {}, + } + return false; + } + }; pub const Payload = struct { @@ -434,11 +464,6 @@ pub const Payload = struct { }, }; - pub const Break = struct { - base: Payload, - data: ?[]const u8, - }; - pub const BreakVal = struct { base: Payload, data: struct { @@ -855,22 +880,14 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { .rhs = undefined, }, }), - .@"break" => { - const payload = node.castTag(.@"break").?.data; - const tok = try c.addToken(.keyword_break, "break"); - const break_label = if (payload) |some| blk: { - _ = try c.addToken(.colon, ":"); - break :blk try c.addIdentifier(some); - } else 0; - return c.addNode(.{ - .tag = .@"break", - .main_token = tok, - .data = .{ - .lhs = break_label, - .rhs = 0, - }, - }); - }, + .@"break" => return c.addNode(.{ + .tag = .@"break", + .main_token = try c.addToken(.keyword_break, "break"), + .data = .{ + .lhs = 0, + .rhs = 0, + }, + }), .break_val => { const payload = node.castTag(.break_val).?.data; const tok = try c.addToken(.keyword_break, "break"); @@ -1447,15 +1464,37 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { }, .switch_prong => { const payload = node.castTag(.switch_prong).?.data; - const item = try renderNode(c, payload.lhs); - return c.addNode(.{ - .tag = .switch_case_one, - .main_token = try c.addToken(.equal_angle_bracket_right, "=>"), - .data = .{ - .lhs = item, - .rhs = try renderNode(c, payload.rhs), - }, - }); + var items = try c.gpa.alloc(NodeIndex, std.math.max(payload.cases.len, 1)); + defer c.gpa.free(items); + items[0] = 0; + for (payload.cases) |item, i| { + if (i != 0) _ = try c.addToken(.comma, ","); + items[i] = try renderNode(c, item); + } + _ = try c.addToken(.r_brace, "}"); + if (items.len < 2) { + return c.addNode(.{ + .tag = .switch_case_one, + .main_token = try c.addToken(.equal_angle_bracket_right, "=>"), + .data = .{ + .lhs = items[0], + .rhs = try renderNode(c, payload.cond), + }, + }); + } else { + const span = try c.listToSpan(items); + return c.addNode(.{ + .tag = .switch_case, + .main_token = try c.addToken(.equal_angle_bracket_right, "=>"), + .data = .{ + .lhs = try c.addExtra(NodeSubRange{ + .start = span.start, + .end = span.end, + }), + .rhs = try renderNode(c, payload.cond), + }, + }); + } }, .opaque_literal => { const opaque_tok = try c.addToken(.keyword_opaque, "opaque"); @@ -1870,7 +1909,7 @@ fn addSemicolonIfNeeded(c: *Context, node: Node) !void { fn addSemicolonIfNotBlock(c: *Context, node: Node) !void { switch (node.tag()) { - .block, .empty_block, .block_single, => {}, + .block, .empty_block, .block_single => {}, .@"if" => { const payload = node.castTag(.@"if").?.data; if (payload.@"else") |some| |
