Skip to content

Commit

Permalink
Merge pull request #28 from Srekel/srekel-nested-structs
Browse files Browse the repository at this point in the history
Nested structs & unions
  • Loading branch information
lassade authored Mar 19, 2024
2 parents ba3ab48 + e7ef478 commit 49ead61
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 28 deletions.
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

0 comments on commit 49ead61

Please sign in to comment.