| 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: *std.Io.Writer) !void { |
| 26 | try self.node.dump(self, writer, 0); |
| 27 | } |
| 28 | }; |
| 29 | |
| 30 | pub const CodePageLookup = struct { |
| 31 | lookup: std.ArrayList(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: *std.Io.Writer, |
| 730 | indent: usize, |
| 731 | ) std.Io.Writer.Error!void { |
| 732 | try writer.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', indent + 1); |
| 881 | try writer.writeAll(arg ++ ":\n"); |
| 882 | try @field(toolbar, arg).dump(tree, writer, indent + 2); |
| 883 | } |
| 884 | try writer.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', indent + 1); |
| 902 | try writer.writeAll("help_id:\n"); |
| 903 | try help_id.dump(tree, writer, indent + 2); |
| 904 | } |
| 905 | try writer.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', indent + 1); |
| 954 | try writer.writeAll(arg ++ ":\n"); |
| 955 | try val_node.dump(tree, writer, indent + 2); |
| 956 | } |
| 957 | } |
| 958 | try writer.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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.splatByteAll(' ', 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 | }; |