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 }