ast.zig (47075B) - Raw
1 const std = @import("std"); 2 const Allocator = std.mem.Allocator; 3 const Token = @import("lex.zig").Token; 4 const SupportedCodePage = @import("code_pages.zig").SupportedCodePage; 5 6 pub const Tree = struct { 7 node: *Node, 8 input_code_pages: CodePageLookup, 9 output_code_pages: CodePageLookup, 10 11 /// not owned by the tree 12 source: []const u8, 13 14 arena: std.heap.ArenaAllocator.State, 15 allocator: Allocator, 16 17 pub fn deinit(self: *Tree) void { 18 self.arena.promote(self.allocator).deinit(); 19 } 20 21 pub fn root(self: *Tree) *Node.Root { 22 return @alignCast(@fieldParentPtr("base", self.node)); 23 } 24 25 pub fn dump(self: *Tree, writer: anytype) @TypeOf(writer).Error!void { 26 try self.node.dump(self, writer, 0); 27 } 28 }; 29 30 pub const CodePageLookup = struct { 31 lookup: std.ArrayListUnmanaged(SupportedCodePage) = .empty, 32 allocator: Allocator, 33 default_code_page: SupportedCodePage, 34 35 pub fn init(allocator: Allocator, default_code_page: SupportedCodePage) CodePageLookup { 36 return .{ 37 .allocator = allocator, 38 .default_code_page = default_code_page, 39 }; 40 } 41 42 pub fn deinit(self: *CodePageLookup) void { 43 self.lookup.deinit(self.allocator); 44 } 45 46 /// line_num is 1-indexed 47 pub fn setForLineNum(self: *CodePageLookup, line_num: usize, code_page: SupportedCodePage) !void { 48 const index = line_num - 1; 49 if (index >= self.lookup.items.len) { 50 const new_size = line_num; 51 const missing_lines_start_index = self.lookup.items.len; 52 try self.lookup.resize(self.allocator, new_size); 53 54 // If there are any gaps created, we need to fill them in with the value of the 55 // last line before the gap. This can happen for e.g. string literals that 56 // span multiple lines, or if the start of a file has multiple empty lines. 57 const fill_value = if (missing_lines_start_index > 0) 58 self.lookup.items[missing_lines_start_index - 1] 59 else 60 self.default_code_page; 61 var i: usize = missing_lines_start_index; 62 while (i < new_size - 1) : (i += 1) { 63 self.lookup.items[i] = fill_value; 64 } 65 } 66 self.lookup.items[index] = code_page; 67 } 68 69 pub fn setForToken(self: *CodePageLookup, token: Token, code_page: SupportedCodePage) !void { 70 return self.setForLineNum(token.line_number, code_page); 71 } 72 73 /// line_num is 1-indexed 74 pub fn getForLineNum(self: CodePageLookup, line_num: usize) SupportedCodePage { 75 return self.lookup.items[line_num - 1]; 76 } 77 78 pub fn getForToken(self: CodePageLookup, token: Token) SupportedCodePage { 79 return self.getForLineNum(token.line_number); 80 } 81 }; 82 83 test "CodePageLookup" { 84 var lookup = CodePageLookup.init(std.testing.allocator, .windows1252); 85 defer lookup.deinit(); 86 87 try lookup.setForLineNum(5, .utf8); 88 try std.testing.expectEqual(SupportedCodePage.windows1252, lookup.getForLineNum(1)); 89 try std.testing.expectEqual(SupportedCodePage.windows1252, lookup.getForLineNum(2)); 90 try std.testing.expectEqual(SupportedCodePage.windows1252, lookup.getForLineNum(3)); 91 try std.testing.expectEqual(SupportedCodePage.windows1252, lookup.getForLineNum(4)); 92 try std.testing.expectEqual(SupportedCodePage.utf8, lookup.getForLineNum(5)); 93 try std.testing.expectEqual(@as(usize, 5), lookup.lookup.items.len); 94 95 try lookup.setForLineNum(7, .windows1252); 96 try std.testing.expectEqual(SupportedCodePage.windows1252, lookup.getForLineNum(1)); 97 try std.testing.expectEqual(SupportedCodePage.windows1252, lookup.getForLineNum(2)); 98 try std.testing.expectEqual(SupportedCodePage.windows1252, lookup.getForLineNum(3)); 99 try std.testing.expectEqual(SupportedCodePage.windows1252, lookup.getForLineNum(4)); 100 try std.testing.expectEqual(SupportedCodePage.utf8, lookup.getForLineNum(5)); 101 try std.testing.expectEqual(SupportedCodePage.utf8, lookup.getForLineNum(6)); 102 try std.testing.expectEqual(SupportedCodePage.windows1252, lookup.getForLineNum(7)); 103 try std.testing.expectEqual(@as(usize, 7), lookup.lookup.items.len); 104 } 105 106 pub const Node = struct { 107 id: Id, 108 109 pub const Id = enum { 110 root, 111 resource_external, 112 resource_raw_data, 113 literal, 114 binary_expression, 115 grouped_expression, 116 not_expression, 117 accelerators, 118 accelerator, 119 dialog, 120 control_statement, 121 toolbar, 122 menu, 123 menu_item, 124 menu_item_separator, 125 menu_item_ex, 126 popup, 127 popup_ex, 128 version_info, 129 version_statement, 130 block, 131 block_value, 132 block_value_value, 133 string_table, 134 string_table_string, 135 language_statement, 136 font_statement, 137 simple_statement, 138 invalid, 139 140 pub fn Type(comptime id: Id) type { 141 return switch (id) { 142 .root => Root, 143 .resource_external => ResourceExternal, 144 .resource_raw_data => ResourceRawData, 145 .literal => Literal, 146 .binary_expression => BinaryExpression, 147 .grouped_expression => GroupedExpression, 148 .not_expression => NotExpression, 149 .accelerators => Accelerators, 150 .accelerator => Accelerator, 151 .dialog => Dialog, 152 .control_statement => ControlStatement, 153 .toolbar => Toolbar, 154 .menu => Menu, 155 .menu_item => MenuItem, 156 .menu_item_separator => MenuItemSeparator, 157 .menu_item_ex => MenuItemEx, 158 .popup => Popup, 159 .popup_ex => PopupEx, 160 .version_info => VersionInfo, 161 .version_statement => VersionStatement, 162 .block => Block, 163 .block_value => BlockValue, 164 .block_value_value => BlockValueValue, 165 .string_table => StringTable, 166 .string_table_string => StringTableString, 167 .language_statement => LanguageStatement, 168 .font_statement => FontStatement, 169 .simple_statement => SimpleStatement, 170 .invalid => Invalid, 171 }; 172 } 173 }; 174 175 pub fn cast(base: *Node, comptime id: Id) ?*id.Type() { 176 if (base.id == id) { 177 return @alignCast(@fieldParentPtr("base", base)); 178 } 179 return null; 180 } 181 182 pub const Root = struct { 183 base: Node = .{ .id = .root }, 184 body: []*Node, 185 }; 186 187 pub const ResourceExternal = struct { 188 base: Node = .{ .id = .resource_external }, 189 id: Token, 190 type: Token, 191 common_resource_attributes: []Token, 192 filename: *Node, 193 }; 194 195 pub const ResourceRawData = struct { 196 base: Node = .{ .id = .resource_raw_data }, 197 id: Token, 198 type: Token, 199 common_resource_attributes: []Token, 200 begin_token: Token, 201 raw_data: []*Node, 202 end_token: Token, 203 }; 204 205 pub const Literal = struct { 206 base: Node = .{ .id = .literal }, 207 token: Token, 208 }; 209 210 pub const BinaryExpression = struct { 211 base: Node = .{ .id = .binary_expression }, 212 operator: Token, 213 left: *Node, 214 right: *Node, 215 }; 216 217 pub const GroupedExpression = struct { 218 base: Node = .{ .id = .grouped_expression }, 219 open_token: Token, 220 expression: *Node, 221 close_token: Token, 222 }; 223 224 pub const NotExpression = struct { 225 base: Node = .{ .id = .not_expression }, 226 not_token: Token, 227 number_token: Token, 228 }; 229 230 pub const Accelerators = struct { 231 base: Node = .{ .id = .accelerators }, 232 id: Token, 233 type: Token, 234 common_resource_attributes: []Token, 235 optional_statements: []*Node, 236 begin_token: Token, 237 accelerators: []*Node, 238 end_token: Token, 239 }; 240 241 pub const Accelerator = struct { 242 base: Node = .{ .id = .accelerator }, 243 event: *Node, 244 idvalue: *Node, 245 type_and_options: []Token, 246 }; 247 248 pub const Dialog = struct { 249 base: Node = .{ .id = .dialog }, 250 id: Token, 251 type: Token, 252 common_resource_attributes: []Token, 253 x: *Node, 254 y: *Node, 255 width: *Node, 256 height: *Node, 257 help_id: ?*Node, 258 optional_statements: []*Node, 259 begin_token: Token, 260 controls: []*Node, 261 end_token: Token, 262 }; 263 264 pub const ControlStatement = struct { 265 base: Node = .{ .id = .control_statement }, 266 type: Token, 267 text: ?Token, 268 /// Only relevant for the user-defined CONTROL control 269 class: ?*Node, 270 id: *Node, 271 x: *Node, 272 y: *Node, 273 width: *Node, 274 height: *Node, 275 style: ?*Node, 276 exstyle: ?*Node, 277 help_id: ?*Node, 278 extra_data_begin: ?Token, 279 extra_data: []*Node, 280 extra_data_end: ?Token, 281 282 /// Returns true if this node describes a user-defined CONTROL control 283 /// https://learn.microsoft.com/en-us/windows/win32/menurc/control-control 284 pub fn isUserDefined(self: *const ControlStatement) bool { 285 return self.class != null; 286 } 287 }; 288 289 pub const Toolbar = struct { 290 base: Node = .{ .id = .toolbar }, 291 id: Token, 292 type: Token, 293 common_resource_attributes: []Token, 294 button_width: *Node, 295 button_height: *Node, 296 begin_token: Token, 297 /// Will contain Literal and SimpleStatement nodes 298 buttons: []*Node, 299 end_token: Token, 300 }; 301 302 pub const Menu = struct { 303 base: Node = .{ .id = .menu }, 304 id: Token, 305 type: Token, 306 common_resource_attributes: []Token, 307 optional_statements: []*Node, 308 /// `help_id` will never be non-null if `type` is MENU 309 help_id: ?*Node, 310 begin_token: Token, 311 items: []*Node, 312 end_token: Token, 313 }; 314 315 pub const MenuItem = struct { 316 base: Node = .{ .id = .menu_item }, 317 menuitem: Token, 318 text: Token, 319 result: *Node, 320 option_list: []Token, 321 }; 322 323 pub const MenuItemSeparator = struct { 324 base: Node = .{ .id = .menu_item_separator }, 325 menuitem: Token, 326 separator: Token, 327 }; 328 329 pub const MenuItemEx = struct { 330 base: Node = .{ .id = .menu_item_ex }, 331 menuitem: Token, 332 text: Token, 333 id: ?*Node, 334 type: ?*Node, 335 state: ?*Node, 336 }; 337 338 pub const Popup = struct { 339 base: Node = .{ .id = .popup }, 340 popup: Token, 341 text: Token, 342 option_list: []Token, 343 begin_token: Token, 344 items: []*Node, 345 end_token: Token, 346 }; 347 348 pub const PopupEx = struct { 349 base: Node = .{ .id = .popup_ex }, 350 popup: Token, 351 text: Token, 352 id: ?*Node, 353 type: ?*Node, 354 state: ?*Node, 355 help_id: ?*Node, 356 begin_token: Token, 357 items: []*Node, 358 end_token: Token, 359 }; 360 361 pub const VersionInfo = struct { 362 base: Node = .{ .id = .version_info }, 363 id: Token, 364 versioninfo: Token, 365 common_resource_attributes: []Token, 366 /// Will contain VersionStatement and/or SimpleStatement nodes 367 fixed_info: []*Node, 368 begin_token: Token, 369 block_statements: []*Node, 370 end_token: Token, 371 }; 372 373 /// Used for FILEVERSION and PRODUCTVERSION statements 374 pub const VersionStatement = struct { 375 base: Node = .{ .id = .version_statement }, 376 type: Token, 377 /// Between 1-4 parts 378 parts: []*Node, 379 }; 380 381 pub const Block = struct { 382 base: Node = .{ .id = .block }, 383 /// The BLOCK token itself 384 identifier: Token, 385 key: Token, 386 /// This is undocumented but BLOCK statements support values after 387 /// the key just like VALUE statements. 388 values: []*Node, 389 begin_token: Token, 390 children: []*Node, 391 end_token: Token, 392 }; 393 394 pub const BlockValue = struct { 395 base: Node = .{ .id = .block_value }, 396 /// The VALUE token itself 397 identifier: Token, 398 key: Token, 399 /// These will be BlockValueValue nodes 400 values: []*Node, 401 }; 402 403 pub const BlockValueValue = struct { 404 base: Node = .{ .id = .block_value_value }, 405 expression: *Node, 406 /// Whether or not the value has a trailing comma is relevant 407 trailing_comma: bool, 408 }; 409 410 pub const StringTable = struct { 411 base: Node = .{ .id = .string_table }, 412 type: Token, 413 common_resource_attributes: []Token, 414 optional_statements: []*Node, 415 begin_token: Token, 416 strings: []*Node, 417 end_token: Token, 418 }; 419 420 pub const StringTableString = struct { 421 base: Node = .{ .id = .string_table_string }, 422 id: *Node, 423 maybe_comma: ?Token, 424 string: Token, 425 }; 426 427 pub const LanguageStatement = struct { 428 base: Node = .{ .id = .language_statement }, 429 /// The LANGUAGE token itself 430 language_token: Token, 431 primary_language_id: *Node, 432 sublanguage_id: *Node, 433 }; 434 435 pub const FontStatement = struct { 436 base: Node = .{ .id = .font_statement }, 437 /// The FONT token itself 438 identifier: Token, 439 point_size: *Node, 440 typeface: Token, 441 weight: ?*Node, 442 italic: ?*Node, 443 char_set: ?*Node, 444 }; 445 446 /// A statement with one value associated with it. 447 /// Used for CAPTION, CHARACTERISTICS, CLASS, EXSTYLE, MENU, STYLE, VERSION, 448 /// as well as VERSIONINFO-specific statements FILEFLAGSMASK, FILEFLAGS, FILEOS, 449 /// FILETYPE, FILESUBTYPE 450 pub const SimpleStatement = struct { 451 base: Node = .{ .id = .simple_statement }, 452 identifier: Token, 453 value: *Node, 454 }; 455 456 pub const Invalid = struct { 457 base: Node = .{ .id = .invalid }, 458 context: []Token, 459 }; 460 461 pub fn isNumberExpression(node: *const Node) bool { 462 switch (node.id) { 463 .literal => { 464 const literal: *const Node.Literal = @alignCast(@fieldParentPtr("base", node)); 465 return switch (literal.token.id) { 466 .number => true, 467 else => false, 468 }; 469 }, 470 .binary_expression, .grouped_expression, .not_expression => return true, 471 else => return false, 472 } 473 } 474 475 pub fn isStringLiteral(node: *const Node) bool { 476 switch (node.id) { 477 .literal => { 478 const literal: *const Node.Literal = @alignCast(@fieldParentPtr("base", node)); 479 return switch (literal.token.id) { 480 .quoted_ascii_string, .quoted_wide_string => true, 481 else => false, 482 }; 483 }, 484 else => return false, 485 } 486 } 487 488 pub fn getFirstToken(node: *const Node) Token { 489 switch (node.id) { 490 .root => unreachable, 491 .resource_external => { 492 const casted: *const Node.ResourceExternal = @alignCast(@fieldParentPtr("base", node)); 493 return casted.id; 494 }, 495 .resource_raw_data => { 496 const casted: *const Node.ResourceRawData = @alignCast(@fieldParentPtr("base", node)); 497 return casted.id; 498 }, 499 .literal => { 500 const casted: *const Node.Literal = @alignCast(@fieldParentPtr("base", node)); 501 return casted.token; 502 }, 503 .binary_expression => { 504 const casted: *const Node.BinaryExpression = @alignCast(@fieldParentPtr("base", node)); 505 return casted.left.getFirstToken(); 506 }, 507 .grouped_expression => { 508 const casted: *const Node.GroupedExpression = @alignCast(@fieldParentPtr("base", node)); 509 return casted.open_token; 510 }, 511 .not_expression => { 512 const casted: *const Node.NotExpression = @alignCast(@fieldParentPtr("base", node)); 513 return casted.not_token; 514 }, 515 .accelerators => { 516 const casted: *const Node.Accelerators = @alignCast(@fieldParentPtr("base", node)); 517 return casted.id; 518 }, 519 .accelerator => { 520 const casted: *const Node.Accelerator = @alignCast(@fieldParentPtr("base", node)); 521 return casted.event.getFirstToken(); 522 }, 523 .dialog => { 524 const casted: *const Node.Dialog = @alignCast(@fieldParentPtr("base", node)); 525 return casted.id; 526 }, 527 .control_statement => { 528 const casted: *const Node.ControlStatement = @alignCast(@fieldParentPtr("base", node)); 529 return casted.type; 530 }, 531 .toolbar => { 532 const casted: *const Node.Toolbar = @alignCast(@fieldParentPtr("base", node)); 533 return casted.id; 534 }, 535 .menu => { 536 const casted: *const Node.Menu = @alignCast(@fieldParentPtr("base", node)); 537 return casted.id; 538 }, 539 inline .menu_item, .menu_item_separator, .menu_item_ex => |menu_item_type| { 540 const casted: *const menu_item_type.Type() = @alignCast(@fieldParentPtr("base", node)); 541 return casted.menuitem; 542 }, 543 inline .popup, .popup_ex => |popup_type| { 544 const casted: *const popup_type.Type() = @alignCast(@fieldParentPtr("base", node)); 545 return casted.popup; 546 }, 547 .version_info => { 548 const casted: *const Node.VersionInfo = @alignCast(@fieldParentPtr("base", node)); 549 return casted.id; 550 }, 551 .version_statement => { 552 const casted: *const Node.VersionStatement = @alignCast(@fieldParentPtr("base", node)); 553 return casted.type; 554 }, 555 .block => { 556 const casted: *const Node.Block = @alignCast(@fieldParentPtr("base", node)); 557 return casted.identifier; 558 }, 559 .block_value => { 560 const casted: *const Node.BlockValue = @alignCast(@fieldParentPtr("base", node)); 561 return casted.identifier; 562 }, 563 .block_value_value => { 564 const casted: *const Node.BlockValueValue = @alignCast(@fieldParentPtr("base", node)); 565 return casted.expression.getFirstToken(); 566 }, 567 .string_table => { 568 const casted: *const Node.StringTable = @alignCast(@fieldParentPtr("base", node)); 569 return casted.type; 570 }, 571 .string_table_string => { 572 const casted: *const Node.StringTableString = @alignCast(@fieldParentPtr("base", node)); 573 return casted.id.getFirstToken(); 574 }, 575 .language_statement => { 576 const casted: *const Node.LanguageStatement = @alignCast(@fieldParentPtr("base", node)); 577 return casted.language_token; 578 }, 579 .font_statement => { 580 const casted: *const Node.FontStatement = @alignCast(@fieldParentPtr("base", node)); 581 return casted.identifier; 582 }, 583 .simple_statement => { 584 const casted: *const Node.SimpleStatement = @alignCast(@fieldParentPtr("base", node)); 585 return casted.identifier; 586 }, 587 .invalid => { 588 const casted: *const Node.Invalid = @alignCast(@fieldParentPtr("base", node)); 589 return casted.context[0]; 590 }, 591 } 592 } 593 594 pub fn getLastToken(node: *const Node) Token { 595 switch (node.id) { 596 .root => unreachable, 597 .resource_external => { 598 const casted: *const Node.ResourceExternal = @alignCast(@fieldParentPtr("base", node)); 599 return casted.filename.getLastToken(); 600 }, 601 .resource_raw_data => { 602 const casted: *const Node.ResourceRawData = @alignCast(@fieldParentPtr("base", node)); 603 return casted.end_token; 604 }, 605 .literal => { 606 const casted: *const Node.Literal = @alignCast(@fieldParentPtr("base", node)); 607 return casted.token; 608 }, 609 .binary_expression => { 610 const casted: *const Node.BinaryExpression = @alignCast(@fieldParentPtr("base", node)); 611 return casted.right.getLastToken(); 612 }, 613 .grouped_expression => { 614 const casted: *const Node.GroupedExpression = @alignCast(@fieldParentPtr("base", node)); 615 return casted.close_token; 616 }, 617 .not_expression => { 618 const casted: *const Node.NotExpression = @alignCast(@fieldParentPtr("base", node)); 619 return casted.number_token; 620 }, 621 .accelerators => { 622 const casted: *const Node.Accelerators = @alignCast(@fieldParentPtr("base", node)); 623 return casted.end_token; 624 }, 625 .accelerator => { 626 const casted: *const Node.Accelerator = @alignCast(@fieldParentPtr("base", node)); 627 if (casted.type_and_options.len > 0) return casted.type_and_options[casted.type_and_options.len - 1]; 628 return casted.idvalue.getLastToken(); 629 }, 630 .dialog => { 631 const casted: *const Node.Dialog = @alignCast(@fieldParentPtr("base", node)); 632 return casted.end_token; 633 }, 634 .control_statement => { 635 const casted: *const Node.ControlStatement = @alignCast(@fieldParentPtr("base", node)); 636 if (casted.extra_data_end) |token| return token; 637 if (casted.help_id) |help_id_node| return help_id_node.getLastToken(); 638 if (casted.exstyle) |exstyle_node| return exstyle_node.getLastToken(); 639 // For user-defined CONTROL controls, the style comes before 'x', but 640 // otherwise it comes after 'height' so it could be the last token if 641 // it's present. 642 if (!casted.isUserDefined()) { 643 if (casted.style) |style_node| return style_node.getLastToken(); 644 } 645 return casted.height.getLastToken(); 646 }, 647 .toolbar => { 648 const casted: *const Node.Toolbar = @alignCast(@fieldParentPtr("base", node)); 649 return casted.end_token; 650 }, 651 .menu => { 652 const casted: *const Node.Menu = @alignCast(@fieldParentPtr("base", node)); 653 return casted.end_token; 654 }, 655 .menu_item => { 656 const casted: *const Node.MenuItem = @alignCast(@fieldParentPtr("base", node)); 657 if (casted.option_list.len > 0) return casted.option_list[casted.option_list.len - 1]; 658 return casted.result.getLastToken(); 659 }, 660 .menu_item_separator => { 661 const casted: *const Node.MenuItemSeparator = @alignCast(@fieldParentPtr("base", node)); 662 return casted.separator; 663 }, 664 .menu_item_ex => { 665 const casted: *const Node.MenuItemEx = @alignCast(@fieldParentPtr("base", node)); 666 if (casted.state) |state_node| return state_node.getLastToken(); 667 if (casted.type) |type_node| return type_node.getLastToken(); 668 if (casted.id) |id_node| return id_node.getLastToken(); 669 return casted.text; 670 }, 671 inline .popup, .popup_ex => |popup_type| { 672 const casted: *const popup_type.Type() = @alignCast(@fieldParentPtr("base", node)); 673 return casted.end_token; 674 }, 675 .version_info => { 676 const casted: *const Node.VersionInfo = @alignCast(@fieldParentPtr("base", node)); 677 return casted.end_token; 678 }, 679 .version_statement => { 680 const casted: *const Node.VersionStatement = @alignCast(@fieldParentPtr("base", node)); 681 return casted.parts[casted.parts.len - 1].getLastToken(); 682 }, 683 .block => { 684 const casted: *const Node.Block = @alignCast(@fieldParentPtr("base", node)); 685 return casted.end_token; 686 }, 687 .block_value => { 688 const casted: *const Node.BlockValue = @alignCast(@fieldParentPtr("base", node)); 689 if (casted.values.len > 0) return casted.values[casted.values.len - 1].getLastToken(); 690 return casted.key; 691 }, 692 .block_value_value => { 693 const casted: *const Node.BlockValueValue = @alignCast(@fieldParentPtr("base", node)); 694 return casted.expression.getLastToken(); 695 }, 696 .string_table => { 697 const casted: *const Node.StringTable = @alignCast(@fieldParentPtr("base", node)); 698 return casted.end_token; 699 }, 700 .string_table_string => { 701 const casted: *const Node.StringTableString = @alignCast(@fieldParentPtr("base", node)); 702 return casted.string; 703 }, 704 .language_statement => { 705 const casted: *const Node.LanguageStatement = @alignCast(@fieldParentPtr("base", node)); 706 return casted.sublanguage_id.getLastToken(); 707 }, 708 .font_statement => { 709 const casted: *const Node.FontStatement = @alignCast(@fieldParentPtr("base", node)); 710 if (casted.char_set) |char_set_node| return char_set_node.getLastToken(); 711 if (casted.italic) |italic_node| return italic_node.getLastToken(); 712 if (casted.weight) |weight_node| return weight_node.getLastToken(); 713 return casted.typeface; 714 }, 715 .simple_statement => { 716 const casted: *const Node.SimpleStatement = @alignCast(@fieldParentPtr("base", node)); 717 return casted.value.getLastToken(); 718 }, 719 .invalid => { 720 const casted: *const Node.Invalid = @alignCast(@fieldParentPtr("base", node)); 721 return casted.context[casted.context.len - 1]; 722 }, 723 } 724 } 725 726 pub fn dump( 727 node: *const Node, 728 tree: *const Tree, 729 writer: anytype, 730 indent: usize, 731 ) @TypeOf(writer).Error!void { 732 try writer.writeByteNTimes(' ', indent); 733 try writer.writeAll(@tagName(node.id)); 734 switch (node.id) { 735 .root => { 736 try writer.writeAll("\n"); 737 const root: *const Node.Root = @alignCast(@fieldParentPtr("base", node)); 738 for (root.body) |body_node| { 739 try body_node.dump(tree, writer, indent + 1); 740 } 741 }, 742 .resource_external => { 743 const resource: *const Node.ResourceExternal = @alignCast(@fieldParentPtr("base", node)); 744 try writer.print(" {s} {s} [{d} common_resource_attributes]\n", .{ resource.id.slice(tree.source), resource.type.slice(tree.source), resource.common_resource_attributes.len }); 745 try resource.filename.dump(tree, writer, indent + 1); 746 }, 747 .resource_raw_data => { 748 const resource: *const Node.ResourceRawData = @alignCast(@fieldParentPtr("base", node)); 749 try writer.print(" {s} {s} [{d} common_resource_attributes] raw data: {}\n", .{ resource.id.slice(tree.source), resource.type.slice(tree.source), resource.common_resource_attributes.len, resource.raw_data.len }); 750 for (resource.raw_data) |data_expression| { 751 try data_expression.dump(tree, writer, indent + 1); 752 } 753 }, 754 .literal => { 755 const literal: *const Node.Literal = @alignCast(@fieldParentPtr("base", node)); 756 try writer.writeAll(" "); 757 try writer.writeAll(literal.token.slice(tree.source)); 758 try writer.writeAll("\n"); 759 }, 760 .binary_expression => { 761 const binary: *const Node.BinaryExpression = @alignCast(@fieldParentPtr("base", node)); 762 try writer.writeAll(" "); 763 try writer.writeAll(binary.operator.slice(tree.source)); 764 try writer.writeAll("\n"); 765 try binary.left.dump(tree, writer, indent + 1); 766 try binary.right.dump(tree, writer, indent + 1); 767 }, 768 .grouped_expression => { 769 const grouped: *const Node.GroupedExpression = @alignCast(@fieldParentPtr("base", node)); 770 try writer.writeAll("\n"); 771 try writer.writeByteNTimes(' ', indent); 772 try writer.writeAll(grouped.open_token.slice(tree.source)); 773 try writer.writeAll("\n"); 774 try grouped.expression.dump(tree, writer, indent + 1); 775 try writer.writeByteNTimes(' ', indent); 776 try writer.writeAll(grouped.close_token.slice(tree.source)); 777 try writer.writeAll("\n"); 778 }, 779 .not_expression => { 780 const not: *const Node.NotExpression = @alignCast(@fieldParentPtr("base", node)); 781 try writer.writeAll(" "); 782 try writer.writeAll(not.not_token.slice(tree.source)); 783 try writer.writeAll(" "); 784 try writer.writeAll(not.number_token.slice(tree.source)); 785 try writer.writeAll("\n"); 786 }, 787 .accelerators => { 788 const accelerators: *const Node.Accelerators = @alignCast(@fieldParentPtr("base", node)); 789 try writer.print(" {s} {s} [{d} common_resource_attributes]\n", .{ accelerators.id.slice(tree.source), accelerators.type.slice(tree.source), accelerators.common_resource_attributes.len }); 790 for (accelerators.optional_statements) |statement| { 791 try statement.dump(tree, writer, indent + 1); 792 } 793 try writer.writeByteNTimes(' ', indent); 794 try writer.writeAll(accelerators.begin_token.slice(tree.source)); 795 try writer.writeAll("\n"); 796 for (accelerators.accelerators) |accelerator| { 797 try accelerator.dump(tree, writer, indent + 1); 798 } 799 try writer.writeByteNTimes(' ', indent); 800 try writer.writeAll(accelerators.end_token.slice(tree.source)); 801 try writer.writeAll("\n"); 802 }, 803 .accelerator => { 804 const accelerator: *const Node.Accelerator = @alignCast(@fieldParentPtr("base", node)); 805 for (accelerator.type_and_options, 0..) |option, i| { 806 if (i != 0) try writer.writeAll(","); 807 try writer.writeByte(' '); 808 try writer.writeAll(option.slice(tree.source)); 809 } 810 try writer.writeAll("\n"); 811 try accelerator.event.dump(tree, writer, indent + 1); 812 try accelerator.idvalue.dump(tree, writer, indent + 1); 813 }, 814 .dialog => { 815 const dialog: *const Node.Dialog = @alignCast(@fieldParentPtr("base", node)); 816 try writer.print(" {s} {s} [{d} common_resource_attributes]\n", .{ dialog.id.slice(tree.source), dialog.type.slice(tree.source), dialog.common_resource_attributes.len }); 817 inline for (.{ "x", "y", "width", "height" }) |arg| { 818 try writer.writeByteNTimes(' ', indent + 1); 819 try writer.writeAll(arg ++ ":\n"); 820 try @field(dialog, arg).dump(tree, writer, indent + 2); 821 } 822 if (dialog.help_id) |help_id| { 823 try writer.writeByteNTimes(' ', indent + 1); 824 try writer.writeAll("help_id:\n"); 825 try help_id.dump(tree, writer, indent + 2); 826 } 827 for (dialog.optional_statements) |statement| { 828 try statement.dump(tree, writer, indent + 1); 829 } 830 try writer.writeByteNTimes(' ', indent); 831 try writer.writeAll(dialog.begin_token.slice(tree.source)); 832 try writer.writeAll("\n"); 833 for (dialog.controls) |control| { 834 try control.dump(tree, writer, indent + 1); 835 } 836 try writer.writeByteNTimes(' ', indent); 837 try writer.writeAll(dialog.end_token.slice(tree.source)); 838 try writer.writeAll("\n"); 839 }, 840 .control_statement => { 841 const control: *const Node.ControlStatement = @alignCast(@fieldParentPtr("base", node)); 842 try writer.print(" {s}", .{control.type.slice(tree.source)}); 843 if (control.text) |text| { 844 try writer.print(" text: {s}", .{text.slice(tree.source)}); 845 } 846 try writer.writeByte('\n'); 847 if (control.class) |class| { 848 try writer.writeByteNTimes(' ', indent + 1); 849 try writer.writeAll("class:\n"); 850 try class.dump(tree, writer, indent + 2); 851 } 852 inline for (.{ "id", "x", "y", "width", "height" }) |arg| { 853 try writer.writeByteNTimes(' ', indent + 1); 854 try writer.writeAll(arg ++ ":\n"); 855 try @field(control, arg).dump(tree, writer, indent + 2); 856 } 857 inline for (.{ "style", "exstyle", "help_id" }) |arg| { 858 if (@field(control, arg)) |val_node| { 859 try writer.writeByteNTimes(' ', indent + 1); 860 try writer.writeAll(arg ++ ":\n"); 861 try val_node.dump(tree, writer, indent + 2); 862 } 863 } 864 if (control.extra_data_begin != null) { 865 try writer.writeByteNTimes(' ', indent); 866 try writer.writeAll(control.extra_data_begin.?.slice(tree.source)); 867 try writer.writeAll("\n"); 868 for (control.extra_data) |data_node| { 869 try data_node.dump(tree, writer, indent + 1); 870 } 871 try writer.writeByteNTimes(' ', indent); 872 try writer.writeAll(control.extra_data_end.?.slice(tree.source)); 873 try writer.writeAll("\n"); 874 } 875 }, 876 .toolbar => { 877 const toolbar: *const Node.Toolbar = @alignCast(@fieldParentPtr("base", node)); 878 try writer.print(" {s} {s} [{d} common_resource_attributes]\n", .{ toolbar.id.slice(tree.source), toolbar.type.slice(tree.source), toolbar.common_resource_attributes.len }); 879 inline for (.{ "button_width", "button_height" }) |arg| { 880 try writer.writeByteNTimes(' ', indent + 1); 881 try writer.writeAll(arg ++ ":\n"); 882 try @field(toolbar, arg).dump(tree, writer, indent + 2); 883 } 884 try writer.writeByteNTimes(' ', indent); 885 try writer.writeAll(toolbar.begin_token.slice(tree.source)); 886 try writer.writeAll("\n"); 887 for (toolbar.buttons) |button_or_sep| { 888 try button_or_sep.dump(tree, writer, indent + 1); 889 } 890 try writer.writeByteNTimes(' ', indent); 891 try writer.writeAll(toolbar.end_token.slice(tree.source)); 892 try writer.writeAll("\n"); 893 }, 894 .menu => { 895 const menu: *const Node.Menu = @alignCast(@fieldParentPtr("base", node)); 896 try writer.print(" {s} {s} [{d} common_resource_attributes]\n", .{ menu.id.slice(tree.source), menu.type.slice(tree.source), menu.common_resource_attributes.len }); 897 for (menu.optional_statements) |statement| { 898 try statement.dump(tree, writer, indent + 1); 899 } 900 if (menu.help_id) |help_id| { 901 try writer.writeByteNTimes(' ', indent + 1); 902 try writer.writeAll("help_id:\n"); 903 try help_id.dump(tree, writer, indent + 2); 904 } 905 try writer.writeByteNTimes(' ', indent); 906 try writer.writeAll(menu.begin_token.slice(tree.source)); 907 try writer.writeAll("\n"); 908 for (menu.items) |item| { 909 try item.dump(tree, writer, indent + 1); 910 } 911 try writer.writeByteNTimes(' ', indent); 912 try writer.writeAll(menu.end_token.slice(tree.source)); 913 try writer.writeAll("\n"); 914 }, 915 .menu_item => { 916 const menu_item: *const Node.MenuItem = @alignCast(@fieldParentPtr("base", node)); 917 try writer.print(" {s} {s} [{d} options]\n", .{ menu_item.menuitem.slice(tree.source), menu_item.text.slice(tree.source), menu_item.option_list.len }); 918 try menu_item.result.dump(tree, writer, indent + 1); 919 }, 920 .menu_item_separator => { 921 const menu_item: *const Node.MenuItemSeparator = @alignCast(@fieldParentPtr("base", node)); 922 try writer.print(" {s} {s}\n", .{ menu_item.menuitem.slice(tree.source), menu_item.separator.slice(tree.source) }); 923 }, 924 .menu_item_ex => { 925 const menu_item: *const Node.MenuItemEx = @alignCast(@fieldParentPtr("base", node)); 926 try writer.print(" {s} {s}\n", .{ menu_item.menuitem.slice(tree.source), menu_item.text.slice(tree.source) }); 927 inline for (.{ "id", "type", "state" }) |arg| { 928 if (@field(menu_item, arg)) |val_node| { 929 try writer.writeByteNTimes(' ', indent + 1); 930 try writer.writeAll(arg ++ ":\n"); 931 try val_node.dump(tree, writer, indent + 2); 932 } 933 } 934 }, 935 .popup => { 936 const popup: *const Node.Popup = @alignCast(@fieldParentPtr("base", node)); 937 try writer.print(" {s} {s} [{d} options]\n", .{ popup.popup.slice(tree.source), popup.text.slice(tree.source), popup.option_list.len }); 938 try writer.writeByteNTimes(' ', indent); 939 try writer.writeAll(popup.begin_token.slice(tree.source)); 940 try writer.writeAll("\n"); 941 for (popup.items) |item| { 942 try item.dump(tree, writer, indent + 1); 943 } 944 try writer.writeByteNTimes(' ', indent); 945 try writer.writeAll(popup.end_token.slice(tree.source)); 946 try writer.writeAll("\n"); 947 }, 948 .popup_ex => { 949 const popup: *const Node.PopupEx = @alignCast(@fieldParentPtr("base", node)); 950 try writer.print(" {s} {s}\n", .{ popup.popup.slice(tree.source), popup.text.slice(tree.source) }); 951 inline for (.{ "id", "type", "state", "help_id" }) |arg| { 952 if (@field(popup, arg)) |val_node| { 953 try writer.writeByteNTimes(' ', indent + 1); 954 try writer.writeAll(arg ++ ":\n"); 955 try val_node.dump(tree, writer, indent + 2); 956 } 957 } 958 try writer.writeByteNTimes(' ', indent); 959 try writer.writeAll(popup.begin_token.slice(tree.source)); 960 try writer.writeAll("\n"); 961 for (popup.items) |item| { 962 try item.dump(tree, writer, indent + 1); 963 } 964 try writer.writeByteNTimes(' ', indent); 965 try writer.writeAll(popup.end_token.slice(tree.source)); 966 try writer.writeAll("\n"); 967 }, 968 .version_info => { 969 const version_info: *const Node.VersionInfo = @alignCast(@fieldParentPtr("base", node)); 970 try writer.print(" {s} {s} [{d} common_resource_attributes]\n", .{ version_info.id.slice(tree.source), version_info.versioninfo.slice(tree.source), version_info.common_resource_attributes.len }); 971 for (version_info.fixed_info) |fixed_info| { 972 try fixed_info.dump(tree, writer, indent + 1); 973 } 974 try writer.writeByteNTimes(' ', indent); 975 try writer.writeAll(version_info.begin_token.slice(tree.source)); 976 try writer.writeAll("\n"); 977 for (version_info.block_statements) |block| { 978 try block.dump(tree, writer, indent + 1); 979 } 980 try writer.writeByteNTimes(' ', indent); 981 try writer.writeAll(version_info.end_token.slice(tree.source)); 982 try writer.writeAll("\n"); 983 }, 984 .version_statement => { 985 const version_statement: *const Node.VersionStatement = @alignCast(@fieldParentPtr("base", node)); 986 try writer.print(" {s}\n", .{version_statement.type.slice(tree.source)}); 987 for (version_statement.parts) |part| { 988 try part.dump(tree, writer, indent + 1); 989 } 990 }, 991 .block => { 992 const block: *const Node.Block = @alignCast(@fieldParentPtr("base", node)); 993 try writer.print(" {s} {s}\n", .{ block.identifier.slice(tree.source), block.key.slice(tree.source) }); 994 for (block.values) |value| { 995 try value.dump(tree, writer, indent + 1); 996 } 997 try writer.writeByteNTimes(' ', indent); 998 try writer.writeAll(block.begin_token.slice(tree.source)); 999 try writer.writeAll("\n"); 1000 for (block.children) |child| { 1001 try child.dump(tree, writer, indent + 1); 1002 } 1003 try writer.writeByteNTimes(' ', indent); 1004 try writer.writeAll(block.end_token.slice(tree.source)); 1005 try writer.writeAll("\n"); 1006 }, 1007 .block_value => { 1008 const block_value: *const Node.BlockValue = @alignCast(@fieldParentPtr("base", node)); 1009 try writer.print(" {s} {s}\n", .{ block_value.identifier.slice(tree.source), block_value.key.slice(tree.source) }); 1010 for (block_value.values) |value| { 1011 try value.dump(tree, writer, indent + 1); 1012 } 1013 }, 1014 .block_value_value => { 1015 const block_value: *const Node.BlockValueValue = @alignCast(@fieldParentPtr("base", node)); 1016 if (block_value.trailing_comma) { 1017 try writer.writeAll(" ,"); 1018 } 1019 try writer.writeAll("\n"); 1020 try block_value.expression.dump(tree, writer, indent + 1); 1021 }, 1022 .string_table => { 1023 const string_table: *const Node.StringTable = @alignCast(@fieldParentPtr("base", node)); 1024 try writer.print(" {s} [{d} common_resource_attributes]\n", .{ string_table.type.slice(tree.source), string_table.common_resource_attributes.len }); 1025 for (string_table.optional_statements) |statement| { 1026 try statement.dump(tree, writer, indent + 1); 1027 } 1028 try writer.writeByteNTimes(' ', indent); 1029 try writer.writeAll(string_table.begin_token.slice(tree.source)); 1030 try writer.writeAll("\n"); 1031 for (string_table.strings) |string| { 1032 try string.dump(tree, writer, indent + 1); 1033 } 1034 try writer.writeByteNTimes(' ', indent); 1035 try writer.writeAll(string_table.end_token.slice(tree.source)); 1036 try writer.writeAll("\n"); 1037 }, 1038 .string_table_string => { 1039 try writer.writeAll("\n"); 1040 const string: *const Node.StringTableString = @alignCast(@fieldParentPtr("base", node)); 1041 try string.id.dump(tree, writer, indent + 1); 1042 try writer.writeByteNTimes(' ', indent + 1); 1043 try writer.print("{s}\n", .{string.string.slice(tree.source)}); 1044 }, 1045 .language_statement => { 1046 const language: *const Node.LanguageStatement = @alignCast(@fieldParentPtr("base", node)); 1047 try writer.print(" {s}\n", .{language.language_token.slice(tree.source)}); 1048 try language.primary_language_id.dump(tree, writer, indent + 1); 1049 try language.sublanguage_id.dump(tree, writer, indent + 1); 1050 }, 1051 .font_statement => { 1052 const font: *const Node.FontStatement = @alignCast(@fieldParentPtr("base", node)); 1053 try writer.print(" {s} typeface: {s}\n", .{ font.identifier.slice(tree.source), font.typeface.slice(tree.source) }); 1054 try writer.writeByteNTimes(' ', indent + 1); 1055 try writer.writeAll("point_size:\n"); 1056 try font.point_size.dump(tree, writer, indent + 2); 1057 inline for (.{ "weight", "italic", "char_set" }) |arg| { 1058 if (@field(font, arg)) |arg_node| { 1059 try writer.writeByteNTimes(' ', indent + 1); 1060 try writer.writeAll(arg ++ ":\n"); 1061 try arg_node.dump(tree, writer, indent + 2); 1062 } 1063 } 1064 }, 1065 .simple_statement => { 1066 const statement: *const Node.SimpleStatement = @alignCast(@fieldParentPtr("base", node)); 1067 try writer.print(" {s}\n", .{statement.identifier.slice(tree.source)}); 1068 try statement.value.dump(tree, writer, indent + 1); 1069 }, 1070 .invalid => { 1071 const invalid: *const Node.Invalid = @alignCast(@fieldParentPtr("base", node)); 1072 try writer.print(" context.len: {}\n", .{invalid.context.len}); 1073 for (invalid.context) |context_token| { 1074 try writer.writeByteNTimes(' ', indent + 1); 1075 try writer.print("{s}:{s}", .{ @tagName(context_token.id), context_token.slice(tree.source) }); 1076 try writer.writeByte('\n'); 1077 } 1078 }, 1079 } 1080 } 1081 };