Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested structs & unions #28

Merged
merged 5 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,15 @@ pub fn build(b: *std.Build) void {
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/include/c005_inheritance.cpp" }, .flags = cflags });
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/include/c013_cpp_vector.cpp" }, .flags = cflags });
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/include/c022_cpp_string.cpp" }, .flags = cflags });
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/include/c023_cpp_nested_structs.cpp" }, .flags = cflags });
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/include/c024_cpp_bitfields.cpp" }, .flags = cflags });
// glue
//lib.addCSourceFile("./test_cases/c001_c_structs_glue.cpp", cflags);
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/c005_inheritance_glue.cpp" }, .flags = cflags });
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/c009_enum_flags_glue.cpp" }, .flags = cflags });
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/c011_index_this_glue.cpp" }, .flags = cflags });
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/c013_cpp_vector_glue.cpp" }, .flags = cflags });
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/c022_cpp_string_glue.cpp" }, .flags = cflags });
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/c023_cpp_nested_structs_glue.cpp" }, .flags = cflags });
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/c024_cpp_bitfields_glue.cpp" }, .flags = cflags });
test_cases.linkLibrary(lib);

Expand Down
124 changes: 97 additions & 27 deletions src/Transpiler.zig
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,15 @@ const NamespaceScope = struct {
}
};

// ClassInfo is now stored in class_info both with its name as the key,
// and with it's line and column.
const ClassInfo = struct {
is_polymorphic: bool,
name: []const u8,
};

allocator: Allocator,
arena: std.heap.ArenaAllocator,

buffer: std.ArrayList(u8),
out: std.ArrayList(u8).Writer,
Expand All @@ -240,6 +244,7 @@ header: []const u8 = "",
pub fn init(allocator: Allocator) Self {
return Self{
.allocator = allocator,
.arena = std.heap.ArenaAllocator.init(allocator),
.buffer = std.ArrayList(u8).init(allocator),
.out = undefined, // can't be initialized because Self will be moved
.c_buffer = std.ArrayList(u8).init(allocator),
Expand All @@ -253,6 +258,7 @@ pub fn init(allocator: Allocator) Self {
}

pub fn deinit(self: *Self) void {
self.arena.deinit();
self.buffer.deinit();
self.c_buffer.deinit();
self.namespace.deinit();
Expand Down Expand Up @@ -545,19 +551,18 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void {
self.nodes_visited += 1;

const tag = value.object.get("tagUsed").?.string;

// nested unamed strucs or unions are treated as implicit fields
var is_field = false;
const is_union = mem.eql(u8, tag, "union");

var is_generated_name = false;
var name: []const u8 = undefined;
if (value.object.get("name")) |v| {
name = v.string;
} else if (self.scope.tag == .class) {
// unamed struct, class or union inside a class definition should be treated as a field
is_field = true;
is_generated_name = true;
name = try fmt.allocPrint(self.allocator, "__field{d}", .{self.scope.fields});
name = try fmt.allocPrint(self.allocator, "__{s}{d}", .{
if (is_union) "Union" else "Struct",
self.scope.fields,
});
self.scope.fields += 1;
} else {
// referenced by someone else
Expand Down Expand Up @@ -594,11 +599,10 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void {

try self.writeDocs(inner);

if (is_field) {
try self.out.print("{s}: extern {s} {{\n", .{ name, tag });
} else {
try self.out.print("pub const {s} = extern struct {{\n", .{name});
}
try self.out.print("pub const {s} = extern {s} {{\n", .{
name,
if (is_union) "union" else "struct",
});

const v_bases = value.object.get("bases");

Expand All @@ -620,7 +624,22 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void {
try self.out.print(" vtable: *const anyopaque,\n\n", .{});
}

_ = try self.class_info.put(name, .{ .is_polymorphic = is_polymorphic });
_ = try self.class_info.put(name, .{
.is_polymorphic = is_polymorphic,
.name = name,
});

// Double-storing class info by the line and col so we can look up it's name using that.
// Need to store this 'globally' so using an arena
// For anonymous structs and similar constructs, the corresponding FieldDecl comes *after*
// the CXXRecordDecl.
if (location(value)) |loc| {
const line_col_key = try fmt.allocPrint(self.arena.allocator(), "{d}:{d}", .{ loc.line, loc.col });
_ = try self.class_info.put(line_col_key, .{
.is_polymorphic = is_polymorphic,
.name = try fmt.allocPrint(self.arena.allocator(), "{s}", .{name}),
});
}

if (v_bases != null) {
if (v_bases.?.array.items.len > 1) {
Expand Down Expand Up @@ -656,20 +675,34 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void {
var bitfield_struct_bits_remaining: u32 = 0;

for (inner.?.array.items) |*item| {
const kind = item.object.getPtr("kind").?.string;

// FieldDecls that are implicit shouldn't be skipped. This is things like
// anonymous structs.
const inner_is_field = mem.eql(u8, kind, "FieldDecl");
var is_implicit = false;
if (item.object.getPtr("isImplicit")) |implicit| {
if (implicit.bool) {
self.nodes_visited += nodeCount(item);
continue;
is_implicit = true;
if (!inner_is_field) {
self.nodes_visited += nodeCount(item);
continue;
}
}
}

const kind = item.object.getPtr("kind").?.string;
if (mem.eql(u8, kind, "FullComment")) {
// skip
} else if (mem.eql(u8, kind, "FieldDecl")) {
} else if (inner_is_field) {
self.nodes_visited += 1;

const field_name = item.object.getPtr("name").?.string;
const field_name = if (is_implicit) blk: {
const type_name = item.object.getPtr("type").?.object.get("qualType").?.string;
const field_type = if (mem.indexOf(u8, type_name, "union at") != null) "union_field" else if (mem.indexOf(u8, type_name, "struct at") != null) "struct_field" else "field";
const field_name_tmp = try fmt.allocPrint(self.arena.allocator(), "__{s}{d}", .{ field_type, self.scope.fields });
self.scope.fields += 1;
break :blk field_name_tmp;
} else item.object.getPtr("name").?.string;

if (item.object.getPtr("isInvalid")) |invalid| {
if (invalid.bool) {
Expand All @@ -683,8 +716,6 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void {
defer self.allocator.free(item_type);
const bitfield_signed = if (TypeToSignedLUT.has(item_type)) TypeToSignedLUT.get(item_type).? else false;

try self.writeDocs(item_inner);

var bitfield_field_bits: u32 = 0;

if (item.object.getPtr("isBitfield")) |is_bitfield| {
Expand Down Expand Up @@ -740,6 +771,8 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void {
bitfield_type_bytes_curr = null;
}

try self.writeDocs(item_inner);

const field_type = switch (bitfield_type_bytes_curr != null) {
true => blk: {
bitfield_struct_bits_remaining -= bitfield_field_bits;
Expand Down Expand Up @@ -784,8 +817,11 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void {
// nested enums
try self.visitEnumDecl(item);
} else if (mem.eql(u8, kind, "CXXRecordDecl")) {
// nested stucts, classes and unions
// nested stucts, classes and unions, mustn't be intermixed with fields.
const out = self.out;
self.out = functions.writer();
try self.visitCXXRecordDecl(item);
self.out = out;
} else if (mem.eql(u8, kind, "VarDecl")) {
const out = self.out;
self.out = functions.writer();
Expand Down Expand Up @@ -842,11 +878,7 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void {

try self.endNamespace(parent_namespace);

if (is_field) {
try self.out.print("}},\n\n", .{});
} else {
try self.out.print("}};\n\n", .{});
}
try self.out.print("}};\n\n", .{});
}

fn startBitfield(self: *Self, bitfield_group: u32, bitfield_type_bits: u32) !void {
Expand All @@ -864,7 +896,7 @@ fn finalizeBitfield(self: *Self, bits_remaining: u32) !void {
try self.out.print(" /// Padding added by c2z\n", .{});
try self.out.print(" _dummy_padding: u{d},\n", .{bits_remaining});
}
try self.out.print(" }},\n", .{});
try self.out.print(" }},\n\n", .{});
}

fn visitVarDecl(self: *Self, value: *const json.Value) !void {
Expand Down Expand Up @@ -2831,6 +2863,25 @@ inline fn typeQualifier(value: *const json.Value) ?[]const u8 {
return null;
}

inline fn location(value: *const json.Value) ?struct { line: i64, col: i64 } {
if (value.object.getPtr("loc")) |loc| {
if (loc.object.getPtr("spellingLoc")) |spelling_loc| {
const line = spelling_loc.object.get("line").?.integer;
const col = spelling_loc.object.get("col").?.integer;
return .{ .line = line, .col = col };
} else if (loc.object.getPtr("expansionLoc")) |expansion_loc| {
const line = expansion_loc.object.get("line").?.integer;
const col = expansion_loc.object.get("col").?.integer;
return .{ .line = line, .col = col };
}

const line = loc.object.get("line").?.integer;
const col = loc.object.get("col").?.integer;
return .{ .line = line, .col = col };
}
return null;
}

inline fn resolveEnumVariantName(base: []const u8, variant: []const u8) []const u8 {
return if (mem.startsWith(u8, variant, base)) variant[base.len..] else variant;
}
Expand Down Expand Up @@ -2910,6 +2961,11 @@ fn transpileType(self: *Self, tname: []const u8) ![]u8 {
ttname = ttname["class ".len..];
}

// remove union from C style definition
if (mem.startsWith(u8, ttname, "union ")) {
ttname = ttname["union ".len..];
}

const ch = ttname[ttname.len - 1];
if (ch == '*' or ch == '&') {
// note: avoid c-style pointers `[*c]` when dealing with references types
Expand Down Expand Up @@ -2941,6 +2997,20 @@ fn transpileType(self: *Self, tname: []const u8) ![]u8 {
const inner = try self.transpileType(raw_name);
defer self.allocator.free(inner);
return try fmt.allocPrint(self.allocator, "{s}{s}{s}", .{ ptr, constness, inner });
} else if (mem.indexOf(u8, ttname, "struct at ") != null or
mem.indexOf(u8, ttname, "union at ") != null) // or
// mem.indexOf(u8, ttname, "enum at ") != null)
{
// "qualType": "RootStruct::(anonymous union at bitfieldtest.h:4:5)"
// "qualType": "RootStruct::(anonymous struct at bitfieldtest.h:25:5)"
// "qualType": "struct (unnamed struct at header.h:4:5)"
var separator_index = mem.lastIndexOf(u8, ttname, ":").?;
const tmpname = ttname[0 .. separator_index - 1];
separator_index = mem.lastIndexOf(u8, tmpname, ":").?;
ttname = ttname[separator_index + 1 ..];
ttname = ttname[0 .. ttname.len - 1];
const class = self.class_info.get(ttname).?;
ttname = class.name;
} else if (mem.endsWith(u8, ttname, " *const")) {
// NOTE: This can probably be improved to handle more cases, or maybe combined with the
// above case.
Expand Down Expand Up @@ -2968,7 +3038,7 @@ fn transpileType(self: *Self, tname: []const u8) ![]u8 {

return try fmt.allocPrint(self.allocator, "?*const fn({s}) callconv(.C) {s} ", .{ args.items, tret });
} else {
log.err("unknow type `{s}`, falling back to `*anyopaque`", .{ttname});
log.err("unknown type `{s}`, falling back to `*anyopaque`", .{ttname});
ttname = "*anyopaque";
}
} else if (ch == '>') {
Expand Down
71 changes: 71 additions & 0 deletions test_cases/c023_cpp_nested_structs.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// auto generated by c2z
const std = @import("std");
//const cpp = @import("cpp");

pub const RootStruct = extern struct {
value_begin: c_int,
nested_struct_1a: __Struct0,
nested_struct_1b: __Struct0,
value_mid: c_int,
nested_struct_2b: NestedStruct2a,
nested_struct_3b: NestedStruct3a,
nested_struct_3c: NestedStruct3a,
__struct_field2: __Struct1,
value_end: c_int,

pub const __Struct0 = extern struct {
m1: f32,
};

pub const NestedStruct2a = extern struct {
m2: f32,
};

pub const NestedStruct3a = extern struct {
m3: f32,
};

/// Fully anonymous 4
pub const __Struct1 = extern struct {
m44: f32,
};
};

extern fn _1_test_sizeof_RootStruct_() c_int;
pub const test_sizeof_RootStruct = _1_test_sizeof_RootStruct_;

pub const RootUnion = extern struct {
value_begin: c_int,
nested_union_1a: __Union0,
nested_union_1b: __Union0,
value_mid: c_int,
nested_union_2b: NestedUnion2a,
nested_union_3b: NestedUnion3a,
nested_union_3c: NestedUnion3a,
__union_field2: __Union1,
value_end: c_int,

pub const __Union0 = extern union {
iii1: c_int,
fff1: f32,
};

pub const NestedUnion2a = extern union {
iii2: c_int,
fff2: f32,
};

pub const NestedUnion3a = extern union {
iii3: c_int,
fff3: f32,
};

/// Fully anonymous 4
pub const __Union1 = extern union {
iii4: c_int,
fff4: f32,
};
};

extern fn _1_test_sizeof_RootUnion_() c_int;
pub const test_sizeof_RootUnion = _1_test_sizeof_RootUnion_;
6 changes: 6 additions & 0 deletions test_cases/c023_cpp_nested_structs_glue.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// auto generated by c2z
#include <new>
#include "c023_cpp_nested_structs.h"

extern "C" int _1_test_sizeof_RootStruct_() { return ::test_sizeof_RootStruct(); }
extern "C" int _1_test_sizeof_RootUnion_() { return ::test_sizeof_RootUnion(); }
11 changes: 11 additions & 0 deletions test_cases/include/c023_cpp_nested_structs.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include "c023_cpp_nested_structs.h"

int test_sizeof_RootStruct()
{
return (int)sizeof(RootStruct);
}

int test_sizeof_RootUnion()
{
return (int)sizeof(RootUnion);
}
Loading
Loading