authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2021-02-17 22:11:26+02:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2021-02-17 22:11:26+02:00
log7ca53bdfaab59e61c38d0bedb6b16739904f7519
treeb7678de14625868401c95001a77e697cb8fffa66
parent3717bedb4e3198fe2ded167b41f9b0441e817b9c
signature Commit is signed but in an unrecognized format.

translate-c: improve switch translation


4 files changed, 293 insertions(+), 259 deletions(-)

src/clang.zig+3-3
...@@ -273,12 +273,12 @@ pub const CompoundAssignOperator = opaque {...@@ -273,12 +273,12 @@ pub const CompoundAssignOperator = opaque {
273273
274pub const CompoundStmt = opaque {274pub const CompoundStmt = opaque {
275 pub const body_begin = ZigClangCompoundStmt_body_begin;275 pub const body_begin = ZigClangCompoundStmt_body_begin;
276 extern fn ZigClangCompoundStmt_body_begin(*const CompoundStmt) const_body_iterator;276 extern fn ZigClangCompoundStmt_body_begin(*const CompoundStmt) ConstBodyIterator;
277277
278 pub const body_end = ZigClangCompoundStmt_body_end;278 pub const body_end = ZigClangCompoundStmt_body_end;
279 extern fn ZigClangCompoundStmt_body_end(*const CompoundStmt) const_body_iterator;279 extern fn ZigClangCompoundStmt_body_end(*const CompoundStmt) ConstBodyIterator;
280280
281 pub const const_body_iterator = [*]const *Stmt;281 pub const ConstBodyIterator = [*]const *Stmt;
282};282};
283283
284pub const ConditionalOperator = opaque {};284pub const ConditionalOperator = opaque {};
src/translate_c.zig+179-180
...@@ -31,7 +31,6 @@ const Scope = struct {...@@ -31,7 +31,6 @@ const Scope = struct {
31 parent: ?*Scope,31 parent: ?*Scope,
3232
33 const Id = enum {33 const Id = enum {
34 @"switch",
35 block,34 block,
36 root,35 root,
37 condition,36 condition,
...@@ -39,17 +38,6 @@ const Scope = struct {...@@ -39,17 +38,6 @@ const Scope = struct {
39 do_loop,38 do_loop,
40 };39 };
4140
42 /// Represents an in-progress Node.Switch. This struct is stack-allocated.
43 /// When it is deinitialized, it produces an Node.Switch which is allocated
44 /// into the main arena.
45 const Switch = struct {
46 base: Scope,
47 pending_block: Block,
48 cases: std.ArrayList(Node),
49 switch_label: ?[]const u8,
50 default_label: ?[]const u8,
51 };
52
53 /// Used for the scope of condition expressions, for example `if (cond)`.41 /// Used for the scope of condition expressions, for example `if (cond)`.
54 /// The block is lazily initialised because it is only needed for rare42 /// The block is lazily initialised because it is only needed for rare
55 /// cases of comma operators being used.43 /// cases of comma operators being used.
...@@ -230,7 +218,7 @@ const Scope = struct {...@@ -230,7 +218,7 @@ const Scope = struct {
230 return switch (scope.id) {218 return switch (scope.id) {
231 .root => return name,219 .root => return name,
232 .block => @fieldParentPtr(Block, "base", scope).getAlias(name),220 .block => @fieldParentPtr(Block, "base", scope).getAlias(name),
233 .@"switch", .loop, .do_loop, .condition => scope.parent.?.getAlias(name),221 .loop, .do_loop, .condition => scope.parent.?.getAlias(name),
234 };222 };
235 }223 }
236224
...@@ -238,7 +226,7 @@ const Scope = struct {...@@ -238,7 +226,7 @@ const Scope = struct {
238 return switch (scope.id) {226 return switch (scope.id) {
239 .root => @fieldParentPtr(Root, "base", scope).contains(name),227 .root => @fieldParentPtr(Root, "base", scope).contains(name),
240 .block => @fieldParentPtr(Block, "base", scope).contains(name),228 .block => @fieldParentPtr(Block, "base", scope).contains(name),
241 .@"switch", .loop, .do_loop, .condition => scope.parent.?.contains(name),229 .loop, .do_loop, .condition => scope.parent.?.contains(name),
242 };230 };
243 }231 }
244232
...@@ -247,24 +235,12 @@ const Scope = struct {...@@ -247,24 +235,12 @@ const Scope = struct {
247 while (true) {235 while (true) {
248 switch (scope.id) {236 switch (scope.id) {
249 .root => unreachable,237 .root => unreachable,
250 .@"switch" => return scope,
251 .loop, .do_loop => return scope,238 .loop, .do_loop => return scope,
252 else => scope = scope.parent.?,239 else => scope = scope.parent.?,
253 }240 }
254 }241 }
255 }242 }
256243
257 fn getSwitch(inner: *Scope) *Scope.Switch {
258 var scope = inner;
259 while (true) {
260 switch (scope.id) {
261 .root => unreachable,
262 .@"switch" => return @fieldParentPtr(Switch, "base", scope),
263 else => scope = scope.parent.?,
264 }
265 }
266 }
267
268 /// Appends a node to the first block scope if inside a function, or to the root tree if not.244 /// Appends a node to the first block scope if inside a function, or to the root tree if not.
269 fn appendNode(inner: *Scope, node: Node) !void {245 fn appendNode(inner: *Scope, node: Node) !void {
270 var scope = inner;246 var scope = inner;
...@@ -570,7 +546,7 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void {...@@ -570,7 +546,7 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void {
570 }546 }
571547
572 const casted_body = @ptrCast(*const clang.CompoundStmt, body_stmt);548 const casted_body = @ptrCast(*const clang.CompoundStmt, body_stmt);
573 transCompoundStmtInline(c, &block_scope.base, casted_body, &block_scope) catch |err| switch (err) {549 transCompoundStmtInline(c, casted_body, &block_scope) catch |err| switch (err) {
574 error.OutOfMemory => |e| return e,550 error.OutOfMemory => |e| return e,
575 error.UnsupportedTranslation,551 error.UnsupportedTranslation,
576 error.UnsupportedType,552 error.UnsupportedType,
...@@ -583,24 +559,10 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void {...@@ -583,24 +559,10 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void {
583 };559 };
584 // add return statement if the function didn't have one560 // add return statement if the function didn't have one
585 blk: {561 blk: {
586 if (fn_ty.getNoReturnAttr()) break :blk;562 const maybe_body = try block_scope.complete(c);
587 if (isCVoid(return_qt)) break :blk;563 if (fn_ty.getNoReturnAttr() or isCVoid(return_qt) or maybe_body.isNoreturn(false)) {
588564 proto_node.data.body = maybe_body;
589 if (block_scope.statements.items.len > 0) {565 break :blk;
590 var last = block_scope.statements.items[block_scope.statements.items.len - 1];
591 while (true) {
592 switch (last.tag()) {
593 .block => {
594 const block = last.castTag(.block).?;
595 if (block.data.stmts.len == 0) break;
596
597 last = block.data.stmts[block.data.stmts.len - 1];
598 },
599 // no extra return needed
600 .@"return", .return_void => break :blk,
601 else => break,
602 }
603 }
604 }566 }
605567
606 const rhs = transZeroInitExpr(c, scope, fn_decl_loc, return_qt.getTypePtr()) catch |err| switch (err) {568 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 {...@@ -616,9 +578,9 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void {
616 };578 };
617 const ret = try Tag.@"return".create(c.arena, rhs);579 const ret = try Tag.@"return".create(c.arena, rhs);
618 try block_scope.statements.append(ret);580 try block_scope.statements.append(ret);
581 proto_node.data.body = try block_scope.complete(c);
619 }582 }
620583
621 proto_node.data.body = try block_scope.complete(c);
622 return addTopLevelDecl(c, fn_name, Node.initPayload(&proto_node.base));584 return addTopLevelDecl(c, fn_name, Node.initPayload(&proto_node.base));
623}585}
624586
...@@ -1079,7 +1041,7 @@ fn transStmt(...@@ -1079,7 +1041,7 @@ fn transStmt(
1079 return Tag.empty_block.init();1041 return Tag.empty_block.init();
1080 },1042 },
1081 .ContinueStmtClass => return Tag.@"continue".init(),1043 .ContinueStmtClass => return Tag.@"continue".init(),
1082 .BreakStmtClass => return transBreak(c, scope),1044 .BreakStmtClass => return Tag.@"break".init(),
1083 .ForStmtClass => return transForLoop(c, scope, @ptrCast(*const clang.ForStmt, stmt)),1045 .ForStmtClass => return transForLoop(c, scope, @ptrCast(*const clang.ForStmt, stmt)),
1084 .FloatingLiteralClass => return transFloatingLiteral(c, scope, @ptrCast(*const clang.FloatingLiteral, stmt), result_used),1046 .FloatingLiteralClass => return transFloatingLiteral(c, scope, @ptrCast(*const clang.FloatingLiteral, stmt), result_used),
1085 .ConditionalOperatorClass => {1047 .ConditionalOperatorClass => {
...@@ -1089,8 +1051,9 @@ fn transStmt(...@@ -1089,8 +1051,9 @@ fn transStmt(
1089 return transBinaryConditionalOperator(c, scope, @ptrCast(*const clang.BinaryConditionalOperator, stmt), result_used);1051 return transBinaryConditionalOperator(c, scope, @ptrCast(*const clang.BinaryConditionalOperator, stmt), result_used);
1090 },1052 },
1091 .SwitchStmtClass => return transSwitch(c, scope, @ptrCast(*const clang.SwitchStmt, stmt)),1053 .SwitchStmtClass => return transSwitch(c, scope, @ptrCast(*const clang.SwitchStmt, stmt)),
1092 .CaseStmtClass => return transCase(c, scope, @ptrCast(*const clang.CaseStmt, stmt)),1054 .CaseStmtClass, .DefaultStmtClass => {
1093 .DefaultStmtClass => return transDefault(c, scope, @ptrCast(*const clang.DefaultStmt, stmt)),1055 return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "TODO complex switch", .{});
1056 },
1094 .ConstantExprClass => return transConstantExpr(c, scope, @ptrCast(*const clang.Expr, stmt), result_used),1057 .ConstantExprClass => return transConstantExpr(c, scope, @ptrCast(*const clang.Expr, stmt), result_used),
1095 .PredefinedExprClass => return transPredefinedExpr(c, scope, @ptrCast(*const clang.PredefinedExpr, stmt), result_used),1058 .PredefinedExprClass => return transPredefinedExpr(c, scope, @ptrCast(*const clang.PredefinedExpr, stmt), result_used),
1096 .CharacterLiteralClass => return transCharLiteral(c, scope, @ptrCast(*const clang.CharacterLiteral, stmt), result_used, .with_as),1059 .CharacterLiteralClass => return transCharLiteral(c, scope, @ptrCast(*const clang.CharacterLiteral, stmt), result_used, .with_as),
...@@ -1107,13 +1070,7 @@ fn transStmt(...@@ -1107,13 +1070,7 @@ fn transStmt(
1107 return maybeSuppressResult(c, scope, result_used, expr);1070 return maybeSuppressResult(c, scope, result_used, expr);
1108 },1071 },
1109 else => {1072 else => {
1110 return fail(1073 return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "TODO implement translation of stmt class {s}", .{@tagName(sc)});
1111 c,
1112 error.UnsupportedTranslation,
1113 stmt.getBeginLoc(),
1114 "TODO implement translation of stmt class {s}",
1115 .{@tagName(sc)},
1116 );
1117 },1074 },
1118 }1075 }
1119}1076}
...@@ -1255,14 +1212,13 @@ fn transBinaryOperator(...@@ -1255,14 +1212,13 @@ fn transBinaryOperator(
12551212
1256fn transCompoundStmtInline(1213fn transCompoundStmtInline(
1257 c: *Context,1214 c: *Context,
1258 parent_scope: *Scope,
1259 stmt: *const clang.CompoundStmt,1215 stmt: *const clang.CompoundStmt,
1260 block: *Scope.Block,1216 block: *Scope.Block,
1261) TransError!void {1217) TransError!void {
1262 var it = stmt.body_begin();1218 var it = stmt.body_begin();
1263 const end_it = stmt.body_end();1219 const end_it = stmt.body_end();
1264 while (it != end_it) : (it += 1) {1220 while (it != end_it) : (it += 1) {
1265 const result = try transStmt(c, parent_scope, it[0], .unused);1221 const result = try transStmt(c, &block.base, it[0], .unused);
1266 if (result.tag() == .declaration) continue;1222 if (result.tag() == .declaration) continue;
1267 try block.statements.append(result);1223 try block.statements.append(result);
1268 }1224 }
...@@ -1271,7 +1227,7 @@ fn transCompoundStmtInline(...@@ -1271,7 +1227,7 @@ fn transCompoundStmtInline(
1271fn transCompoundStmt(c: *Context, scope: *Scope, stmt: *const clang.CompoundStmt) TransError!Node {1227fn transCompoundStmt(c: *Context, scope: *Scope, stmt: *const clang.CompoundStmt) TransError!Node {
1272 var block_scope = try Scope.Block.init(c, scope, false);1228 var block_scope = try Scope.Block.init(c, scope, false);
1273 defer block_scope.deinit();1229 defer block_scope.deinit();
1274 try transCompoundStmtInline(c, &block_scope.base, stmt, &block_scope);1230 try transCompoundStmtInline(c, stmt, &block_scope);
1275 return try block_scope.complete(c);1231 return try block_scope.complete(c);
1276}1232}
12771233
...@@ -2162,7 +2118,7 @@ fn transDoWhileLoop(...@@ -2162,7 +2118,7 @@ fn transDoWhileLoop(
2162 defer cond_scope.deinit();2118 defer cond_scope.deinit();
2163 const cond = try transBoolExpr(c, &cond_scope.base, @ptrCast(*const clang.Expr, stmt.getCond()), .used);2119 const cond = try transBoolExpr(c, &cond_scope.base, @ptrCast(*const clang.Expr, stmt.getCond()), .used);
2164 const if_not_break = switch (cond.tag()) {2120 const if_not_break = switch (cond.tag()) {
2165 .false_literal => try Tag.@"break".create(c.arena, null),2121 .false_literal => Tag.@"break".init(),
2166 .true_literal => {2122 .true_literal => {
2167 const body_node = try transStmt(c, scope, stmt.getBody(), .unused);2123 const body_node = try transStmt(c, scope, stmt.getBody(), .unused);
2168 return Tag.while_true.create(c.arena, body_node);2124 return Tag.while_true.create(c.arena, body_node);
...@@ -2263,133 +2219,189 @@ fn transSwitch(...@@ -2263,133 +2219,189 @@ fn transSwitch(
2263 };2219 };
2264 defer cond_scope.deinit();2220 defer cond_scope.deinit();
2265 const switch_expr = try transExpr(c, &cond_scope.base, stmt.getCond(), .used);2221 const switch_expr = try transExpr(c, &cond_scope.base, stmt.getCond(), .used);
2266 const switch_node = try c.arena.create(ast.Payload.Switch);
2267 switch_node.* = .{
2268 .base = .{ .tag = .@"switch" },
2269 .data = .{
2270 .cond = switch_expr,
2271 .cases = undefined, // set later
2272 },
2273 };
2274
2275 var switch_scope = Scope.Switch{
2276 .base = .{
2277 .id = .@"switch",
2278 .parent = scope,
2279 },
2280 .cases = std.ArrayList(Node).init(c.gpa),
2281 .pending_block = undefined,
2282 .default_label = null,
2283 .switch_label = null,
2284 };
2285 defer switch_scope.cases.deinit();
22862222
2287 // tmp block that all statements will go before being picked up by a case or default2223 var cases = std.ArrayList(Node).init(c.gpa);
2288 var block_scope = try Scope.Block.init(c, &switch_scope.base, false);2224 defer cases.deinit();
2289 defer block_scope.deinit();2225 var has_default = false;
22902226
2291 // Note that we do not defer a deinit here; the switch_scope.pending_block field2227 const body = stmt.getBody();
2292 // has its own memory management. This resource is freed inside `transCase` and2228 assert(body.getStmtClass() == .CompoundStmtClass);
2293 // then the final pending_block is freed at the bottom of this function with2229 const compound_stmt = @ptrCast(*const clang.CompoundStmt, body);
2294 // pending_block.deinit().2230 var it = compound_stmt.body_begin();
2295 switch_scope.pending_block = try Scope.Block.init(c, scope, false);2231 const end_it = compound_stmt.body_end();
2296 try switch_scope.pending_block.statements.append(Node.initPayload(&switch_node.base));2232 // Iterate over switch body and collect all cases.
2233 // Fallthrough is handled by duplicating statements.
2234 while (it != end_it) : (it += 1) {
2235 switch (it[0].getStmtClass()) {
2236 .CaseStmtClass => {
2237 var items = std.ArrayList(Node).init(c.gpa);
2238 defer items.deinit();
2239 const sub = try transCaseStmt(c, scope, it[0], &items);
2240 const res = try transSwitchProngStmt(c, scope, sub, it, end_it);
2241
2242 if (items.items.len == 0) {
2243 has_default = true;
2244 const switch_else = try Tag.switch_else.create(c.arena, res);
2245 try cases.append(switch_else);
2246 } else {
2247 const switch_prong = try Tag.switch_prong.create(c.arena, .{
2248 .cases = try c.arena.dupe(Node, items.items),
2249 .cond = res,
2250 });
2251 try cases.append(switch_prong);
2252 }
2253 },
2254 .DefaultStmtClass => {
2255 has_default = true;
2256 const default_stmt = @ptrCast(*const clang.DefaultStmt, it[0]);
2257
2258 var sub = default_stmt.getSubStmt();
2259 while (true) switch (sub.getStmtClass()) {
2260 .CaseStmtClass => sub = @ptrCast(*const clang.CaseStmt, sub).getSubStmt(),
2261 .DefaultStmtClass => sub = @ptrCast(*const clang.DefaultStmt, sub).getSubStmt(),
2262 else => break,
2263 };
22972264
2298 const last = try transStmt(c, &block_scope.base, stmt.getBody(), .unused);2265 const res = try transSwitchProngStmt(c, scope, sub, it, end_it);
22992266
2300 // take all pending statements2267 const switch_else = try Tag.switch_else.create(c.arena, res);
2301 const last_block_stmts = last.castTag(.block).?.data.stmts;2268 try cases.append(switch_else);
2302 try switch_scope.pending_block.statements.ensureCapacity(2269 },
2303 switch_scope.pending_block.statements.items.len + last_block_stmts.len,2270 else => {}, // collected in transSwitchProngStmt
2304 );2271 }
2305 for (last_block_stmts) |n| {
2306 switch_scope.pending_block.statements.appendAssumeCapacity(n);
2307 }2272 }
23082273
2309 if (switch_scope.default_label == null) {2274 if (!has_default) {
2310 switch_scope.switch_label = try block_scope.makeMangledName(c, "switch");2275 const else_prong = try Tag.switch_else.create(c.arena, Tag.@"break".init());
2311 }2276 try cases.append(else_prong);
2312 if (switch_scope.switch_label) |l| {
2313 switch_scope.pending_block.label = l;
2314 }2277 }
2315 if (switch_scope.default_label == null) {
2316 const else_prong = try Tag.switch_else.create(
2317 c.arena,
2318 try Tag.@"break".create(c.arena, switch_scope.switch_label.?),
2319 );
2320 try switch_scope.cases.append(else_prong);
2321 }
2322
2323 switch_node.data.cases = try c.arena.dupe(Node, switch_scope.cases.items);
2324 const result_node = try switch_scope.pending_block.complete(c);
2325 switch_scope.pending_block.deinit();
2326 return result_node;
2327}
2328
2329fn transCase(
2330 c: *Context,
2331 scope: *Scope,
2332 stmt: *const clang.CaseStmt,
2333) TransError!Node {
2334 const block_scope = try scope.findBlockScope(c);
2335 const switch_scope = scope.getSwitch();
2336 const label = try block_scope.makeMangledName(c, "case");
2337
2338 const expr = if (stmt.getRHS()) |rhs| blk: {
2339 const lhs_node = try transExpr(c, scope, stmt.getLHS(), .used);
2340 const rhs_node = try transExpr(c, scope, rhs, .used);
2341
2342 break :blk try Tag.ellipsis3.create(c.arena, .{ .lhs = lhs_node, .rhs = rhs_node });
2343 } else
2344 try transExpr(c, scope, stmt.getLHS(), .used);
23452278
2346 const switch_prong = try Tag.switch_prong.create(c.arena, .{2279 return Tag.@"switch".create(c.arena, .{
2347 .lhs = expr,2280 .cond = switch_expr,
2348 .rhs = try Tag.@"break".create(c.arena, label),2281 .cases = try c.arena.dupe(Node, cases.items),
2349 });2282 });
2350 try switch_scope.cases.append(switch_prong);2283}
23512284
2352 switch_scope.pending_block.label = label;2285/// Collects all items for this case, returns the first statement after the labels.
2286/// If items ends up empty, the prong should be translated as an else.
2287fn transCaseStmt(c: *Context, scope: *Scope, stmt: *const clang.Stmt, items: *std.ArrayList(Node)) TransError!*const clang.Stmt {
2288 var sub = stmt;
2289 var seen_default = false;
2290 while (true) {
2291 switch (sub.getStmtClass()) {
2292 .DefaultStmtClass => {
2293 seen_default = true;
2294 items.items.len = 0;
2295 const default_stmt = @ptrCast(*const clang.DefaultStmt, sub);
2296 sub = default_stmt.getSubStmt();
2297 },
2298 .CaseStmtClass => {
2299 const case_stmt = @ptrCast(*const clang.CaseStmt, sub);
23532300
2354 // take all pending statements2301 if (seen_default) {
2355 try switch_scope.pending_block.statements.appendSlice(block_scope.statements.items);2302 items.items.len = 0;
2356 block_scope.statements.shrinkAndFree(0);2303 sub = case_stmt.getSubStmt();
2304 continue;
2305 }
23572306
2358 const pending_node = try switch_scope.pending_block.complete(c);2307 const expr = if (case_stmt.getRHS()) |rhs| blk: {
2359 switch_scope.pending_block.deinit();2308 const lhs_node = try transExprCoercing(c, scope, case_stmt.getLHS(), .used);
2360 switch_scope.pending_block = try Scope.Block.init(c, scope, false);2309 const rhs_node = try transExprCoercing(c, scope, rhs, .used);
23612310
2362 try switch_scope.pending_block.statements.append(pending_node);2311 break :blk try Tag.ellipsis3.create(c.arena, .{ .lhs = lhs_node, .rhs = rhs_node });
2312 } else
2313 try transExprCoercing(c, scope, case_stmt.getLHS(), .used);
23632314
2364 return transStmt(c, scope, stmt.getSubStmt(), .unused);2315 try items.append(expr);
2316 sub = case_stmt.getSubStmt();
2317 },
2318 else => return sub,
2319 }
2320 }
2365}2321}
23662322
2367fn transDefault(2323/// Collects all statements seen by this case into a block.
2324/// Avoids creating a block if the first statement is a break or return.
2325fn transSwitchProngStmt(
2368 c: *Context,2326 c: *Context,
2369 scope: *Scope,2327 scope: *Scope,
2370 stmt: *const clang.DefaultStmt,2328 stmt: *const clang.Stmt,
2329 parent_it: clang.CompoundStmt.ConstBodyIterator,
2330 parent_end_it: clang.CompoundStmt.ConstBodyIterator,
2371) TransError!Node {2331) TransError!Node {
2372 const block_scope = try scope.findBlockScope(c);2332 switch (stmt.getStmtClass()) {
2373 const switch_scope = scope.getSwitch();2333 .BreakStmtClass => return Tag.empty_block.init(),
2374 switch_scope.default_label = try block_scope.makeMangledName(c, "default");2334 .ReturnStmtClass => return transStmt(c, scope, stmt, .unused),
23752335 .CaseStmtClass, .DefaultStmtClass => unreachable,
2376 const else_prong = try Tag.switch_else.create(2336 else => {
2377 c.arena,2337 var block_scope = try Scope.Block.init(c, scope, false);
2378 try Tag.@"break".create(c.arena, switch_scope.default_label.?),2338 defer block_scope.deinit();
2379 );
2380 try switch_scope.cases.append(else_prong);
2381 switch_scope.pending_block.label = switch_scope.default_label.?;
2382
2383 // take all pending statements
2384 try switch_scope.pending_block.statements.appendSlice(block_scope.statements.items);
2385 block_scope.statements.shrinkAndFree(0);
23862339
2387 const pending_node = try switch_scope.pending_block.complete(c);2340 // we do not need to translate `stmt` since it is the first stmt of `parent_it`
2388 switch_scope.pending_block.deinit();2341 try transSwitchProngStmtInline(c, &block_scope, parent_it, parent_end_it);
2389 switch_scope.pending_block = try Scope.Block.init(c, scope, false);2342 return try block_scope.complete(c);
2390 try switch_scope.pending_block.statements.append(pending_node);2343 },
2344 }
2345}
23912346
2392 return transStmt(c, scope, stmt.getSubStmt(), .unused);2347/// Collects all statements seen by this case into a block.
2348fn transSwitchProngStmtInline(
2349 c: *Context,
2350 block: *Scope.Block,
2351 start_it: clang.CompoundStmt.ConstBodyIterator,
2352 end_it: clang.CompoundStmt.ConstBodyIterator,
2353) TransError!void {
2354 var it = start_it;
2355 while (it != end_it) : (it += 1) {
2356 switch (it[0].getStmtClass()) {
2357 .ReturnStmtClass => {
2358 const result = try transStmt(c, &block.base, it[0], .unused);
2359 try block.statements.append(result);
2360 return;
2361 },
2362 .BreakStmtClass => return,
2363 .CaseStmtClass => {
2364 var sub = @ptrCast(*const clang.CaseStmt, it[0]).getSubStmt();
2365 while (true) switch (sub.getStmtClass()) {
2366 .CaseStmtClass => sub = @ptrCast(*const clang.CaseStmt, sub).getSubStmt(),
2367 .DefaultStmtClass => sub = @ptrCast(*const clang.DefaultStmt, sub).getSubStmt(),
2368 else => break,
2369 };
2370 const result = try transStmt(c, &block.base, sub, .unused);
2371 assert(result.tag() != .declaration);
2372 try block.statements.append(result);
2373 },
2374 .DefaultStmtClass => {
2375 var sub = @ptrCast(*const clang.DefaultStmt, it[0]).getSubStmt();
2376 while (true) switch (sub.getStmtClass()) {
2377 .CaseStmtClass => sub = @ptrCast(*const clang.CaseStmt, sub).getSubStmt(),
2378 .DefaultStmtClass => sub = @ptrCast(*const clang.DefaultStmt, sub).getSubStmt(),
2379 else => break,
2380 };
2381 const result = try transStmt(c, &block.base, sub, .unused);
2382 assert(result.tag() != .declaration);
2383 try block.statements.append(result);
2384 },
2385 .CompoundStmtClass => {
2386 const compound_stmt = @ptrCast(*const clang.CompoundStmt, it[0]);
2387 var child_block = try Scope.Block.init(c, &block.base, false);
2388 defer child_block.deinit();
2389
2390 try transCompoundStmtInline(c, compound_stmt, &child_block);
2391 const result = try child_block.complete(c);
2392 try block.statements.append(result);
2393 if (result.isNoreturn(true)) {
2394 return;
2395 }
2396 },
2397 else => {
2398 const result = try transStmt(c, &block.base, it[0], .unused);
2399 if (result.tag() == .declaration) continue;
2400 try block.statements.append(result);
2401 },
2402 }
2403 }
2404 return;
2393}2405}
23942406
2395fn transConstantExpr(c: *Context, scope: *Scope, expr: *const clang.Expr, used: ResultUsed) TransError!Node {2407fn transConstantExpr(c: *Context, scope: *Scope, expr: *const clang.Expr, used: ResultUsed) TransError!Node {
...@@ -3025,19 +3037,6 @@ fn transCPtrCast(...@@ -3025,19 +3037,6 @@ fn transCPtrCast(
3025 }3037 }
3026}3038}
30273039
3028fn transBreak(c: *Context, scope: *Scope) TransError!Node {
3029 const break_scope = scope.getBreakableScope();
3030 const label_text: ?[]const u8 = if (break_scope.id == .@"switch") blk: {
3031 const swtch = @fieldParentPtr(Scope.Switch, "base", break_scope);
3032 const block_scope = try scope.findBlockScope(c);
3033 swtch.switch_label = try block_scope.makeMangledName(c, "switch");
3034 break :blk swtch.switch_label;
3035 } else
3036 null;
3037
3038 return Tag.@"break".create(c.arena, label_text);
3039}
3040
3041fn transFloatingLiteral(c: *Context, scope: *Scope, stmt: *const clang.FloatingLiteral, used: ResultUsed) TransError!Node {3040fn transFloatingLiteral(c: *Context, scope: *Scope, stmt: *const clang.FloatingLiteral, used: ResultUsed) TransError!Node {
3042 // TODO use something more accurate3041 // TODO use something more accurate
3043 var dbl = stmt.getValueAsApproximateDouble();3042 var dbl = stmt.getValueAsApproximateDouble();
src/translate_c/ast.zig+75-36
...@@ -30,6 +30,7 @@ pub const Node = extern union {...@@ -30,6 +30,7 @@ pub const Node = extern union {
30 noreturn_type,30 noreturn_type,
31 @"anytype",31 @"anytype",
32 @"continue",32 @"continue",
33 @"break",
33 /// pub usingnamespace @import("std").c.builtins;34 /// pub usingnamespace @import("std").c.builtins;
34 usingnamespace_builtins,35 usingnamespace_builtins,
35 // After this, the tag requires a payload.36 // After this, the tag requires a payload.
...@@ -48,9 +49,8 @@ pub const Node = extern union {...@@ -48,9 +49,8 @@ pub const Node = extern union {
48 @"switch",49 @"switch",
49 /// else => operand,50 /// else => operand,
50 switch_else,51 switch_else,
51 /// lhs => rhs,52 /// items => body,
52 switch_prong,53 switch_prong,
53 @"break",
54 break_val,54 break_val,
55 @"return",55 @"return",
56 field_access,56 field_access,
...@@ -219,6 +219,7 @@ pub const Node = extern union {...@@ -219,6 +219,7 @@ pub const Node = extern union {
219 .noreturn_type,219 .noreturn_type,
220 .@"anytype",220 .@"anytype",
221 .@"continue",221 .@"continue",
222 .@"break",
222 => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),223 => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
223224
224 .std_mem_zeroes,225 .std_mem_zeroes,
...@@ -294,7 +295,6 @@ pub const Node = extern union {...@@ -294,7 +295,6 @@ pub const Node = extern union {
294 .int_to_ptr,295 .int_to_ptr,
295 .array_cat,296 .array_cat,
296 .ellipsis3,297 .ellipsis3,
297 .switch_prong,
298 .assign,298 .assign,
299 .align_cast,299 .align_cast,
300 .array_access,300 .array_access,
...@@ -312,8 +312,7 @@ pub const Node = extern union {...@@ -312,8 +312,7 @@ pub const Node = extern union {
312 => Payload.Value,312 => Payload.Value,
313 .@"if" => Payload.If,313 .@"if" => Payload.If,
314 .@"while" => Payload.While,314 .@"while" => Payload.While,
315 .@"switch", .array_init => Payload.Switch,315 .@"switch", .array_init,.switch_prong => Payload.Switch,
316 .@"break" => Payload.Break,
317 .break_val => Payload.BreakVal,316 .break_val => Payload.BreakVal,
318 .call => Payload.Call,317 .call => Payload.Call,
319 .var_decl => Payload.VarDecl,318 .var_decl => Payload.VarDecl,
...@@ -377,6 +376,37 @@ pub const Node = extern union {...@@ -377,6 +376,37 @@ pub const Node = extern union {
377 std.debug.assert(@enumToInt(payload.tag) >= Tag.no_payload_count);376 std.debug.assert(@enumToInt(payload.tag) >= Tag.no_payload_count);
378 return .{ .ptr_otherwise = payload };377 return .{ .ptr_otherwise = payload };
379 }378 }
379
380 pub fn isNoreturn(node: Node, break_counts: bool) bool {
381 switch (node.tag()) {
382 .block => {
383 const block_node = node.castTag(.block).?;
384 if (block_node.data.stmts.len == 0) return false;
385
386 const last = block_node.data.stmts[block_node.data.stmts.len - 1];
387 return last.isNoreturn(break_counts);
388 },
389 .@"switch" => {
390 const switch_node = node.castTag(.@"switch").?;
391
392 for (switch_node.data.cases) |case| {
393 const body = if (case.castTag(.switch_else)) |some|
394 some.data
395 else if (case.castTag(.switch_prong)) |some|
396 some.data.cond
397 else unreachable;
398
399 if (!body.isNoreturn(break_counts)) return false;
400 }
401 return true;
402 },
403 .@"return", .return_void => return true,
404 .break_val, .@"break" => if (break_counts) return true,
405 else => {},
406 }
407 return false;
408 }
409
380};410};
381411
382pub const Payload = struct {412pub const Payload = struct {
...@@ -434,11 +464,6 @@ pub const Payload = struct {...@@ -434,11 +464,6 @@ pub const Payload = struct {
434 },464 },
435 };465 };
436466
437 pub const Break = struct {
438 base: Payload,
439 data: ?[]const u8,
440 };
441
442 pub const BreakVal = struct {467 pub const BreakVal = struct {
443 base: Payload,468 base: Payload,
444 data: struct {469 data: struct {
...@@ -855,22 +880,14 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {...@@ -855,22 +880,14 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
855 .rhs = undefined,880 .rhs = undefined,
856 },881 },
857 }),882 }),
858 .@"break" => {883 .@"break" => return c.addNode(.{
859 const payload = node.castTag(.@"break").?.data;884 .tag = .@"break",
860 const tok = try c.addToken(.keyword_break, "break");885 .main_token = try c.addToken(.keyword_break, "break"),
861 const break_label = if (payload) |some| blk: {886 .data = .{
862 _ = try c.addToken(.colon, ":");887 .lhs = 0,
863 break :blk try c.addIdentifier(some);888 .rhs = 0,
864 } else 0;889 },
865 return c.addNode(.{890 }),
866 .tag = .@"break",
867 .main_token = tok,
868 .data = .{
869 .lhs = break_label,
870 .rhs = 0,
871 },
872 });
873 },
874 .break_val => {891 .break_val => {
875 const payload = node.castTag(.break_val).?.data;892 const payload = node.castTag(.break_val).?.data;
876 const tok = try c.addToken(.keyword_break, "break");893 const tok = try c.addToken(.keyword_break, "break");
...@@ -1447,15 +1464,37 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {...@@ -1447,15 +1464,37 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
1447 },1464 },
1448 .switch_prong => {1465 .switch_prong => {
1449 const payload = node.castTag(.switch_prong).?.data;1466 const payload = node.castTag(.switch_prong).?.data;
1450 const item = try renderNode(c, payload.lhs);1467 var items = try c.gpa.alloc(NodeIndex, std.math.max(payload.cases.len, 1));
1451 return c.addNode(.{1468 defer c.gpa.free(items);
1452 .tag = .switch_case_one,1469 items[0] = 0;
1453 .main_token = try c.addToken(.equal_angle_bracket_right, "=>"),1470 for (payload.cases) |item, i| {
1454 .data = .{1471 if (i != 0) _ = try c.addToken(.comma, ",");
1455 .lhs = item,1472 items[i] = try renderNode(c, item);
1456 .rhs = try renderNode(c, payload.rhs),1473 }
1457 },1474 _ = try c.addToken(.r_brace, "}");
1458 });1475 if (items.len < 2) {
1476 return c.addNode(.{
1477 .tag = .switch_case_one,
1478 .main_token = try c.addToken(.equal_angle_bracket_right, "=>"),
1479 .data = .{
1480 .lhs = items[0],
1481 .rhs = try renderNode(c, payload.cond),
1482 },
1483 });
1484 } else {
1485 const span = try c.listToSpan(items);
1486 return c.addNode(.{
1487 .tag = .switch_case,
1488 .main_token = try c.addToken(.equal_angle_bracket_right, "=>"),
1489 .data = .{
1490 .lhs = try c.addExtra(NodeSubRange{
1491 .start = span.start,
1492 .end = span.end,
1493 }),
1494 .rhs = try renderNode(c, payload.cond),
1495 },
1496 });
1497 }
1459 },1498 },
1460 .opaque_literal => {1499 .opaque_literal => {
1461 const opaque_tok = try c.addToken(.keyword_opaque, "opaque");1500 const opaque_tok = try c.addToken(.keyword_opaque, "opaque");
...@@ -1870,7 +1909,7 @@ fn addSemicolonIfNeeded(c: *Context, node: Node) !void {...@@ -1870,7 +1909,7 @@ fn addSemicolonIfNeeded(c: *Context, node: Node) !void {
18701909
1871fn addSemicolonIfNotBlock(c: *Context, node: Node) !void {1910fn addSemicolonIfNotBlock(c: *Context, node: Node) !void {
1872 switch (node.tag()) {1911 switch (node.tag()) {
1873 .block, .empty_block, .block_single, => {},1912 .block, .empty_block, .block_single => {},
1874 .@"if" => {1913 .@"if" => {
1875 const payload = node.castTag(.@"if").?.data;1914 const payload = node.castTag(.@"if").?.data;
1876 if (payload.@"else") |some|1915 if (payload.@"else") |some|
test/translate_c.zig+36-40
...@@ -276,27 +276,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void {...@@ -276,27 +276,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
276 \\ }276 \\ }
277 \\}277 \\}
278 , &[_][]const u8{ // TODO properly translate this278 , &[_][]const u8{ // TODO properly translate this
279 \\pub export fn main() c_int {279 \\source.h:5:13: warning: TODO complex switch
280 \\ var i: c_int = 2;280 ,
281 \\ @"switch": {281 \\source.h:1:5: warning: unable to translate function, demoted to extern
282 \\ case_1: {282 \\pub extern fn main() c_int;
283 \\ case: {
284 \\ switch (i) {
285 \\ @as(c_int, 0) => break :case,
286 \\ @as(c_int, 2) => break :case_1,
287 \\ else => break :@"switch",
288 \\ }
289 \\ }
290 \\ }
291 \\ {
292 \\ {
293 \\ i += @as(c_int, 2);
294 \\ }
295 \\ i += @as(c_int, 1);
296 \\ }
297 \\ }
298 \\ return 0;
299 \\}
300 });283 });
301284
302 cases.add("correct semicolon after infixop",285 cases.add("correct semicolon after infixop",
...@@ -2013,34 +1996,47 @@ pub fn addCases(cases: *tests.TranslateCContext) void {...@@ -2013,34 +1996,47 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
2013 \\ default:1996 \\ default:
2014 \\ res = 3 * i;1997 \\ res = 3 * i;
2015 \\ break;1998 \\ break;
1999 \\ break;
2016 \\ case 4:2000 \\ case 4:
2001 \\ case 5:
2002 \\ res = 69;
2003 \\ {
2017 \\ res = 5;2004 \\ res = 5;
2005 \\ return;
2006 \\ }
2007 \\ case 6:
2008 \\ res = 1;
2009 \\ return;
2018 \\ }2010 \\ }
2019 \\}2011 \\}
2020 , &[_][]const u8{2012 , &[_][]const u8{
2021 \\pub export fn switch_fn(arg_i: c_int) void {2013 \\pub export fn switch_fn(arg_i: c_int) void {
2022 \\ var i = arg_i;2014 \\ var i = arg_i;
2023 \\ var res: c_int = 0;2015 \\ var res: c_int = 0;
2024 \\ @"switch": {2016 \\ switch (i) {
2025 \\ case_2: {2017 \\ @as(c_int, 0) => {
2026 \\ default: {2018 \\ res = 1;
2027 \\ case_1: {2019 \\ res = 2;
2028 \\ case: {
2029 \\ switch (i) {
2030 \\ @as(c_int, 0) => break :case,
2031 \\ @as(c_int, 1)...@as(c_int, 3) => break :case_1,
2032 \\ else => break :default,
2033 \\ @as(c_int, 4) => break :case_2,
2034 \\ }
2035 \\ }
2036 \\ res = 1;
2037 \\ }
2038 \\ res = 2;
2039 \\ }
2040 \\ res = @as(c_int, 3) * i;2020 \\ res = @as(c_int, 3) * i;
2041 \\ break :@"switch";2021 \\ },
2042 \\ }2022 \\ @as(c_int, 1)...@as(c_int, 3) => {
2043 \\ res = 5;2023 \\ res = 2;
2024 \\ res = @as(c_int, 3) * i;
2025 \\ },
2026 \\ else => {
2027 \\ res = @as(c_int, 3) * i;
2028 \\ },
2029 \\ @as(c_int, 4), @as(c_int, 5) => {
2030 \\ res = 69;
2031 \\ {
2032 \\ res = 5;
2033 \\ return;
2034 \\ }
2035 \\ },
2036 \\ @as(c_int, 6) => {
2037 \\ res = 1;
2038 \\ return;
2039 \\ },
2044 \\ }2040 \\ }
2045 \\}2041 \\}
2046 });2042 });