diff --git a/.gitignore b/.gitignore index acd2c4a..541e1b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ *.swp -zig-cache/ +zig-* headers/ diff --git a/README.md b/README.md index 431c412..4358683 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Here are the following supported language binding outputs: - [x] C Bindings -- [ ] Python Bindings +- [x] Python Bindings - [ ] Rust Bindings - [ ] Go Bindings - [ ] Nim Bindings @@ -28,7 +28,7 @@ const std = @import("std"); export fn print_msg(msg_ptr: ?[*]const u8, len: usize) bool { if (msg_ptr) |raw_msg| { const msg = raw_msg[0 .. len - 1]; - std.debug.warn("Msg is: {}", .{msg}); + std.debug.print("Msg is: {}", .{msg}); return true; } return false; diff --git a/build.zig b/build.zig index 6d6e851..6e0b60b 100644 --- a/build.zig +++ b/build.zig @@ -1,8 +1,7 @@ const Builder = @import("std").build.Builder; -const header_gen = @import("header_gen.zig"); const std = @import("std"); -const warn = std.debug.warn; +const warn = std.debug.print; // This build.zig is only used as an example of using header_gen @@ -11,18 +10,17 @@ pub fn build(b: *Builder) void { const mode = b.standardReleaseOptions(); - const exe = b.addExecutable("example", "src/main.zig"); + // HEADER GEN BUILD STEP + const exe = b.addExecutable("example", "src/example/exports.zig"); + exe.main_pkg_path = "src/"; exe.setTarget(target); exe.setBuildMode(mode); + exe.addPackagePath("header_gen", "src/header_gen.zig"); exe.install(); const run_cmd = exe.run(); run_cmd.step.dependOn(b.getInstallStep()); - const run_step = b.step("run", "Run the app"); + const run_step = b.step("headergen", "Run the app"); run_step.dependOn(&run_cmd.step); - - // --- Using header_gen - const gen = header_gen.HeaderGen("src/exports.zig").init(); - gen.exec(header_gen.C_Generator); } diff --git a/src/deps_graph.zig b/src/deps_graph.zig new file mode 100644 index 0000000..03fd0f7 --- /dev/null +++ b/src/deps_graph.zig @@ -0,0 +1,473 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const StringHashMap = std.StringHashMap; +const ArrayList = std.ArrayList; +const TailQueue = std.TailQueue; + +pub fn DepsGraph(comptime T: type) type { + return struct { + allocator: Allocator, + // All the pointers to symbols inside this struct are owned by this struct + // More specifically, they sould be freed when removed from the symbols + // hash map. And they should only be removed from there when there are + // no references to them in the dependants_of hash map + symbols: StringHashMap(*Symbol), + // *Symbol owned by self.symbols + dependants_of: StringHashMap(ArrayList(*Symbol)), + // ?*Symbol owned by self.symbols + current_symbol: ?*Symbol, + // Queue containing symbols ready to be emitted + // Can be updated each time after calling endSymbol() + emitted: TailQueue(EmittedSymbol), + + const Self = @This(); + + pub fn init(allocator: Allocator) Self { + return .{ + .allocator = allocator, + .symbols = StringHashMap(*Symbol).init(allocator), + .dependants_of = StringHashMap(ArrayList(*Symbol)).init(allocator), + .current_symbol = null, + .emitted = TailQueue(EmittedSymbol){}, + }; + } + + pub fn deinit(self: *Self) void { + var s_iter = self.symbols.iterator(); + while (s_iter.next()) |entry| { + // Here entry.value is a *Symbol, so we deinit the symbol + entry.value_ptr.*.deinit(self.allocator); + + // And free the pointer + self.allocator.destroy(entry.value_ptr.*); + } + + self.symbols.deinit(); + + var d_iter = self.dependants_of.iterator(); + while (d_iter.next()) |entry| { + // Here entry.value is an ArrayList(*Symbol), so we simply + // deinit the array list (since the pointers were freed already) + entry.value_ptr.*.deinit(); + } + + self.dependants_of.deinit(); + + while (self.emitted.popFirst()) |node| { + self.allocator.destroy(node); + } + + self.current_symbol = null; + } + + pub fn isBlocking(self: *Self, symbol_name: []const u8) bool { + // A symbol_name can be blocking if either: + // 1. There is no symbol declared with that name yet + // 2. There is a symbol, but it is blocked by some dependencies too + const symbol = self.symbols.get(symbol_name) orelse return true; + + // TODO Should a symbol be able to depend on itself? + // If so, what to do in that case? For now, it blocks itself + // if (symbol == self.current_symbol) return false; + + return symbol.hasDependenciesOfType(.Linear); + } + + pub const BeginSymbolError = error{ DuplicateSymbol, OutOfMemory }; + + pub fn beginSymbol(self: *Self, name: []const u8, payload: T) BeginSymbolError!void { + var result = try self.symbols.getOrPut(name); + + if (result.found_existing) { + return error.DuplicateSymbol; + } + + // Since the allocation can fail, we do not want to leave the state + // inconsistent (with a KV whose value is empty) + errdefer std.debug.assert(self.symbols.remove(name)); + + result.value_ptr.* = try self.allocator.create(Symbol); + + result.value_ptr.*.* = Symbol.init(self.allocator, name, payload); + + self.current_symbol = result.value_ptr.*; + } + + pub const AddDependencyError = error{ NoSymbol, OutOfMemory }; + + pub fn addDependency(self: *Self, dependency_name: []const u8) AddDependencyError!void { + // If the dependency is not blocking, then there's no need to add it + if (!self.isBlocking(dependency_name)) return; + + var current_symbol = self.current_symbol orelse return error.NoSymbol; + + // If a symbol depends on itself, whatever, not our business + if (std.mem.eql(u8, dependency_name, current_symbol.name)) return; + + var already_added: bool = false; + var is_circular: bool = false; + + // Checks if there are other symbols that depend on dependency_name + var result = try self.dependants_of.getOrPut(dependency_name); + + // Creates or retrieves the array list that contains what symbols + // depend on dependency_name. Also checks if this symbol is already there + if (result.found_existing) { + for (result.value_ptr.items) |symbol| { + if (symbol == current_symbol) { + already_added = true; + } + } + } else { + result.value_ptr.* = ArrayList(*Symbol).init(self.allocator); + } + + if (!already_added) { + try result.value_ptr.append(current_symbol); + + if (self.dependants_of.getEntry(current_symbol.name)) |dependants| { + for (dependants.value_ptr.items) |dep| { + if (std.mem.eql(u8, dep.name, dependency_name)) { + try dep.addDependency(.{ .Circular = current_symbol.name }); + + is_circular = true; + + break; + } + } + } + + if (is_circular) { + try current_symbol.addDependency(.{ .Circular = dependency_name }); + } else { + try current_symbol.addDependency(.{ .Linear = dependency_name }); + } + } + } + + pub const EndSymbolError = error{OutOfMemory}; + + pub fn createNode(comptime V: type, data: V, allocator: Allocator) !*TailQueue(V).Node { + var node = try allocator.create(TailQueue(V).Node); + node.* = .{ .data = data }; + return node; + } + + pub fn endSymbol(self: *Self) EndSymbolError!void { + var current_symbol = self.current_symbol orelse return; + + var unblock_queue = std.TailQueue(EmittedSymbol){}; + + if (!self.isBlocking(current_symbol.name)) { + const node = try createNode(EmittedSymbol, .{ + .symbol = current_symbol, + .partial = current_symbol.hasDependencies(), + }, self.allocator); + + unblock_queue.append(node); + } + + // All items in unblock_queue have already been unblocked, and so + // should be emitted. Also, any dependants of them should be checked + // if they themselves can be unblocked as well + while (unblock_queue.popFirst()) |symbol_node| { + self.emitted.append(symbol_node); + + const symbol = symbol_node.data.symbol; + + if (self.dependants_of.getEntry(symbol.name)) |kv| { + for (kv.value_ptr.items) |dependant| { + if (dependant.removeDependency(symbol.name)) |_| { + const unblock_dep = (!dependant.emitted and !dependant.hasDependenciesOfType(.Linear)) or !dependant.hasDependencies(); + + if (!unblock_dep) continue; + + dependant.emitted = true; + + const node = try createNode(EmittedSymbol, .{ + .symbol = dependant, + .partial = dependant.hasDependencies(), + }, self.allocator); + + unblock_queue.append(node); + } + } + } + } + + self.current_symbol = null; + } + + pub fn readEmitted(self: *Self) ?EmittedSymbol { + var symbol_node = self.emitted.popFirst() orelse return null; + + var symbol = symbol_node.data; + + self.allocator.destroy(symbol_node); + + return symbol; + } + + pub fn blockedIterator(self: *Self) BlockedSymbolsIterator { + return BlockedSymbolsIterator.init(self); + } + + const Dependency = union(enum) { + Linear: []const u8, + Circular: []const u8, + + pub fn getName(self: Dependency) []const u8 { + return switch (self) { + .Linear => |n| n, + .Circular => |n| n, + }; + } + + pub fn eql(self: Dependency, other: Dependency) bool { + switch (self) { + .Linear => |n| return other == .Linear and std.mem.eql(u8, other.Linear, n), + .Circular => |n| return other == .Circular and std.mem.eql(u8, other.Circular, n), + } + } + + pub fn eqlName(self: Dependency, other: Dependency) bool { + return std.mem.eql(u8, self.getName(), other.getName()); + } + }; + + const EmittedSymbol = struct { + symbol: *Symbol, + partial: bool, + }; + + const Symbol = struct { + // Not owned + name: []const u8, + // Slices not owned + dependencies: ArrayList(Dependency), + emitted: bool = false, + payload: T, + + pub fn init(allocator: Allocator, name: []const u8, payload: T) Symbol { + return .{ + .name = name, + .dependencies = ArrayList(Dependency).init(allocator), + .payload = payload, + }; + } + + pub fn deinit(self: *Symbol, allocator: Allocator) void { + _ = allocator; + self.dependencies.deinit(); + } + + pub fn addDependency(self: *Symbol, dependency: Dependency) !void { + for (self.dependencies.items) |*existing| { + if (dependency.eqlName(existing.*)) { + existing.* = dependency; + + return; + } + } + + try self.dependencies.append(dependency); + } + + pub fn removeDependency(self: *Symbol, dependency_name: []const u8) ?Dependency { + _ = dependency_name; + var maybe_dep_index: ?usize = null; + + for (self.dependencies.items) |dependency, i| { + if (dependency.eqlName(dependency)) { + maybe_dep_index = i; + break; + } + } + + if (maybe_dep_index) |dep_index| { + // Since dependencies are not stored in any particurarly + // important order, we can use swapRemove which is more + // efficient than orderedRemove + return self.dependencies.swapRemove(dep_index); + } + + return null; + } + + pub fn getDependency(self: *Symbol, dependency_name: []const u8) ?Dependency { + _ = dependency_name; + for (self.dependencies.items) |dependency| { + if (dependency.eqlName(dependency)) { + return dependency; + } + } + + return null; + } + + pub fn hasDependencies(self: *Symbol) bool { + return self.dependencies.items.len > 0; + } + + pub fn hasDependenciesOfType(self: *Symbol, tag: std.meta.TagType(Dependency)) bool { + for (self.dependencies.items) |dep| { + if (dep == tag) return true; + } + + return false; + } + }; + + pub const BlockedSymbolsIterator = struct { + graph: *Self, + hash_iter: StringHashMap(*Symbol).Iterator, + + pub fn init(graph: *Self) BlockedSymbolsIterator { + return .{ + .graph = graph, + .hash_iter = graph.symbols.iterator(), + }; + } + + pub fn next(self: *BlockedSymbolsIterator) ?*Symbol { + while (self.hash_iter.next()) |symbol| { + if (symbol.value_ptr.*.hasDependenciesOfType(.Linear)) { + return symbol.value_ptr.*; + } + } + + return null; + } + }; + }; +} + +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectEqualStrings = std.testing.expectEqualStrings; + +fn expectSymbol(emitted: ?DepsGraph(void).EmittedSymbol, expected_name: []const u8, expected_partial: bool) !void { + try expect(emitted != null); + try expectEqualStrings(expected_name, emitted.?.symbol.name); + try expectEqual(expected_partial, emitted.?.partial); +} + +test "Simple dependency graph with circular dependencies" { + const allocator = std.testing.allocator; + + var deps = DepsGraph(void).init(allocator); + + try deps.beginSymbol("SourceMap", {}); + try deps.addDependency("TextSpan"); + try deps.endSymbol(); + try expect(deps.readEmitted() == null); + + try deps.beginSymbol("TextSpan", {}); + try deps.addDependency("TextPosition"); + try deps.endSymbol(); + try expect(deps.readEmitted() == null); + + try deps.beginSymbol("TextPosition", {}); + try deps.addDependency("TextSpan"); + try deps.endSymbol(); + + try expect(deps.emitted.first != null); + if (deps.readEmitted()) |s| { + try expectEqualStrings(s.symbol.name, "TextPosition"); + try expectEqual(s.partial, true); + } + + try expect(deps.emitted.first != null); + if (deps.readEmitted()) |s| { + try expectEqualStrings(s.symbol.name, "TextSpan"); + try expectEqual(s.partial, false); + } + + try expect(deps.emitted.first != null); + if (deps.readEmitted()) |s| { + try expectEqualStrings(s.symbol.name, "SourceMap"); + try expectEqual(s.partial, false); + } + + try expect(deps.emitted.first != null); + if (deps.readEmitted()) |s| { + try expectEqualStrings(s.symbol.name, "TextPosition"); + try expectEqual(s.partial, false); + } + + try expect(deps.readEmitted() == null); + + deps.deinit(); +} + +test "Blocked symbols iterator" { + const allocator = std.testing.allocator; + + var deps = DepsGraph(void).init(allocator); + + try deps.beginSymbol("SourceMap", {}); + try deps.addDependency("TextSpan"); + try deps.endSymbol(); + try expect(deps.readEmitted() == null); + + try deps.beginSymbol("TextSpan", {}); + try deps.endSymbol(); + try expect(deps.emitted.first != null); + if (deps.readEmitted()) |s| { + try expectEqualStrings(s.symbol.name, "TextSpan"); + try expectEqual(s.partial, false); + } + try expect(deps.emitted.first != null); + if (deps.readEmitted()) |s| { + try expectEqualStrings(s.symbol.name, "SourceMap"); + try expectEqual(s.partial, false); + } + try expect(deps.readEmitted() == null); + + try deps.beginSymbol("TextPosition", {}); + try deps.addDependency("Cursor"); + try deps.endSymbol(); + try expect(deps.readEmitted() == null); + + var iter = deps.blockedIterator(); + var symbol = iter.next(); + + try expect(symbol != null); + try expectEqualStrings(symbol.?.name, "TextPosition"); + try expect(iter.next() == null); + + deps.deinit(); +} + +test "Three tier circular dependencies" { + const allocator = std.testing.allocator; + + var deps = DepsGraph(void).init(allocator); + + try deps.beginSymbol("LookMaAnEnum", {}); + try deps.endSymbol(); + + try deps.beginSymbol("WackType", {}); + try deps.addDependency("LameType"); + try deps.endSymbol(); + + try deps.beginSymbol("LameType", {}); + try deps.addDependency("WackType"); + try deps.addDependency("WhatsAUnion"); + try deps.endSymbol(); + + try deps.beginSymbol("WhatsAUnion", {}); + try deps.addDependency("LameType"); + try deps.endSymbol(); + + try expectSymbol(deps.readEmitted(), "LookMaAnEnum", false); + try expectSymbol(deps.readEmitted(), "WhatsAUnion", true); + try expectSymbol(deps.readEmitted(), "LameType", true); + try expectSymbol(deps.readEmitted(), "WackType", false); + try expectSymbol(deps.readEmitted(), "WhatsAUnion", false); + try expectSymbol(deps.readEmitted(), "LameType", false); + + try expect(deps.readEmitted() == null); + + deps.deinit(); +} diff --git a/src/example/exports.zig b/src/example/exports.zig new file mode 100644 index 0000000..72af2d1 --- /dev/null +++ b/src/example/exports.zig @@ -0,0 +1,43 @@ +const header_gen = @import("header_gen"); + +export fn thing(one: usize, two: *LameType, three: [*]u16) bool { + _ = three; + _ = two; + return one == 1; +} + +export fn break_point(v: [*]u8) callconv(.Naked) void { + _ = v; + @breakpoint(); +} + +const WackType = packed struct { + mr_field: *LameType, +}; + +const LameType = extern struct { + blah: WackType, + bleh: *WhatsAUnion, +}; +const WhatsAUnion = extern union { + a: *LameType, + b: u64, +}; + +const ThisWillBeVoid = struct { + a: u64, +}; + +const LookMaAnEnum = enum(c_int) { + one = 1, + three = 3, + four, + five = 5, +}; + +pub fn main () void { + comptime var gen = header_gen.HeaderGen(@This(), "lib").init(); + + gen.exec(header_gen.C_Generator); + gen.exec(header_gen.Ordered_Generator(header_gen.Python_Generator)); +} \ No newline at end of file diff --git a/src/example/main.zig b/src/example/main.zig new file mode 100644 index 0000000..7c1f53d --- /dev/null +++ b/src/example/main.zig @@ -0,0 +1,5 @@ +const std = @import("std"); + +pub fn main() anyerror!void { + std.debug.print("All your codebase are belong to us.\n", .{}); +} diff --git a/src/exports.zig b/src/exports.zig deleted file mode 100644 index 89755fd..0000000 --- a/src/exports.zig +++ /dev/null @@ -1,31 +0,0 @@ -export fn thing(one: usize, two: *LameType, three: [*]u16) bool { - return one == 1; -} - -export fn break_point(v: [*]ThisWillBeVoid) callconv(.Naked) void { - @breakpoint(); -} - -const LameType = extern struct { - blah: u64, -}; - -const WackType = packed struct { - mr_field: u8, -}; - -const WhatsAUnion = extern union { - a: LameType, - b: u64, -}; - -const ThisWillBeVoid = struct { - a: u64, -}; - -const LookMaAnEnum = extern enum { - one = 1, - three = 3, - four, - five = 5, -}; diff --git a/generators/c.zig b/src/generators/c.zig similarity index 86% rename from generators/c.zig rename to src/generators/c.zig index 6655e60..bdeb428 100644 --- a/generators/c.zig +++ b/src/generators/c.zig @@ -1,11 +1,11 @@ const std = @import("std"); const Dir = std.fs.Dir; -const FnMeta = std.builtin.TypeInfo.Fn; -const FnDecl = std.builtin.TypeInfo.Declaration.Data.FnDecl; -const StructMeta = std.builtin.TypeInfo.Struct; -const EnumMeta = std.builtin.TypeInfo.Enum; -const UnionMeta = std.builtin.TypeInfo.Union; -const warn = std.debug.warn; +const FnMeta = std.builtin.Type.Fn; +const FnDecl = std.builtin.Type.Declaration.Data.FnDecl; +const StructMeta = std.builtin.Type.Struct; +const EnumMeta = std.builtin.Type.Enum; +const UnionMeta = std.builtin.Type.Union; +const warn = std.debug.print; pub const C_Generator = struct { file: std.fs.File, @@ -13,27 +13,30 @@ pub const C_Generator = struct { const Self = @This(); pub fn init(comptime src_file: []const u8, dst_dir: *Dir) Self { - comptime const filebaseext = std.fs.path.basename(src_file); - comptime const filebase = filebaseext[0 .. filebaseext.len - 4]; - var file = dst_dir.createFile(filebase ++ ".h", .{}) catch + var file = dst_dir.createFile(comptime filebase(src_file) ++ ".h", .{}) catch @panic("Failed to create header file for source: " ++ src_file); var res = Self{ .file = file }; // write the header's header, lol - res.write("#ifndef _" ++ filebase ++ "_H\n\n#define _" ++ filebase ++ "_H\n"); + res.write("#ifndef _" ++ comptime filebase(src_file) ++ "_H\n\n#define _" ++ filebase(src_file) ++ "_H\n"); res.write("#include \n#include \n#include \n\n"); return res; } + fn filebase(src_file: []const u8) []const u8 { + const filebaseext = std.fs.path.basename(src_file); + return filebaseext[0 .. filebaseext.len - 4]; + } + pub fn deinit(self: *Self) void { self.write("\n#endif\n"); self.file.close(); } - pub fn gen_func(self: *Self, comptime name: []const u8, comptime func: FnDecl, comptime meta: FnMeta) void { + pub fn gen_func(self: *Self, comptime name: []const u8, comptime meta: FnMeta) void { switch (meta.calling_convention) { .Naked => self.write("__attribute__((naked)) "), .Stdcall => self.write("__attribute__((stdcall)) "), @@ -42,14 +45,14 @@ pub const C_Generator = struct { else => {}, } - self.writeType(func.return_type); + self.writeType(meta.return_type.?); self.write(" " ++ name ++ "("); - inline for (meta.args) |arg, i| { - self.writeType(arg.arg_type.?); + inline for (meta.params) |arg, i| { + self.writeType(arg.type.?); //TODO: Figure out how to get arg names; for now just do arg0..argN _ = self.file.writer().print(" arg{}", .{i}) catch unreachable; - if (i != meta.args.len - 1) + if (i != meta.params.len - 1) self.write(", "); } diff --git a/src/generators/ordered.zig b/src/generators/ordered.zig new file mode 100644 index 0000000..c37f30a --- /dev/null +++ b/src/generators/ordered.zig @@ -0,0 +1,185 @@ +const std = @import("std"); +const StringHashMap = std.StringHashMap; +const Allocator = std.mem.Allocator; +const FnMeta = std.builtin.Type.Fn; +const FnDecl = std.builtin.Type.Declaration.Data.FnDecl; +const StructMeta = std.builtin.Type.Struct; +const EnumMeta = std.builtin.Type.Enum; +const UnionMeta = std.builtin.Type.Union; +const Dir = std.fs.Dir; +const DepsGraph = @import("../deps_graph.zig").DepsGraph; +const rt = @import("../runtime.zig"); + +const SymbolDeclaration = union(enum) { + Struct: rt.TypeInfo.Struct, + Union: rt.TypeInfo.Union, + Enum: rt.TypeInfo.Enum, + Fn: rt.TypeInfo.Fn, + + pub fn deinit(self: SymbolDeclaration, allocator: Allocator) void { + switch (self) { + .Struct => |s| s.deinit(allocator), + .Union => |u| u.deinit(allocator), + .Enum => |e| e.deinit(allocator), + .Fn => |f| f.deinit(allocator), + } + } +}; + +fn isSymbolDependency(comptime symbol_type: type) bool { + const info = @typeInfo(symbol_type); + + return switch (info) { + .Struct, .Union, .Enum => true, + .Pointer => |p| isSymbolDependency(p.child), + .Array => |a| isSymbolDependency(a.child), + else => false, + }; +} + +fn getTypeName(comptime T: type) []const u8 { + const type_info = @typeInfo(T); + + return switch (type_info) { + .Pointer => |p| getTypeName(p.child), + .Array => |p| getTypeName(p.child), + else => @typeName(T), + }; +} + +pub const SymbolPhase = enum { + Signature, Body, Full +}; + +pub fn Ordered_Generator(comptime Generator: type) type { + return struct { + inner_gen: Generator, + allocator: Allocator, + symbols: DepsGraph(SymbolDeclaration), + emitted_phase: StringHashMap(SymbolPhase), + + const Self = @This(); + + pub fn init(comptime src_file: []const u8, dst_dir: *Dir) Self { + var allocator = std.heap.page_allocator; + + return Self{ + .inner_gen = Generator.init(src_file, dst_dir), + .allocator = allocator, + .symbols = DepsGraph(SymbolDeclaration).init(allocator), + .emitted_phase = StringHashMap(SymbolPhase).init(allocator), + }; + } + + pub fn deinit(self: *Self) void { + self.flush(); + + self.symbols.deinit(); + + self.inner_gen.deinit(); + + self.emitted_phase.deinit(); + } + + fn getNextPhaseFor(self: *Self, symbol_name: []const u8, partial: bool) !?SymbolPhase { + var result = try self.emitted_phase.getOrPut(symbol_name); + + if (!result.found_existing) { + result.value_ptr.* = if (partial) .Signature else .Full; + + return result.value_ptr.*; + } else if (result.value_ptr.* == .Signature) { + if (partial) { + return null; + } else { + result.value_ptr.* = .Full; + + return .Body; + } + } + + return null; + } + + fn flush(self: *Self) void { + while (self.symbols.readEmitted()) |emitted| { + const partial = if (emitted.symbol.payload == .Fn) false else emitted.partial; + _ = partial; + + var phase = self.getNextPhaseFor(emitted.symbol.name, emitted.partial) catch unreachable orelse continue; + + switch (emitted.symbol.payload) { + .Struct => |meta| self.inner_gen.gen_struct(emitted.symbol.name, meta, phase), + .Union => |meta| self.inner_gen.gen_union(emitted.symbol.name, meta, phase), + .Enum => |meta| self.inner_gen.gen_enum(emitted.symbol.name, meta, phase), + .Fn => |meta| self.inner_gen.gen_func(emitted.symbol.name, meta), + } + } + } + + pub fn gen_func(self: *Self, comptime name: []const u8, comptime meta: FnMeta) void { + const decl: SymbolDeclaration = SymbolDeclaration{ + .Fn = rt.TypeInfo.Fn.init(meta), + }; + + self.symbols.beginSymbol(name, decl) catch |err| @panic(@errorName(err)); + inline for (meta.params) |f| { + if (f.arg_type != null and comptime isSymbolDependency(f.arg_type.?)) { + self.symbols.addDependency(getTypeName(f.arg_type.?)) catch |err| @panic(@errorName(err)); + } + } + if (meta.return_type) |t| { + if (comptime isSymbolDependency(t)) { + self.symbols.addDependency(getTypeName(t)) catch |err| @panic(@errorName(err)); + } + } + self.symbols.endSymbol() catch |err| @panic(@errorName(err)); + + self.flush(); + } + + pub fn gen_struct(self: *Self, comptime name: []const u8, comptime meta: StructMeta) void { + const decl: SymbolDeclaration = SymbolDeclaration{ + .Struct = rt.TypeInfo.Struct.init(meta, name), + }; + + self.symbols.beginSymbol(name, decl) catch |err| @panic(@errorName(err)); + inline for (meta.fields) |f| { + if (comptime isSymbolDependency(f.field_type)) { + self.symbols.addDependency(getTypeName(f.field_type)) catch |err| @panic(@errorName(err)); + } + } + self.symbols.endSymbol() catch |err| @panic(@errorName(err)); + + self.flush(); + } + + pub fn gen_enum(self: *Self, comptime name: []const u8, comptime meta: EnumMeta) void { + const decl: SymbolDeclaration = SymbolDeclaration{ + .Enum = rt.TypeInfo.Enum.init(meta, name), + }; + + self.symbols.beginSymbol(name, decl) catch |err| @panic(@errorName(err)); + // Enums have no type dependencies I think, yay + self.symbols.endSymbol() catch |err| @panic(@errorName(err)); + + self.flush(); + } + + pub fn gen_union(self: *Self, comptime name: []const u8, comptime meta: UnionMeta) void { + const decl: SymbolDeclaration = SymbolDeclaration{ + .Union = rt.TypeInfo.Union.init(meta, name), + }; + + self.symbols.beginSymbol(name, decl) catch |err| @panic(@errorName(err)); + inline for (meta.fields) |f| { + if (comptime isSymbolDependency(f.field_type)) { + self.symbols.addDependency(getTypeName(f.field_type)) catch |err| @panic(@errorName(err)); + } + } + self.symbols.endSymbol() catch |err| @panic(@errorName(err)); + + self.flush(); + } + }; +} diff --git a/src/generators/python.zig b/src/generators/python.zig new file mode 100644 index 0000000..667f12a --- /dev/null +++ b/src/generators/python.zig @@ -0,0 +1,231 @@ +const std = @import("std"); +const Dir = std.fs.Dir; +const warn = std.debug.print; +const rt = @import("../runtime.zig"); +const FnDecl = rt.TypeInfo.Declaration.Data.FnDecl; +const FnMeta = rt.TypeInfo.Fn; +const StructMeta = rt.TypeInfo.Struct; +const EnumMeta = rt.TypeInfo.Enum; +const UnionMeta = rt.TypeInfo.Union; +const SymbolPhase = @import("ordered.zig").SymbolPhase; + +pub const Python_Generator = struct { + pub const symbols_order: bool = false; + + file: std.fs.File, + + const Self = @This(); + + pub fn init(comptime src_file: []const u8, dst_dir: *Dir) Self { + var file = dst_dir.createFile(comptime filebase(src_file) ++ ".py", .{}) catch + @panic("Failed to create header file for source: " ++ src_file); + + var res = Self{ .file = file }; + + res.write( + \\import ctypes + \\import enum + \\ + ); + + res.write("lib = ctypes.cdll.LoadLibrary(\"" ++ comptime filebase(src_file) ++ ".dll\")\n\n"); + + return res; + } + + fn filebase(src_file: []const u8) []const u8 { + const filebaseext = std.fs.path.basename(src_file); + return filebaseext[0 .. filebaseext.len - 4]; + } + + pub fn deinit(self: *Self) void { + self.file.close(); + } + + pub fn gen_func(self: *Self, name: []const u8, meta: FnMeta) void { + self.print("lib.{s}.argtypes = [", .{name}); + + for (meta.params) |arg, i| { + if (arg.type) |t| { + self.writeType(t.*); + } else { + self.write("None"); + } + + if (i != meta.params.len - 1) { + self.write(", "); + } + } + + self.write("]\n"); + + self.print("lib.{s}.restype = ", .{name}); + if (meta.return_type) |return_type| { + self.writeType(return_type.*); + } else { + self.write("None"); + } + self.write("\n\n"); + } + + pub fn _gen_fields(self: *Self, name: []const u8, fields: anytype, phase: SymbolPhase) void { + const prefix = "\t "; + + if (phase == .Body) { + self.print("{s}._fields_ = [", .{name}); + } else { + self.write("\t_fields_ = ["); + } + + for (fields) |field, i| { + if (i > 0) { + self.write(prefix); + } + + self.print("(\"{s}\", ", .{field.name}); + + self.writeType(field.field_type.*); + + self.write(")"); + + if (i != fields.len - 1) { + self.write(",\n"); + } + } + + self.write("]\n"); + } + + pub fn gen_struct(self: *Self, name: []const u8, meta: StructMeta, phase: SymbolPhase) void { + if (phase != .Body) { + self.print("class {s}(ctypes.Structure):\n", .{name}); + + if (meta.layout == .Packed) { + self.write("\t_pack_ = 1\n"); + } + } + + if (phase != .Signature) { + self._gen_fields(name, meta.fields, phase); + } else if (meta.layout != .Packed) { + self.write("\tpass\n"); + } + + self.write("\n"); + } + + pub fn gen_enum(self: *Self, name: []const u8, meta: EnumMeta, phase: SymbolPhase) void { + _ = phase; + self.print("class {s}(enum.IntEnum):\n", .{name}); + + for (meta.fields) |field| { + self.write("\t"); + self.writeScreamingSnakeCase(field.name); + self.print(" = {}\n", .{field.value}); + } + + if (meta.fields.len == 0) { + self.write("\tpass"); + } + + self.write("\n"); + } + + pub fn gen_union(self: *Self, name: []const u8, meta: UnionMeta, phase: SymbolPhase) void { + if (phase != .Body) { + self.print("class {s}(ctypes.Union):\n", .{name}); + } + + if (phase != .Signature) { + self._gen_fields(name, meta.fields, phase); + } else { + self.write("\tpass\n"); + } + + self.write("\n"); + } + + fn writeType(self: *Self, meta: rt.TypeInfo) void { + switch (meta) { + .Void => self.write("None"), + .Bool => self.write("ctypes.c_bool"), + // .usize => self.writeCtype("c_usize"), // TODO + // .isize => self.writeCtype("c_isize"), // TODO + .Int => |i| { + switch (i.signedness == .signed) { + true => self.print("ctypes.c_int{}", .{i.bits}), + false => self.print("ctypes.c_uint{}", .{i.bits}), + } + }, + .Float => |f| { + switch (f.bits) { + 32 => self.write("c_float"), + 64 => self.write("c_double"), + 128 => self.write("c_longdouble"), + else => self.print("ctypes.c_f{}", .{f.bits}), + } + }, + .Struct => |s| self.write(s.name orelse "__unknown__"), + .Union => |s| self.write(s.name orelse "__unknown__"), + .Enum => |s| self.write(s.name orelse "__unknown__"), + .Pointer => |p| { + const childmeta = p.child.*; + self.writeCtype("POINTER("); + if (childmeta == .Struct and childmeta.Struct.layout != .Extern) { + self.writeCtype("c_size_t"); + } else { + self.writeType(childmeta); + } + self.write(")"); + }, + .Optional => self.writeType(meta.Optional.child.*), + .Array => |a| { + self.writeType(a.child.*); + self.print(" * {}", .{a.len}); + }, + else => self.write(@tagName(meta)), // TODO!!!!! + } + } + + fn writeScreamingSnakeCase(self: *Self, str: []const u8) void { + var new_word: bool = false; + var was_lower: bool = false; + var is_upper: bool = undefined; + + for (str) |char, i| { + is_upper = std.ascii.isUpper(char); + + if (char == '_' and i > 0) { + new_word = true; + continue; + } + + if (new_word == true or (is_upper and was_lower)) { + new_word = false; + was_lower = false; + + self.writeChar('_'); + } else { + was_lower = !is_upper; + } + + self.writeChar(std.ascii.toUpper(char)); + } + } + + fn writeCtype(self: *Self, comptime str: []const u8) void { + self.write("ctypes." ++ str); + } + + fn writeChar(self: *Self, char: u8) void { + self.write(&[1]u8{char}); + } + + fn print(self: *Self, comptime fmt: []const u8, args: anytype) void { + self.file.writer().print(fmt, args) catch unreachable; + } + + fn write(self: *Self, str: []const u8) void { + _ = self.file.writeAll(str) catch unreachable; + } +}; diff --git a/header_gen.zig b/src/header_gen.zig similarity index 70% rename from header_gen.zig rename to src/header_gen.zig index 65bee6c..c3f9fb4 100644 --- a/header_gen.zig +++ b/src/header_gen.zig @@ -1,11 +1,13 @@ const std = @import("std"); const builtin = std.builtin; -const TypeInfo = builtin.TypeInfo; +const TypeInfo = builtin.Type; const Declaration = TypeInfo.Declaration; -const warn = std.debug.warn; +const warn = std.debug.print; // Provided generators pub const C_Generator = @import("generators/c.zig").C_Generator; +pub const Python_Generator = @import("generators/python.zig").Python_Generator; +pub const Ordered_Generator = @import("generators/ordered.zig").Ordered_Generator; const GeneratorInterface = struct { fn init() void {} @@ -16,6 +18,22 @@ const GeneratorInterface = struct { fn gen_union() void {} }; +fn includeSymbol(comptime decl: Declaration) bool { + if (decl.data == .Type) { + const T = decl.data.Type; + const info = @typeInfo(T); + + return switch (info) { + .Struct => |s| s.layout == .Extern or s.layout == .Packed, + .Union => |u| u.layout == .Extern, + .Enum => |e| e.layout == .Extern, + else => false, + }; + } + + return false; +} + fn validateGenerator(comptime Generator: type) void { comptime { const interface = @typeInfo(GeneratorInterface).Struct.decls; @@ -31,15 +49,12 @@ fn validateGenerator(comptime Generator: type) void { } } -pub fn HeaderGen(comptime fname: []const u8) type { - comptime var all_decls: []const Declaration = undefined; - comptime { - const import = @typeInfo(@import(fname)); - all_decls = import.Struct.decls; - } +pub fn HeaderGen(comptime S: type, comptime libname: []const u8) type { + comptime var all_decls: []const Declaration = @typeInfo(S).Struct.decls; + return struct { decls: @TypeOf(all_decls) = all_decls, - source_file: []const u8 = fname, + source_file: []const u8 = libname ++ ".zig", const Self = @This(); @@ -65,8 +80,8 @@ pub fn HeaderGen(comptime fname: []const u8) type { // iterate exported enums // do this first in case target lang needs enums defined before use inline for (self.decls) |decl| { - if (decl.data == .Type) { - const T = decl.data.Type; + if (@typeInfo(S) == .Type) { + const T = S; const info = @typeInfo(T); if (info == .Enum) { const layout = info.Enum.layout; @@ -79,8 +94,8 @@ pub fn HeaderGen(comptime fname: []const u8) type { // iterate exported structs inline for (self.decls) |decl| { - if (decl.data == .Type) { - const T = decl.data.Type; + if (@typeInfo(S) == .Type) { + const T = S; const info = @typeInfo(T); if (info == .Struct) { const layout = info.Struct.layout; @@ -92,7 +107,7 @@ pub fn HeaderGen(comptime fname: []const u8) type { } inline for (self.decls) |decl| { - if (decl.data == .Type) { + if (@typeInfo(S) == .Type) { const T = decl.data.Type; const info = @typeInfo(T); if (info == .Union) { @@ -106,12 +121,18 @@ pub fn HeaderGen(comptime fname: []const u8) type { // iterate exported fns inline for (self.decls) |decl| { - if (decl.data == .Fn) { + if (@typeInfo(S) == .Fn) { const func = decl.data.Fn; if (func.is_export) { //TODO: Look into parsing file for argument names const fn_meta = @typeInfo(func.fn_type).Fn; - gen.gen_func(decl.name, func, fn_meta); + gen.gen_func(decl.name, fn_meta); + } + } else if (@typeInfo(S) == .Type) { + const fn_meta = @typeInfo(decl.data.Type); + + if (fn_meta == .Fn) { + gen.gen_func(decl.name, fn_meta.Fn); } } } diff --git a/src/main.zig b/src/main.zig deleted file mode 100644 index c6a70af..0000000 --- a/src/main.zig +++ /dev/null @@ -1,5 +0,0 @@ -const std = @import("std"); - -pub fn main() anyerror!void { - std.debug.warn("All your codebase are belong to us.\n", .{}); -} diff --git a/src/runtime.zig b/src/runtime.zig new file mode 100644 index 0000000..4be9056 --- /dev/null +++ b/src/runtime.zig @@ -0,0 +1,979 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +pub const TypeId = std.builtin.TypeId(TypeInfo); + +const TypeInfoSingleton = struct { + resolved: bool = false, + info: TypeInfo = .{ .Void = {} }, +}; + +pub const TypeInfo = union(enum) { + Type: void, + Void: void, + Bool: void, + NoReturn: void, + Int: Int, + Float: Float, + Pointer: Pointer, + Array: Array, + Struct: Struct, + ComptimeFloat: void, + ComptimeInt: void, + Undefined: void, + Null: void, + Optional: Optional, + ErrorUnion: ErrorUnion, + ErrorSet: ErrorSet, + Enum: Enum, + Union: Union, + Fn: Fn, + BoundFn: Fn, + Opaque: void, // TODO Opaque + Frame: Frame, + AnyFrame: AnyFrame, + Vector: Vector, + EnumLiteral: void, + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const Int = struct { + signedness: Signedness, + bits: i32, + + pub fn init(comptime m: std.builtin.Type.Int) Int { + return comptime .{ + .signedness = @intToEnum(Signedness, @enumToInt(m.signedness)), + .bits = m.bits, + }; + } + }; + comptime { + validateSymbolInSync(Int, std.builtin.Type.Int, .{}); + } + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const Float = struct { + bits: i32, + + pub fn init(comptime m: std.builtin.Type.Float) Float { + return comptime .{ + .bits = m.bits, + }; + } + }; + comptime { + validateSymbolInSync(Float, std.builtin.Type.Float, .{}); + } + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const Pointer = struct { + size: Size, + is_const: bool, + is_volatile: bool, + alignment: i32, + address_space: std.builtin.AddressSpace, + child: *const TypeInfo, + is_allowzero: bool, + /// This field is an optional type. + /// The type of the sentinel is the element type of the pointer, which is + /// the value of the `child` field in this struct. However there is no way + /// to refer to that type here, so we use `var`. + // sentinel: anytype, + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const Size = enum { + One, + Many, + Slice, + C, + }; + + pub fn init(comptime m: std.builtin.Type.Pointer) Pointer { + return comptime .{ + .size = @intToEnum(TypeInfo.Pointer.Size, @enumToInt(m.size)), + .is_const = m.is_const, + .is_volatile = m.is_volatile, + .alignment = m.alignment, + .child = &TypeInfo.init(m.child), + .is_allowzero = m.is_allowzero, + }; + } + + pub fn deinit(self: *const Pointer, allocator: Allocator) void { + self.child.deinit(allocator); + + allocator.destroy(self.child); + } + }; + comptime { + validateSymbolInSync(Pointer, std.builtin.Type.Pointer, .{ + .ignore_fields = .{"sentinel"}, + }); + } + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const Array = struct { + len: i32, + child: *const TypeInfo, + /// This field is an optional type. + /// The type of the sentinel is the element type of the array, which is + /// the value of the `child` field in this struct. However there is no way + /// to refer to that type here, so we use `var`. + // sentinel: anytype, + pub fn init(comptime m: std.builtin.Type.Array) Array { + return comptime .{ + .len = m.len, + .child = &TypeInfo.init(m.child), + }; + } + + pub fn deinit(self: *const Array, allocator: Allocator) void { + self.child.deinit(allocator); + + allocator.destroy(self.child); + } + }; + comptime { + validateSymbolInSync(Array, std.builtin.Type.Array, .{ + .ignore_fields = .{"sentinel"}, + }); + } + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const ContainerLayout = enum { + Auto, + Extern, + Packed, + }; + comptime { + validateSymbolInSync(ContainerLayout, std.builtin.Type.ContainerLayout, .{}); + } + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const StructField = struct { + name: []const u8, + field_type: *const TypeInfo, + type: ?*const TypeInfo, + default_value: ?*const anyopaque, + is_comptime: bool, + alignment: i32, + + pub fn init(comptime f: std.builtin.Type.StructField) StructField { + return comptime .{ + .name = f.name, + .field_type = &TypeInfo.init(f.field_type), + .is_comptime = f.is_comptime, + .alignment = f.alignment, + }; + } + + pub fn deinit(self: *const StructField, allocator: Allocator) void { + allocator.free(self.name); + + self.field_type.deinit(allocator); + + allocator.destroy(self.field_type); + } + }; + comptime { + validateSymbolInSync(StructField, std.builtin.Type.StructField, .{ + .ignore_fields = .{"default_value"}, + }); + } + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const Struct = struct { + // Additional Field + name: ?[]const u8, + + layout: ContainerLayout, + backing_integer: ?*const TypeInfo = null, + fields: []const StructField, + decls: []const Declaration, + is_tuple: bool, + + pub fn init(comptime m: std.builtin.Type.Struct, comptime name: []const u8) Struct { + return comptime .{ + .name = name, + .layout = @intToEnum(TypeInfo.ContainerLayout, @enumToInt(m.layout)), + .fields = fields: { + comptime var arr: [m.fields.len]StructField = undefined; + + inline for (m.fields) |f, i| { + arr[i] = StructField.init(f); + } + + break :fields &arr; + }, + .decls = decls: { + comptime var arr: [m.decls.len]Declaration = undefined; + + inline for (m.decls) |f, i| { + arr[i] = Declaration.init(f); + } + + break :decls &arr; + }, + .is_tuple = m.is_tuple, + }; + } + comptime { + validateSymbolInSync(Struct, std.builtin.Type.Struct, .{}); + } + + pub fn deinit(self: *const Struct, allocator: Allocator) void { + for (self.fields) |f| f.deinit(allocator); + for (self.decls) |f| f.deinit(allocator); + + allocator.free(self.fields); + allocator.free(self.decls); + } + }; + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const Optional = struct { + child: *const TypeInfo, + + pub fn init(comptime m: std.builtin.Type.Optional) Optional { + return comptime .{ + .child = &TypeInfo.init(m.child), + }; + } + + pub fn deinit(self: *const Optional, allocator: Allocator) void { + self.child.deinit(allocator); + + allocator.destroy(self.child); + } + }; + comptime { + validateSymbolInSync(Optional, std.builtin.Type.Optional, .{}); + } + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const ErrorUnion = struct { + error_set: *const TypeInfo, + payload: *const TypeInfo, + + pub fn init(comptime m: std.builtin.Type.ErrorUnion) ErrorUnion { + return comptime .{ + .error_set = &TypeInfo.init(m.error_set), + .payload = &TypeInfo.init(m.payload), + }; + } + + pub fn deinit(self: *const ErrorUnion, allocator: Allocator) void { + self.error_set.deinit(allocator); + allocator.destroy(self.error_set); + + self.payload.deinit(allocator); + allocator.destroy(self.payload); + } + }; + comptime { + validateSymbolInSync(ErrorUnion, std.builtin.Type.ErrorUnion, .{}); + } + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const Error = struct { + name: []const u8, + + pub fn deinit(self: *const Error, allocator: Allocator) void { + allocator.free(self.name); + } + }; + comptime { + validateSymbolInSync(Error, std.builtin.Type.Error, .{}); + } + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const ErrorSet = ?[]const Error; + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const EnumField = struct { + name: []const u8, + value: i32, + + pub fn init(comptime f: std.builtin.Type.EnumField) EnumField { + return comptime .{ + .name = f.name, + .value = f.value, + }; + } + + pub fn deinit(self: *const EnumField, allocator: Allocator) void { + allocator.free(self.name); + } + }; + comptime { + validateSymbolInSync(EnumField, std.builtin.Type.EnumField, .{}); + } + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const Enum = struct { + // Additional Field + name: ?[]const u8, + + layout: ContainerLayout, + tag_type: *const TypeInfo, + fields: []const EnumField, + decls: []const Declaration, + is_exhaustive: bool, + + pub fn init(comptime m: std.builtin.Type.Enum, comptime name: []const u8) Enum { + return comptime .{ + .name = name, + .layout = @intToEnum(TypeInfo.ContainerLayout, @enumToInt(m.layout)), + .tag_type = &TypeInfo.init(m.tag_type), + .fields = fields: { + comptime var arr: [m.fields.len]EnumField = undefined; + + inline for (m.fields) |f, i| { + arr[i] = EnumField.init(f); + } + + break :fields &arr; + }, + .decls = decls: { + comptime var arr: [m.decls.len]Declaration = undefined; + + inline for (m.decls) |f, i| { + arr[i] = Declaration.init(f); + } + + break :decls &arr; + }, + .is_exhaustive = m.is_exhaustive, + }; + } + + pub fn deinit(self: *const Enum, allocator: Allocator) void { + for (self.fields) |f| f.deinit(allocator); + for (self.decls) |f| f.deinit(allocator); + + allocator.free(self.fields); + allocator.free(self.decls); + + self.tag_type.deinit(allocator); + allocator.destroy(self.tag_type); + } + }; + comptime { + validateSymbolInSync(Enum, std.builtin.Type.Enum, .{}); + } + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const UnionField = struct { + // Additional Field + name: []const u8, + type: ?*const TypeInfo, + field_type: *const TypeInfo, + alignment: i32, + + pub fn init(comptime f: std.builtin.Type.UnionField) UnionField { + return comptime .{ + .name = f.name, + .field_type = &TypeInfo.init(f.field_type), + .alignment = f.alignment, + }; + } + + pub fn deinit(self: *const UnionField, allocator: Allocator) void { + allocator.free(self.name); + + self.field_type.deinit(allocator); + + allocator.destroy(self.field_type); + + if (self.enum_field) |ef| { + ef.deinit(allocator); + } + } + }; + comptime { + validateSymbolInSync(UnionField, std.builtin.Type.UnionField, .{}); + } + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const Union = struct { + // Additional Field + name: ?[]const u8, + + layout: ContainerLayout, + tag_type: ?*const TypeInfo, + fields: []const UnionField, + decls: []const Declaration, + + pub fn init(comptime m: std.builtin.Type.Union, comptime name: []const u8) Union { + return comptime .{ + .name = name, + .layout = @intToEnum(TypeInfo.ContainerLayout, @enumToInt(m.layout)), + .tag_type = if (m.tag_type) |t| &TypeInfo.init(t) else null, + .fields = fields: { + comptime var arr: [m.fields.len]UnionField = undefined; + + inline for (m.fields) |f, i| { + arr[i] = UnionField.init(f); + } + + break :fields &arr; + }, + .decls = decls: { + comptime var arr: [m.decls.len]Declaration = undefined; + + inline for (m.decls) |f, i| { + arr[i] = Declaration.init(f); + } + + break :decls &arr; + }, + }; + } + + pub fn deinit(self: *const Union, allocator: Allocator) void { + for (self.fields) |f| f.deinit(allocator); + for (self.decls) |f| f.deinit(allocator); + + allocator.free(self.fields); + allocator.free(self.decls); + + if (self.tag_type) |tag_type| { + tag_type.deinit(allocator); + + allocator.destroy(tag_type); + } + } + }; + comptime { + validateSymbolInSync(Union, std.builtin.Type.Union, .{}); + } + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const Param = struct { + is_generic: bool, + is_noalias: bool, + type: ?*const TypeInfo, + + pub fn init(comptime f: std.builtin.Type.Param) Param { + return comptime .{ + .is_generic = f.is_generic, + .is_noalias = f.is_noalias, + .type = if (f.type) |t| &TypeInfo.init(t) else null, + }; + } + + pub fn deinit(self: *const Param, allocator: Allocator) void { + if (self.arg_type) |t| { + t.deinit(allocator); + + allocator.destroy(self.arg_type); + } + } + }; + comptime { + validateSymbolInSync(Param, std.builtin.Type.Fn.Param, .{}); + } + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const Fn = struct { + calling_convention: CallingConvention, + alignment: i32, + is_generic: bool, + is_var_args: bool, + return_type: ?*const TypeInfo, + params: []const Param, + + pub fn init(comptime m: std.builtin.Type.Fn) Fn { + return comptime .{ + .calling_convention = @intToEnum(CallingConvention, @enumToInt(m.calling_convention)), + .alignment = m.alignment, + .is_generic = m.is_generic, + .is_var_args = m.is_var_args, + .return_type = if (m.return_type) |t| &TypeInfo.init(t) else null, + .args = args: { + comptime var arr: [m.args.len]Param = undefined; + + inline for (m.args) |f, i| { + arr[i] = Param.init(f); + } + + break :args &arr; + }, + }; + } + + pub fn deinit(self: *const Fn, allocator: Allocator) void { + if (self.return_type) |r| { + r.deinit(allocator); + + allocator.destroy(r); + } + + for (self.args) |arg| arg.deinit(allocator); + + allocator.free(self.args); + } + }; + comptime { + validateSymbolInSync(Fn, std.builtin.Type.Fn, .{}); + } + + pub const Opaque = struct { + decls: []const Declaration, + + pub fn init(comptime m: std.builtin.Type.Opaque) Opaque { + return comptime .{ + .decls = decls: { + comptime var arr: [m.decls.len]Declaration = undefined; + + inline for (m.decls) |f, i| { + arr[i] = Declaration.init(f); + } + + break :decls &arr; + }, + }; + } + }; + comptime { + validateSymbolInSync(Opaque, std.builtin.Type.Opaque, .{}); + } + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const Frame = struct { + // function: anytype, + }; + comptime { + validateSymbolInSync(Frame, std.builtin.Type.Frame, .{ + .ignore_fields = .{"function"}, + }); + } + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const AnyFrame = struct { + child: ?*const TypeInfo, + + pub fn init(comptime m: std.builtin.Type.AnyFrame) AnyFrame { + return comptime .{ + .child = if (m.child) |t| &TypeInfo.init(t) else null, + }; + } + + pub fn deinit(self: *const AnyFrame, allocator: Allocator) void { + if (self.child) |child| { + child.deinit(allocator); + + allocator.destroy(child); + } + } + }; + comptime { + validateSymbolInSync(AnyFrame, std.builtin.Type.AnyFrame, .{}); + } + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const Vector = struct { + len: i32, + child: *const TypeInfo, + + pub fn init(comptime m: std.builtin.Type.Vector) Vector { + return comptime .{ + .len = m.len, + .child = &TypeInfo.init(m.child), + }; + } + + pub fn deinit(self: *const Vector, allocator: Allocator) void { + self.child.deinit(allocator); + + allocator.destroy(self.child); + } + }; + comptime { + validateSymbolInSync(Vector, std.builtin.Type.Vector, .{}); + } + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const Declaration = struct { + name: []const u8, + is_pub: bool, + // data: Data, + + pub fn init(comptime f: std.builtin.Type.Declaration) Declaration { + return comptime .{ + .name = f.name, + .is_pub = f.is_pub, + // .data = Data.init(f.data), + }; + } + + pub fn deinit(self: *const Declaration, allocator: Allocator) void { + self.data.deinit(allocator); + + allocator.free(self.name); + } + }; + comptime { + validateSymbolInSync(Declaration, std.builtin.Type.Declaration, .{}); + } + + // Validate the whole TypeInfo sync + comptime { + @setEvalBranchQuota(2000); + validateSymbolInSync(TypeInfo, std.builtin.Type, .{}); + } + + usingnamespace blk: { + var uniqueIdCounter: usize = 0; + + break :blk struct { + pub fn uniqueId(comptime T: type) usize { + _ = T; + comptime { + var id = uniqueIdCounter; + + uniqueIdCounter += 1; + + return id; + } + } + }; + }; + + pub fn alloc(comptime T: type) *TypeInfoSingleton { + _ = T; + comptime var ptr = TypeInfoSingleton{}; + + return &ptr; + } + + pub fn init(comptime T: type) TypeInfo { + return TypeInfo.initPtr(T).*; + } + + pub fn initPtr(comptime T: type) *const TypeInfo { + comptime var ptr = TypeInfo.alloc(T); + + if (ptr.resolved) { + return &ptr.info; + } + + ptr.resolved = true; + + const info = @typeInfo(T); + + ptr.info = comptime switch (info) { + .Type => .{ .Type = {} }, + .Void => .{ .Void = {} }, + .Bool => .{ .Bool = {} }, + .NoReturn => .{ .NoReturn = {} }, + .Int => |m| .{ .Int = Int.init(m) }, + .Float => |m| .{ .Float = Float.init(m) }, + .Pointer => |m| .{ .Pointer = Pointer.init(m) }, + .Array => |m| .{ .Array = Array.init(m) }, + .Struct => |m| .{ .Struct = Struct.init(m, @typeName(T)) }, + .ComptimeFloat => .{ .ComptimeFloat = {} }, + .ComptimeInt => .{ .ComptimeInt = {} }, + .Undefined => .{ .Undefined = {} }, + .Null => .{ .Null = {} }, + .Optional => |m| .{ .Optional = Optional.init(m) }, + .ErrorUnion => |m| .{ .ErrorUnion = ErrorUnion.init(m) }, // TODO + .ErrorSet => |m| .{ + .ErrorSet = errorset: { + if (m == null) return null; + + comptime var arr: [m.?.len]Error = undefined; + + inline for (m.?) |f, i| { + arr[i] = .{ + .name = f.name, + }; + } + + break :errorset &arr; + }, + }, + .Enum => |m| .{ .Enum = Enum.init(m, @typeName(T)) }, + .Union => |m| .{ .Union = Union.init(m, @typeName(T)) }, + .Fn => |m| .{ .Fn = Fn.init(m) }, + .BoundFn => |m| .{ .BoundedFn = Fn.init(m) }, + .Opaque => .{ .Opaque = {} }, + .Frame => .{ .Frame = {} }, // TODO + .AnyFrame => |m| .{ .AnyFrame = AnyFrame.init(m) }, + .Vector => |m| .{ .Vector = Vector.init(m) }, + .EnumLiteral => .{ .EnumLiteral = {} }, + }; + + return &ptr.info; + } + + pub fn deinit(self: *TypeInfo, allocator: Allocator) void { + switch (self.*) { + .Array => |a| a.deinit(allocator), + .Pointer => |p| p.deinit(allocator), + .Struct => |s| s.deinit(allocator), + .Union => |u| u.deinit(allocator), + .Enum => |e| e.deinit(allocator), + .Optional => |o| o.deinit(allocator), + .Fn => |f| f.deinit(allocator), + .ErrorUnion => |e| e.deinit(allocator), + .ErrorSet => |maybe_set| { + if (maybe_set) |set| { + for (set) |err| err.deinit(allocator); + + allocator.free(set); + } + }, + .AnyFrame => |a| a.deinit(allocator), + .Vector => |v| v.deinit(allocator), + else => {}, + } + } +}; + +pub const CallingConvention = enum { + Unspecified, + C, + Naked, + Async, + Inline, + Interrupt, + Signal, + Stdcall, + Fastcall, + Vectorcall, + Thiscall, + APCS, + AAPCS, + AAPCSVFP, + SysV, +}; + +pub const Signedness = enum { + signed, + unsigned, +}; + +pub fn hasField(comptime T: type, comptime field_name: []const u8) bool { + inline for (comptime std.meta.fields(T)) |field| { + if (std.mem.eql(u8, field.name, field_name) == true) { + return true; + } + } + + return false; +} + +/// Function to be run in compile time, responsible for verifying if the +/// structures/enums/unions defined in this file to represent the TypeInfo at +/// runtime in sync with the current Zig version's comptime structures/enums/unions +pub fn validateSymbolInSync(comptime runtime_type: type, comptime builtin_type: type, comptime options: anytype) void { + const builtin_type_info = @typeInfo(builtin_type); + const runtime_type_info = @typeInfo(runtime_type); + + // Make sure that the runtime type is a struct as well + if (std.mem.eql(u8, @tagName(builtin_type_info), @tagName(runtime_type_info)) == false) { + @compileError( + "Type of " ++ @typeName(builtin_type) ++ + " is " ++ @tagName(builtin_type_info) ++ + " but runtime type is " ++ @tagName(runtime_type_info), + ); + } + + switch (builtin_type_info) { + .Struct, .Enum, .Union => { + // Compare the fields + inline for (std.meta.fields(builtin_type)) |builtin_field| { + var missing_field: bool = false; + + if (hasField(runtime_type, builtin_field.name) == false) { + missing_field = true; + + if (@hasField(@TypeOf(options), "ignore_fields")) { + inline for (options.ignore_fields) |ignore_field| { + if (std.mem.eql(u8, ignore_field, builtin_field.name) == true) { + missing_field = false; + break; + } + } + } + + if (missing_field == true) { + @compileError( + "Field " ++ builtin_field.name ++ + " is missing in type " ++ @typeName(runtime_type), + ); + } + } + } + }, + else => @compileError( + "Cannot validate symbol in sync " ++ @typeName(builtin_type) ++ + " because type " ++ @tagName(builtin_type_info) ++ + " is not supported", + ), + } +} + +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectEqualStrings = std.testing.expectEqualStrings; + +const talloc = std.testing.allocator; + +// TODO .Type + +test "Runtime TypeInfo.Void" { + var info_void = TypeInfo.init(void); + try expect(info_void == .Void); +} + +test "Runtime TypeInfo.Bool" { + var info_bool = TypeInfo.init(bool); + try expect(info_bool == .Bool); +} + +// TODO .NoReturn + +test "Runtime TypeInfo.Int" { + var info_i32 = TypeInfo.init(i32); + try expect(info_i32 == .Int); + try expectEqual(@as(i32, 32), info_i32.Int.bits); + try expectEqual(true, info_i32.Int.signedness == .signed); +} + +test "Runtime TypeInfo.Float" { + var info_f64 = TypeInfo.init(f64); + try expect(info_f64 == .Float); + try expectEqual(@as(i32, 64), info_f64.Float.bits); +} + +test "Runtime TypeInfo.Pointer" { + var info_pointer_f64 = TypeInfo.init(*f64); + try expect(info_pointer_f64 == .Pointer); + try expectEqual(TypeInfo.Pointer.Size.One, info_pointer_f64.Pointer.size); + try expectEqual(false, info_pointer_f64.Pointer.is_const); + try expectEqual(false, info_pointer_f64.Pointer.is_volatile); + try expectEqual(@as(i32, 8), info_pointer_f64.Pointer.alignment); + try expect(info_pointer_f64.Pointer.child.* == .Float); + try expectEqual(false, info_pointer_f64.Pointer.is_allowzero); + + var info_pointer_many = TypeInfo.init([*]f64); + try expect(info_pointer_many == .Pointer); + try expectEqual(TypeInfo.Pointer.Size.Many, info_pointer_many.Pointer.size); + try expectEqual(false, info_pointer_many.Pointer.is_const); + try expectEqual(false, info_pointer_many.Pointer.is_volatile); + try expectEqual(@as(i32, 8), info_pointer_many.Pointer.alignment); + try expect(info_pointer_many.Pointer.child.* == .Float); + try expectEqual(false, info_pointer_many.Pointer.is_allowzero); +} + +test "Runtime TypeInfo.Array" { + var info_array = TypeInfo.init([2]i32); + try expect(info_array == .Array); + try expectEqual(@as(i32, 2), info_array.Array.len); + try expect(info_array.Array.child.* == .Int); +} + +test "Runtime TypeInfo.Struct" { + const FooStruct = struct { + int: i32, + + pub fn bar() void {} + }; + + var info_struct = TypeInfo.init(FooStruct); + try expect(info_struct == .Struct); + try expect(info_struct.Struct.layout == .Auto); + try expectEqual(@as(usize, 1), info_struct.Struct.fields.len); + try expectEqualStrings("int", info_struct.Struct.fields[0].name); + try expect(info_struct.Struct.fields[0].field_type.* == .Int); +} + +test "Runtime TypeInfo.ComptimeFloat" { + var info_comptime_float = TypeInfo.init(comptime_float); + try expect(info_comptime_float == .ComptimeFloat); +} + +test "Runtime TypeInfo.ComptimeInt" { + var info_comptime_int = TypeInfo.init(comptime_int); + try expect(info_comptime_int == .ComptimeInt); +} + +// // TODO .Undefined +// // TODO .Null + +test "Runtime TypeInfo.Optional" { + var info_optional = TypeInfo.init(?i32); + try expect(info_optional == .Optional); + try expect(info_optional.Optional.child.* == .Int); +} + +// // TODO .ErrorUnion +// // TODO .ErrorSet + +test "Runtime TypeInfo.Enum" { + const FooEnum = enum { Foo, Bar }; + + var info_enum = TypeInfo.init(FooEnum); + try expect(info_enum == .Enum); +} + +test "Runtime TypeInfo.Union" { + const FooUnion = union { Foo: void, Bar: i32 }; + + var info_union = TypeInfo.init(FooUnion); + try expect(info_union == .Union); +} + +test "Runtime TypeInfo.Fn" { + // .Fn + var info_fn = TypeInfo.init(fn () void); + try expect(info_fn == .Fn); +} + +test "Runtime TypeInfo.Struct declarations" { + // .Fn + var info_fn = TypeInfo.init(struct { + const WackType = packed struct { mr_field: *LameType, ola: u8 }; + + const LameType = struct { + blah: **WackType, + }; + + pub fn thing(one: usize, two: *LameType, three: [*]u16) bool { + _ = three; + _ = two; + return one == 1; + } + }); + try expect(info_fn == .Struct); +} + +// TODO .BoundFn +// TODO .Opaque +// TODO .Frame +// TODO .AnyFrame +// TODO .Vector +// TODO .EnumLiteral