mirror of
https://github.com/ziglang/zig.git
synced 2026-02-13 04:48:20 +00:00
translate-c: improve switch translation
This commit is contained in:
parent
3717bedb4e
commit
7ca53bdfaa
@ -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 {};
|
||||
|
||||
@ -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();
|
||||
var cases = std.ArrayList(Node).init(c.gpa);
|
||||
defer cases.deinit();
|
||||
var has_default = false;
|
||||
|
||||
// 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();
|
||||
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);
|
||||
|
||||
// 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));
|
||||
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]);
|
||||
|
||||
const last = try transStmt(c, &block_scope.base, stmt.getBody(), .unused);
|
||||
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,
|
||||
};
|
||||
|
||||
// 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 res = try transSwitchProngStmt(c, scope, sub, it, end_it);
|
||||
|
||||
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 (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);
|
||||
if (!has_default) {
|
||||
const else_prong = try Tag.switch_else.create(c.arena, Tag.@"break".init());
|
||||
try 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;
|
||||
|
||||
// take all pending statements
|
||||
try switch_scope.pending_block.statements.appendSlice(block_scope.statements.items);
|
||||
block_scope.statements.shrinkAndFree(0);
|
||||
|
||||
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);
|
||||
|
||||
return transStmt(c, scope, stmt.getSubStmt(), .unused);
|
||||
}
|
||||
|
||||
fn transDefault(
|
||||
/// 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);
|
||||
|
||||
if (seen_default) {
|
||||
items.items.len = 0;
|
||||
sub = case_stmt.getSubStmt();
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
break :blk try Tag.ellipsis3.create(c.arena, .{ .lhs = lhs_node, .rhs = rhs_node });
|
||||
} else
|
||||
try transExprCoercing(c, scope, case_stmt.getLHS(), .used);
|
||||
|
||||
try items.append(expr);
|
||||
sub = case_stmt.getSubStmt();
|
||||
},
|
||||
else => return sub,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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");
|
||||
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 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.?;
|
||||
// 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);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// take all pending statements
|
||||
try switch_scope.pending_block.statements.appendSlice(block_scope.statements.items);
|
||||
block_scope.statements.shrinkAndFree(0);
|
||||
/// 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();
|
||||
|
||||
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);
|
||||
|
||||
return transStmt(c, scope, stmt.getSubStmt(), .unused);
|
||||
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();
|
||||
|
||||
@ -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|
|
||||
|
||||
@ -276,27 +276,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
|
||||
\\ }
|
||||
\\}
|
||||
, &[_][]const u8{ // TODO properly translate this
|
||||
\\pub export fn main() c_int {
|
||||
\\ var i: c_int = 2;
|
||||
\\ @"switch": {
|
||||
\\ case_1: {
|
||||
\\ case: {
|
||||
\\ switch (i) {
|
||||
\\ @as(c_int, 0) => break :case,
|
||||
\\ @as(c_int, 2) => break :case_1,
|
||||
\\ else => break :@"switch",
|
||||
\\ }
|
||||
\\ }
|
||||
\\ }
|
||||
\\ {
|
||||
\\ {
|
||||
\\ i += @as(c_int, 2);
|
||||
\\ }
|
||||
\\ i += @as(c_int, 1);
|
||||
\\ }
|
||||
\\ }
|
||||
\\ return 0;
|
||||
\\}
|
||||
\\source.h:5:13: warning: TODO complex switch
|
||||
,
|
||||
\\source.h:1:5: warning: unable to translate function, demoted to extern
|
||||
\\pub extern fn main() c_int;
|
||||
});
|
||||
|
||||
cases.add("correct semicolon after infixop",
|
||||
@ -2013,34 +1996,47 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
|
||||
\\ default:
|
||||
\\ res = 3 * i;
|
||||
\\ break;
|
||||
\\ break;
|
||||
\\ case 4:
|
||||
\\ case 5:
|
||||
\\ res = 69;
|
||||
\\ {
|
||||
\\ res = 5;
|
||||
\\ return;
|
||||
\\ }
|
||||
\\ case 6:
|
||||
\\ res = 1;
|
||||
\\ return;
|
||||
\\ }
|
||||
\\}
|
||||
, &[_][]const u8{
|
||||
\\pub export fn switch_fn(arg_i: c_int) void {
|
||||
\\ var i = arg_i;
|
||||
\\ var res: c_int = 0;
|
||||
\\ @"switch": {
|
||||
\\ case_2: {
|
||||
\\ default: {
|
||||
\\ case_1: {
|
||||
\\ case: {
|
||||
\\ switch (i) {
|
||||
\\ @as(c_int, 0) => break :case,
|
||||
\\ @as(c_int, 1)...@as(c_int, 3) => break :case_1,
|
||||
\\ else => break :default,
|
||||
\\ @as(c_int, 4) => break :case_2,
|
||||
\\ }
|
||||
\\ }
|
||||
\\ res = 1;
|
||||
\\ }
|
||||
\\ res = 2;
|
||||
\\ }
|
||||
\\ switch (i) {
|
||||
\\ @as(c_int, 0) => {
|
||||
\\ res = 1;
|
||||
\\ res = 2;
|
||||
\\ res = @as(c_int, 3) * i;
|
||||
\\ break :@"switch";
|
||||
\\ }
|
||||
\\ res = 5;
|
||||
\\ },
|
||||
\\ @as(c_int, 1)...@as(c_int, 3) => {
|
||||
\\ res = 2;
|
||||
\\ res = @as(c_int, 3) * i;
|
||||
\\ },
|
||||
\\ else => {
|
||||
\\ res = @as(c_int, 3) * i;
|
||||
\\ },
|
||||
\\ @as(c_int, 4), @as(c_int, 5) => {
|
||||
\\ res = 69;
|
||||
\\ {
|
||||
\\ res = 5;
|
||||
\\ return;
|
||||
\\ }
|
||||
\\ },
|
||||
\\ @as(c_int, 6) => {
|
||||
\\ res = 1;
|
||||
\\ return;
|
||||
\\ },
|
||||
\\ }
|
||||
\\}
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user