Commit 62162a0717

Veikka Tuominen <git@vexu.eu>
2021-02-14 15:32:31
translate-c: render control flow
1 parent c4dfabf
Changed files (2)
src
src/translate_c/ast.zig
@@ -848,8 +848,8 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
                 break :blk try c.addIdentifier(some);
             } else 0;
             return c.addNode(.{
-                .tag = .identifier,
-                .main_token = try c.addToken(.keyword_break, "break"),
+                .tag = .@"break",
+                .main_token = tok,
                 .data = .{
                     .lhs = break_label,
                     .rhs = 0,
@@ -864,8 +864,8 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
                 break :blk try c.addIdentifier(some);
             } else 0;
             return c.addNode(.{
-                .tag = .identifier,
-                .main_token = try c.addToken(.keyword_break, "break"),
+                .tag = .@"break",
+                .main_token = tok,
                 .data = .{
                     .lhs = break_label,
                     .rhs = try renderNode(c, payload.val),
@@ -1183,7 +1183,7 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
             const l_brace = try c.addToken(.l_brace, "{");
 
             const stmt = try renderNode(c, payload);
-            _ = try c.addToken(.semicolon, ";");
+            try addSemicolonIfNeeded(c, payload);
 
             _ = try c.addToken(.r_brace, "}");
             return c.addNode(.{
@@ -1207,11 +1207,8 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
             defer stmts.deinit();
             for (payload.stmts) |stmt| {
                 const res = try renderNode(c, stmt);
-                switch (stmt.tag()) {
-                    .warning => continue,
-                    .var_decl, .var_simple => {},
-                    else => _ = try c.addToken(.semicolon, ";"),
-                }
+                if (res == 0) continue;
+                try addSemicolonIfNeeded(c, stmt);
                 try stmts.append(res);
             }
             const span = try c.listToSpan(stmts.items);
@@ -1245,6 +1242,194 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
                 },
             });
         },
+        .@"while" => {
+            const payload = node.castTag(.@"while").?.data;
+            const while_tok = try c.addToken(.keyword_while, "while");
+            _ = try c.addToken(.l_paren, "(");
+            const cond = try renderNode(c, payload.cond);
+            _ = try c.addToken(.r_paren, ")");
+
+            const cont_expr = if (payload.cont_expr) |some| blk: {
+                _ = try c.addToken(.colon, ":");
+                _ = try c.addToken(.l_paren, "(");
+                const res = try renderNode(c, some);
+                _ = try c.addToken(.r_paren, ")");
+                break :blk res;
+            } else 0;
+            const body = try renderNode(c, payload.body);
+
+            if (cont_expr == 0) {
+                return c.addNode(.{
+                    .tag = .while_simple,
+                    .main_token = while_tok,
+                    .data = .{
+                        .lhs = cond,
+                        .rhs = body,
+                    },
+                });
+            } else {
+                return c.addNode(.{
+                    .tag = .while_cont,
+                    .main_token = while_tok,
+                    .data = .{
+                        .lhs = cond,
+                        .rhs = try c.addExtra(std.zig.ast.Node.WhileCont{
+                            .cont_expr = cont_expr,
+                            .then_expr = body,
+                        }),
+                    },
+                });
+            }
+        },
+        .while_true => {
+            const payload = node.castTag(.while_true).?.data;
+            const while_tok = try c.addToken(.keyword_while, "while");
+            _ = try c.addToken(.l_paren, "(");
+            const cond = try c.addNode(.{
+                .tag = .true_literal,
+                .main_token = try c.addToken(.keyword_true, "true"),
+                .data = undefined,
+            });
+            _ = try c.addToken(.r_paren, ")");
+            const body = try renderNode(c, payload);
+
+            return c.addNode(.{
+                .tag = .while_simple,
+                .main_token = while_tok,
+                .data = .{
+                    .lhs = cond,
+                    .rhs = body,
+                },
+            });
+        },
+        .@"if" => {
+            const payload = node.castTag(.@"if").?.data;
+            const if_tok = try c.addToken(.keyword_if, "if");
+            _ = try c.addToken(.l_paren, "(");
+            const cond = try renderNode(c, payload.cond);
+            _ = try c.addToken(.r_paren, ")");
+
+            const then_expr = try renderNode(c, payload.then);
+            const else_node = payload.@"else" orelse return c.addNode(.{
+                .tag = .if_simple,
+                .main_token = if_tok,
+                .data = .{
+                    .lhs = cond,
+                    .rhs = then_expr,
+                },
+            });
+            _ = try c.addToken(.keyword_else, "else");
+            const else_expr = try renderNode(c, else_node);
+
+            return c.addNode(.{
+                .tag = .@"if",
+                .main_token = if_tok,
+                .data = .{
+                    .lhs = cond,
+                    .rhs = try c.addExtra(std.zig.ast.Node.If{
+                        .then_expr = then_expr,
+                        .else_expr = else_expr,
+                    }),
+                },
+            });
+        },
+        .if_not_break => {
+            const payload = node.castTag(.if_not_break).?.data;
+            const if_tok = try c.addToken(.keyword_if, "if");
+            _ = try c.addToken(.l_paren, "(");
+            const cond = try c.addNode(.{
+                .tag = .bool_not,
+                .main_token = try c.addToken(.bang,  "!"),
+                .data = .{
+                    .lhs = try renderNodeGrouped(c, payload),
+                    .rhs = undefined,
+                },
+            });
+            _ = try c.addToken(.r_paren, ")");
+            const then_expr = try c.addNode(.{
+                .tag = .@"break",
+                .main_token = try c.addToken(.keyword_break, "break"),
+                .data = .{
+                    .lhs = 0,
+                    .rhs = 0,
+                },
+            });
+
+            return c.addNode(.{
+                .tag = .if_simple,
+                .main_token = if_tok,
+                .data = .{
+                    .lhs = cond,
+                    .rhs = then_expr,
+                },
+            });
+        },
+        .@"switch" => {
+            const payload = node.castTag(.@"switch").?.data;
+            const switch_tok = try c.addToken(.keyword_switch, "switch");
+            _ = try c.addToken(.l_paren, "(");
+            const cond = try renderNode(c, payload.cond);
+            _ = try c.addToken(.r_paren, ")");
+
+            _ = try c.addToken(.l_brace, "{");
+            var cases = try c.gpa.alloc(NodeIndex, payload.cases.len);
+            defer c.gpa.free(cases);
+            for (payload.cases) |case, i| {
+                if (i != 0) _ = try c.addToken(.comma, ",");
+                cases[i] = try renderNode(c, case);
+            }
+            const span = try c.listToSpan(cases);
+            _ = try c.addToken(.r_brace, "}");
+            return c.addNode(.{
+                .tag = .@"switch",
+                .main_token = switch_tok,
+                .data = .{
+                    .lhs = cond,
+                    .rhs = try c.addExtra(NodeSubRange{
+                        .start = span.start,
+                        .end = span.end,
+                    }),
+                },
+            });
+        },
+        .switch_else => {
+            const payload = node.castTag(.switch_else).?.data;
+            _ = try c.addToken(.keyword_else, "else");
+            return c.addNode(.{
+                .tag = .switch_case_one,
+                .main_token = try c.addToken(.equal_angle_bracket_right, "=>"),
+                .data = .{
+                    .lhs = 0,
+                    .rhs = try renderNode(c, payload),
+                },
+            });
+        },
+        .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),
+                },
+            });
+        },
+        .opaque_literal => {
+            const opaque_tok = try c.addToken(.keyword_opaque, "opaque");
+            _ = try c.addToken(.l_brace, "{");
+            _ = try c.addToken(.r_brace, "}");
+
+            return c.addNode(.{
+                .tag = .container_decl_two,
+                .main_token = opaque_tok,
+                .data = .{
+                    .lhs = 0,
+                    .rhs = 0,
+                },
+            });
+        },
         else => return c.addNode(.{
             .tag = .identifier,
             .main_token = try c.addTokenFmt(.identifier, "@\"TODO {}\"", .{node.tag()}),
@@ -1256,6 +1441,28 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
     }
 }
 
+fn addSemicolonIfNeeded(c: *Context, node: Node) !void {
+    switch (node.tag()) {
+        .warning => unreachable,
+        .var_decl, .var_simple, .block, .empty_block, .@"switch" => {},
+        .while_true => {
+            const payload = node.castTag(.while_true).?.data;
+            return addSemicolonIfNeeded(c, payload);
+        },
+        .@"while" => {
+            const payload = node.castTag(.@"while").?.data;
+            return addSemicolonIfNeeded(c, payload.body);
+        },
+        .@"if" => {
+            const payload = node.castTag(.@"if").?.data;
+            if (payload.@"else") |some|
+                return addSemicolonIfNeeded(c, some);
+            return addSemicolonIfNeeded(c, payload.then);
+        },
+        else => _ = try c.addToken(.semicolon, ";"),
+    }
+}
+
 fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex {
     switch (node.tag()) {
         .null_literal,
@@ -1303,19 +1510,19 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex {
         .optional_type,
         .c_pointer,
         .single_pointer,
+        .unwrap,
+        .deref,
+        .address_of,
+        .not,
+        .negate,
+        .negate_wrap,
+        .bit_not,
         => {
             // no grouping needed
             return renderNode(c, node);
         },
 
-        .negate,
-        .negate_wrap,
-        .bit_not,
         .opaque_literal,
-        .not,
-        .address_of,
-        .unwrap,
-        .deref,
         .empty_array,
         .block_single,
         .bool_to_int,
@@ -1407,13 +1614,11 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex {
 
 fn renderPrefixOp(c: *Context, node: Node, tag: std.zig.ast.Node.Tag, tok_tag: TokenTag, bytes: []const u8) !NodeIndex {
     const payload = @fieldParentPtr(Payload.UnOp, "base", node.ptr_otherwise).data;
-    const tok = try c.addToken(tok_tag, bytes);
-    const operand = try renderNodeGrouped(c, payload);
     return c.addNode(.{
         .tag = tag,
-        .main_token = tok,
+        .main_token = try c.addToken(tok_tag, bytes),
         .data = .{
-            .lhs = operand,
+            .lhs = try renderNodeGrouped(c, payload),
             .rhs = undefined,
         },
     });
@@ -1508,7 +1713,7 @@ fn renderCall(c: *Context, lhs: NodeIndex, args: []const Node) !NodeIndex {
                 .main_token = lparen,
                 .data = .{
                     .lhs = lhs,
-                    .rhs = try c.addExtra(std.zig.ast.Node.SubRange{
+                    .rhs = try c.addExtra(NodeSubRange{
                         .start = span.start,
                         .end = span.end,
                     }),
@@ -1728,7 +1933,7 @@ fn renderFunc(c: *Context, node: Node) !NodeIndex {
                     .tag = .fn_proto_multi,
                     .main_token = fn_token,
                     .data = .{
-                        .lhs = try c.addExtra(std.zig.ast.Node.SubRange{
+                        .lhs = try c.addExtra(NodeSubRange{
                             .start = span.start,
                             .end = span.end,
                         }),
src/translate_c.zig
@@ -36,6 +36,7 @@ const Scope = struct {
         root,
         condition,
         loop,
+        do_loop,
     };
 
     /// Represents an in-progress Node.Switch. This struct is stack-allocated.
@@ -103,15 +104,22 @@ const Scope = struct {
         }
 
         fn complete(self: *Block, c: *Context) !Node {
-            // We reserve 1 extra statement if the parent is a Loop. This is in case of
-            // do while, we want to put `if (cond) break;` at the end.
-            const alloc_len = self.statements.items.len + @boolToInt(self.base.parent.?.id == .loop);
-            var stmts = try c.arena.alloc(Node, alloc_len);
-            stmts.len = self.statements.items.len;
-            mem.copy(Node, stmts, self.statements.items);
+            if (self.base.parent.?.id == .do_loop) {
+                // We reserve 1 extra statement if the parent is a do_loop. This is in case of
+                // do while, we want to put `if (cond) break;` at the end.
+                const alloc_len = self.statements.items.len + @boolToInt(self.base.parent.?.id == .do_loop);
+                var stmts = try c.arena.alloc(Node, alloc_len);
+                stmts.len = self.statements.items.len;
+                mem.copy(Node, stmts, self.statements.items);
+                return Tag.block.create(c.arena, .{
+                    .label = self.label,
+                    .stmts = stmts,
+                });
+            }
+            if (self.statements.items.len == 0) return Tag.empty_block.init();
             return Tag.block.create(c.arena, .{
                 .label = self.label,
-                .stmts = stmts,
+                .stmts = try c.arena.dupe(Node, self.statements.items),
             });
         }
 
@@ -222,7 +230,7 @@ const Scope = struct {
         return switch (scope.id) {
             .root => return name,
             .block => @fieldParentPtr(Block, "base", scope).getAlias(name),
-            .@"switch", .loop, .condition => scope.parent.?.getAlias(name),
+            .@"switch", .loop, .do_loop, .condition => scope.parent.?.getAlias(name),
         };
     }
 
@@ -230,7 +238,7 @@ const Scope = struct {
         return switch (scope.id) {
             .root => @fieldParentPtr(Root, "base", scope).contains(name),
             .block => @fieldParentPtr(Block, "base", scope).contains(name),
-            .@"switch", .loop, .condition => scope.parent.?.contains(name),
+            .@"switch", .loop, .do_loop, .condition => scope.parent.?.contains(name),
         };
     }
 
@@ -240,7 +248,7 @@ const Scope = struct {
             switch (scope.id) {
                 .root => unreachable,
                 .@"switch" => return scope,
-                .loop => return scope,
+                .loop, .do_loop => return scope,
                 else => scope = scope.parent.?,
             }
         }
@@ -2063,7 +2071,7 @@ fn transDoWhileLoop(
 ) TransError!Node {
     var loop_scope = Scope{
         .parent = scope,
-        .id = .loop,
+        .id = .do_loop,
     };
 
     // if (!cond) break;
@@ -2075,7 +2083,14 @@ 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 = try Tag.if_not_break.create(c.arena, cond);
+    const if_not_break = switch (cond.tag()) {
+        .false_literal => try Tag.@"break".create(c.arena, null),
+        .true_literal => {
+            const body_node = try transStmt(c, scope, stmt.getBody(), .unused);
+            return Tag.while_true.create(c.arena, body_node);
+        },
+        else => try Tag.if_not_break.create(c.arena, cond),
+    };
 
     const body_node = if (stmt.getBody().getStmtClass() == .CompoundStmtClass) blk: {
         // there's already a block in C, so we'll append our condition to it.
@@ -4099,7 +4114,7 @@ fn transMacroFnDefine(c: *Context, m: *MacroCtx) ParseError!void {
         const br = blk_last.castTag(.break_val).?;
         break :blk br.data.val;
     } else expr;
-    const return_type = if (typeof_arg.castTag(.std_meta_cast)) |some| 
+    const return_type = if (typeof_arg.castTag(.std_meta_cast)) |some|
         some.data.lhs
     else
         try Tag.typeof.create(c.arena, typeof_arg);