stevee

My wayland statusbar
git clone git://gtms.dev/stevee
Log | Files | Refs | README | LICENSE

scanner.zig (55425B)


      1 const std = @import("std");
      2 const assert = std.debug.assert;
      3 const posix = std.posix;
      4 const fs = std.fs;
      5 const mem = std.mem;
      6 const fmtId = std.zig.fmtId;
      7 
      8 const log = std.log.scoped(.@"zig-wayland");
      9 
     10 const xml = @import("xml.zig");
     11 
     12 const gpa = general_purpose_allocator.allocator();
     13 var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
     14 
     15 pub const Target = struct {
     16     /// Name of the target global interface
     17     name: []const u8,
     18     /// Interface version for which to generate code.
     19     /// If the version found in the protocol xml is less than this version,
     20     /// an error will be printed and code generation will fail.
     21     /// This version applies to interfaces that may be created through the
     22     /// global interface as well.
     23     version: u32,
     24 };
     25 
     26 pub fn main() !void {
     27     defer assert(general_purpose_allocator.deinit() == .ok);
     28 
     29     var protocols = std.ArrayList([]const u8).init(gpa);
     30     defer protocols.deinit();
     31 
     32     var targets = std.ArrayList(Target).init(gpa);
     33     defer targets.deinit();
     34 
     35     var out_path_opt: ?[]const u8 = null;
     36 
     37     var args = std.process.args();
     38 
     39     while (args.next()) |arg| {
     40         if (mem.eql(u8, arg, "-i")) {
     41             const protocol_path = args.next() orelse return error.MissingArg;
     42             try protocols.append(protocol_path);
     43         } else if (mem.eql(u8, arg, "-g")) {
     44             const name = args.next() orelse return error.MissingArg;
     45             const version = args.next() orelse return error.MissingArg;
     46             try targets.append(.{
     47                 .name = name,
     48                 .version = try std.fmt.parseInt(u32, version, 10),
     49             });
     50         } else if (mem.eql(u8, arg, "-o")) {
     51             out_path_opt = args.next() orelse return error.MissingArg;
     52         }
     53     }
     54 
     55     const out_path = out_path_opt orelse return error.MissingArg;
     56 
     57     var buffer = std.ArrayList(u8).init(gpa);
     58     defer buffer.deinit();
     59 
     60     try scan(buffer.writer(), protocols.items, targets.items);
     61 
     62     const generated = try buffer.toOwnedSliceSentinel(0);
     63     defer gpa.free(generated);
     64 
     65     var tree = try std.zig.Ast.parse(gpa, generated, .zig);
     66     defer tree.deinit(gpa);
     67 
     68     const formatted = try tree.render(gpa);
     69     defer gpa.free(formatted);
     70 
     71     const out = try std.fs.createFileAbsolute(out_path, .{});
     72     defer out.close();
     73 
     74     try out.writeAll(formatted);
     75 }
     76 
     77 fn scan(
     78     writer: anytype,
     79     protocols: []const []const u8,
     80     targets: []const Target,
     81 ) !void {
     82     var scanner = try Scanner.init(targets);
     83     defer scanner.deinit();
     84 
     85     for (protocols) |xml_path| {
     86         try scanner.scanProtocol(xml_path);
     87     }
     88 
     89     if (scanner.remaining_targets.items.len != 0) {
     90         fatal("requested global interface '{s}' not found in provided protocol xml", .{
     91             scanner.remaining_targets.items[0].name,
     92         });
     93     }
     94 
     95     try writer.writeAll(
     96         \\// Generated by zig-wayland
     97         \\
     98         \\const std = @import("std");
     99         \\const assert = std.debug.assert;
    100         \\const posix = std.posix;
    101         \\
    102         \\pub const client = struct {
    103     );
    104 
    105     {
    106         var iter = scanner.client.iterator();
    107         while (iter.next()) |entry| {
    108             try writer.print("pub const {s} = struct {{", .{entry.key_ptr.*});
    109             if (mem.eql(u8, entry.key_ptr.*, "wl")) {
    110                 try writer.writeAll(@embedFile("wayland_client_core.zig"));
    111             }
    112             try writer.writeAll(entry.value_ptr.items);
    113             try writer.writeAll("};");
    114         }
    115     }
    116 
    117     try writer.writeAll(
    118         \\};
    119         \\
    120         \\pub const server = struct {
    121     );
    122 
    123     {
    124         var iter = scanner.server.iterator();
    125         while (iter.next()) |entry| {
    126             try writer.print("pub const {s} = struct {{", .{entry.key_ptr.*});
    127             if (mem.eql(u8, entry.key_ptr.*, "wl")) {
    128                 try writer.writeAll(@embedFile("wayland_server_core.zig"));
    129             }
    130             try writer.writeAll(entry.value_ptr.items);
    131             try writer.writeAll("};");
    132         }
    133     }
    134 
    135     try writer.writeAll(
    136         \\};
    137         \\
    138         \\const common = struct {
    139     );
    140 
    141     {
    142         try writer.writeAll(@embedFile("common_core.zig"));
    143 
    144         var iter = scanner.common.iterator();
    145         while (iter.next()) |entry| {
    146             try writer.print("const {s} = struct {{", .{entry.key_ptr.*});
    147             try writer.writeAll(entry.value_ptr.items);
    148             try writer.writeAll("};");
    149         }
    150     }
    151     try writer.writeAll("};");
    152 }
    153 
    154 const Side = enum {
    155     client,
    156     server,
    157 };
    158 
    159 const Scanner = struct {
    160     /// Map from namespace to source code content of the namespace.
    161     const Map = std.StringArrayHashMap(std.ArrayListUnmanaged(u8));
    162     client: Map = Map.init(gpa),
    163     server: Map = Map.init(gpa),
    164     common: Map = Map.init(gpa),
    165 
    166     remaining_targets: std.ArrayListUnmanaged(Target),
    167 
    168     fn init(targets: []const Target) !Scanner {
    169         return Scanner{
    170             .remaining_targets = .{
    171                 .items = try gpa.dupe(Target, targets),
    172                 .capacity = targets.len,
    173             },
    174         };
    175     }
    176 
    177     fn deinit(scanner: *Scanner) void {
    178         deinit_map(&scanner.client);
    179         deinit_map(&scanner.server);
    180         deinit_map(&scanner.common);
    181 
    182         scanner.remaining_targets.deinit(gpa);
    183     }
    184 
    185     fn deinit_map(map: *Map) void {
    186         for (map.keys()) |namespace| gpa.free(namespace);
    187         for (map.values()) |*list| {
    188             list.deinit(gpa);
    189         }
    190         map.deinit();
    191     }
    192 
    193     fn scanProtocol(scanner: *Scanner, xml_path: []const u8) !void {
    194         const xml_file = try fs.cwd().openFile(xml_path, .{});
    195         defer xml_file.close();
    196 
    197         var arena = std.heap.ArenaAllocator.init(gpa);
    198         defer arena.deinit();
    199 
    200         const xml_bytes = try xml_file.readToEndAlloc(arena.allocator(), 512 * 4096);
    201         const protocol = Protocol.parseXML(arena.allocator(), xml_bytes) catch |err| {
    202             fatal("failed to parse {s}: {s}", .{ xml_path, @errorName(err) });
    203         };
    204 
    205         {
    206             const gop = try scanner.client.getOrPutValue(protocol.namespace, .{});
    207             if (!gop.found_existing) {
    208                 gop.key_ptr.* = try gpa.dupe(u8, protocol.namespace);
    209             }
    210             try protocol.emit(.client, scanner.remaining_targets.items, gop.value_ptr.writer(gpa));
    211         }
    212 
    213         {
    214             const gop = try scanner.server.getOrPutValue(protocol.namespace, .{});
    215             if (!gop.found_existing) {
    216                 gop.key_ptr.* = try gpa.dupe(u8, protocol.namespace);
    217             }
    218             try protocol.emit(.server, scanner.remaining_targets.items, gop.value_ptr.writer(gpa));
    219         }
    220 
    221         {
    222             const gop = try scanner.common.getOrPutValue(protocol.namespace, .{});
    223             if (!gop.found_existing) {
    224                 gop.key_ptr.* = try gpa.dupe(u8, protocol.namespace);
    225             }
    226             try protocol.emitCommon(scanner.remaining_targets.items, gop.value_ptr.writer(gpa));
    227         }
    228 
    229         {
    230             var i: usize = 0;
    231             outer: while (i < scanner.remaining_targets.items.len) {
    232                 const target = scanner.remaining_targets.items[i];
    233                 for (protocol.globals) |global| {
    234                     if (mem.eql(u8, target.name, global.interface.name)) {
    235                         // We check this in emitClient() which is called first.
    236                         assert(global.interface.version >= target.version);
    237                         _ = scanner.remaining_targets.swapRemove(i);
    238                         continue :outer;
    239                     }
    240                 }
    241                 i += 1;
    242             }
    243         }
    244     }
    245 };
    246 
    247 /// All data in this struct is immutable after creation in parse().
    248 const Protocol = struct {
    249     const Global = struct {
    250         interface: Interface,
    251         children: []const Interface,
    252     };
    253 
    254     name: []const u8,
    255     namespace: []const u8,
    256     copyright: ?[]const u8,
    257     toplevel_description: ?[]const u8,
    258 
    259     version_locked_interfaces: []const Interface,
    260     globals: []const Global,
    261 
    262     fn parseXML(arena: mem.Allocator, xml_bytes: []const u8) !Protocol {
    263         var parser = xml.Parser.init(xml_bytes);
    264         while (parser.next()) |ev| switch (ev) {
    265             .open_tag => |tag| if (mem.eql(u8, tag, "protocol")) return parse(arena, &parser),
    266             else => {},
    267         };
    268         return error.UnexpectedEndOfFile;
    269     }
    270 
    271     fn parse(arena: mem.Allocator, parser: *xml.Parser) !Protocol {
    272         var name: ?[]const u8 = null;
    273         var copyright: ?[]const u8 = null;
    274         var toplevel_description: ?[]const u8 = null;
    275         var version_locked_interfaces = std.ArrayList(Interface).init(gpa);
    276         defer version_locked_interfaces.deinit();
    277         var interfaces = std.StringArrayHashMap(Interface).init(gpa);
    278         defer interfaces.deinit();
    279 
    280         while (parser.next()) |ev| switch (ev) {
    281             .open_tag => |tag| {
    282                 if (mem.eql(u8, tag, "copyright")) {
    283                     if (copyright != null)
    284                         return error.DuplicateCopyright;
    285                     const e = parser.next() orelse return error.UnexpectedEndOfFile;
    286                     switch (e) {
    287                         .character_data => |data| copyright = try arena.dupe(u8, data),
    288                         else => return error.BadCopyright,
    289                     }
    290                 } else if (mem.eql(u8, tag, "description")) {
    291                     if (toplevel_description != null)
    292                         return error.DuplicateToplevelDescription;
    293                     while (parser.next()) |e| {
    294                         switch (e) {
    295                             .character_data => |data| {
    296                                 toplevel_description = try arena.dupe(u8, data);
    297                                 break;
    298                             },
    299                             .attribute => continue,
    300                             else => return error.BadToplevelDescription,
    301                         }
    302                     } else {
    303                         return error.UnexpectedEndOfFile;
    304                     }
    305                 } else if (mem.eql(u8, tag, "interface")) {
    306                     const interface = try Interface.parse(arena, parser);
    307                     if (Interface.version_locked(interface.name)) {
    308                         try version_locked_interfaces.append(interface);
    309                     } else {
    310                         const gop = try interfaces.getOrPut(interface.name);
    311                         if (gop.found_existing) return error.DuplicateInterfaceName;
    312                         gop.value_ptr.* = interface;
    313                     }
    314                 }
    315             },
    316             .attribute => |attr| if (mem.eql(u8, attr.name, "name")) {
    317                 if (name != null) return error.DuplicateName;
    318                 name = try attr.dupeValue(arena);
    319             },
    320             .close_tag => |tag| if (mem.eql(u8, tag, "protocol")) {
    321                 if (interfaces.count() == 0) return error.NoInterfaces;
    322 
    323                 const globals = try find_globals(arena, interfaces);
    324                 if (globals.len == 0) return error.NoGlobals;
    325 
    326                 const namespace = prefix(interfaces.values()[0].name) orelse return error.NoNamespace;
    327                 for (interfaces.values()) |interface| {
    328                     const other = prefix(interface.name) orelse return error.NoNamespace;
    329                     if (!mem.eql(u8, namespace, other)) return error.InconsistentNamespaces;
    330                 }
    331 
    332                 return Protocol{
    333                     .name = name orelse return error.MissingName,
    334                     .namespace = namespace,
    335 
    336                     // Missing copyright or toplevel description is bad style, but not illegal.
    337                     .copyright = copyright,
    338                     .toplevel_description = toplevel_description,
    339                     .version_locked_interfaces = try arena.dupe(Interface, version_locked_interfaces.items),
    340                     .globals = globals,
    341                 };
    342             },
    343             else => {},
    344         };
    345         return error.UnexpectedEndOfFile;
    346     }
    347 
    348     fn find_globals(arena: mem.Allocator, interfaces: std.StringArrayHashMap(Interface)) ![]const Global {
    349         var non_globals = std.StringHashMap(void).init(gpa);
    350         defer non_globals.deinit();
    351 
    352         for (interfaces.values()) |interface| {
    353             assert(!Interface.version_locked(interface.name));
    354             for (interface.requests) |message| {
    355                 if (message.kind == .constructor) {
    356                     if (message.kind.constructor) |child_interface_name| {
    357                         try non_globals.put(child_interface_name, {});
    358                     }
    359                 }
    360             }
    361             for (interface.events) |message| {
    362                 if (message.kind == .constructor) {
    363                     if (message.kind.constructor) |child_interface_name| {
    364                         try non_globals.put(child_interface_name, {});
    365                     }
    366                 }
    367             }
    368         }
    369 
    370         var globals = std.ArrayList(Global).init(gpa);
    371         defer globals.deinit();
    372 
    373         for (interfaces.values()) |interface| {
    374             if (!non_globals.contains(interface.name)) {
    375                 var children = std.StringArrayHashMap(Interface).init(gpa);
    376                 defer children.deinit();
    377 
    378                 try find_children(interface, interfaces, &children);
    379 
    380                 try globals.append(.{
    381                     .interface = interface,
    382                     .children = try arena.dupe(Interface, children.values()),
    383                 });
    384             }
    385         }
    386 
    387         return arena.dupe(Global, globals.items);
    388     }
    389 
    390     fn find_children(
    391         parent: Interface,
    392         interfaces: std.StringArrayHashMap(Interface),
    393         children: *std.StringArrayHashMap(Interface),
    394     ) error{ OutOfMemory, InvalidInterface }!void {
    395         for ([_][]const Message{ parent.requests, parent.events }) |messages| {
    396             for (messages) |message| {
    397                 if (message.kind == .constructor) {
    398                     if (message.kind.constructor) |child_name| {
    399                         if (Interface.version_locked(child_name)) continue;
    400 
    401                         const child = interfaces.get(child_name) orelse {
    402                             log.err("interface '{s}' constructed by message '{s}' not defined in the protocol and not wl_callback or wl_buffer", .{
    403                                 child_name,
    404                                 message.name,
    405                             });
    406                             return error.InvalidInterface;
    407                         };
    408                         try children.put(child_name, child);
    409                         try find_children(child, interfaces, children);
    410                     }
    411                 }
    412             }
    413         }
    414     }
    415 
    416     fn emit(protocol: Protocol, side: Side, targets: []const Target, writer: anytype) !void {
    417         for (protocol.version_locked_interfaces) |interface| {
    418             assert(interface.version == 1);
    419             try interface.emit(side, 1, protocol.namespace, writer);
    420         }
    421 
    422         for (targets) |target| {
    423             for (protocol.globals) |global| {
    424                 if (mem.eql(u8, target.name, global.interface.name)) {
    425                     if (global.interface.version < target.version) {
    426                         fatal("requested {s} version {d} but only version {d} is available in provided xml", .{
    427                             target.name,
    428                             target.version,
    429                             global.interface.version,
    430                         });
    431                     }
    432                     try global.interface.emit(side, target.version, protocol.namespace, writer);
    433                     for (global.children) |child| {
    434                         try child.emit(side, target.version, protocol.namespace, writer);
    435                     }
    436                 }
    437             }
    438         }
    439     }
    440 
    441     fn emitCommon(protocol: Protocol, targets: []const Target, writer: anytype) !void {
    442         for (protocol.version_locked_interfaces) |interface| {
    443             assert(interface.version == 1);
    444             try interface.emitCommon(1, writer);
    445         }
    446 
    447         for (protocol.globals) |global| {
    448             for (targets) |target| {
    449                 if (mem.eql(u8, target.name, global.interface.name)) {
    450                     // We check this in emitClient() which is called first.
    451                     assert(global.interface.version >= target.version);
    452 
    453                     try global.interface.emitCommon(target.version, writer);
    454                     for (global.children) |child| {
    455                         try child.emitCommon(target.version, writer);
    456                     }
    457                     break;
    458                 }
    459             } else {
    460                 try global.interface.emitCommon(null, writer);
    461                 for (global.children) |child| {
    462                     try child.emitCommon(null, writer);
    463                 }
    464             }
    465         }
    466     }
    467 };
    468 
    469 /// All data in this struct is immutable after creation in parse().
    470 const Interface = struct {
    471     name: []const u8,
    472     version: u32,
    473     requests: []const Message,
    474     events: []const Message,
    475     enums: []const Enum,
    476 
    477     // These interfaces are special in that their version may never be increased.
    478     // That is, they are pinned to version 1 forever. They also may break the
    479     // normally required tree object creation hierarchy.
    480     const version_locked_interfaces = std.StaticStringMap(void).initComptime(.{
    481         .{"wl_display"},
    482         .{"wl_registry"},
    483         .{"wl_callback"},
    484         .{"wl_buffer"},
    485     });
    486     fn version_locked(interface_name: []const u8) bool {
    487         return version_locked_interfaces.has(interface_name);
    488     }
    489 
    490     fn parse(arena: mem.Allocator, parser: *xml.Parser) !Interface {
    491         var name: ?[]const u8 = null;
    492         var version: ?u32 = null;
    493         var requests = std.ArrayList(Message).init(gpa);
    494         defer requests.deinit();
    495         var events = std.ArrayList(Message).init(gpa);
    496         defer events.deinit();
    497         var enums = std.ArrayList(Enum).init(gpa);
    498         defer enums.deinit();
    499 
    500         while (parser.next()) |ev| switch (ev) {
    501             .open_tag => |tag| {
    502                 // TODO: parse description
    503                 if (mem.eql(u8, tag, "request"))
    504                     try requests.append(try Message.parse(arena, parser))
    505                 else if (mem.eql(u8, tag, "event"))
    506                     try events.append(try Message.parse(arena, parser))
    507                 else if (mem.eql(u8, tag, "enum"))
    508                     try enums.append(try Enum.parse(arena, parser));
    509             },
    510             .attribute => |attr| {
    511                 if (mem.eql(u8, attr.name, "name")) {
    512                     if (name != null) return error.DuplicateName;
    513                     name = try attr.dupeValue(arena);
    514                 } else if (mem.eql(u8, attr.name, "version")) {
    515                     if (version != null) return error.DuplicateVersion;
    516                     version = try std.fmt.parseInt(u32, try attr.dupeValue(arena), 10);
    517                 }
    518             },
    519             .close_tag => |tag| if (mem.eql(u8, tag, "interface")) {
    520                 return Interface{
    521                     .name = name orelse return error.MissingName,
    522                     .version = version orelse return error.MissingVersion,
    523                     .requests = try arena.dupe(Message, requests.items),
    524                     .events = try arena.dupe(Message, events.items),
    525                     .enums = try arena.dupe(Enum, enums.items),
    526                 };
    527             },
    528             else => {},
    529         };
    530         return error.UnexpectedEndOfFile;
    531     }
    532 
    533     fn emit(interface: Interface, side: Side, target_version: u32, namespace: []const u8, writer: anytype) !void {
    534         try writer.print(
    535             \\pub const {[type]} = opaque {{
    536             \\ pub const generated_version = {[version]};
    537             \\ pub const interface = &common.{[namespace]}.{[interface]}.interface;
    538         , .{
    539             .type = titleCaseTrim(interface.name),
    540             .version = @min(interface.version, target_version),
    541             .namespace = fmtId(namespace),
    542             .interface = fmtId(trimPrefix(interface.name)),
    543         });
    544 
    545         for (interface.enums) |e| {
    546             if (e.since <= target_version) {
    547                 try writer.print("pub const {[type]} = common.{[namespace]}.{[interface]}.{[type]};\n", .{
    548                     .type = titleCase(e.name),
    549                     .namespace = fmtId(namespace),
    550                     .interface = fmtId(trimPrefix(interface.name)),
    551                 });
    552             }
    553         }
    554 
    555         if (side == .client) {
    556             inline for (.{
    557                 .{ .name = "getId", .return_type = "u32" },
    558                 .{ .name = "getVersion", .return_type = "u32" },
    559                 .{ .name = "getUserData", .return_type = "?*anyopaque" },
    560             }) |func| {
    561                 try writer.print(
    562                     \\pub fn {[function]s}(_{[interface]}: *{[type]}) {[return_type]s} {{
    563                     \\    return @as(*client.wl.Proxy, @ptrCast(_{[interface]})).{[function]s}();
    564                     \\}}
    565                 , .{
    566                     .function = func.name,
    567                     .interface = fmtId(trimPrefix(interface.name)),
    568                     .type = titleCaseTrim(interface.name),
    569                     .return_type = func.return_type,
    570                 });
    571             }
    572 
    573             try writer.print(
    574                 \\pub fn setQueue(_{[interface]}: *{[type]}, _queue: *client.wl.EventQueue) void {{
    575                 \\    const _proxy: *client.wl.Proxy = @ptrCast(_{[interface]});
    576                 \\    _proxy.setQueue(_queue);
    577                 \\}}
    578             , .{
    579                 .interface = fmtId(trimPrefix(interface.name)),
    580                 .type = titleCaseTrim(interface.name),
    581             });
    582 
    583             const has_event = for (interface.events) |event| {
    584                 if (event.since <= target_version) break true;
    585             } else false;
    586 
    587             if (has_event) {
    588                 try writer.writeAll("pub const Event = union(enum) {");
    589                 for (interface.events) |event| {
    590                     if (event.since <= target_version) {
    591                         try event.emitField(.client, writer);
    592                     }
    593                 }
    594                 try writer.writeAll("};\n");
    595                 try writer.print(
    596                     \\pub inline fn setListener(
    597                     \\    _{[interface]}: *{[type]},
    598                     \\    comptime T: type,
    599                     \\    _listener: *const fn ({[interface]}: *{[type]}, event: Event, data: T) void,
    600                     \\    _data: T,
    601                     \\) void {{
    602                     \\    const _proxy: *client.wl.Proxy = @ptrCast(_{[interface]});
    603                     \\    const _mut_data: ?*anyopaque = @ptrFromInt(@intFromPtr(_data));
    604                     \\    _proxy.addDispatcher(common.Dispatcher({[type]}, T).dispatcher, _listener, _mut_data);
    605                     \\}}
    606                 , .{
    607                     .interface = fmtId(trimPrefix(interface.name)),
    608                     .type = titleCaseTrim(interface.name),
    609                 });
    610             }
    611 
    612             var has_destroy = false;
    613             for (interface.requests, 0..) |request, opcode| {
    614                 if (request.since <= target_version) {
    615                     if (mem.eql(u8, request.name, "destroy")) has_destroy = true;
    616                     try request.emitFn(side, writer, interface, opcode);
    617                 }
    618             }
    619 
    620             if (mem.eql(u8, interface.name, "wl_display")) {
    621                 try writer.writeAll(@embedFile("client_display_functions.zig"));
    622             } else if (!has_destroy) {
    623                 try writer.print(
    624                     \\pub fn destroy(_{[interface]}: *{[type]}) void {{
    625                     \\    const _proxy: *client.wl.Proxy = @ptrCast(_{[interface]});
    626                     \\    _proxy.destroy();
    627                     \\}}
    628                 , .{
    629                     .interface = fmtId(trimPrefix(interface.name)),
    630                     .type = titleCaseTrim(interface.name),
    631                 });
    632             }
    633         } else {
    634             try writer.print(
    635                 \\pub fn create(_client: *server.wl.Client, _version: u32, _id: u32) !*{(tc)} {{
    636                 \\    return @ptrCast(try server.wl.Resource.create(_client, {[type]}, _version, _id));
    637                 \\}}pub fn destroy(_{[interface]}: *{[type]}) void {{
    638                 \\    return @as(*server.wl.Resource, @ptrCast(_{[interface]})).destroy();
    639                 \\}}pub fn fromLink(_link: *server.wl.list.Link) *{[type]} {{
    640                 \\    return @ptrCast(server.wl.Resource.fromLink(_link));
    641                 \\}}
    642             , .{
    643                 .type = titleCaseTrim(interface.name),
    644                 .interface = fmtId(trimPrefix(interface.name)),
    645             });
    646 
    647             inline for (.{
    648                 .{ .name = "getLink", .return_type = "*server.wl.list.Link" },
    649                 .{ .name = "getClient", .return_type = "*server.wl.Client" },
    650                 .{ .name = "getId", .return_type = "u32" },
    651                 .{ .name = "getVersion", .return_type = "u32" },
    652                 .{ .name = "postNoMemory", .return_type = "void" },
    653                 .{ .name = "getUserData", .return_type = "?*anyopaque" },
    654             }) |func|
    655                 try writer.print(
    656                     \\pub fn {[function]s}(_{[interface]}: *{[type]}) {[return_type]s} {{
    657                     \\    return @as(*server.wl.Resource, @ptrCast(_{[interface]})).{[function]s}();
    658                     \\}}
    659                 , .{
    660                     .function = func.name,
    661                     .interface = fmtId(trimPrefix(interface.name)),
    662                     .type = titleCaseTrim(interface.name),
    663                     .return_type = func.return_type,
    664                 });
    665 
    666             const has_error = for (interface.enums) |e| {
    667                 if (mem.eql(u8, e.name, "error")) break true;
    668             } else false;
    669             if (has_error) {
    670                 try writer.print(
    671                     \\pub fn postError(_{[interface]}: *{[type]}, _err: Error, _message: [*:0]const u8) void {{
    672                     \\    return @as(*server.wl.Resource, @ptrCast(_{[interface]})).postError(@intCast(@intFromEnum(_err)), _message);
    673                     \\}}
    674                 , .{
    675                     .interface = fmtId(trimPrefix(interface.name)),
    676                     .type = titleCaseTrim(interface.name),
    677                 });
    678             }
    679 
    680             const has_request = for (interface.requests) |request| {
    681                 if (request.since <= target_version) break true;
    682             } else false;
    683 
    684             if (has_request) {
    685                 try writer.writeAll("pub const Request = union(enum) {");
    686                 for (interface.requests) |request| {
    687                     if (request.since <= target_version) {
    688                         try request.emitField(.server, writer);
    689                     }
    690                 }
    691                 try writer.writeAll("};\n");
    692                 @setEvalBranchQuota(2500);
    693                 try writer.print(
    694                     \\pub inline fn setHandler(
    695                     \\    _{[interface]}: *{[type]},
    696                     \\    comptime T: type,
    697                     \\    handle_request: *const fn (_{[interface]}: *{[type]}, request: Request, data: T) void,
    698                     \\    comptime handle_destroy: ?fn (_{[interface]}: *{[type]}, data: T) void,
    699                     \\    _data: T,
    700                     \\) void {{
    701                     \\    const _resource: *server.wl.Resource = @ptrCast(_{[interface]});
    702                     \\    _resource.setDispatcher(
    703                     \\        common.Dispatcher({[type]}, T).dispatcher,
    704                     \\        handle_request,
    705                     \\        @ptrFromInt(@intFromPtr(_data)),
    706                     \\        if (handle_destroy) |_handler| struct {{
    707                     \\            fn _wrapper(__resource: *server.wl.Resource) callconv(.C) void {{
    708                     \\                @call(.always_inline, _handler, .{{
    709                     \\                    @as(*{[type]}, @ptrCast(__resource)),
    710                     \\                    @as(T, @ptrCast(@alignCast(__resource.getUserData()))),
    711                     \\                }});
    712                     \\            }}
    713                     \\        }}._wrapper else null,
    714                     \\    );
    715                     \\}}
    716                 , .{
    717                     .interface = fmtId(trimPrefix(interface.name)),
    718                     .type = titleCaseTrim(interface.name),
    719                 });
    720             } else {
    721                 try writer.print(
    722                     \\pub inline fn setHandler(
    723                     \\    _{[interface]}: *{[type]},
    724                     \\    comptime T: type,
    725                     \\    comptime handle_destroy: ?fn (_{[interface]}: *{[type]}, data: T) void,
    726                     \\    _data: T,
    727                     \\) void {{
    728                     \\    const _resource: *server.wl.Resource = @ptrCast(_{[interface]});
    729                     \\    _resource.setDispatcher(
    730                     \\        null,
    731                     \\        null,
    732                     \\        @ptrFromInt(@intFromPtr(_data)),
    733                     \\        if (handle_destroy) |_handler| struct {{
    734                     \\            fn _wrapper(__resource: *server.wl.Resource) callconv(.C) void {{
    735                     \\                @call(.always_inline, _handler, .{{
    736                     \\                    @as(*{[type]}, @ptrCast(__resource)),
    737                     \\                    @as(?*anyopaque, @ptrFromInt(@intFromPtr(__resource.getUserData()))),
    738                     \\                }});
    739                     \\            }}
    740                     \\        }}._wrapper else null,
    741                     \\    );
    742                     \\}}
    743                 , .{
    744                     .interface = fmtId(trimPrefix(interface.name)),
    745                     .type = titleCaseTrim(interface.name),
    746                 });
    747             }
    748 
    749             for (interface.events, 0..) |event, opcode| {
    750                 if (event.since <= target_version) {
    751                     try event.emitFn(side, writer, interface, opcode);
    752                 }
    753             }
    754         }
    755 
    756         try writer.writeAll("};\n");
    757     }
    758 
    759     fn emitCommon(interface: Interface, target_version: ?u32, writer: anytype) !void {
    760         try writer.print("const {} = struct {{", .{fmtId(trimPrefix(interface.name))});
    761 
    762         try writer.print(
    763             \\const interface: common.Interface = .{{
    764             \\    .name = "{[name]s}",
    765             \\    .version = {[version]d},
    766             \\    .method_count = {[requests_len]d},
    767         , .{
    768             .name = interface.name,
    769             .version = interface.version,
    770             .requests_len = interface.requests.len,
    771         });
    772         if (interface.requests.len == 0) {
    773             try writer.writeAll(".methods = null,");
    774         } else {
    775             try writer.writeAll(".methods = &.{");
    776             for (interface.requests) |request| {
    777                 try request.emitCommon(writer);
    778             }
    779             try writer.writeAll("},");
    780         }
    781         try writer.print(".event_count = {d},", .{interface.events.len});
    782         if (interface.events.len == 0) {
    783             try writer.writeAll(".events = null,");
    784         } else {
    785             try writer.writeAll(".events = &.{");
    786             for (interface.events) |event| {
    787                 try event.emitCommon(writer);
    788             }
    789             try writer.writeAll("},");
    790         }
    791         try writer.writeAll("};");
    792 
    793         if (target_version) |target| {
    794             for (interface.enums) |e| {
    795                 if (e.since <= target) {
    796                     try e.emit(target, writer);
    797                 }
    798             }
    799         }
    800 
    801         try writer.writeAll("};");
    802     }
    803 };
    804 
    805 /// All data in this struct is immutable after creation in parse().
    806 const Message = struct {
    807     name: []const u8,
    808     since: u32,
    809     args: []const Arg,
    810     kind: union(enum) {
    811         normal: void,
    812         constructor: ?[]const u8,
    813         destructor: void,
    814     },
    815 
    816     fn parse(arena: mem.Allocator, parser: *xml.Parser) !Message {
    817         var name: ?[]const u8 = null;
    818         var since: ?u32 = null;
    819         var args = std.ArrayList(Arg).init(gpa);
    820         defer args.deinit();
    821         var destructor = false;
    822 
    823         while (parser.next()) |ev| switch (ev) {
    824             .open_tag => |tag| {
    825                 // TODO: parse description
    826                 if (mem.eql(u8, tag, "arg"))
    827                     try args.append(try Arg.parse(arena, parser));
    828             },
    829             .attribute => |attr| {
    830                 if (mem.eql(u8, attr.name, "name")) {
    831                     if (name != null) return error.DuplicateName;
    832                     name = try attr.dupeValue(arena);
    833                 } else if (mem.eql(u8, attr.name, "since")) {
    834                     if (since != null) return error.DuplicateSince;
    835                     since = try std.fmt.parseInt(u32, try attr.dupeValue(arena), 10);
    836                 } else if (mem.eql(u8, attr.name, "type")) {
    837                     if (attr.valueEql("destructor")) {
    838                         destructor = true;
    839                     } else {
    840                         return error.InvalidType;
    841                     }
    842                 }
    843             },
    844             .close_tag => |tag| if (mem.eql(u8, tag, "request") or mem.eql(u8, tag, "event")) {
    845                 return Message{
    846                     .name = name orelse return error.MissingName,
    847                     .since = since orelse 1,
    848                     .args = try arena.dupe(Arg, args.items),
    849                     .kind = blk: {
    850                         if (destructor) break :blk .destructor;
    851                         for (args.items) |arg|
    852                             if (arg.kind == .new_id) break :blk .{ .constructor = arg.kind.new_id };
    853                         break :blk .normal;
    854                     },
    855                 };
    856             },
    857             else => {},
    858         };
    859         return error.UnexpectedEndOfFile;
    860     }
    861 
    862     fn emitField(message: Message, side: Side, writer: anytype) !void {
    863         try writer.print("{}", .{fmtId(message.name)});
    864         if (message.args.len == 0) {
    865             try writer.writeAll(": void,");
    866             return;
    867         }
    868         try writer.writeAll(": struct {");
    869         for (message.args) |arg| {
    870             if (side == .server and arg.kind == .new_id and arg.kind.new_id == null) {
    871                 try writer.print("interface_name: [*:0]const u8, version: u32,{}: u32", .{fmtId(arg.name)});
    872             } else if (side == .client and arg.kind == .new_id) {
    873                 try writer.print("{}: *", .{fmtId(arg.name)});
    874                 try printAbsolute(.client, writer, arg.kind.new_id.?);
    875                 assert(!arg.allow_null);
    876             } else {
    877                 try writer.print("{}:", .{fmtId(arg.name)});
    878                 // See notes on NULL in doc comment for wl_message in wayland-util.h
    879                 if (side == .client and arg.kind == .object and !arg.allow_null)
    880                     try writer.writeByte('?');
    881                 try arg.emitType(side, writer);
    882             }
    883             try writer.writeByte(',');
    884         }
    885         try writer.writeAll("},\n");
    886     }
    887 
    888     fn emitFn(message: Message, side: Side, writer: anytype, interface: Interface, opcode: usize) !void {
    889         try writer.writeAll("pub fn ");
    890         if (side == .server) {
    891             if (message.kind == .destructor) {
    892                 try writer.print("destroySend{}", .{titleCase(message.name)});
    893             } else {
    894                 try writer.print("send{}", .{titleCase(message.name)});
    895             }
    896         } else {
    897             try writer.print("{}", .{camelCase(message.name)});
    898         }
    899         try writer.print("(_{}: *{}", .{
    900             fmtId(trimPrefix(interface.name)),
    901             titleCaseTrim(interface.name),
    902         });
    903         for (message.args) |arg| {
    904             if (side == .server and arg.kind == .new_id) {
    905                 try writer.print(", _{s}:", .{arg.name});
    906                 if (arg.allow_null) try writer.writeByte('?');
    907                 try writer.writeByte('*');
    908                 if (arg.kind.new_id) |iface| {
    909                     try printAbsolute(side, writer, iface);
    910                 } else {
    911                     try writer.writeAll("server.wl.Resource");
    912                 }
    913             } else if (side == .client and arg.kind == .new_id) {
    914                 if (arg.kind.new_id == null) try writer.writeAll(", comptime T: type, _version: u32");
    915             } else {
    916                 try writer.print(", _{s}:", .{arg.name});
    917                 try arg.emitType(side, writer);
    918             }
    919         }
    920         if (side == .server or message.kind != .constructor) {
    921             try writer.writeAll(") void {");
    922         } else if (message.kind.constructor) |new_iface| {
    923             try writer.writeAll(") !*");
    924             try printAbsolute(side, writer, new_iface);
    925             try writer.writeByte('{');
    926         } else {
    927             try writer.writeAll(") !*T {");
    928         }
    929         if (side == .server) {
    930             try writer.writeAll("const _resource: *server.wl.Resource = @ptrCast(_");
    931         } else {
    932             // wl_registry.bind for example needs special handling
    933             if (message.kind == .constructor and message.kind.constructor == null) {
    934                 try writer.writeAll("const version_to_construct = @min(T.generated_version, _version);");
    935             }
    936             try writer.writeAll("const _proxy: *client.wl.Proxy = @ptrCast(_");
    937         }
    938         try writer.print("{});", .{fmtId(trimPrefix(interface.name))});
    939         if (message.args.len > 0) {
    940             try writer.writeAll("var _args = [_]common.Argument{");
    941             for (message.args) |arg| {
    942                 switch (arg.kind) {
    943                     .int, .uint, .fixed, .string, .array, .fd => {
    944                         try writer.writeAll(switch (arg.kind) {
    945                             .int => ".{ .i = ",
    946                             .uint => ".{ .u = ",
    947                             .fixed => ".{ .f = ",
    948                             .string => ".{ .s = ",
    949                             .array => ".{ .a = ",
    950                             .fd => ".{ .h = ",
    951                             else => unreachable,
    952                         });
    953                         if (arg.enum_name != null) {
    954                             try writer.writeAll("switch (@typeInfo(");
    955                             try arg.emitType(side, writer);
    956 
    957                             // TODO We know the type of the enum at scanning time, but it's
    958                             //      currently a bit difficult to access it.
    959                             const c_type = if (arg.kind == .uint) "u32" else "i32";
    960                             try writer.print(
    961                                 \\ )) {{
    962                                 \\    .@"enum" => @as({[ct]s}, @intCast(@intFromEnum(_{[an]}))),
    963                                 \\    .@"struct" => @bitCast(_{[an]}),
    964                                 \\    else => unreachable,
    965                                 \\ }}
    966                             , .{ .ct = c_type, .an = fmtId(arg.name) });
    967                         } else {
    968                             try writer.print("_{s}", .{arg.name});
    969                         }
    970                         try writer.writeAll("},");
    971                     },
    972                     .object, .new_id => |new_iface| {
    973                         if (arg.kind == .object or side == .server) {
    974                             try writer.print(".{{ .o = @ptrCast(_{s}) }},", .{arg.name});
    975                         } else {
    976                             if (new_iface == null) {
    977                                 try writer.writeAll(
    978                                     \\.{ .s = T.interface.name },
    979                                     \\.{ .u = version_to_construct },
    980                                 );
    981                             }
    982                             try writer.writeAll(".{ .o = null },");
    983                         }
    984                     },
    985                 }
    986             }
    987             try writer.writeAll("};\n");
    988         }
    989         const args = if (message.args.len > 0) "&_args" else "null";
    990         if (side == .server) {
    991             try writer.print("_resource.postEvent({}, {s});", .{ opcode, args });
    992             if (message.kind == .destructor) try writer.writeAll("_resource.destroy();");
    993         } else switch (message.kind) {
    994             .normal, .destructor => {
    995                 try writer.print("_proxy.marshal({}, {s});", .{ opcode, args });
    996                 if (message.kind == .destructor) try writer.writeAll("_proxy.destroy();");
    997             },
    998             .constructor => |new_iface| {
    999                 if (new_iface) |i| {
   1000                     try writer.print("return @ptrCast(try _proxy.marshalConstructor({}, &_args, ", .{opcode});
   1001                     try printAbsolute(side, writer, i);
   1002                     try writer.writeAll(".interface));");
   1003                 } else {
   1004                     try writer.print(
   1005                         \\return @ptrCast(try _proxy.marshalConstructorVersioned({[opcode]}, &_args, T.interface, version_to_construct));
   1006                     , .{
   1007                         .opcode = opcode,
   1008                     });
   1009                 }
   1010             },
   1011         }
   1012         try writer.writeAll("}\n");
   1013     }
   1014 
   1015     fn emitCommon(message: Message, writer: anytype) !void {
   1016         try writer.print(
   1017             \\.{{ .name = "{s}", .signature = "
   1018         , .{message.name});
   1019         if (message.since > 1) {
   1020             try writer.print("{d}", .{message.since});
   1021         }
   1022         for (message.args) |arg| {
   1023             try arg.emitSignature(writer);
   1024         }
   1025         try writer.writeAll("\",");
   1026         if (message.args.len == 0) {
   1027             try writer.writeAll(".types = null,");
   1028         } else {
   1029             try writer.writeAll(".types = &.{");
   1030             for (message.args) |arg| {
   1031                 switch (arg.kind) {
   1032                     .new_id, .object => |interface| {
   1033                         if (interface) |name| {
   1034                             try writer.print("&common.{s}.{s}.interface,", .{
   1035                                 prefix(name).?,
   1036                                 trimPrefix(name),
   1037                             });
   1038                         } else if (arg.kind == .new_id) {
   1039                             try writer.writeAll("null,null,null,");
   1040                         } else {
   1041                             try writer.writeAll("null,");
   1042                         }
   1043                     },
   1044                     .int,
   1045                     .uint,
   1046                     .fixed,
   1047                     .string,
   1048                     .array,
   1049                     .fd,
   1050                     => try writer.writeAll("null,"),
   1051                 }
   1052             }
   1053             try writer.writeAll("},");
   1054         }
   1055         try writer.writeAll("},");
   1056     }
   1057 };
   1058 
   1059 /// All data in this struct is immutable after creation in parse().
   1060 const Arg = struct {
   1061     const Type = union(enum) {
   1062         int,
   1063         uint,
   1064         fixed,
   1065         string,
   1066         new_id: ?[]const u8,
   1067         object: ?[]const u8,
   1068         array,
   1069         fd,
   1070     };
   1071     name: []const u8,
   1072     kind: Type,
   1073     allow_null: bool,
   1074     enum_name: ?[]const u8,
   1075 
   1076     fn parse(arena: mem.Allocator, parser: *xml.Parser) !Arg {
   1077         var name: ?[]const u8 = null;
   1078         var kind: ?std.meta.Tag(Type) = null;
   1079         var interface: ?[]const u8 = null;
   1080         var allow_null: ?bool = null;
   1081         var enum_name: ?[]const u8 = null;
   1082 
   1083         while (parser.next()) |ev| switch (ev) {
   1084             .attribute => |attr| {
   1085                 if (mem.eql(u8, attr.name, "name")) {
   1086                     if (name != null) return error.DuplicateName;
   1087                     name = try attr.dupeValue(arena);
   1088                 } else if (mem.eql(u8, attr.name, "type")) {
   1089                     if (kind != null) return error.DuplicateType;
   1090                     kind = std.meta.stringToEnum(std.meta.Tag(Type), try attr.dupeValue(arena)) orelse
   1091                         return error.InvalidType;
   1092                 } else if (mem.eql(u8, attr.name, "interface")) {
   1093                     if (interface != null) return error.DuplicateInterface;
   1094                     interface = try attr.dupeValue(arena);
   1095                 } else if (mem.eql(u8, attr.name, "allow-null")) {
   1096                     if (allow_null != null) return error.DuplicateAllowNull;
   1097                     if (!attr.valueEql("true") and !attr.valueEql("false")) return error.InvalidBoolValue;
   1098                     allow_null = attr.valueEql("true");
   1099                 } else if (mem.eql(u8, attr.name, "enum")) {
   1100                     if (enum_name != null) return error.DuplicateEnum;
   1101                     enum_name = try attr.dupeValue(arena);
   1102                 }
   1103             },
   1104             .close_tag => |tag| if (mem.eql(u8, tag, "arg")) {
   1105                 return Arg{
   1106                     .name = name orelse return error.MissingName,
   1107                     .kind = switch (kind orelse return error.MissingType) {
   1108                         .object => .{ .object = interface },
   1109                         .new_id => .{ .new_id = interface },
   1110                         .int => .int,
   1111                         .uint => .uint,
   1112                         .fixed => .fixed,
   1113                         .string => .string,
   1114                         .array => .array,
   1115                         .fd => .fd,
   1116                     },
   1117                     .allow_null = allow_null orelse false,
   1118                     .enum_name = enum_name,
   1119                 };
   1120             },
   1121             else => {},
   1122         };
   1123         return error.UnexpectedEndOfFile;
   1124     }
   1125 
   1126     fn emitSignature(arg: Arg, writer: anytype) !void {
   1127         switch (arg.kind) {
   1128             .int => try writer.writeByte('i'),
   1129             .uint => try writer.writeByte('u'),
   1130             .fixed => try writer.writeByte('f'),
   1131             .string => {
   1132                 if (arg.allow_null) try writer.writeByte('?');
   1133                 try writer.writeByte('s');
   1134             },
   1135             .new_id => |interface| if (interface == null)
   1136                 try writer.writeAll("sun")
   1137             else
   1138                 try writer.writeByte('n'),
   1139             .object => {
   1140                 if (arg.allow_null) try writer.writeByte('?');
   1141                 try writer.writeByte('o');
   1142             },
   1143             .array => try writer.writeByte('a'),
   1144             .fd => try writer.writeByte('h'),
   1145         }
   1146     }
   1147 
   1148     fn emitType(arg: Arg, side: Side, writer: anytype) !void {
   1149         switch (arg.kind) {
   1150             .int, .uint => {
   1151                 if (arg.enum_name) |name| {
   1152                     if (mem.indexOfScalar(u8, name, '.')) |dot_index| {
   1153                         // Turn a reference like wl_shm.format into common.wl.shm.Format
   1154                         const us_index = mem.indexOfScalar(u8, name, '_') orelse 0;
   1155                         try writer.print("common.{s}.{s}{}", .{
   1156                             name[0..us_index],
   1157                             name[us_index + 1 .. dot_index + 1],
   1158                             titleCase(name[dot_index + 1 ..]),
   1159                         });
   1160                     } else {
   1161                         try writer.print("{}", .{titleCase(name)});
   1162                     }
   1163                 } else if (arg.kind == .int) {
   1164                     try writer.writeAll("i32");
   1165                 } else {
   1166                     try writer.writeAll("u32");
   1167                 }
   1168             },
   1169             .new_id => try writer.writeAll("u32"),
   1170             .fixed => try writer.writeAll("common.Fixed"),
   1171             .string => {
   1172                 if (arg.allow_null) try writer.writeByte('?');
   1173                 try writer.writeAll("[*:0]const u8");
   1174             },
   1175             .object => |interface| if (interface) |i| {
   1176                 if (arg.allow_null) try writer.writeAll("?*") else try writer.writeByte('*');
   1177                 try printAbsolute(side, writer, i);
   1178             } else {
   1179                 if (arg.allow_null) try writer.writeByte('?');
   1180                 try writer.writeAll("*common.Object");
   1181             },
   1182             .array => {
   1183                 try writer.writeAll("*common.Array");
   1184             },
   1185             .fd => try writer.writeAll("i32"),
   1186         }
   1187     }
   1188 };
   1189 
   1190 /// All data in this struct is immutable after creation in parse().
   1191 const Enum = struct {
   1192     name: []const u8,
   1193     since: u32,
   1194     entries: []const Entry,
   1195     bitfield: bool,
   1196 
   1197     fn parse(arena: mem.Allocator, parser: *xml.Parser) !Enum {
   1198         var name: ?[]const u8 = null;
   1199         var since: ?u32 = null;
   1200         var entries = std.ArrayList(Entry).init(gpa);
   1201         defer entries.deinit();
   1202         var bitfield: ?bool = null;
   1203 
   1204         while (parser.next()) |ev| switch (ev) {
   1205             .open_tag => |tag| {
   1206                 // TODO: parse description
   1207                 if (mem.eql(u8, tag, "entry"))
   1208                     try entries.append(try Entry.parse(arena, parser));
   1209             },
   1210             .attribute => |attr| {
   1211                 if (mem.eql(u8, attr.name, "name")) {
   1212                     if (name != null) return error.DuplicateName;
   1213                     name = try attr.dupeValue(arena);
   1214                 } else if (mem.eql(u8, attr.name, "since")) {
   1215                     if (since != null) return error.DuplicateSince;
   1216                     since = try std.fmt.parseInt(u32, try attr.dupeValue(arena), 10);
   1217                 } else if (mem.eql(u8, attr.name, "bitfield")) {
   1218                     if (bitfield != null) return error.DuplicateBitfield;
   1219                     if (!attr.valueEql("true") and !attr.valueEql("false")) return error.InvalidBoolValue;
   1220                     bitfield = attr.valueEql("true");
   1221                 }
   1222             },
   1223             .close_tag => |tag| if (mem.eql(u8, tag, "enum")) {
   1224                 return Enum{
   1225                     .name = name orelse return error.MissingName,
   1226                     .since = since orelse 1,
   1227                     .entries = try arena.dupe(Entry, entries.items),
   1228                     .bitfield = bitfield orelse false,
   1229                 };
   1230             },
   1231             else => {},
   1232         };
   1233         return error.UnexpectedEndOfFile;
   1234     }
   1235 
   1236     fn emit(e: Enum, target_version: u32, writer: anytype) !void {
   1237         try writer.print("const {}", .{titleCase(e.name)});
   1238 
   1239         if (e.bitfield) {
   1240             try writer.writeAll(" = packed struct(u32) {");
   1241             for (0..32) |i| {
   1242                 for (e.entries) |entry| {
   1243                     if (entry.since > target_version) continue;
   1244 
   1245                     const value = entry.intValue();
   1246                     if (value == 0) continue;
   1247 
   1248                     if (value == (@as(u32, 1) << @intCast(i))) {
   1249                         try writer.print("{s}: bool = false,", .{entry.name});
   1250                         break;
   1251                     }
   1252                 } else {
   1253                     try writer.print("_padding{}: bool = false,", .{i});
   1254                 }
   1255             }
   1256 
   1257             // Emit the normal C abi enum as well as it may be needed to interface
   1258             // with C code.
   1259             try writer.writeAll("pub const Enum ");
   1260         }
   1261 
   1262         try writer.writeAll(" = enum(c_int) {");
   1263         for (e.entries) |entry| {
   1264             if (entry.since <= target_version) {
   1265                 try writer.print("{}= {s},", .{ fmtId(entry.name), entry.value });
   1266             }
   1267         }
   1268         // Always generate non-exhaustive enums to ensure forward compatability.
   1269         // Entries have been added to wl_shm.format without bumping the version.
   1270         try writer.writeAll("_,};\n");
   1271 
   1272         if (e.bitfield) try writer.writeAll("};\n");
   1273     }
   1274 };
   1275 
   1276 /// All data in this struct is immutable after creation in parse().
   1277 const Entry = struct {
   1278     name: []const u8,
   1279     since: u32,
   1280     value: []const u8,
   1281 
   1282     fn parse(arena: mem.Allocator, parser: *xml.Parser) !Entry {
   1283         var name: ?[]const u8 = null;
   1284         var since: ?u32 = null;
   1285         var value: ?[]const u8 = null;
   1286 
   1287         while (parser.next()) |ev| switch (ev) {
   1288             .attribute => |attr| {
   1289                 if (mem.eql(u8, attr.name, "name")) {
   1290                     if (name != null) return error.DuplicateName;
   1291                     name = try attr.dupeValue(arena);
   1292                 } else if (mem.eql(u8, attr.name, "since")) {
   1293                     if (since != null) return error.DuplicateSince;
   1294                     since = try std.fmt.parseInt(u32, try attr.dupeValue(arena), 10);
   1295                 } else if (mem.eql(u8, attr.name, "value")) {
   1296                     if (value != null) return error.DuplicateName;
   1297                     value = try attr.dupeValue(arena);
   1298                 }
   1299             },
   1300             .close_tag => |tag| if (mem.eql(u8, tag, "entry")) {
   1301                 return Entry{
   1302                     .name = name orelse return error.MissingName,
   1303                     .since = since orelse 1,
   1304                     .value = value orelse return error.MissingValue,
   1305                 };
   1306             },
   1307             else => {},
   1308         };
   1309         return error.UnexpectedEndOfFile;
   1310     }
   1311 
   1312     // Return numeric value of enum entry. Can be base 10 and hexadecimal notation.
   1313     fn intValue(e: Entry) u32 {
   1314         return std.fmt.parseInt(u32, e.value, 10) catch blk: {
   1315             const index = mem.indexOfScalar(u8, e.value, 'x').?;
   1316             break :blk std.fmt.parseInt(u32, e.value[index + 1 ..], 16) catch @panic("Can't parse enum entry.");
   1317         };
   1318     }
   1319 };
   1320 
   1321 fn prefix(s: []const u8) ?[]const u8 {
   1322     return s[0 .. mem.indexOfScalar(u8, s, '_') orelse return null];
   1323 }
   1324 
   1325 fn trimPrefix(s: []const u8) []const u8 {
   1326     return s[mem.indexOfScalar(u8, s, '_').? + 1 ..];
   1327 }
   1328 
   1329 const Case = enum { title, camel };
   1330 
   1331 fn formatCaseImpl(comptime case: Case, comptime trim: bool) type {
   1332     return struct {
   1333         pub fn f(
   1334             bytes: []const u8,
   1335             comptime _: []const u8,
   1336             _: std.fmt.FormatOptions,
   1337             writer: anytype,
   1338         ) !void {
   1339             if (case == .camel and std.zig.Token.getKeyword(bytes) != null) {
   1340                 try writer.print("@\"{s}\"", .{bytes});
   1341                 return;
   1342             }
   1343             var upper = case == .title;
   1344             const str = if (trim) trimPrefix(bytes) else bytes;
   1345             for (str) |c| {
   1346                 if (c == '_') {
   1347                     upper = true;
   1348                     continue;
   1349                 }
   1350                 try writer.writeByte(if (upper) std.ascii.toUpper(c) else c);
   1351                 upper = false;
   1352             }
   1353         }
   1354     };
   1355 }
   1356 
   1357 fn titleCase(bytes: []const u8) std.fmt.Formatter(formatCaseImpl(.title, false).f) {
   1358     return .{ .data = bytes };
   1359 }
   1360 
   1361 fn titleCaseTrim(bytes: []const u8) std.fmt.Formatter(formatCaseImpl(.title, true).f) {
   1362     return .{ .data = bytes };
   1363 }
   1364 
   1365 fn camelCase(bytes: []const u8) std.fmt.Formatter(formatCaseImpl(.camel, false).f) {
   1366     return .{ .data = bytes };
   1367 }
   1368 
   1369 fn camelCaseTrim(bytes: []const u8) std.fmt.Formatter(formatCaseImpl(.camel, true).f) {
   1370     return .{ .data = bytes };
   1371 }
   1372 
   1373 fn printAbsolute(side: Side, writer: anytype, interface: []const u8) !void {
   1374     try writer.print("{s}.{s}.{}", .{
   1375         @tagName(side),
   1376         prefix(interface) orelse return error.MissingPrefix,
   1377         titleCaseTrim(interface),
   1378     });
   1379 }
   1380 
   1381 inline fn fatal(comptime fmt: []const u8, args: anytype) noreturn {
   1382     log.err(fmt, args);
   1383     posix.exit(1);
   1384 }