zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

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 };