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