1const std = @import("std");
2const xml = @import("panthera").xml;
3
4const ArgInfo = struct {
5 type: []const u8,
6 interface: ?[]const u8,
7};
8
9const MessageInfo = struct {
10 name: []const u8,
11 opcode: u16,
12 args: []const ArgInfo,
13};
14
15const InterfaceInfo = struct {
16 name: []const u8,
17 version: u32,
18 requests: []const MessageInfo,
19 events: []const MessageInfo,
20};
21
22fn argTypeName(xml_type: []const u8) []const u8 {
23 if (std.mem.eql(u8, xml_type, "int")) return "int";
24 if (std.mem.eql(u8, xml_type, "uint")) return "uint";
25 if (std.mem.eql(u8, xml_type, "fixed")) return "fixed";
26 if (std.mem.eql(u8, xml_type, "string")) return "string";
27 if (std.mem.eql(u8, xml_type, "object")) return "object";
28 if (std.mem.eql(u8, xml_type, "new_id")) return "new_id";
29 if (std.mem.eql(u8, xml_type, "array")) return "array";
30 if (std.mem.eql(u8, xml_type, "fd")) return "fd";
31 return "unknown";
32}
33
34fn getAttr(obj: *const xml.ObjectMap, name: []const u8) ?[]const u8 {
35 const v = obj.get(name) orelse return null;
36 if (v != .string) return null;
37 return v.string;
38}
39
40fn parseMessages(alloc: std.mem.Allocator, children: []const xml.Value, tag: []const u8) ![]MessageInfo {
41 var msgs = std.ArrayList(MessageInfo).initCapacity(alloc, 16) catch return &.{};
42 var opcode: u16 = 0;
43
44 for (children) |child| {
45 if (child != .object) continue;
46 const name = getAttr(&child.object, "name") orelse continue;
47 if (!std.mem.eql(u8, name, tag)) continue;
48
49 const attrs = child.object.get("attrs") orelse return error.MissingAttrs;
50 const msg_name = getAttr(&attrs.object, "name") orelse return error.MissingMessageName;
51
52 var args = std.ArrayList(ArgInfo).initCapacity(alloc, 4) catch return &.{};
53 const maybe_children = child.object.get("children");
54 if (maybe_children) |grand| {
55 if (grand == .array) {
56 for (grand.array.items) |arg_child| {
57 if (arg_child != .object) continue;
58 const arg_name = getAttr(&arg_child.object, "name") orelse continue;
59 if (!std.mem.eql(u8, arg_name, "arg")) continue;
60
61 const arg_attrs = arg_child.object.get("attrs") orelse continue;
62 const arg_type = getAttr(&arg_attrs.object, "type") orelse return error.MissingArgType;
63 const arg_iface = getAttr(&arg_attrs.object, "interface");
64
65 try args.append(alloc, .{
66 .type = arg_type,
67 .interface = arg_iface,
68 });
69 }
70 }
71 }
72
73 try msgs.append(alloc, .{
74 .name = msg_name,
75 .opcode = opcode,
76 .args = args.items,
77 });
78 opcode += 1;
79 }
80
81 return msgs.items;
82}
83
84fn parseProtocolXml(alloc: std.mem.Allocator, content: []const u8) ![]InterfaceInfo {
85 const v = try xml.parseValue(alloc, content);
86
87 const root = v.object;
88 const root_name = getAttr(&root, "name") orelse return error.MissingRootName;
89 if (!std.mem.eql(u8, root_name, "protocol")) return error.ExpectedProtocolRoot;
90
91 var ifaces = std.ArrayList(InterfaceInfo).initCapacity(alloc, 16) catch return &.{};
92
93 const children = root.get("children") orelse return error.MissingChildren;
94 if (children != .array) return error.ExpectedArray;
95
96 for (children.array.items) |child| {
97 if (child != .object) continue;
98 const el_name = getAttr(&child.object, "name") orelse continue;
99 if (!std.mem.eql(u8, el_name, "interface")) continue;
100
101 const attrs = child.object.get("attrs") orelse return error.MissingInterfaceAttrs;
102 const iface_name = getAttr(&attrs.object, "name") orelse return error.MissingInterfaceName;
103 const iface_version_str = getAttr(&attrs.object, "version") orelse return error.MissingInterfaceVersion;
104 const iface_version = std.fmt.parseInt(u32, iface_version_str, 10) catch return error.InvalidVersion;
105
106 var reqs: []const MessageInfo = &.{};
107 var evts: []const MessageInfo = &.{};
108
109 const maybe_children = child.object.get("children");
110 if (maybe_children) |grand| {
111 if (grand == .array) {
112 reqs = try parseMessages(alloc, grand.array.items, "request");
113 evts = try parseMessages(alloc, grand.array.items, "event");
114 }
115 }
116
117 try ifaces.append(alloc, .{
118 .name = iface_name,
119 .version = iface_version,
120 .requests = reqs,
121 .events = evts,
122 });
123 }
124
125 return ifaces.items;
126}
127
128fn lessThanString(_: void, a: []const u8, b: []const u8) bool {
129 return std.mem.lessThan(u8, a, b);
130}
131
132fn lessThanInterface(_: void, a: InterfaceInfo, b: InterfaceInfo) bool {
133 return std.mem.lessThan(u8, a.name, b.name);
134}
135
136fn emitMessageSigs(fw: *std.Io.File.Writer, msgs: []const MessageInfo, indent: []const u8) !void {
137 const w = &fw.interface;
138 if (msgs.len == 0) {
139 try w.print("&.{{}}", .{});
140 return;
141 }
142 try w.print("&.{{\n", .{});
143 for (msgs) |msg| {
144 try w.print("{s}.{{ .opcode = {d}, .args = &.{{", .{ indent, msg.opcode });
145 for (msg.args, 0..) |arg, i| {
146 if (i > 0) try w.print(", ", .{});
147 try w.print(".{s}", .{argTypeName(arg.type)});
148 }
149 try w.print("}} }},\n", .{});
150 }
151 try w.print("{s}}}", .{indent[0 .. indent.len - 4]});
152}
153
154pub fn main(init: std.process.Init) !void {
155 const io = init.io;
156 const arena = @constCast(init.arena);
157 const alloc = arena.allocator();
158
159 const arg_slice = try init.minimal.args.toSlice(alloc);
160 if (arg_slice.len < 3) {
161 var stderr_fw = std.Io.File.stderr().writer(io, &.{});
162 try stderr_fw.interface.print("Usage: {s} <protocol_dir> <output_path>\n", .{arg_slice[0]});
163 std.process.exit(1);
164 }
165
166 const protocol_dir = arg_slice[1];
167 const output_path = arg_slice[2];
168
169 const cwd = std.Io.Dir.cwd();
170
171 var dir = try cwd.openDir(io, protocol_dir, .{ .iterate = true });
172 defer dir.close(io);
173
174 var walker = try dir.walk(alloc);
175 defer walker.deinit();
176
177 var xml_paths = std.ArrayList([]const u8).initCapacity(alloc, 16) catch
178 std.ArrayList([]const u8).empty;
179
180 while (try walker.next(io)) |entry| {
181 if (entry.kind != .file) continue;
182 if (std.mem.endsWith(u8, entry.basename, ".xml")) {
183 try xml_paths.append(alloc, try alloc.dupe(u8, entry.path));
184 }
185 }
186
187 if (xml_paths.items.len == 0) {
188 var stderr_fw2 = std.Io.File.stderr().writer(io, &.{});
189try stderr_fw2.interface.print("error: no .xml files found in '{s}/'\n", .{protocol_dir});
190 std.process.exit(1);
191 }
192
193 std.mem.sort([]const u8, xml_paths.items, {}, lessThanString);
194
195 var all_interfaces = std.ArrayList(InterfaceInfo).initCapacity(alloc, 32) catch
196 std.ArrayList(InterfaceInfo).empty;
197
198 for (xml_paths.items) |rel_path| {
199 const full_path = try std.fs.path.join(alloc, &.{ protocol_dir, rel_path });
200 const content = try cwd.readFileAlloc(io, full_path, alloc, .unlimited);
201 const ifaces = try parseProtocolXml(alloc, content);
202 for (ifaces) |iface| {
203 try all_interfaces.append(alloc, iface);
204 }
205 }
206
207 std.mem.sort(InterfaceInfo, all_interfaces.items, {}, lessThanInterface);
208
209 var file_buf: [8192]u8 = undefined;
210 const out_file = try cwd.createFile(io, output_path, .{});
211 defer out_file.close(io);
212 var file_fw = out_file.writer(io, &file_buf);
213 defer file_fw.flush() catch {};
214 const w = &file_fw.interface;
215 const fwp = &file_fw;
216
217 try w.print(
218 \\const wire = @import("wire.zig");
219 \\const ArgType = wire.ArgType;
220 \\
221 \\pub const Interface = enum(u8) {{
222 \\
223 , .{});
224
225 for (all_interfaces.items) |iface| {
226 try w.print(" {s},\n", .{iface.name});
227 }
228
229 try w.print(
230 \\}};
231 \\
232 \\pub const MessageSig = struct {{
233 \\ opcode: u16,
234 \\ args: []const ArgType,
235 \\}};
236 \\
237 \\pub fn requestSig(iface: Interface, opcode: u16) ?[]const ArgType {{
238 \\ const sigs = requestSigs(iface);
239 \\ for (sigs) |s| if (s.opcode == opcode) return s.args;
240 \\ return null;
241 \\}}
242 \\
243 \\pub fn eventSig(iface: Interface, opcode: u16) ?[]const ArgType {{
244 \\ const sigs = eventSigs(iface);
245 \\ for (sigs) |s| if (s.opcode == opcode) return s.args;
246 \\ return null;
247 \\}}
248 \\
249 \\fn requestSigs(iface: Interface) []const MessageSig {{
250 \\ return switch (iface) {{
251 \\
252 , .{});
253
254 for (all_interfaces.items) |iface| {
255 try w.print(" .{s} => ", .{iface.name});
256 try emitMessageSigs(fwp, iface.requests, " ");
257 try w.print(",\n", .{});
258 }
259
260 try w.print(
261 \\ }};
262 \\}}
263 \\
264 \\fn eventSigs(iface: Interface) []const MessageSig {{
265 \\ return switch (iface) {{
266 \\
267 , .{});
268
269 for (all_interfaces.items) |iface| {
270 try w.print(" .{s} => ", .{iface.name});
271 try emitMessageSigs(fwp, iface.events, " ");
272 try w.print(",\n", .{});
273 }
274
275 try w.print(
276 \\ }};
277 \\}}
278 \\
279 \\pub fn globalName(iface: Interface) []const u8 {{
280 \\ return switch (iface) {{
281 \\
282 , .{});
283
284 for (all_interfaces.items) |iface| {
285 try w.print(" .{s} => \"{s}\",\n", .{ iface.name, iface.name });
286 }
287
288 try w.print(
289 \\ }};
290 \\}}
291 \\
292 \\pub fn globalVersion(iface: Interface) u32 {{
293 \\ return switch (iface) {{
294 \\
295 , .{});
296
297 for (all_interfaces.items) |iface| {
298 try w.print(" .{s} => {d},\n", .{ iface.name, iface.version });
299 }
300
301 try w.print(
302 \\ }};
303 \\}}
304 \\
305 , .{});
306}