Skip to content

Commit f28836c

Browse files
authored
Luals docgen (#99)
1 parent d105efd commit f28836c

File tree

6 files changed

+266
-0
lines changed

6 files changed

+266
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
.zig-cache/
22
zig-out/
3+
definitions.lua

build.zig

+13
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,19 @@ pub fn build(b: *Build) void {
124124

125125
const docs_step = b.step("docs", "Build and install the documentation");
126126
docs_step.dependOn(&install_docs.step);
127+
128+
// definitions example
129+
const def_exe = b.addExecutable(.{
130+
.root_source_file = b.path("examples/define-exe.zig"),
131+
.name = "define-zig-types",
132+
.target = target,
133+
});
134+
def_exe.root_module.addImport("ziglua", ziglua);
135+
var run_def_exe = b.addRunArtifact(def_exe);
136+
run_def_exe.addFileArg(b.path("definitions.lua"));
137+
138+
const define_step = b.step("define", "Generate definitions.lua file");
139+
define_step.dependOn(&run_def_exe.step);
127140
}
128141

129142
fn buildLua(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, upstream: *Build.Dependency, lang: Language, shared: bool) *Step.Compile {

examples/define-exe.zig

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const std = @import("std");
2+
const ziglua = @import("ziglua");
3+
4+
const T = struct { foo: i32 };
5+
const MyEnum = enum { asdf, fdsa, qwer, rewq };
6+
const SubType = struct { foo: i32, bar: bool, bip: MyEnum, bap: ?[]MyEnum };
7+
const Bippity = struct { A: ?i32, B: *bool, C: []const u8, D: ?*SubType };
8+
const TestType = struct { a: i32, b: f32, c: bool, d: SubType, e: [10]Bippity };
9+
const Foo = struct { far: MyEnum, near: SubType };
10+
11+
pub fn main() !void {
12+
const output_file_path = std.mem.sliceTo(std.os.argv[1], 0);
13+
try ziglua.define(std.heap.c_allocator, output_file_path, &.{ T, TestType, Foo });
14+
}

src/define.zig

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
const std = @import("std");
2+
3+
const String = std.ArrayList(u8);
4+
const Database = std.StringHashMap(void);
5+
6+
pub const DefineState = struct {
7+
allocator: std.mem.Allocator,
8+
database: Database,
9+
definitions: std.ArrayList(String),
10+
11+
pub fn init(alloc: std.mem.Allocator) DefineState {
12+
return DefineState{
13+
.allocator = alloc,
14+
.database = Database.init(alloc),
15+
.definitions = std.ArrayList(String).init(alloc),
16+
};
17+
}
18+
19+
pub fn deinit(self: *@This()) void {
20+
for (self.definitions.items) |def| {
21+
def.deinit();
22+
}
23+
defer self.database.deinit();
24+
defer self.definitions.deinit();
25+
}
26+
};
27+
28+
pub fn define(
29+
alloc: std.mem.Allocator,
30+
absolute_output_path: []const u8,
31+
comptime to_define: []const type,
32+
) !void {
33+
var state = DefineState.init(alloc);
34+
defer state.deinit();
35+
36+
inline for (to_define) |T| {
37+
_ = try addClass(&state, T);
38+
}
39+
40+
var file = try std.fs.createFileAbsolute(absolute_output_path, .{});
41+
defer file.close();
42+
43+
try file.seekTo(0);
44+
try file.writeAll(file_header);
45+
46+
for (state.definitions.items) |def| {
47+
try file.writeAll(def.items);
48+
try file.writeAll("\n");
49+
}
50+
51+
try file.setEndPos(try file.getPos());
52+
}
53+
54+
const file_header: []const u8 =
55+
\\---@meta
56+
\\
57+
\\--- This is an autogenerated file,
58+
\\--- Do not modify
59+
\\
60+
\\
61+
;
62+
63+
fn name(comptime T: type) []const u8 {
64+
return (comptime std.fs.path.extension(@typeName(T)))[1..];
65+
}
66+
67+
fn addEnum(
68+
state: *DefineState,
69+
comptime T: type,
70+
) !void {
71+
if (state.database.contains(@typeName(T)) == false) {
72+
try state.database.put(@typeName(T), {});
73+
try state.definitions.append(String.init(state.allocator));
74+
const index = state.definitions.items.len - 1;
75+
76+
try state.definitions.items[index].appendSlice("---@alias ");
77+
try state.definitions.items[index].appendSlice(name(T));
78+
try state.definitions.items[index].appendSlice("\n");
79+
80+
inline for (@typeInfo(T).Enum.fields) |field| {
81+
try state.definitions.items[index].appendSlice("---|\' \"");
82+
try state.definitions.items[index].appendSlice(field.name);
83+
try state.definitions.items[index].appendSlice("\" \'\n");
84+
}
85+
}
86+
}
87+
88+
pub fn addClass(
89+
state: *DefineState,
90+
comptime T: type,
91+
) !void {
92+
if (state.database.contains(@typeName(T)) == false) {
93+
try state.database.put(@typeName(T), {});
94+
try state.definitions.append(String.init(state.allocator));
95+
const index = state.definitions.items.len - 1;
96+
97+
try state.definitions.items[index].appendSlice("---@class (exact) ");
98+
try state.definitions.items[index].appendSlice(name(T));
99+
try state.definitions.items[index].appendSlice("\n");
100+
101+
inline for (@typeInfo(T).Struct.fields) |field| {
102+
try state.definitions.items[index].appendSlice("---@field ");
103+
try state.definitions.items[index].appendSlice(field.name);
104+
105+
if (field.default_value != null) {
106+
try state.definitions.items[index].appendSlice("?");
107+
}
108+
try state.definitions.items[index].appendSlice(" ");
109+
try luaTypeName(state, index, field.type);
110+
try state.definitions.items[index].appendSlice("\n");
111+
}
112+
}
113+
}
114+
115+
fn luaTypeName(
116+
state: *DefineState,
117+
index: usize,
118+
comptime T: type,
119+
) !void {
120+
switch (@typeInfo(T)) {
121+
.Struct => {
122+
try state.definitions.items[index].appendSlice(name(T));
123+
try addClass(state, T);
124+
},
125+
.Pointer => |info| {
126+
if (info.child == u8 and info.size == .Slice) {
127+
try state.definitions.items[index].appendSlice("string");
128+
} else switch (info.size) {
129+
.One => {
130+
try state.definitions.items[index].appendSlice("lightuserdata");
131+
},
132+
.C, .Many, .Slice => {
133+
try luaTypeName(state, index, info.child);
134+
try state.definitions.items[index].appendSlice("[]");
135+
},
136+
}
137+
},
138+
.Array => |info| {
139+
try luaTypeName(state, index, info.child);
140+
try state.definitions.items[index].appendSlice("[]");
141+
},
142+
143+
.Vector => |info| {
144+
try luaTypeName(state, index, info.child);
145+
try state.definitions.items[index].appendSlice("[]");
146+
},
147+
.Optional => |info| {
148+
try luaTypeName(state, index, info.child);
149+
try state.definitions.items[index].appendSlice(" | nil");
150+
},
151+
.Enum => {
152+
try state.definitions.items[index].appendSlice(name(T));
153+
try addEnum(state, T);
154+
},
155+
.Int => {
156+
try state.definitions.items[index].appendSlice("integer");
157+
},
158+
.Float => {
159+
try state.definitions.items[index].appendSlice("number");
160+
},
161+
.Bool => {
162+
try state.definitions.items[index].appendSlice("boolean");
163+
},
164+
else => {
165+
@compileLog(T);
166+
@compileError("Type not supported");
167+
},
168+
}
169+
}

src/lib.zig

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
const std = @import("std");
22

3+
pub const def = @import("define.zig");
4+
pub const define = def.define;
5+
36
const c = @cImport({
47
@cInclude("luaconf.h");
58
@cInclude("lua.h");

src/tests.zig

+66
Original file line numberDiff line numberDiff line change
@@ -2816,3 +2816,69 @@ test "doFile" {
28162816

28172817
try expectEqualStrings("testing", try lua.get([]const u8, "GLOBAL"));
28182818
}
2819+
2820+
test "define" {
2821+
const expected =
2822+
\\---@class (exact) T
2823+
\\---@field foo integer
2824+
\\
2825+
\\---@class (exact) TestType
2826+
\\---@field a integer
2827+
\\---@field b number
2828+
\\---@field c boolean
2829+
\\---@field d SubType
2830+
\\---@field e Bippity[]
2831+
\\
2832+
\\---@class (exact) SubType
2833+
\\---@field foo integer
2834+
\\---@field bar boolean
2835+
\\---@field bip MyEnum
2836+
\\---@field bap MyEnum[] | nil
2837+
\\
2838+
\\---@alias MyEnum
2839+
\\---|' "asdf" '
2840+
\\---|' "fdsa" '
2841+
\\---|' "qwer" '
2842+
\\---|' "rewq" '
2843+
\\
2844+
\\---@class (exact) Bippity
2845+
\\---@field A integer | nil
2846+
\\---@field B lightuserdata
2847+
\\---@field C string
2848+
\\---@field D lightuserdata | nil
2849+
\\
2850+
\\---@class (exact) Foo
2851+
\\---@field far MyEnum
2852+
\\---@field near SubType
2853+
\\
2854+
\\
2855+
;
2856+
2857+
const T = struct { foo: i32 };
2858+
const MyEnum = enum { asdf, fdsa, qwer, rewq };
2859+
const SubType = struct { foo: i32, bar: bool, bip: MyEnum, bap: ?[]MyEnum };
2860+
const Bippity = struct { A: ?i32, B: *bool, C: []const u8, D: ?*SubType };
2861+
const TestType = struct { a: i32, b: f32, c: bool, d: SubType, e: [10]Bippity };
2862+
const Foo = struct { far: MyEnum, near: SubType };
2863+
2864+
const a = std.testing.allocator;
2865+
2866+
var state = ziglua.def.DefineState.init(a);
2867+
defer state.deinit();
2868+
2869+
const to_define: []const type = &.{ T, TestType, Foo };
2870+
inline for (to_define) |my_type| {
2871+
_ = try ziglua.def.addClass(&state, my_type);
2872+
}
2873+
2874+
var buffer: [10000]u8 = .{0} ** 10000;
2875+
var buffer_stream = std.io.fixedBufferStream(&buffer);
2876+
var writer = buffer_stream.writer();
2877+
2878+
for (state.definitions.items) |def| {
2879+
try writer.writeAll(def.items);
2880+
try writer.writeAll("\n");
2881+
}
2882+
2883+
try std.testing.expectEqualSlices(u8, expected, buffer_stream.getWritten());
2884+
}

0 commit comments

Comments
 (0)