master
   1const std = @import("std");
   2const Lexer = @import("lex.zig").Lexer;
   3const Token = @import("lex.zig").Token;
   4const Node = @import("ast.zig").Node;
   5const Tree = @import("ast.zig").Tree;
   6const CodePageLookup = @import("ast.zig").CodePageLookup;
   7const ResourceType = @import("rc.zig").ResourceType;
   8const Allocator = std.mem.Allocator;
   9const ErrorDetails = @import("errors.zig").ErrorDetails;
  10const ErrorDetailsWithoutCodePage = @import("errors.zig").ErrorDetailsWithoutCodePage;
  11const Diagnostics = @import("errors.zig").Diagnostics;
  12const SourceBytes = @import("literals.zig").SourceBytes;
  13const Compiler = @import("compile.zig").Compiler;
  14const rc = @import("rc.zig");
  15const res = @import("res.zig");
  16
  17// TODO: Make these configurable?
  18pub const max_nested_menu_level: u32 = 512;
  19pub const max_nested_version_level: u32 = 512;
  20pub const max_nested_expression_level: u32 = 200;
  21
  22pub 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.ArrayList(*Node) = .empty;
  86        defer statements.deinit(self.state.allocator);
  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.ArrayList(*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(self.state.allocator, 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.ArrayList(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.ArrayList(*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.ArrayList(*Node) = .empty;
 359                defer strings.deinit(self.state.allocator);
 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(self.state.allocator, &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.ArrayList(*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.ArrayList(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.ArrayList(*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.ArrayList(*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.ArrayList(*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.ArrayList(*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.ArrayList(*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.ArrayList(*Node) = .empty;
 856        defer raw_data.deinit(self.state.allocator);
 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(self.state.allocator, 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.ArrayList(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.ArrayList(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.ArrayList(*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.ArrayList(*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.ArrayList(*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.ArrayList(*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.ArrayList(*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};