zig

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

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