parse.zig (91390B) - Raw
1 const std = @import("std"); 2 const Lexer = @import("lex.zig").Lexer; 3 const Token = @import("lex.zig").Token; 4 const Node = @import("ast.zig").Node; 5 const Tree = @import("ast.zig").Tree; 6 const CodePageLookup = @import("ast.zig").CodePageLookup; 7 const ResourceType = @import("rc.zig").ResourceType; 8 const Allocator = std.mem.Allocator; 9 const ErrorDetails = @import("errors.zig").ErrorDetails; 10 const ErrorDetailsWithoutCodePage = @import("errors.zig").ErrorDetailsWithoutCodePage; 11 const Diagnostics = @import("errors.zig").Diagnostics; 12 const SourceBytes = @import("literals.zig").SourceBytes; 13 const Compiler = @import("compile.zig").Compiler; 14 const rc = @import("rc.zig"); 15 const res = @import("res.zig"); 16 17 // TODO: Make these configurable? 18 pub const max_nested_menu_level: u32 = 512; 19 pub const max_nested_version_level: u32 = 512; 20 pub const max_nested_expression_level: u32 = 200; 21 22 pub const Parser = struct { 23 const Self = @This(); 24 25 lexer: *Lexer, 26 /// values that need to be initialized per-parse 27 state: Parser.State = undefined, 28 options: Parser.Options, 29 30 pub const Error = error{ParseError} || Allocator.Error; 31 32 pub const Options = struct { 33 warn_instead_of_error_on_invalid_code_page: bool = false, 34 disjoint_code_page: bool = false, 35 }; 36 37 pub fn init(lexer: *Lexer, options: Options) Parser { 38 return Parser{ 39 .lexer = lexer, 40 .options = options, 41 }; 42 } 43 44 pub const State = struct { 45 token: Token, 46 lookahead_lexer: Lexer, 47 allocator: Allocator, 48 arena: Allocator, 49 diagnostics: *Diagnostics, 50 input_code_page_lookup: CodePageLookup, 51 output_code_page_lookup: CodePageLookup, 52 warned_about_disjoint_code_page: bool, 53 }; 54 55 pub fn parse(self: *Self, allocator: Allocator, diagnostics: *Diagnostics) Error!*Tree { 56 var arena = std.heap.ArenaAllocator.init(allocator); 57 errdefer arena.deinit(); 58 59 self.state = Parser.State{ 60 .token = undefined, 61 .lookahead_lexer = undefined, 62 .allocator = allocator, 63 .arena = arena.allocator(), 64 .diagnostics = diagnostics, 65 .input_code_page_lookup = CodePageLookup.init(arena.allocator(), self.lexer.default_code_page), 66 .output_code_page_lookup = CodePageLookup.init(arena.allocator(), self.lexer.default_code_page), 67 .warned_about_disjoint_code_page = false, 68 }; 69 70 const parsed_root = try self.parseRoot(); 71 72 const tree = try self.state.arena.create(Tree); 73 tree.* = .{ 74 .node = parsed_root, 75 .input_code_pages = self.state.input_code_page_lookup, 76 .output_code_pages = self.state.output_code_page_lookup, 77 .source = self.lexer.buffer, 78 .arena = arena.state, 79 .allocator = allocator, 80 }; 81 return tree; 82 } 83 84 fn parseRoot(self: *Self) Error!*Node { 85 var statements = std.array_list.Managed(*Node).init(self.state.allocator); 86 defer statements.deinit(); 87 88 try self.parseStatements(&statements); 89 try self.check(.eof); 90 91 const node = try self.state.arena.create(Node.Root); 92 node.* = .{ 93 .body = try self.state.arena.dupe(*Node, statements.items), 94 }; 95 return &node.base; 96 } 97 98 fn parseStatements(self: *Self, statements: *std.array_list.Managed(*Node)) Error!void { 99 while (true) { 100 try self.nextToken(.whitespace_delimiter_only); 101 if (self.state.token.id == .eof) break; 102 // The Win32 compiler will sometimes try to recover from errors 103 // and then restart parsing afterwards. We don't ever do this 104 // because it almost always leads to unhelpful error messages 105 // (usually it will end up with bogus things like 'file 106 // not found: {') 107 const statement = try self.parseStatement(); 108 try statements.append(statement); 109 } 110 } 111 112 /// Expects the current token to be the token before possible common resource attributes. 113 /// After return, the current token will be the token immediately before the end of the 114 /// common resource attributes (if any). If there are no common resource attributes, the 115 /// current token is unchanged. 116 /// The returned slice is allocated by the parser's arena 117 fn parseCommonResourceAttributes(self: *Self) ![]Token { 118 var common_resource_attributes: std.ArrayListUnmanaged(Token) = .empty; 119 while (true) { 120 const maybe_common_resource_attribute = try self.lookaheadToken(.normal); 121 if (maybe_common_resource_attribute.id == .literal and rc.CommonResourceAttributes.map.has(maybe_common_resource_attribute.slice(self.lexer.buffer))) { 122 try common_resource_attributes.append(self.state.arena, maybe_common_resource_attribute); 123 try self.nextToken(.normal); 124 } else { 125 break; 126 } 127 } 128 return common_resource_attributes.toOwnedSlice(self.state.arena); 129 } 130 131 /// Expects the current token to have already been dealt with, and that the 132 /// optional statements will potentially start on the next token. 133 /// After return, the current token will be the token immediately before the end of the 134 /// optional statements (if any). If there are no optional statements, the 135 /// current token is unchanged. 136 /// The returned slice is allocated by the parser's arena 137 fn parseOptionalStatements(self: *Self, resource: ResourceType) ![]*Node { 138 var optional_statements: std.ArrayListUnmanaged(*Node) = .empty; 139 140 const num_statement_types = @typeInfo(rc.OptionalStatements).@"enum".fields.len; 141 var statement_type_has_duplicates = [_]bool{false} ** num_statement_types; 142 var last_statement_per_type = [_]?*Node{null} ** num_statement_types; 143 144 while (true) { 145 const lookahead_token = try self.lookaheadToken(.normal); 146 if (lookahead_token.id != .literal) break; 147 const slice = lookahead_token.slice(self.lexer.buffer); 148 const optional_statement_type = rc.OptionalStatements.map.get(slice) orelse switch (resource) { 149 .dialog, .dialogex => rc.OptionalStatements.dialog_map.get(slice) orelse break, 150 else => break, 151 }; 152 try self.nextToken(.normal); 153 154 const type_i = @intFromEnum(optional_statement_type); 155 if (last_statement_per_type[type_i] != null) { 156 statement_type_has_duplicates[type_i] = true; 157 } 158 159 switch (optional_statement_type) { 160 .language => { 161 const language = try self.parseLanguageStatement(); 162 try optional_statements.append(self.state.arena, language); 163 }, 164 // Number only 165 .version, .characteristics, .style, .exstyle => { 166 const identifier = self.state.token; 167 const value = try self.parseExpression(.{ 168 .can_contain_not_expressions = optional_statement_type == .style or optional_statement_type == .exstyle, 169 .allowed_types = .{ .number = true }, 170 }); 171 const node = try self.state.arena.create(Node.SimpleStatement); 172 node.* = .{ 173 .identifier = identifier, 174 .value = value, 175 }; 176 try optional_statements.append(self.state.arena, &node.base); 177 }, 178 // String only 179 .caption => { 180 const identifier = self.state.token; 181 try self.nextToken(.normal); 182 const value = self.state.token; 183 if (!value.isStringLiteral()) { 184 return self.addErrorDetailsAndFail(.{ 185 .err = .expected_something_else, 186 .token = value, 187 .extra = .{ .expected_types = .{ 188 .string_literal = true, 189 } }, 190 }); 191 } 192 const value_node = try self.state.arena.create(Node.Literal); 193 value_node.* = .{ 194 .token = value, 195 }; 196 const node = try self.state.arena.create(Node.SimpleStatement); 197 node.* = .{ 198 .identifier = identifier, 199 .value = &value_node.base, 200 }; 201 try optional_statements.append(self.state.arena, &node.base); 202 }, 203 // String or number 204 .class => { 205 const identifier = self.state.token; 206 const value = try self.parseExpression(.{ .allowed_types = .{ .number = true, .string = true } }); 207 const node = try self.state.arena.create(Node.SimpleStatement); 208 node.* = .{ 209 .identifier = identifier, 210 .value = value, 211 }; 212 try optional_statements.append(self.state.arena, &node.base); 213 }, 214 // Special case 215 .menu => { 216 const identifier = self.state.token; 217 try self.nextToken(.whitespace_delimiter_only); 218 try self.check(.literal); 219 const value_node = try self.state.arena.create(Node.Literal); 220 value_node.* = .{ 221 .token = self.state.token, 222 }; 223 const node = try self.state.arena.create(Node.SimpleStatement); 224 node.* = .{ 225 .identifier = identifier, 226 .value = &value_node.base, 227 }; 228 try optional_statements.append(self.state.arena, &node.base); 229 }, 230 .font => { 231 const identifier = self.state.token; 232 const point_size = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 233 234 // The comma between point_size and typeface is both optional and 235 // there can be any number of them 236 try self.skipAnyCommas(); 237 238 try self.nextToken(.normal); 239 const typeface = self.state.token; 240 if (!typeface.isStringLiteral()) { 241 return self.addErrorDetailsAndFail(.{ 242 .err = .expected_something_else, 243 .token = typeface, 244 .extra = .{ .expected_types = .{ 245 .string_literal = true, 246 } }, 247 }); 248 } 249 250 const ExSpecificValues = struct { 251 weight: ?*Node = null, 252 italic: ?*Node = null, 253 char_set: ?*Node = null, 254 }; 255 var ex_specific = ExSpecificValues{}; 256 ex_specific: { 257 var optional_param_parser = OptionalParamParser{ .parser = self }; 258 switch (resource) { 259 .dialogex => { 260 { 261 ex_specific.weight = try optional_param_parser.parse(.{}); 262 if (optional_param_parser.finished) break :ex_specific; 263 } 264 { 265 if (!(try self.parseOptionalToken(.comma))) break :ex_specific; 266 ex_specific.italic = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 267 } 268 { 269 ex_specific.char_set = try optional_param_parser.parse(.{}); 270 if (optional_param_parser.finished) break :ex_specific; 271 } 272 }, 273 .dialog => {}, 274 else => unreachable, // only DIALOG and DIALOGEX have FONT optional-statements 275 } 276 } 277 278 const node = try self.state.arena.create(Node.FontStatement); 279 node.* = .{ 280 .identifier = identifier, 281 .point_size = point_size, 282 .typeface = typeface, 283 .weight = ex_specific.weight, 284 .italic = ex_specific.italic, 285 .char_set = ex_specific.char_set, 286 }; 287 try optional_statements.append(self.state.arena, &node.base); 288 }, 289 } 290 291 last_statement_per_type[type_i] = optional_statements.items[optional_statements.items.len - 1]; 292 } 293 294 for (optional_statements.items) |optional_statement| { 295 const type_i = type_i: { 296 switch (optional_statement.id) { 297 .simple_statement => { 298 const simple_statement: *Node.SimpleStatement = @alignCast(@fieldParentPtr("base", optional_statement)); 299 const statement_identifier = simple_statement.identifier; 300 const slice = statement_identifier.slice(self.lexer.buffer); 301 const optional_statement_type = rc.OptionalStatements.map.get(slice) orelse 302 rc.OptionalStatements.dialog_map.get(slice).?; 303 break :type_i @intFromEnum(optional_statement_type); 304 }, 305 .font_statement => { 306 break :type_i @intFromEnum(rc.OptionalStatements.font); 307 }, 308 .language_statement => { 309 break :type_i @intFromEnum(rc.OptionalStatements.language); 310 }, 311 else => unreachable, 312 } 313 }; 314 if (!statement_type_has_duplicates[type_i]) continue; 315 if (optional_statement == last_statement_per_type[type_i].?) continue; 316 317 try self.addErrorDetails(.{ 318 .err = .duplicate_optional_statement_skipped, 319 .type = .warning, 320 .token = optional_statement.getFirstToken(), 321 .token_span_start = optional_statement.getFirstToken(), 322 .token_span_end = optional_statement.getLastToken(), 323 }); 324 } 325 326 return optional_statements.toOwnedSlice(self.state.arena); 327 } 328 329 /// Expects the current token to be the first token of the statement. 330 fn parseStatement(self: *Self) Error!*Node { 331 const first_token = self.state.token; 332 std.debug.assert(first_token.id == .literal); 333 334 if (rc.TopLevelKeywords.map.get(first_token.slice(self.lexer.buffer))) |keyword| switch (keyword) { 335 .language => { 336 const language_statement = try self.parseLanguageStatement(); 337 return language_statement; 338 }, 339 .version, .characteristics => { 340 const identifier = self.state.token; 341 const value = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 342 const node = try self.state.arena.create(Node.SimpleStatement); 343 node.* = .{ 344 .identifier = identifier, 345 .value = value, 346 }; 347 return &node.base; 348 }, 349 .stringtable => { 350 // common resource attributes must all be contiguous and come before optional-statements 351 const common_resource_attributes = try self.parseCommonResourceAttributes(); 352 const optional_statements = try self.parseOptionalStatements(.stringtable); 353 354 try self.nextToken(.normal); 355 const begin_token = self.state.token; 356 try self.check(.begin); 357 358 var strings = std.array_list.Managed(*Node).init(self.state.allocator); 359 defer strings.deinit(); 360 while (true) { 361 const maybe_end_token = try self.lookaheadToken(.normal); 362 switch (maybe_end_token.id) { 363 .end => { 364 try self.nextToken(.normal); 365 break; 366 }, 367 .eof => { 368 return self.addErrorDetailsWithCodePageAndFail(.{ 369 .err = .unfinished_string_table_block, 370 .code_page = self.lexer.current_code_page, 371 .token = maybe_end_token, 372 }); 373 }, 374 else => {}, 375 } 376 const id_expression = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 377 378 const comma_token: ?Token = if (try self.parseOptionalToken(.comma)) self.state.token else null; 379 380 try self.nextToken(.normal); 381 if (self.state.token.id != .quoted_ascii_string and self.state.token.id != .quoted_wide_string) { 382 return self.addErrorDetailsAndFail(.{ 383 .err = .expected_something_else, 384 .token = self.state.token, 385 .extra = .{ .expected_types = .{ .string_literal = true } }, 386 }); 387 } 388 389 const string_node = try self.state.arena.create(Node.StringTableString); 390 string_node.* = .{ 391 .id = id_expression, 392 .maybe_comma = comma_token, 393 .string = self.state.token, 394 }; 395 try strings.append(&string_node.base); 396 } 397 398 if (strings.items.len == 0) { 399 return self.addErrorDetailsAndFail(.{ 400 .err = .expected_token, // TODO: probably a more specific error message 401 .token = self.state.token, 402 .extra = .{ .expected = .number }, 403 }); 404 } 405 406 const end_token = self.state.token; 407 try self.check(.end); 408 409 const node = try self.state.arena.create(Node.StringTable); 410 node.* = .{ 411 .type = first_token, 412 .common_resource_attributes = common_resource_attributes, 413 .optional_statements = optional_statements, 414 .begin_token = begin_token, 415 .strings = try self.state.arena.dupe(*Node, strings.items), 416 .end_token = end_token, 417 }; 418 return &node.base; 419 }, 420 }; 421 422 // The Win32 RC compiler allows for a 'dangling' literal at the end of a file 423 // (as long as it's not a valid top-level keyword), and there is actually an 424 // .rc file with a such a dangling literal in the Windows-classic-samples set 425 // of projects. So, we have special compatibility for this particular case. 426 const maybe_eof = try self.lookaheadToken(.whitespace_delimiter_only); 427 if (maybe_eof.id == .eof) { 428 try self.addErrorDetails(.{ 429 .err = .dangling_literal_at_eof, 430 .type = .warning, 431 .token = first_token, 432 }); 433 434 var context = try self.state.arena.alloc(Token, 2); 435 context[0] = first_token; 436 context[1] = maybe_eof; 437 const invalid_node = try self.state.arena.create(Node.Invalid); 438 invalid_node.* = .{ 439 .context = context, 440 }; 441 return &invalid_node.base; 442 } 443 444 const id_token = first_token; 445 const id_code_page = self.lexer.current_code_page; 446 try self.nextToken(.whitespace_delimiter_only); 447 const resource = try self.checkResource(); 448 const type_token = self.state.token; 449 450 if (resource == .string_num) { 451 try self.addErrorDetails(.{ 452 .err = .string_resource_as_numeric_type, 453 .token = type_token, 454 }); 455 return self.addErrorDetailsAndFail(.{ 456 .err = .string_resource_as_numeric_type, 457 .token = type_token, 458 .type = .note, 459 .print_source_line = false, 460 }); 461 } 462 463 if (resource == .font) { 464 const id_bytes = SourceBytes{ 465 .slice = id_token.slice(self.lexer.buffer), 466 .code_page = id_code_page, 467 }; 468 const maybe_ordinal = res.NameOrOrdinal.maybeOrdinalFromString(id_bytes); 469 if (maybe_ordinal == null) { 470 const would_be_win32_rc_ordinal = res.NameOrOrdinal.maybeNonAsciiOrdinalFromString(id_bytes); 471 if (would_be_win32_rc_ordinal) |win32_rc_ordinal| { 472 try self.addErrorDetails(.{ 473 .err = .id_must_be_ordinal, 474 .token = id_token, 475 .extra = .{ .resource = resource }, 476 }); 477 return self.addErrorDetailsAndFail(.{ 478 .err = .win32_non_ascii_ordinal, 479 .token = id_token, 480 .type = .note, 481 .print_source_line = false, 482 .extra = .{ .number = win32_rc_ordinal.ordinal }, 483 }); 484 } else { 485 return self.addErrorDetailsAndFail(.{ 486 .err = .id_must_be_ordinal, 487 .token = id_token, 488 .extra = .{ .resource = resource }, 489 }); 490 } 491 } 492 } 493 494 switch (resource) { 495 .accelerators => { 496 // common resource attributes must all be contiguous and come before optional-statements 497 const common_resource_attributes = try self.parseCommonResourceAttributes(); 498 const optional_statements = try self.parseOptionalStatements(resource); 499 500 try self.nextToken(.normal); 501 const begin_token = self.state.token; 502 try self.check(.begin); 503 504 var accelerators: std.ArrayListUnmanaged(*Node) = .empty; 505 506 while (true) { 507 const lookahead = try self.lookaheadToken(.normal); 508 switch (lookahead.id) { 509 .end, .eof => { 510 try self.nextToken(.normal); 511 break; 512 }, 513 else => {}, 514 } 515 const event = try self.parseExpression(.{ .allowed_types = .{ .number = true, .string = true } }); 516 517 try self.nextToken(.normal); 518 try self.check(.comma); 519 520 const idvalue = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 521 522 var type_and_options: std.ArrayListUnmanaged(Token) = .empty; 523 while (true) { 524 if (!(try self.parseOptionalToken(.comma))) break; 525 526 try self.nextToken(.normal); 527 if (!rc.AcceleratorTypeAndOptions.map.has(self.tokenSlice())) { 528 return self.addErrorDetailsAndFail(.{ 529 .err = .expected_something_else, 530 .token = self.state.token, 531 .extra = .{ .expected_types = .{ 532 .accelerator_type_or_option = true, 533 } }, 534 }); 535 } 536 try type_and_options.append(self.state.arena, self.state.token); 537 } 538 539 const node = try self.state.arena.create(Node.Accelerator); 540 node.* = .{ 541 .event = event, 542 .idvalue = idvalue, 543 .type_and_options = try type_and_options.toOwnedSlice(self.state.arena), 544 }; 545 try accelerators.append(self.state.arena, &node.base); 546 } 547 548 const end_token = self.state.token; 549 try self.check(.end); 550 551 const node = try self.state.arena.create(Node.Accelerators); 552 node.* = .{ 553 .id = id_token, 554 .type = type_token, 555 .common_resource_attributes = common_resource_attributes, 556 .optional_statements = optional_statements, 557 .begin_token = begin_token, 558 .accelerators = try accelerators.toOwnedSlice(self.state.arena), 559 .end_token = end_token, 560 }; 561 return &node.base; 562 }, 563 .dialog, .dialogex => { 564 // common resource attributes must all be contiguous and come before optional-statements 565 const common_resource_attributes = try self.parseCommonResourceAttributes(); 566 567 const x = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 568 _ = try self.parseOptionalToken(.comma); 569 570 const y = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 571 _ = try self.parseOptionalToken(.comma); 572 573 const width = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 574 _ = try self.parseOptionalToken(.comma); 575 576 const height = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 577 578 var optional_param_parser = OptionalParamParser{ .parser = self }; 579 const help_id: ?*Node = try optional_param_parser.parse(.{}); 580 581 const optional_statements = try self.parseOptionalStatements(resource); 582 583 try self.nextToken(.normal); 584 const begin_token = self.state.token; 585 try self.check(.begin); 586 587 var controls: std.ArrayListUnmanaged(*Node) = .empty; 588 defer controls.deinit(self.state.allocator); 589 while (try self.parseControlStatement(resource)) |control_node| { 590 // The number of controls must fit in a u16 in order for it to 591 // be able to be written into the relevant field in the .res data. 592 if (controls.items.len >= std.math.maxInt(u16)) { 593 try self.addErrorDetails(.{ 594 .err = .too_many_dialog_controls_or_toolbar_buttons, 595 .token = id_token, 596 .extra = .{ .resource = resource }, 597 }); 598 return self.addErrorDetailsAndFail(.{ 599 .err = .too_many_dialog_controls_or_toolbar_buttons, 600 .type = .note, 601 .token = control_node.getFirstToken(), 602 .token_span_end = control_node.getLastToken(), 603 .extra = .{ .resource = resource }, 604 }); 605 } 606 607 try controls.append(self.state.allocator, control_node); 608 } 609 610 try self.nextToken(.normal); 611 const end_token = self.state.token; 612 try self.check(.end); 613 614 const node = try self.state.arena.create(Node.Dialog); 615 node.* = .{ 616 .id = id_token, 617 .type = type_token, 618 .common_resource_attributes = common_resource_attributes, 619 .x = x, 620 .y = y, 621 .width = width, 622 .height = height, 623 .help_id = help_id, 624 .optional_statements = optional_statements, 625 .begin_token = begin_token, 626 .controls = try self.state.arena.dupe(*Node, controls.items), 627 .end_token = end_token, 628 }; 629 return &node.base; 630 }, 631 .toolbar => { 632 // common resource attributes must all be contiguous and come before optional-statements 633 const common_resource_attributes = try self.parseCommonResourceAttributes(); 634 635 const button_width = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 636 637 try self.nextToken(.normal); 638 try self.check(.comma); 639 640 const button_height = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 641 642 try self.nextToken(.normal); 643 const begin_token = self.state.token; 644 try self.check(.begin); 645 646 var buttons: std.ArrayListUnmanaged(*Node) = .empty; 647 defer buttons.deinit(self.state.allocator); 648 while (try self.parseToolbarButtonStatement()) |button_node| { 649 // The number of buttons must fit in a u16 in order for it to 650 // be able to be written into the relevant field in the .res data. 651 if (buttons.items.len >= std.math.maxInt(u16)) { 652 try self.addErrorDetails(.{ 653 .err = .too_many_dialog_controls_or_toolbar_buttons, 654 .token = id_token, 655 .extra = .{ .resource = resource }, 656 }); 657 return self.addErrorDetailsAndFail(.{ 658 .err = .too_many_dialog_controls_or_toolbar_buttons, 659 .type = .note, 660 .token = button_node.getFirstToken(), 661 .token_span_end = button_node.getLastToken(), 662 .extra = .{ .resource = resource }, 663 }); 664 } 665 666 try buttons.append(self.state.allocator, button_node); 667 } 668 669 try self.nextToken(.normal); 670 const end_token = self.state.token; 671 try self.check(.end); 672 673 const node = try self.state.arena.create(Node.Toolbar); 674 node.* = .{ 675 .id = id_token, 676 .type = type_token, 677 .common_resource_attributes = common_resource_attributes, 678 .button_width = button_width, 679 .button_height = button_height, 680 .begin_token = begin_token, 681 .buttons = try self.state.arena.dupe(*Node, buttons.items), 682 .end_token = end_token, 683 }; 684 return &node.base; 685 }, 686 .menu, .menuex => { 687 // common resource attributes must all be contiguous and come before optional-statements 688 const common_resource_attributes = try self.parseCommonResourceAttributes(); 689 // help id is optional but must come between common resource attributes and optional-statements 690 var help_id: ?*Node = null; 691 // Note: No comma is allowed before or after help_id of MENUEX and help_id is not 692 // a possible field of MENU. 693 if (resource == .menuex and try self.lookaheadCouldBeNumberExpression(.not_disallowed)) { 694 help_id = try self.parseExpression(.{ 695 .is_known_to_be_number_expression = true, 696 }); 697 } 698 const optional_statements = try self.parseOptionalStatements(.stringtable); 699 700 try self.nextToken(.normal); 701 const begin_token = self.state.token; 702 try self.check(.begin); 703 704 var items: std.ArrayListUnmanaged(*Node) = .empty; 705 defer items.deinit(self.state.allocator); 706 while (try self.parseMenuItemStatement(resource, id_token, 1)) |item_node| { 707 try items.append(self.state.allocator, item_node); 708 } 709 710 try self.nextToken(.normal); 711 const end_token = self.state.token; 712 try self.check(.end); 713 714 if (items.items.len == 0) { 715 return self.addErrorDetailsAndFail(.{ 716 .err = .empty_menu_not_allowed, 717 .token = type_token, 718 }); 719 } 720 721 const node = try self.state.arena.create(Node.Menu); 722 node.* = .{ 723 .id = id_token, 724 .type = type_token, 725 .common_resource_attributes = common_resource_attributes, 726 .optional_statements = optional_statements, 727 .help_id = help_id, 728 .begin_token = begin_token, 729 .items = try self.state.arena.dupe(*Node, items.items), 730 .end_token = end_token, 731 }; 732 return &node.base; 733 }, 734 .versioninfo => { 735 // common resource attributes must all be contiguous and come before optional-statements 736 const common_resource_attributes = try self.parseCommonResourceAttributes(); 737 738 var fixed_info: std.ArrayListUnmanaged(*Node) = .empty; 739 while (try self.parseVersionStatement()) |version_statement| { 740 try fixed_info.append(self.state.arena, version_statement); 741 } 742 743 try self.nextToken(.normal); 744 const begin_token = self.state.token; 745 try self.check(.begin); 746 747 var block_statements: std.ArrayListUnmanaged(*Node) = .empty; 748 while (try self.parseVersionBlockOrValue(id_token, 1)) |block_node| { 749 try block_statements.append(self.state.arena, block_node); 750 } 751 752 try self.nextToken(.normal); 753 const end_token = self.state.token; 754 try self.check(.end); 755 756 const node = try self.state.arena.create(Node.VersionInfo); 757 node.* = .{ 758 .id = id_token, 759 .versioninfo = type_token, 760 .common_resource_attributes = common_resource_attributes, 761 .fixed_info = try fixed_info.toOwnedSlice(self.state.arena), 762 .begin_token = begin_token, 763 .block_statements = try block_statements.toOwnedSlice(self.state.arena), 764 .end_token = end_token, 765 }; 766 return &node.base; 767 }, 768 .dlginclude => { 769 const common_resource_attributes = try self.parseCommonResourceAttributes(); 770 771 const filename_expression = try self.parseExpression(.{ 772 .allowed_types = .{ .string = true }, 773 }); 774 775 const node = try self.state.arena.create(Node.ResourceExternal); 776 node.* = .{ 777 .id = id_token, 778 .type = type_token, 779 .common_resource_attributes = common_resource_attributes, 780 .filename = filename_expression, 781 }; 782 return &node.base; 783 }, 784 .stringtable => { 785 return self.addErrorDetailsAndFail(.{ 786 .err = .name_or_id_not_allowed, 787 .token = id_token, 788 .extra = .{ .resource = resource }, 789 }); 790 }, 791 // Just try everything as a 'generic' resource (raw data or external file) 792 // TODO: More fine-grained switch cases as necessary 793 else => { 794 const common_resource_attributes = try self.parseCommonResourceAttributes(); 795 796 const maybe_begin = try self.lookaheadToken(.normal); 797 if (maybe_begin.id == .begin) { 798 try self.nextToken(.normal); 799 800 if (!resource.canUseRawData()) { 801 try self.addErrorDetails(.{ 802 .err = .resource_type_cant_use_raw_data, 803 .token = self.state.token, 804 .extra = .{ .resource = resource }, 805 }); 806 return self.addErrorDetailsAndFail(.{ 807 .err = .resource_type_cant_use_raw_data, 808 .type = .note, 809 .print_source_line = false, 810 .token = self.state.token, 811 }); 812 } 813 814 const raw_data = try self.parseRawDataBlock(); 815 const end_token = self.state.token; 816 817 const node = try self.state.arena.create(Node.ResourceRawData); 818 node.* = .{ 819 .id = id_token, 820 .type = type_token, 821 .common_resource_attributes = common_resource_attributes, 822 .begin_token = maybe_begin, 823 .raw_data = raw_data, 824 .end_token = end_token, 825 }; 826 return &node.base; 827 } 828 829 const filename_expression = try self.parseExpression(.{ 830 // Don't tell the user that numbers are accepted since we error on 831 // number expressions and regular number literals are treated as unquoted 832 // literals rather than numbers, so from the users perspective 833 // numbers aren't really allowed. 834 .expected_types_override = .{ 835 .literal = true, 836 .string_literal = true, 837 }, 838 }); 839 840 const node = try self.state.arena.create(Node.ResourceExternal); 841 node.* = .{ 842 .id = id_token, 843 .type = type_token, 844 .common_resource_attributes = common_resource_attributes, 845 .filename = filename_expression, 846 }; 847 return &node.base; 848 }, 849 } 850 } 851 852 /// Expects the current token to be a begin token. 853 /// After return, the current token will be the end token. 854 fn parseRawDataBlock(self: *Self) Error![]*Node { 855 var raw_data = std.array_list.Managed(*Node).init(self.state.allocator); 856 defer raw_data.deinit(); 857 while (true) { 858 const maybe_end_token = try self.lookaheadToken(.normal); 859 switch (maybe_end_token.id) { 860 .comma => { 861 try self.nextToken(.normal); 862 // comma as the first token in a raw data block is an error 863 if (raw_data.items.len == 0) { 864 return self.addErrorDetailsAndFail(.{ 865 .err = .expected_something_else, 866 .token = self.state.token, 867 .extra = .{ .expected_types = .{ 868 .number = true, 869 .number_expression = true, 870 .string_literal = true, 871 } }, 872 }); 873 } 874 // otherwise just skip over commas 875 continue; 876 }, 877 .end => { 878 try self.nextToken(.normal); 879 break; 880 }, 881 .eof => { 882 return self.addErrorDetailsWithCodePageAndFail(.{ 883 .err = .unfinished_raw_data_block, 884 .code_page = self.lexer.current_code_page, 885 .token = maybe_end_token, 886 }); 887 }, 888 else => {}, 889 } 890 const expression = try self.parseExpression(.{ .allowed_types = .{ .number = true, .string = true } }); 891 try raw_data.append(expression); 892 893 if (expression.isNumberExpression()) { 894 const maybe_close_paren = try self.lookaheadToken(.normal); 895 if (maybe_close_paren.id == .close_paren) { 896 // advance to ensure that the code page lookup is populated for this token 897 try self.nextToken(.normal); 898 // <number expression>) is an error 899 return self.addErrorDetailsAndFail(.{ 900 .err = .expected_token, 901 .token = self.state.token, 902 .extra = .{ .expected = .operator }, 903 }); 904 } 905 } 906 } 907 return try self.state.arena.dupe(*Node, raw_data.items); 908 } 909 910 /// Expects the current token to be handled, and that the control statement will 911 /// begin on the next token. 912 /// After return, the current token will be the token immediately before the end of the 913 /// control statement (or unchanged if the function returns null). 914 fn parseControlStatement(self: *Self, resource: ResourceType) Error!?*Node { 915 const control_token = try self.lookaheadToken(.normal); 916 const control = rc.Control.map.get(control_token.slice(self.lexer.buffer)) orelse return null; 917 try self.nextToken(.normal); 918 919 try self.skipAnyCommas(); 920 921 var text: ?Token = null; 922 if (control.hasTextParam()) { 923 try self.nextToken(.normal); 924 switch (self.state.token.id) { 925 .quoted_ascii_string, .quoted_wide_string, .number => { 926 text = self.state.token; 927 }, 928 else => { 929 return self.addErrorDetailsAndFail(.{ 930 .err = .expected_something_else, 931 .token = self.state.token, 932 .extra = .{ .expected_types = .{ 933 .number = true, 934 .string_literal = true, 935 } }, 936 }); 937 }, 938 } 939 try self.skipAnyCommas(); 940 } 941 942 const id = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 943 944 try self.skipAnyCommas(); 945 946 var class: ?*Node = null; 947 var style: ?*Node = null; 948 if (control == .control) { 949 class = try self.parseExpression(.{}); 950 if (class.?.id == .literal) { 951 const class_literal: *Node.Literal = @alignCast(@fieldParentPtr("base", class.?)); 952 const is_invalid_control_class = class_literal.token.id == .literal and !rc.ControlClass.map.has(class_literal.token.slice(self.lexer.buffer)); 953 if (is_invalid_control_class) { 954 return self.addErrorDetailsAndFail(.{ 955 .err = .expected_something_else, 956 .token = self.state.token, 957 .extra = .{ .expected_types = .{ 958 .control_class = true, 959 } }, 960 }); 961 } 962 } 963 try self.skipAnyCommas(); 964 style = try self.parseExpression(.{ 965 .can_contain_not_expressions = true, 966 .allowed_types = .{ .number = true }, 967 }); 968 // If there is no comma after the style paramter, the Win32 RC compiler 969 // could misinterpret the statement and end up skipping over at least one token 970 // that should have been interepeted as the next parameter (x). For example: 971 // CONTROL "text", 1, BUTTON, 15 30, 1, 2, 3, 4 972 // the `15` is the style parameter, but in the Win32 implementation the `30` 973 // is completely ignored (i.e. the `1, 2, 3, 4` are `x`, `y`, `w`, `h`). 974 // If a comma is added after the `15`, then `30` gets interpreted (correctly) 975 // as the `x` value. 976 // 977 // Instead of emulating this behavior, we just warn about the potential for 978 // weird behavior in the Win32 implementation whenever there isn't a comma after 979 // the style parameter. 980 const lookahead_token = try self.lookaheadToken(.normal); 981 if (lookahead_token.id != .comma and lookahead_token.id != .eof) { 982 try self.addErrorDetailsWithCodePage(.{ 983 .err = .rc_could_miscompile_control_params, 984 .type = .warning, 985 .code_page = self.lexer.current_code_page, 986 .token = lookahead_token, 987 }); 988 try self.addErrorDetailsWithCodePage(.{ 989 .err = .rc_could_miscompile_control_params, 990 .type = .note, 991 .code_page = self.lexer.current_code_page, 992 .token = style.?.getFirstToken(), 993 .token_span_end = style.?.getLastToken(), 994 }); 995 } 996 try self.skipAnyCommas(); 997 } 998 999 const x = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 1000 _ = try self.parseOptionalToken(.comma); 1001 const y = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 1002 _ = try self.parseOptionalToken(.comma); 1003 const width = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 1004 _ = try self.parseOptionalToken(.comma); 1005 const height = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 1006 1007 var optional_param_parser = OptionalParamParser{ .parser = self }; 1008 if (control != .control) { 1009 style = try optional_param_parser.parse(.{ .not_expression_allowed = true }); 1010 } 1011 1012 const exstyle: ?*Node = try optional_param_parser.parse(.{ .not_expression_allowed = true }); 1013 const help_id: ?*Node = switch (resource) { 1014 .dialogex => try optional_param_parser.parse(.{}), 1015 else => null, 1016 }; 1017 1018 var extra_data: []*Node = &[_]*Node{}; 1019 var extra_data_begin: ?Token = null; 1020 var extra_data_end: ?Token = null; 1021 // extra data is DIALOGEX-only 1022 if (resource == .dialogex and try self.parseOptionalToken(.begin)) { 1023 extra_data_begin = self.state.token; 1024 extra_data = try self.parseRawDataBlock(); 1025 extra_data_end = self.state.token; 1026 } 1027 1028 const node = try self.state.arena.create(Node.ControlStatement); 1029 node.* = .{ 1030 .type = control_token, 1031 .text = text, 1032 .class = class, 1033 .id = id, 1034 .x = x, 1035 .y = y, 1036 .width = width, 1037 .height = height, 1038 .style = style, 1039 .exstyle = exstyle, 1040 .help_id = help_id, 1041 .extra_data_begin = extra_data_begin, 1042 .extra_data = extra_data, 1043 .extra_data_end = extra_data_end, 1044 }; 1045 return &node.base; 1046 } 1047 1048 fn parseToolbarButtonStatement(self: *Self) Error!?*Node { 1049 const keyword_token = try self.lookaheadToken(.normal); 1050 const button_type = rc.ToolbarButton.map.get(keyword_token.slice(self.lexer.buffer)) orelse return null; 1051 try self.nextToken(.normal); 1052 1053 switch (button_type) { 1054 .separator => { 1055 const node = try self.state.arena.create(Node.Literal); 1056 node.* = .{ 1057 .token = keyword_token, 1058 }; 1059 return &node.base; 1060 }, 1061 .button => { 1062 const button_id = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 1063 1064 const node = try self.state.arena.create(Node.SimpleStatement); 1065 node.* = .{ 1066 .identifier = keyword_token, 1067 .value = button_id, 1068 }; 1069 return &node.base; 1070 }, 1071 } 1072 } 1073 1074 /// Expects the current token to be handled, and that the menuitem/popup statement will 1075 /// begin on the next token. 1076 /// After return, the current token will be the token immediately before the end of the 1077 /// menuitem statement (or unchanged if the function returns null). 1078 fn parseMenuItemStatement(self: *Self, resource: ResourceType, top_level_menu_id_token: Token, nesting_level: u32) Error!?*Node { 1079 const menuitem_token = try self.lookaheadToken(.normal); 1080 const menuitem = rc.MenuItem.map.get(menuitem_token.slice(self.lexer.buffer)) orelse return null; 1081 try self.nextToken(.normal); 1082 1083 if (nesting_level > max_nested_menu_level) { 1084 try self.addErrorDetails(.{ 1085 .err = .nested_resource_level_exceeds_max, 1086 .token = top_level_menu_id_token, 1087 .extra = .{ .resource = resource }, 1088 }); 1089 return self.addErrorDetailsAndFail(.{ 1090 .err = .nested_resource_level_exceeds_max, 1091 .type = .note, 1092 .token = menuitem_token, 1093 .extra = .{ .resource = resource }, 1094 }); 1095 } 1096 1097 switch (resource) { 1098 .menu => switch (menuitem) { 1099 .menuitem => { 1100 try self.nextToken(.normal); 1101 if (rc.MenuItem.isSeparator(self.state.token.slice(self.lexer.buffer))) { 1102 const separator_token = self.state.token; 1103 // There can be any number of trailing commas after SEPARATOR 1104 try self.skipAnyCommas(); 1105 const node = try self.state.arena.create(Node.MenuItemSeparator); 1106 node.* = .{ 1107 .menuitem = menuitem_token, 1108 .separator = separator_token, 1109 }; 1110 return &node.base; 1111 } else { 1112 const text = self.state.token; 1113 if (!text.isStringLiteral()) { 1114 return self.addErrorDetailsAndFail(.{ 1115 .err = .expected_something_else, 1116 .token = text, 1117 .extra = .{ .expected_types = .{ 1118 .string_literal = true, 1119 } }, 1120 }); 1121 } 1122 try self.skipAnyCommas(); 1123 1124 const result = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 1125 1126 _ = try self.parseOptionalToken(.comma); 1127 1128 var options: std.ArrayListUnmanaged(Token) = .empty; 1129 while (true) { 1130 const option_token = try self.lookaheadToken(.normal); 1131 if (!rc.MenuItem.Option.map.has(option_token.slice(self.lexer.buffer))) { 1132 break; 1133 } 1134 try self.nextToken(.normal); 1135 try options.append(self.state.arena, option_token); 1136 try self.skipAnyCommas(); 1137 } 1138 1139 const node = try self.state.arena.create(Node.MenuItem); 1140 node.* = .{ 1141 .menuitem = menuitem_token, 1142 .text = text, 1143 .result = result, 1144 .option_list = try options.toOwnedSlice(self.state.arena), 1145 }; 1146 return &node.base; 1147 } 1148 }, 1149 .popup => { 1150 try self.nextToken(.normal); 1151 const text = self.state.token; 1152 if (!text.isStringLiteral()) { 1153 return self.addErrorDetailsAndFail(.{ 1154 .err = .expected_something_else, 1155 .token = text, 1156 .extra = .{ .expected_types = .{ 1157 .string_literal = true, 1158 } }, 1159 }); 1160 } 1161 try self.skipAnyCommas(); 1162 1163 var options: std.ArrayListUnmanaged(Token) = .empty; 1164 while (true) { 1165 const option_token = try self.lookaheadToken(.normal); 1166 if (!rc.MenuItem.Option.map.has(option_token.slice(self.lexer.buffer))) { 1167 break; 1168 } 1169 try self.nextToken(.normal); 1170 try options.append(self.state.arena, option_token); 1171 try self.skipAnyCommas(); 1172 } 1173 1174 try self.nextToken(.normal); 1175 const begin_token = self.state.token; 1176 try self.check(.begin); 1177 1178 var items: std.ArrayListUnmanaged(*Node) = .empty; 1179 while (try self.parseMenuItemStatement(resource, top_level_menu_id_token, nesting_level + 1)) |item_node| { 1180 try items.append(self.state.arena, item_node); 1181 } 1182 1183 try self.nextToken(.normal); 1184 const end_token = self.state.token; 1185 try self.check(.end); 1186 1187 if (items.items.len == 0) { 1188 return self.addErrorDetailsAndFail(.{ 1189 .err = .empty_menu_not_allowed, 1190 .token = menuitem_token, 1191 }); 1192 } 1193 1194 const node = try self.state.arena.create(Node.Popup); 1195 node.* = .{ 1196 .popup = menuitem_token, 1197 .text = text, 1198 .option_list = try options.toOwnedSlice(self.state.arena), 1199 .begin_token = begin_token, 1200 .items = try items.toOwnedSlice(self.state.arena), 1201 .end_token = end_token, 1202 }; 1203 return &node.base; 1204 }, 1205 }, 1206 .menuex => { 1207 try self.nextToken(.normal); 1208 const text = self.state.token; 1209 if (!text.isStringLiteral()) { 1210 return self.addErrorDetailsAndFail(.{ 1211 .err = .expected_something_else, 1212 .token = text, 1213 .extra = .{ .expected_types = .{ 1214 .string_literal = true, 1215 } }, 1216 }); 1217 } 1218 1219 var param_parser = OptionalParamParser{ .parser = self }; 1220 const id = try param_parser.parse(.{}); 1221 const item_type = try param_parser.parse(.{}); 1222 const state = try param_parser.parse(.{}); 1223 1224 if (menuitem == .menuitem) { 1225 // trailing comma is allowed, skip it 1226 _ = try self.parseOptionalToken(.comma); 1227 1228 const node = try self.state.arena.create(Node.MenuItemEx); 1229 node.* = .{ 1230 .menuitem = menuitem_token, 1231 .text = text, 1232 .id = id, 1233 .type = item_type, 1234 .state = state, 1235 }; 1236 return &node.base; 1237 } 1238 1239 const help_id = try param_parser.parse(.{}); 1240 1241 // trailing comma is allowed, skip it 1242 _ = try self.parseOptionalToken(.comma); 1243 1244 try self.nextToken(.normal); 1245 const begin_token = self.state.token; 1246 try self.check(.begin); 1247 1248 var items: std.ArrayListUnmanaged(*Node) = .empty; 1249 while (try self.parseMenuItemStatement(resource, top_level_menu_id_token, nesting_level + 1)) |item_node| { 1250 try items.append(self.state.arena, item_node); 1251 } 1252 1253 try self.nextToken(.normal); 1254 const end_token = self.state.token; 1255 try self.check(.end); 1256 1257 if (items.items.len == 0) { 1258 return self.addErrorDetailsAndFail(.{ 1259 .err = .empty_menu_not_allowed, 1260 .token = menuitem_token, 1261 }); 1262 } 1263 1264 const node = try self.state.arena.create(Node.PopupEx); 1265 node.* = .{ 1266 .popup = menuitem_token, 1267 .text = text, 1268 .id = id, 1269 .type = item_type, 1270 .state = state, 1271 .help_id = help_id, 1272 .begin_token = begin_token, 1273 .items = try items.toOwnedSlice(self.state.arena), 1274 .end_token = end_token, 1275 }; 1276 return &node.base; 1277 }, 1278 else => unreachable, 1279 } 1280 @compileError("unreachable"); 1281 } 1282 1283 pub const OptionalParamParser = struct { 1284 finished: bool = false, 1285 parser: *Self, 1286 1287 pub const Options = struct { 1288 not_expression_allowed: bool = false, 1289 }; 1290 1291 pub fn parse(self: *OptionalParamParser, options: OptionalParamParser.Options) Error!?*Node { 1292 if (self.finished) return null; 1293 if (!(try self.parser.parseOptionalToken(.comma))) { 1294 self.finished = true; 1295 return null; 1296 } 1297 // If the next lookahead token could be part of a number expression, 1298 // then parse it. Otherwise, treat it as an 'empty' expression and 1299 // continue parsing, since 'empty' values are allowed. 1300 if (try self.parser.lookaheadCouldBeNumberExpression(switch (options.not_expression_allowed) { 1301 true => .not_allowed, 1302 false => .not_disallowed, 1303 })) { 1304 const node = try self.parser.parseExpression(.{ 1305 .allowed_types = .{ .number = true }, 1306 .can_contain_not_expressions = options.not_expression_allowed, 1307 }); 1308 return node; 1309 } 1310 return null; 1311 } 1312 }; 1313 1314 /// Expects the current token to be handled, and that the version statement will 1315 /// begin on the next token. 1316 /// After return, the current token will be the token immediately before the end of the 1317 /// version statement (or unchanged if the function returns null). 1318 fn parseVersionStatement(self: *Self) Error!?*Node { 1319 const type_token = try self.lookaheadToken(.normal); 1320 const statement_type = rc.VersionInfo.map.get(type_token.slice(self.lexer.buffer)) orelse return null; 1321 try self.nextToken(.normal); 1322 switch (statement_type) { 1323 .file_version, .product_version => { 1324 var parts_buffer: [4]*Node = undefined; 1325 var parts = std.ArrayListUnmanaged(*Node).initBuffer(&parts_buffer); 1326 1327 while (true) { 1328 const value = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 1329 parts.addOneAssumeCapacity().* = value; 1330 1331 if (parts.unusedCapacitySlice().len == 0 or 1332 !(try self.parseOptionalToken(.comma))) 1333 { 1334 break; 1335 } 1336 } 1337 1338 const node = try self.state.arena.create(Node.VersionStatement); 1339 node.* = .{ 1340 .type = type_token, 1341 .parts = try self.state.arena.dupe(*Node, parts.items), 1342 }; 1343 return &node.base; 1344 }, 1345 else => { 1346 const value = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 1347 1348 const node = try self.state.arena.create(Node.SimpleStatement); 1349 node.* = .{ 1350 .identifier = type_token, 1351 .value = value, 1352 }; 1353 return &node.base; 1354 }, 1355 } 1356 } 1357 1358 /// Expects the current token to be handled, and that the version BLOCK/VALUE will 1359 /// begin on the next token. 1360 /// After return, the current token will be the token immediately before the end of the 1361 /// version BLOCK/VALUE (or unchanged if the function returns null). 1362 fn parseVersionBlockOrValue(self: *Self, top_level_version_id_token: Token, nesting_level: u32) Error!?*Node { 1363 const keyword_token = try self.lookaheadToken(.normal); 1364 const keyword = rc.VersionBlock.map.get(keyword_token.slice(self.lexer.buffer)) orelse return null; 1365 try self.nextToken(.normal); 1366 1367 if (nesting_level > max_nested_version_level) { 1368 try self.addErrorDetails(.{ 1369 .err = .nested_resource_level_exceeds_max, 1370 .token = top_level_version_id_token, 1371 .extra = .{ .resource = .versioninfo }, 1372 }); 1373 return self.addErrorDetailsAndFail(.{ 1374 .err = .nested_resource_level_exceeds_max, 1375 .type = .note, 1376 .token = keyword_token, 1377 .extra = .{ .resource = .versioninfo }, 1378 }); 1379 } 1380 1381 try self.nextToken(.normal); 1382 const key = self.state.token; 1383 if (!key.isStringLiteral()) { 1384 return self.addErrorDetailsAndFail(.{ 1385 .err = .expected_something_else, 1386 .token = key, 1387 .extra = .{ .expected_types = .{ 1388 .string_literal = true, 1389 } }, 1390 }); 1391 } 1392 // Need to keep track of this to detect a potential miscompilation when 1393 // the comma is omitted and the first value is a quoted string. 1394 const had_comma_before_first_value = try self.parseOptionalToken(.comma); 1395 try self.skipAnyCommas(); 1396 1397 const values = try self.parseBlockValuesList(had_comma_before_first_value); 1398 1399 switch (keyword) { 1400 .block => { 1401 try self.nextToken(.normal); 1402 const begin_token = self.state.token; 1403 try self.check(.begin); 1404 1405 var children: std.ArrayListUnmanaged(*Node) = .empty; 1406 while (try self.parseVersionBlockOrValue(top_level_version_id_token, nesting_level + 1)) |value_node| { 1407 try children.append(self.state.arena, value_node); 1408 } 1409 1410 try self.nextToken(.normal); 1411 const end_token = self.state.token; 1412 try self.check(.end); 1413 1414 const node = try self.state.arena.create(Node.Block); 1415 node.* = .{ 1416 .identifier = keyword_token, 1417 .key = key, 1418 .values = values, 1419 .begin_token = begin_token, 1420 .children = try children.toOwnedSlice(self.state.arena), 1421 .end_token = end_token, 1422 }; 1423 return &node.base; 1424 }, 1425 .value => { 1426 const node = try self.state.arena.create(Node.BlockValue); 1427 node.* = .{ 1428 .identifier = keyword_token, 1429 .key = key, 1430 .values = values, 1431 }; 1432 return &node.base; 1433 }, 1434 } 1435 } 1436 1437 fn parseBlockValuesList(self: *Self, had_comma_before_first_value: bool) Error![]*Node { 1438 var values: std.ArrayListUnmanaged(*Node) = .empty; 1439 var seen_number: bool = false; 1440 var first_string_value: ?*Node = null; 1441 while (true) { 1442 const lookahead_token = try self.lookaheadToken(.normal); 1443 switch (lookahead_token.id) { 1444 .operator, 1445 .number, 1446 .open_paren, 1447 .quoted_ascii_string, 1448 .quoted_wide_string, 1449 => {}, 1450 else => break, 1451 } 1452 const value = try self.parseExpression(.{}); 1453 1454 if (value.isNumberExpression()) { 1455 seen_number = true; 1456 } else if (first_string_value == null) { 1457 std.debug.assert(value.isStringLiteral()); 1458 first_string_value = value; 1459 } 1460 1461 const has_trailing_comma = try self.parseOptionalToken(.comma); 1462 try self.skipAnyCommas(); 1463 1464 const value_value = try self.state.arena.create(Node.BlockValueValue); 1465 value_value.* = .{ 1466 .expression = value, 1467 .trailing_comma = has_trailing_comma, 1468 }; 1469 try values.append(self.state.arena, &value_value.base); 1470 } 1471 if (seen_number and first_string_value != null) { 1472 // The Win32 RC compiler does some strange stuff with the data size: 1473 // Strings are counted as UTF-16 code units including the null-terminator 1474 // Numbers are counted as their byte lengths 1475 // So, when both strings and numbers are within a single value, 1476 // it incorrectly sets the value's type as binary, but then gives the 1477 // data length as a mixture of bytes and UTF-16 code units. This means that 1478 // when the length is read, it will be treated as byte length and will 1479 // not read the full value. We don't reproduce this behavior, so we warn 1480 // of the miscompilation here. 1481 try self.addErrorDetails(.{ 1482 .err = .rc_would_miscompile_version_value_byte_count, 1483 .type = .warning, 1484 .token = first_string_value.?.getFirstToken(), 1485 .token_span_start = values.items[0].getFirstToken(), 1486 .token_span_end = values.items[values.items.len - 1].getLastToken(), 1487 }); 1488 try self.addErrorDetails(.{ 1489 .err = .rc_would_miscompile_version_value_byte_count, 1490 .type = .note, 1491 .token = first_string_value.?.getFirstToken(), 1492 .token_span_start = values.items[0].getFirstToken(), 1493 .token_span_end = values.items[values.items.len - 1].getLastToken(), 1494 .print_source_line = false, 1495 }); 1496 } 1497 if (!had_comma_before_first_value and values.items.len > 0 and values.items[0].cast(.block_value_value).?.expression.isStringLiteral()) { 1498 const token = values.items[0].cast(.block_value_value).?.expression.cast(.literal).?.token; 1499 try self.addErrorDetails(.{ 1500 .err = .rc_would_miscompile_version_value_padding, 1501 .type = .warning, 1502 .token = token, 1503 }); 1504 try self.addErrorDetails(.{ 1505 .err = .rc_would_miscompile_version_value_padding, 1506 .type = .note, 1507 .token = token, 1508 .print_source_line = false, 1509 }); 1510 } 1511 return values.toOwnedSlice(self.state.arena); 1512 } 1513 1514 fn numberExpressionContainsAnyLSuffixes(expression_node: *Node, source: []const u8, code_page_lookup: *const CodePageLookup) bool { 1515 // TODO: This could probably be done without evaluating the whole expression 1516 return Compiler.evaluateNumberExpression(expression_node, source, code_page_lookup).is_long; 1517 } 1518 1519 /// Expects the current token to be a literal token that contains the string LANGUAGE 1520 fn parseLanguageStatement(self: *Self) Error!*Node { 1521 const language_token = self.state.token; 1522 1523 const primary_language = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 1524 1525 try self.nextToken(.normal); 1526 try self.check(.comma); 1527 1528 const sublanguage = try self.parseExpression(.{ .allowed_types = .{ .number = true } }); 1529 1530 // The Win32 RC compiler errors if either parameter contains any number with an L 1531 // suffix. Instead of that, we want to warn and then let the values get truncated. 1532 // The warning is done here to allow the compiler logic to not have to deal with this. 1533 if (numberExpressionContainsAnyLSuffixes(primary_language, self.lexer.buffer, &self.state.input_code_page_lookup)) { 1534 try self.addErrorDetails(.{ 1535 .err = .rc_would_error_u16_with_l_suffix, 1536 .type = .warning, 1537 .token = primary_language.getFirstToken(), 1538 .token_span_end = primary_language.getLastToken(), 1539 .extra = .{ .statement_with_u16_param = .language }, 1540 }); 1541 try self.addErrorDetails(.{ 1542 .err = .rc_would_error_u16_with_l_suffix, 1543 .print_source_line = false, 1544 .type = .note, 1545 .token = primary_language.getFirstToken(), 1546 .token_span_end = primary_language.getLastToken(), 1547 .extra = .{ .statement_with_u16_param = .language }, 1548 }); 1549 } 1550 if (numberExpressionContainsAnyLSuffixes(sublanguage, self.lexer.buffer, &self.state.input_code_page_lookup)) { 1551 try self.addErrorDetails(.{ 1552 .err = .rc_would_error_u16_with_l_suffix, 1553 .type = .warning, 1554 .token = sublanguage.getFirstToken(), 1555 .token_span_end = sublanguage.getLastToken(), 1556 .extra = .{ .statement_with_u16_param = .language }, 1557 }); 1558 try self.addErrorDetails(.{ 1559 .err = .rc_would_error_u16_with_l_suffix, 1560 .print_source_line = false, 1561 .type = .note, 1562 .token = sublanguage.getFirstToken(), 1563 .token_span_end = sublanguage.getLastToken(), 1564 .extra = .{ .statement_with_u16_param = .language }, 1565 }); 1566 } 1567 1568 const node = try self.state.arena.create(Node.LanguageStatement); 1569 node.* = .{ 1570 .language_token = language_token, 1571 .primary_language_id = primary_language, 1572 .sublanguage_id = sublanguage, 1573 }; 1574 return &node.base; 1575 } 1576 1577 pub const ParseExpressionOptions = struct { 1578 is_known_to_be_number_expression: bool = false, 1579 can_contain_not_expressions: bool = false, 1580 nesting_context: NestingContext = .{}, 1581 allowed_types: AllowedTypes = .{ .literal = true, .number = true, .string = true }, 1582 expected_types_override: ?ErrorDetails.ExpectedTypes = null, 1583 1584 pub const AllowedTypes = struct { 1585 literal: bool = false, 1586 number: bool = false, 1587 string: bool = false, 1588 }; 1589 1590 pub const NestingContext = struct { 1591 first_token: ?Token = null, 1592 last_token: ?Token = null, 1593 level: u32 = 0, 1594 1595 /// Returns a new NestingContext with values modified appropriately for an increased nesting level 1596 fn incremented(ctx: NestingContext, first_token: Token, most_recent_token: Token) NestingContext { 1597 return .{ 1598 .first_token = ctx.first_token orelse first_token, 1599 .last_token = most_recent_token, 1600 .level = ctx.level + 1, 1601 }; 1602 } 1603 }; 1604 1605 pub fn toErrorDetails(options: ParseExpressionOptions, token: Token) ErrorDetailsWithoutCodePage { 1606 // TODO: expected_types_override interaction with is_known_to_be_number_expression? 1607 const expected_types = options.expected_types_override orelse ErrorDetails.ExpectedTypes{ 1608 .number = options.allowed_types.number, 1609 .number_expression = options.allowed_types.number, 1610 .string_literal = options.allowed_types.string and !options.is_known_to_be_number_expression, 1611 .literal = options.allowed_types.literal and !options.is_known_to_be_number_expression, 1612 }; 1613 return .{ 1614 .err = .expected_something_else, 1615 .token = token, 1616 .extra = .{ .expected_types = expected_types }, 1617 }; 1618 } 1619 }; 1620 1621 /// Returns true if the next lookahead token is a number or could be the start of a number expression. 1622 /// Only useful when looking for empty expressions in optional fields. 1623 fn lookaheadCouldBeNumberExpression(self: *Self, not_allowed: enum { not_allowed, not_disallowed }) Error!bool { 1624 var lookahead_token = try self.lookaheadToken(.normal); 1625 switch (lookahead_token.id) { 1626 .literal => if (not_allowed == .not_allowed) { 1627 return std.ascii.eqlIgnoreCase("NOT", lookahead_token.slice(self.lexer.buffer)); 1628 } else return false, 1629 .number => return true, 1630 .open_paren => return true, 1631 .operator => { 1632 // + can be a unary operator, see parseExpression's handling of unary + 1633 const operator_char = lookahead_token.slice(self.lexer.buffer)[0]; 1634 return operator_char == '+'; 1635 }, 1636 else => return false, 1637 } 1638 } 1639 1640 fn parsePrimary(self: *Self, options: ParseExpressionOptions) Error!*Node { 1641 try self.nextToken(.normal); 1642 const first_token = self.state.token; 1643 var is_close_paren_expression = false; 1644 var is_unary_plus_expression = false; 1645 switch (self.state.token.id) { 1646 .quoted_ascii_string, .quoted_wide_string => { 1647 if (!options.allowed_types.string) return self.addErrorDetailsAndFail(options.toErrorDetails(self.state.token)); 1648 const node = try self.state.arena.create(Node.Literal); 1649 node.* = .{ .token = self.state.token }; 1650 return &node.base; 1651 }, 1652 .literal => { 1653 if (options.can_contain_not_expressions and std.ascii.eqlIgnoreCase("NOT", self.state.token.slice(self.lexer.buffer))) { 1654 const not_token = self.state.token; 1655 try self.nextToken(.normal); 1656 try self.check(.number); 1657 if (!options.allowed_types.number) return self.addErrorDetailsAndFail(options.toErrorDetails(self.state.token)); 1658 const node = try self.state.arena.create(Node.NotExpression); 1659 node.* = .{ 1660 .not_token = not_token, 1661 .number_token = self.state.token, 1662 }; 1663 return &node.base; 1664 } 1665 if (!options.allowed_types.literal) return self.addErrorDetailsAndFail(options.toErrorDetails(self.state.token)); 1666 const node = try self.state.arena.create(Node.Literal); 1667 node.* = .{ .token = self.state.token }; 1668 return &node.base; 1669 }, 1670 .number => { 1671 if (!options.allowed_types.number) return self.addErrorDetailsAndFail(options.toErrorDetails(self.state.token)); 1672 const node = try self.state.arena.create(Node.Literal); 1673 node.* = .{ .token = self.state.token }; 1674 return &node.base; 1675 }, 1676 .open_paren => { 1677 const open_paren_token = self.state.token; 1678 1679 const expression = try self.parseExpression(.{ 1680 .is_known_to_be_number_expression = true, 1681 .can_contain_not_expressions = options.can_contain_not_expressions, 1682 .nesting_context = options.nesting_context.incremented(first_token, open_paren_token), 1683 .allowed_types = .{ .number = true }, 1684 }); 1685 1686 try self.nextToken(.normal); 1687 // TODO: Add context to error about where the open paren is 1688 try self.check(.close_paren); 1689 1690 if (!options.allowed_types.number) return self.addErrorDetailsAndFail(options.toErrorDetails(open_paren_token)); 1691 const node = try self.state.arena.create(Node.GroupedExpression); 1692 node.* = .{ 1693 .open_token = open_paren_token, 1694 .expression = expression, 1695 .close_token = self.state.token, 1696 }; 1697 return &node.base; 1698 }, 1699 .close_paren => { 1700 // Note: In the Win32 implementation, a single close paren 1701 // counts as a valid "expression", but only when its the first and 1702 // only token in the expression. Such an expression is then treated 1703 // as a 'skip this expression' instruction. For example: 1704 // 1 RCDATA { 1, ), ), ), 2 } 1705 // will be evaluated as if it were `1 RCDATA { 1, 2 }` and only 1706 // 0x0001 and 0x0002 will be written to the .res data. 1707 // 1708 // This behavior is not emulated because it almost certainly has 1709 // no valid use cases and only introduces edge cases that are 1710 // not worth the effort to track down and deal with. Instead, 1711 // we error but also add a note about the Win32 RC behavior if 1712 // this edge case is detected. 1713 if (!options.is_known_to_be_number_expression) { 1714 is_close_paren_expression = true; 1715 } 1716 }, 1717 .operator => { 1718 // In the Win32 implementation, something akin to a unary + 1719 // is allowed but it doesn't behave exactly like a unary +. 1720 // Instead of emulating the Win32 behavior, we instead error 1721 // and add a note about unary plus not being allowed. 1722 // 1723 // This is done because unary + only works in some places, 1724 // and there's no real use-case for it since it's so limited 1725 // in how it can be used (e.g. +1 is accepted but (+1) will error) 1726 // 1727 // Even understanding when unary plus is allowed is difficult, so 1728 // we don't do any fancy detection of when the Win32 RC compiler would 1729 // allow a unary + and instead just output the note in all cases. 1730 // 1731 // Some examples of allowed expressions by the Win32 compiler: 1732 // +1 1733 // 0|+5 1734 // +1+2 1735 // +~-5 1736 // +(1) 1737 // 1738 // Some examples of disallowed expressions by the Win32 compiler: 1739 // (+1) 1740 // ++5 1741 // 1742 // TODO: Potentially re-evaluate and support the unary plus in a bug-for-bug 1743 // compatible way. 1744 const operator_char = self.state.token.slice(self.lexer.buffer)[0]; 1745 if (operator_char == '+') { 1746 is_unary_plus_expression = true; 1747 } 1748 }, 1749 else => {}, 1750 } 1751 1752 try self.addErrorDetails(options.toErrorDetails(self.state.token)); 1753 if (is_close_paren_expression) { 1754 try self.addErrorDetails(.{ 1755 .err = .close_paren_expression, 1756 .type = .note, 1757 .token = self.state.token, 1758 .print_source_line = false, 1759 }); 1760 } 1761 if (is_unary_plus_expression) { 1762 try self.addErrorDetails(.{ 1763 .err = .unary_plus_expression, 1764 .type = .note, 1765 .token = self.state.token, 1766 .print_source_line = false, 1767 }); 1768 } 1769 return error.ParseError; 1770 } 1771 1772 /// Expects the current token to have already been dealt with, and that the 1773 /// expression will start on the next token. 1774 /// After return, the current token will have been dealt with. 1775 fn parseExpression(self: *Self, options: ParseExpressionOptions) Error!*Node { 1776 if (options.nesting_context.level > max_nested_expression_level) { 1777 try self.addErrorDetails(.{ 1778 .err = .nested_expression_level_exceeds_max, 1779 .token = options.nesting_context.first_token.?, 1780 }); 1781 return self.addErrorDetailsAndFail(.{ 1782 .err = .nested_expression_level_exceeds_max, 1783 .type = .note, 1784 .token = options.nesting_context.last_token.?, 1785 }); 1786 } 1787 var expr: *Node = try self.parsePrimary(options); 1788 const first_token = expr.getFirstToken(); 1789 1790 // Non-number expressions can't have operators, so we can just return 1791 if (!expr.isNumberExpression()) return expr; 1792 1793 while (try self.parseOptionalTokenAdvanced(.operator, .normal_expect_operator)) { 1794 const operator = self.state.token; 1795 const rhs_node = try self.parsePrimary(.{ 1796 .is_known_to_be_number_expression = true, 1797 .can_contain_not_expressions = options.can_contain_not_expressions, 1798 .nesting_context = options.nesting_context.incremented(first_token, operator), 1799 .allowed_types = options.allowed_types, 1800 }); 1801 1802 if (!rhs_node.isNumberExpression()) { 1803 return self.addErrorDetailsAndFail(.{ 1804 .err = .expected_something_else, 1805 .token = rhs_node.getFirstToken(), 1806 .token_span_end = rhs_node.getLastToken(), 1807 .extra = .{ .expected_types = .{ 1808 .number = true, 1809 .number_expression = true, 1810 } }, 1811 }); 1812 } 1813 1814 const node = try self.state.arena.create(Node.BinaryExpression); 1815 node.* = .{ 1816 .left = expr, 1817 .operator = operator, 1818 .right = rhs_node, 1819 }; 1820 expr = &node.base; 1821 } 1822 1823 return expr; 1824 } 1825 1826 /// Skips any amount of commas (including zero) 1827 /// In other words, it will skip the regex `,*` 1828 /// Assumes the token(s) should be parsed with `.normal` as the method. 1829 fn skipAnyCommas(self: *Self) !void { 1830 while (try self.parseOptionalToken(.comma)) {} 1831 } 1832 1833 /// Advances the current token only if the token's id matches the specified `id`. 1834 /// Assumes the token should be parsed with `.normal` as the method. 1835 /// Returns true if the token matched, false otherwise. 1836 fn parseOptionalToken(self: *Self, id: Token.Id) Error!bool { 1837 return self.parseOptionalTokenAdvanced(id, .normal); 1838 } 1839 1840 /// Advances the current token only if the token's id matches the specified `id`. 1841 /// Returns true if the token matched, false otherwise. 1842 fn parseOptionalTokenAdvanced(self: *Self, id: Token.Id, comptime method: Lexer.LexMethod) Error!bool { 1843 const maybe_token = try self.lookaheadToken(method); 1844 if (maybe_token.id != id) return false; 1845 try self.nextToken(method); 1846 return true; 1847 } 1848 1849 fn addErrorDetailsWithCodePage(self: *Self, details: ErrorDetails) Allocator.Error!void { 1850 try self.state.diagnostics.append(details); 1851 } 1852 1853 fn addErrorDetailsWithCodePageAndFail(self: *Self, details: ErrorDetails) Error { 1854 try self.addErrorDetailsWithCodePage(details); 1855 return error.ParseError; 1856 } 1857 1858 /// Code page is looked up in input_code_page_lookup using the token, meaning the token 1859 /// must come from nextToken (i.e. it can't be a lookahead token). 1860 fn addErrorDetails(self: *Self, details_without_code_page: ErrorDetailsWithoutCodePage) Allocator.Error!void { 1861 const details = ErrorDetails{ 1862 .err = details_without_code_page.err, 1863 .code_page = self.state.input_code_page_lookup.getForToken(details_without_code_page.token), 1864 .token = details_without_code_page.token, 1865 .token_span_start = details_without_code_page.token_span_start, 1866 .token_span_end = details_without_code_page.token_span_end, 1867 .type = details_without_code_page.type, 1868 .print_source_line = details_without_code_page.print_source_line, 1869 .extra = details_without_code_page.extra, 1870 }; 1871 try self.addErrorDetailsWithCodePage(details); 1872 } 1873 1874 /// Code page is looked up in input_code_page_lookup using the token, meaning the token 1875 /// must come from nextToken (i.e. it can't be a lookahead token). 1876 fn addErrorDetailsAndFail(self: *Self, details_without_code_page: ErrorDetailsWithoutCodePage) Error { 1877 try self.addErrorDetails(details_without_code_page); 1878 return error.ParseError; 1879 } 1880 1881 fn nextToken(self: *Self, comptime method: Lexer.LexMethod) Error!void { 1882 self.state.token = token: while (true) { 1883 const token = self.lexer.next(method) catch |err| switch (err) { 1884 error.CodePagePragmaInIncludedFile => { 1885 // The Win32 RC compiler silently ignores such `#pragma code_page` directives, 1886 // but we want to both ignore them *and* emit a warning 1887 var details = self.lexer.getErrorDetails(err); 1888 details.type = .warning; 1889 try self.addErrorDetailsWithCodePage(details); 1890 continue; 1891 }, 1892 error.CodePagePragmaInvalidCodePage => { 1893 var details = self.lexer.getErrorDetails(err); 1894 if (!self.options.warn_instead_of_error_on_invalid_code_page) { 1895 return self.addErrorDetailsWithCodePageAndFail(details); 1896 } 1897 details.type = .warning; 1898 try self.addErrorDetailsWithCodePage(details); 1899 continue; 1900 }, 1901 error.InvalidDigitCharacterInNumberLiteral => { 1902 const details = self.lexer.getErrorDetails(err); 1903 try self.addErrorDetailsWithCodePage(details); 1904 return self.addErrorDetailsWithCodePageAndFail(.{ 1905 .err = details.err, 1906 .type = .note, 1907 .code_page = self.lexer.current_code_page, 1908 .token = details.token, 1909 .print_source_line = false, 1910 }); 1911 }, 1912 else => return self.addErrorDetailsWithCodePageAndFail(self.lexer.getErrorDetails(err)), 1913 }; 1914 break :token token; 1915 }; 1916 // After every token, set the input code page for its line 1917 try self.state.input_code_page_lookup.setForToken(self.state.token, self.lexer.current_code_page); 1918 // But only set the output code page to the current code page if we are past the first code_page pragma in the file. 1919 // Otherwise, we want to fill the lookup using the default code page so that lookups still work for lines that 1920 // don't have an explicit output code page set. 1921 const is_disjoint_code_page = self.options.disjoint_code_page and self.lexer.seen_pragma_code_pages == 1; 1922 const output_code_page = if (is_disjoint_code_page) 1923 self.state.output_code_page_lookup.default_code_page 1924 else 1925 self.lexer.current_code_page; 1926 1927 if (is_disjoint_code_page and !self.state.warned_about_disjoint_code_page) { 1928 try self.addErrorDetailsWithCodePage(.{ 1929 .err = .disjoint_code_page, 1930 .type = .warning, 1931 .code_page = self.state.input_code_page_lookup.getForLineNum(self.lexer.last_pragma_code_page_token.?.line_number), 1932 .token = self.lexer.last_pragma_code_page_token.?, 1933 }); 1934 try self.addErrorDetailsWithCodePage(.{ 1935 .err = .disjoint_code_page, 1936 .type = .note, 1937 .code_page = self.state.input_code_page_lookup.getForLineNum(self.lexer.last_pragma_code_page_token.?.line_number), 1938 .token = self.lexer.last_pragma_code_page_token.?, 1939 .print_source_line = false, 1940 }); 1941 self.state.warned_about_disjoint_code_page = true; 1942 } 1943 1944 try self.state.output_code_page_lookup.setForToken(self.state.token, output_code_page); 1945 } 1946 1947 fn lookaheadToken(self: *Self, comptime method: Lexer.LexMethod) Error!Token { 1948 self.state.lookahead_lexer = self.lexer.*; 1949 return token: while (true) { 1950 break :token self.state.lookahead_lexer.next(method) catch |err| switch (err) { 1951 // Ignore this error and get the next valid token, we'll deal with this 1952 // properly when getting the token for real 1953 error.CodePagePragmaInIncludedFile => continue, 1954 else => return self.addErrorDetailsWithCodePageAndFail(self.state.lookahead_lexer.getErrorDetails(err)), 1955 }; 1956 }; 1957 } 1958 1959 fn tokenSlice(self: *Self) []const u8 { 1960 return self.state.token.slice(self.lexer.buffer); 1961 } 1962 1963 /// Check that the current token is something that can be used as an ID 1964 fn checkId(self: *Self) !void { 1965 switch (self.state.token.id) { 1966 .literal => {}, 1967 else => { 1968 return self.addErrorDetailsAndFail(.{ 1969 .err = .expected_token, 1970 .token = self.state.token, 1971 .extra = .{ .expected = .literal }, 1972 }); 1973 }, 1974 } 1975 } 1976 1977 fn check(self: *Self, expected_token_id: Token.Id) !void { 1978 if (self.state.token.id != expected_token_id) { 1979 return self.addErrorDetailsAndFail(.{ 1980 .err = .expected_token, 1981 .token = self.state.token, 1982 .extra = .{ .expected = expected_token_id }, 1983 }); 1984 } 1985 } 1986 1987 fn checkResource(self: *Self) !ResourceType { 1988 switch (self.state.token.id) { 1989 .literal => return ResourceType.fromString(.{ 1990 .slice = self.state.token.slice(self.lexer.buffer), 1991 .code_page = self.lexer.current_code_page, 1992 }), 1993 else => { 1994 return self.addErrorDetailsAndFail(.{ 1995 .err = .expected_token, 1996 .token = self.state.token, 1997 .extra = .{ .expected = .literal }, 1998 }); 1999 }, 2000 } 2001 } 2002 };