Skip to content

Commit

Permalink
Luals docgen (#99)
Browse files Browse the repository at this point in the history
  • Loading branch information
VisenDev authored Sep 27, 2024
1 parent d105efd commit f28836c
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.zig-cache/
zig-out/
definitions.lua
13 changes: 13 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,19 @@ pub fn build(b: *Build) void {

const docs_step = b.step("docs", "Build and install the documentation");
docs_step.dependOn(&install_docs.step);

// definitions example
const def_exe = b.addExecutable(.{
.root_source_file = b.path("examples/define-exe.zig"),
.name = "define-zig-types",
.target = target,
});
def_exe.root_module.addImport("ziglua", ziglua);
var run_def_exe = b.addRunArtifact(def_exe);
run_def_exe.addFileArg(b.path("definitions.lua"));

const define_step = b.step("define", "Generate definitions.lua file");
define_step.dependOn(&run_def_exe.step);
}

fn buildLua(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, upstream: *Build.Dependency, lang: Language, shared: bool) *Step.Compile {
Expand Down
14 changes: 14 additions & 0 deletions examples/define-exe.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const std = @import("std");
const ziglua = @import("ziglua");

const T = struct { foo: i32 };
const MyEnum = enum { asdf, fdsa, qwer, rewq };
const SubType = struct { foo: i32, bar: bool, bip: MyEnum, bap: ?[]MyEnum };
const Bippity = struct { A: ?i32, B: *bool, C: []const u8, D: ?*SubType };
const TestType = struct { a: i32, b: f32, c: bool, d: SubType, e: [10]Bippity };
const Foo = struct { far: MyEnum, near: SubType };

pub fn main() !void {
const output_file_path = std.mem.sliceTo(std.os.argv[1], 0);
try ziglua.define(std.heap.c_allocator, output_file_path, &.{ T, TestType, Foo });
}
169 changes: 169 additions & 0 deletions src/define.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
const std = @import("std");

const String = std.ArrayList(u8);
const Database = std.StringHashMap(void);

pub const DefineState = struct {
allocator: std.mem.Allocator,
database: Database,
definitions: std.ArrayList(String),

pub fn init(alloc: std.mem.Allocator) DefineState {
return DefineState{
.allocator = alloc,
.database = Database.init(alloc),
.definitions = std.ArrayList(String).init(alloc),
};
}

pub fn deinit(self: *@This()) void {
for (self.definitions.items) |def| {
def.deinit();
}
defer self.database.deinit();
defer self.definitions.deinit();
}
};

pub fn define(
alloc: std.mem.Allocator,
absolute_output_path: []const u8,
comptime to_define: []const type,
) !void {
var state = DefineState.init(alloc);
defer state.deinit();

inline for (to_define) |T| {
_ = try addClass(&state, T);
}

var file = try std.fs.createFileAbsolute(absolute_output_path, .{});
defer file.close();

try file.seekTo(0);
try file.writeAll(file_header);

for (state.definitions.items) |def| {
try file.writeAll(def.items);
try file.writeAll("\n");
}

try file.setEndPos(try file.getPos());
}

const file_header: []const u8 =
\\---@meta
\\
\\--- This is an autogenerated file,
\\--- Do not modify
\\
\\
;

fn name(comptime T: type) []const u8 {
return (comptime std.fs.path.extension(@typeName(T)))[1..];
}

fn addEnum(
state: *DefineState,
comptime T: type,
) !void {
if (state.database.contains(@typeName(T)) == false) {
try state.database.put(@typeName(T), {});
try state.definitions.append(String.init(state.allocator));
const index = state.definitions.items.len - 1;

try state.definitions.items[index].appendSlice("---@alias ");
try state.definitions.items[index].appendSlice(name(T));
try state.definitions.items[index].appendSlice("\n");

inline for (@typeInfo(T).Enum.fields) |field| {
try state.definitions.items[index].appendSlice("---|\' \"");
try state.definitions.items[index].appendSlice(field.name);
try state.definitions.items[index].appendSlice("\" \'\n");
}
}
}

pub fn addClass(
state: *DefineState,
comptime T: type,
) !void {
if (state.database.contains(@typeName(T)) == false) {
try state.database.put(@typeName(T), {});
try state.definitions.append(String.init(state.allocator));
const index = state.definitions.items.len - 1;

try state.definitions.items[index].appendSlice("---@class (exact) ");
try state.definitions.items[index].appendSlice(name(T));
try state.definitions.items[index].appendSlice("\n");

inline for (@typeInfo(T).Struct.fields) |field| {
try state.definitions.items[index].appendSlice("---@field ");
try state.definitions.items[index].appendSlice(field.name);

if (field.default_value != null) {
try state.definitions.items[index].appendSlice("?");
}
try state.definitions.items[index].appendSlice(" ");
try luaTypeName(state, index, field.type);
try state.definitions.items[index].appendSlice("\n");
}
}
}

fn luaTypeName(
state: *DefineState,
index: usize,
comptime T: type,
) !void {
switch (@typeInfo(T)) {
.Struct => {
try state.definitions.items[index].appendSlice(name(T));
try addClass(state, T);
},
.Pointer => |info| {
if (info.child == u8 and info.size == .Slice) {
try state.definitions.items[index].appendSlice("string");
} else switch (info.size) {
.One => {
try state.definitions.items[index].appendSlice("lightuserdata");
},
.C, .Many, .Slice => {
try luaTypeName(state, index, info.child);
try state.definitions.items[index].appendSlice("[]");
},
}
},
.Array => |info| {
try luaTypeName(state, index, info.child);
try state.definitions.items[index].appendSlice("[]");
},

.Vector => |info| {
try luaTypeName(state, index, info.child);
try state.definitions.items[index].appendSlice("[]");
},
.Optional => |info| {
try luaTypeName(state, index, info.child);
try state.definitions.items[index].appendSlice(" | nil");
},
.Enum => {
try state.definitions.items[index].appendSlice(name(T));
try addEnum(state, T);
},
.Int => {
try state.definitions.items[index].appendSlice("integer");
},
.Float => {
try state.definitions.items[index].appendSlice("number");
},
.Bool => {
try state.definitions.items[index].appendSlice("boolean");
},
else => {
@compileLog(T);
@compileError("Type not supported");
},
}
}
3 changes: 3 additions & 0 deletions src/lib.zig
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const std = @import("std");

pub const def = @import("define.zig");
pub const define = def.define;

const c = @cImport({
@cInclude("luaconf.h");
@cInclude("lua.h");
Expand Down
66 changes: 66 additions & 0 deletions src/tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2816,3 +2816,69 @@ test "doFile" {

try expectEqualStrings("testing", try lua.get([]const u8, "GLOBAL"));
}

test "define" {
const expected =
\\---@class (exact) T
\\---@field foo integer
\\
\\---@class (exact) TestType
\\---@field a integer
\\---@field b number
\\---@field c boolean
\\---@field d SubType
\\---@field e Bippity[]
\\
\\---@class (exact) SubType
\\---@field foo integer
\\---@field bar boolean
\\---@field bip MyEnum
\\---@field bap MyEnum[] | nil
\\
\\---@alias MyEnum
\\---|' "asdf" '
\\---|' "fdsa" '
\\---|' "qwer" '
\\---|' "rewq" '
\\
\\---@class (exact) Bippity
\\---@field A integer | nil
\\---@field B lightuserdata
\\---@field C string
\\---@field D lightuserdata | nil
\\
\\---@class (exact) Foo
\\---@field far MyEnum
\\---@field near SubType
\\
\\
;

const T = struct { foo: i32 };
const MyEnum = enum { asdf, fdsa, qwer, rewq };
const SubType = struct { foo: i32, bar: bool, bip: MyEnum, bap: ?[]MyEnum };
const Bippity = struct { A: ?i32, B: *bool, C: []const u8, D: ?*SubType };
const TestType = struct { a: i32, b: f32, c: bool, d: SubType, e: [10]Bippity };
const Foo = struct { far: MyEnum, near: SubType };

const a = std.testing.allocator;

var state = ziglua.def.DefineState.init(a);
defer state.deinit();

const to_define: []const type = &.{ T, TestType, Foo };
inline for (to_define) |my_type| {
_ = try ziglua.def.addClass(&state, my_type);
}

var buffer: [10000]u8 = .{0} ** 10000;
var buffer_stream = std.io.fixedBufferStream(&buffer);
var writer = buffer_stream.writer();

for (state.definitions.items) |def| {
try writer.writeAll(def.items);
try writer.writeAll("\n");
}

try std.testing.expectEqualSlices(u8, expected, buffer_stream.getWritten());
}

0 comments on commit f28836c

Please sign in to comment.