Merge pull request #5889 from Vexu/translate-c

Translate-c support initializer lists in macros
master
Andrew Kelley 2020-07-16 19:03:03 +00:00 committed by GitHub
commit cc3bceea3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 134 additions and 3 deletions

View File

@ -152,6 +152,11 @@ The relevant tests for this feature are:
same, and that the program exits cleanly. This kind of test coverage is preferred, when
possible, because it makes sure that the resulting Zig code is actually viable.
* `test/stage1/behavior/translate_c_macros.zig` - each test case consists of a Zig test
which checks that the relevant macros in `test/stage1/behavior/translate_c_macros.h`.
have the correct values. Macros have to be tested separately since they are expanded by
Clang in `run_translated_c` tests.
* `test/translate_c.zig` - each test case is C code, with a list of expected strings which
must be found in the resulting Zig code. This kind of test is more precise in what it
measures, but does not provide test coverage of whether the resulting Zig code is valid.

View File

@ -552,7 +552,7 @@ pub fn zeroes(comptime T: type) T {
if (@sizeOf(T) == 0) return T{};
if (comptime meta.containerLayout(T) == .Extern) {
var item: T = undefined;
@memset(@ptrCast([*]u8, &item), 0, @sizeOf(T));
set(u8, asBytes(&item), 0);
return item;
} else {
var structure: T = undefined;
@ -709,6 +709,14 @@ pub fn zeroInit(comptime T: type, init: anytype) T {
.Struct => |init_info| {
var value = std.mem.zeroes(T);
// typeInfo won't tell us if this is a tuple
if (comptime eql(u8, init_info.fields[0].name, "0")) {
inline for (init_info.fields) |field, i| {
@field(value, struct_info.fields[i].name) = @field(init, field.name);
}
return value;
}
inline for (init_info.fields) |field| {
if (!@hasField(T, field.name)) {
@compileError("Encountered an initializer for `" ++ field.name ++ "`, but it is not a field of " ++ @typeName(T));
@ -760,7 +768,7 @@ test "zeroInit" {
.a = 42,
});
testing.expectEqual(s, S{
testing.expectEqual(S{
.a = 42,
.b = null,
.c = .{
@ -768,7 +776,22 @@ test "zeroInit" {
},
.e = [3]u8{ 0, 0, 0 },
.f = -1,
});
}, s);
const Color = struct {
r: u8,
g: u8,
b: u8,
a: u8,
};
const c = zeroInit(Color, .{255, 255});
testing.expectEqual(Color{
.r = 255,
.g = 255,
.b = 0,
.a = 0,
}, c);
}
/// Compares two slices of numbers lexicographically. O(n).

View File

@ -6061,6 +6061,61 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
node = &call_node.base;
continue;
},
.LBrace => {
// must come immediately after `node`
_ = try appendToken(c, .Comma, ",");
const dot = try appendToken(c, .Period, ".");
_ = try appendToken(c, .LBrace, "{");
var init_vals = std.ArrayList(*ast.Node).init(c.gpa);
defer init_vals.deinit();
while (true) {
const val = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
try init_vals.append(val);
const next = it.next().?;
if (next.id == .Comma)
_ = try appendToken(c, .Comma, ",")
else if (next.id == .RBrace)
break
else {
const first_tok = it.list.at(0);
try failDecl(
c,
source_loc,
source[first_tok.start..first_tok.end],
"unable to translate C expr: expected ',' or '}}'",
.{},
);
return error.ParseError;
}
}
const tuple_node = try ast.Node.StructInitializerDot.alloc(c.arena, init_vals.items.len);
tuple_node.* = .{
.dot = dot,
.list_len = init_vals.items.len,
.rtoken = try appendToken(c, .RBrace, "}"),
};
mem.copy(*ast.Node, tuple_node.list(), init_vals.items);
//(@import("std").mem.zeroInit(T, .{x}))
const import_fn_call = try c.createBuiltinCall("@import", 1);
const std_node = try transCreateNodeStringLiteral(c, "\"std\"");
import_fn_call.params()[0] = std_node;
import_fn_call.rparen_token = try appendToken(c, .RParen, ")");
const inner_field_access = try transCreateNodeFieldAccess(c, &import_fn_call.base, "mem");
const outer_field_access = try transCreateNodeFieldAccess(c, inner_field_access, "zeroInit");
const zero_init_call = try c.createCall(outer_field_access, 2);
zero_init_call.params()[0] = node;
zero_init_call.params()[1] = &tuple_node.base;
zero_init_call.rtoken = try appendToken(c, .RParen, ")");
node = &zero_init_call.base;
continue;
},
.BangEqual => {
op_token = try appendToken(c, .BangEqual, "!=");
op_id = .BangEqual;

View File

@ -133,4 +133,5 @@ comptime {
_ = @import("behavior/while.zig");
_ = @import("behavior/widening.zig");
_ = @import("behavior/src.zig");
_ = @import("behavior/translate_c_macros.zig");
}

View File

@ -0,0 +1,9 @@
// initializer list expression
typedef struct Color {
unsigned char r;
unsigned char g;
unsigned char b;
unsigned char a;
} Color;
#define CLITERAL(type) (type)
#define LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray

View File

@ -0,0 +1,12 @@
const expect = @import("std").testing.expect;
const h = @cImport(@cInclude("stage1/behavior/translate_c_macros.h"));
test "initializer list expression" {
@import("std").testing.expectEqual(h.Color{
.r = 200,
.g = 200,
.b = 200,
.a = 255,
}, h.LIGHTGRAY);
}

View File

@ -537,6 +537,7 @@ pub fn addPkgTests(
these_tests.enable_qemu = is_qemu_enabled;
these_tests.enable_wasmtime = is_wasmtime_enabled;
these_tests.glibc_multi_install_dir = glibc_dir;
these_tests.addIncludeDir("test");
step.dependOn(&these_tests.step);
}

View File

@ -3,6 +3,31 @@ const std = @import("std");
const CrossTarget = std.zig.CrossTarget;
pub fn addCases(cases: *tests.TranslateCContext) void {
cases.add("initializer list macro",
\\typedef struct Color {
\\ unsigned char r;
\\ unsigned char g;
\\ unsigned char b;
\\ unsigned char a;
\\} Color;
\\#define CLITERAL(type) (type)
\\#define LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray
, &[_][]const u8{ // TODO properly translate this
\\pub const struct_Color = extern struct {
\\ r: u8,
\\ g: u8,
\\ b: u8,
\\ a: u8,
\\};
\\pub const Color = struct_Color;
,
\\pub inline fn CLITERAL(type_1: anytype) @TypeOf(type_1) {
\\ return type_1;
\\}
,
\\pub const LIGHTGRAY = @import("std").mem.zeroInit(CLITERAL(Color), .{ 200, 200, 200, 255 });
});
cases.add("complex switch",
\\int main() {
\\ int i = 2;