main shar/altaica / tools / scanner.zig
  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}