1//===-- ResourceScriptParser.cpp --------------------------------*- C++-*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===---------------------------------------------------------------------===//
8//
9// This implements the parser defined in ResourceScriptParser.h.
10//
11//===---------------------------------------------------------------------===//
12
13#include "ResourceScriptParser.h"
14#include "llvm/ADT/StringExtras.h"
15#include "llvm/Option/ArgList.h"
16#include "llvm/Support/FileSystem.h"
17#include "llvm/Support/Path.h"
18#include "llvm/Support/Process.h"
19
20// Take an expression returning llvm::Error and forward the error if it exists.
21#define RETURN_IF_ERROR(Expr) \
22 if (auto Err = (Expr)) \
23 return std::move(Err);
24
25// Take an expression returning llvm::Expected<T> and assign it to Var or
26// forward the error out of the function.
27#define ASSIGN_OR_RETURN(Var, Expr) \
28 auto Var = (Expr); \
29 if (!Var) \
30 return Var.takeError();
31
32namespace llvm {
33namespace rc {
34
35RCParser::ParserError::ParserError(const Twine &Expected, const LocIter CurLoc,
36 const LocIter End)
37 : ErrorLoc(CurLoc), FileEnd(End) {
38 CurMessage = "Error parsing file: expected " + Expected.str() + ", got " +
39 (CurLoc == End ? "<EOF>" : CurLoc->value()).str();
40}
41
42char RCParser::ParserError::ID = 0;
43
44RCParser::RCParser(std::vector<RCToken> TokenList)
45 : Tokens(std::move(TokenList)), CurLoc(Tokens.begin()), End(Tokens.end()) {}
46
47bool RCParser::isEof() const { return CurLoc == End; }
48
49RCParser::ParseType RCParser::parseSingleResource() {
50 // The first thing we read is usually a resource's name. However, in some
51 // cases (LANGUAGE and STRINGTABLE) the resources don't have their names
52 // and the first token to be read is the type.
53 ASSIGN_OR_RETURN(NameToken, readTypeOrName());
54
55 if (NameToken->equalsLower(Str: "LANGUAGE"))
56 return parseLanguageResource();
57 else if (NameToken->equalsLower(Str: "STRINGTABLE"))
58 return parseStringTableResource();
59
60 // If it's not an unnamed resource, what we've just read is a name. Now,
61 // read resource type;
62 ASSIGN_OR_RETURN(TypeToken, readTypeOrName());
63
64 ParseType Result = std::unique_ptr<RCResource>();
65 (void)!Result;
66
67 if (TypeToken->equalsLower(Str: "ACCELERATORS"))
68 Result = parseAcceleratorsResource();
69 else if (TypeToken->equalsLower(Str: "BITMAP"))
70 Result = parseBitmapResource();
71 else if (TypeToken->equalsLower(Str: "CURSOR"))
72 Result = parseCursorResource();
73 else if (TypeToken->equalsLower(Str: "DIALOG"))
74 Result = parseDialogResource(IsExtended: false);
75 else if (TypeToken->equalsLower(Str: "DIALOGEX"))
76 Result = parseDialogResource(IsExtended: true);
77 else if (TypeToken->equalsLower(Str: "HTML"))
78 Result = parseHTMLResource();
79 else if (TypeToken->equalsLower(Str: "ICON"))
80 Result = parseIconResource();
81 else if (TypeToken->equalsLower(Str: "MENU"))
82 Result = parseMenuResource();
83 else if (TypeToken->equalsLower(Str: "MENUEX"))
84 Result = parseMenuExResource();
85 else if (TypeToken->equalsLower(Str: "RCDATA"))
86 Result = parseUserDefinedResource(Type: RkRcData);
87 else if (TypeToken->equalsLower(Str: "VERSIONINFO"))
88 Result = parseVersionInfoResource();
89 else
90 Result = parseUserDefinedResource(Type: *TypeToken);
91
92 if (Result)
93 (*Result)->setName(*NameToken);
94
95 return Result;
96}
97
98bool RCParser::isNextTokenKind(Kind TokenKind) const {
99 return !isEof() && look().kind() == TokenKind;
100}
101
102const RCToken &RCParser::look() const {
103 assert(!isEof());
104 return *CurLoc;
105}
106
107const RCToken &RCParser::read() {
108 assert(!isEof());
109 return *CurLoc++;
110}
111
112void RCParser::consume() {
113 assert(!isEof());
114 CurLoc++;
115}
116
117// An integer description might consist of a single integer or
118// an arithmetic expression evaluating to the integer. The expressions
119// can contain the following tokens: <int> ( ) + - | & ~ not. Their meaning
120// is the same as in C++ except for 'not' expression.
121// The operators in the original RC implementation have the following
122// precedence:
123// 1) Unary operators (- ~ not),
124// 2) Binary operators (+ - & |), with no precedence.
125//
126// 'not' expression is mostly useful for style values. It evaluates to 0,
127// but value given to the operator is stored separately from integer value.
128// It's mostly useful for control style expressions and causes bits from
129// default control style to be excluded from generated style. For binary
130// operators the mask from the right operand is applied to the left operand
131// and masks from both operands are combined in operator result.
132//
133// The following grammar is used to parse the expressions Exp1:
134// Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2
135// Exp2 ::= Exp3 || Exp3 * Exp3 || Exp3 / Exp3
136// Exp3 ::= -Exp3 || ~Exp3 || not Expr3 || Int || (Exp1)
137// (More conveniently, Exp1 and Exp2 are non-empty sequences of Exp3
138// expressions, separated by binary operators.)
139//
140// Expressions of type Exp1 are read by parseIntExpr1(Inner) method, Exp2
141// is read by parseIntExpr2() and Exp3 is read by parseIntExpr3().
142//
143// The original Microsoft tool handles multiple unary operators incorrectly.
144// For example, in 16-bit little-endian integers:
145// 1 => 01 00, -1 => ff ff, --1 => ff ff, ---1 => 01 00;
146// 1 => 01 00, ~1 => fe ff, ~~1 => fd ff, ~~~1 => fc ff.
147// Our implementation differs from the original one and handles these
148// operators correctly:
149// 1 => 01 00, -1 => ff ff, --1 => 01 00, ---1 => ff ff;
150// 1 => 01 00, ~1 => fe ff, ~~1 => 01 00, ~~~1 => fe ff.
151
152Expected<RCInt> RCParser::readInt() {
153 ASSIGN_OR_RETURN(Value, parseIntExpr1());
154 return (*Value).getValue();
155}
156
157Expected<IntWithNotMask> RCParser::parseIntExpr1() {
158 // Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2.
159 ASSIGN_OR_RETURN(FirstResult, parseIntExpr2());
160 IntWithNotMask Result = *FirstResult;
161
162 while (!isEof() && look().isLowPrecedenceBinaryOp()) {
163 auto OpToken = read();
164 ASSIGN_OR_RETURN(NextResult, parseIntExpr2());
165
166 switch (OpToken.kind()) {
167 case Kind::Plus:
168 Result += *NextResult;
169 break;
170
171 case Kind::Minus:
172 Result -= *NextResult;
173 break;
174
175 case Kind::Pipe:
176 Result |= *NextResult;
177 break;
178
179 case Kind::Amp:
180 Result &= *NextResult;
181 break;
182
183 default:
184 llvm_unreachable("Already processed all low precedence binary ops.");
185 }
186 }
187
188 return Result;
189}
190
191Expected<IntWithNotMask> RCParser::parseIntExpr2() {
192 // Exp2 ::= Exp3 || Exp3 * Exp3 || Exp3 / Exp3.
193 ASSIGN_OR_RETURN(FirstResult, parseIntExpr3());
194 IntWithNotMask Result = *FirstResult;
195
196 while (!isEof() && look().isHighPrecedenceBinaryOp()) {
197 auto OpToken = read();
198 ASSIGN_OR_RETURN(NextResult, parseIntExpr3());
199
200 switch (OpToken.kind()) {
201 case Kind::Asterisk:
202 Result *= *NextResult;
203 break;
204
205 case Kind::Slash:
206 Result /= *NextResult;
207 break;
208
209 default:
210 llvm_unreachable("Already processed all high precedence binary ops.");
211 }
212 }
213
214 return Result;
215}
216
217Expected<IntWithNotMask> RCParser::parseIntExpr3() {
218 // Exp3 ::= -Exp3 || ~Exp3 || not Expr3 || Int || (Exp1).
219 static const char ErrorMsg[] = "'-', '~', integer or '('";
220
221 if (isEof())
222 return getExpectedError(Message: ErrorMsg);
223
224 switch (look().kind()) {
225 case Kind::Minus: {
226 consume();
227 ASSIGN_OR_RETURN(Result, parseIntExpr3());
228 return -(*Result);
229 }
230
231 case Kind::Tilde: {
232 consume();
233 ASSIGN_OR_RETURN(Result, parseIntExpr3());
234 return ~(*Result);
235 }
236
237 case Kind::Int:
238 return RCInt(read());
239
240 case Kind::LeftParen: {
241 consume();
242 ASSIGN_OR_RETURN(Result, parseIntExpr1());
243 RETURN_IF_ERROR(consumeType(Kind::RightParen));
244 return *Result;
245 }
246
247 case Kind::Identifier: {
248 if (!read().value().equals_insensitive(RHS: "not"))
249 return getExpectedError(Message: ErrorMsg, IsAlreadyRead: true);
250 ASSIGN_OR_RETURN(Result, parseIntExpr3());
251 return IntWithNotMask(0, (*Result).getValue());
252 }
253
254 default:
255 return getExpectedError(Message: ErrorMsg);
256 }
257}
258
259Expected<StringRef> RCParser::readString() {
260 if (!isNextTokenKind(TokenKind: Kind::String))
261 return getExpectedError(Message: "string");
262 return read().value();
263}
264
265Expected<StringRef> RCParser::readFilename() {
266 if (!isNextTokenKind(TokenKind: Kind::String) && !isNextTokenKind(TokenKind: Kind::Identifier))
267 return getExpectedError(Message: "string");
268 const RCToken &Token = read();
269 StringRef Str = Token.value();
270 if (Token.kind() != Kind::String)
271 return Str;
272 while (isNextTokenKind(TokenKind: Kind::String)) {
273 const RCToken &NextToken = read();
274 StringRef Next = NextToken.value();
275 bool IsWide = Str.consume_front_insensitive(Prefix: "L");
276 Next.consume_front_insensitive(Prefix: "L");
277 bool StrUnquoted = Str.consume_front(Prefix: "\"") && Str.consume_back(Suffix: "\"");
278 bool NextUnquoted = Next.consume_front(Prefix: "\"") && Next.consume_back(Suffix: "\"");
279 assert(StrUnquoted && NextUnquoted);
280 (void)StrUnquoted;
281 (void)NextUnquoted;
282
283 Str = Saver.save(S: Twine(IsWide ? "L" : "") + "\"" + Str + Next + "\"");
284 }
285 return Str;
286}
287
288Expected<StringRef> RCParser::readIdentifier() {
289 if (!isNextTokenKind(TokenKind: Kind::Identifier))
290 return getExpectedError(Message: "identifier");
291 return read().value();
292}
293
294Expected<IntOrString> RCParser::readIntOrString() {
295 if (!isNextTokenKind(TokenKind: Kind::Int) && !isNextTokenKind(TokenKind: Kind::String))
296 return getExpectedError(Message: "int or string");
297 return IntOrString(read());
298}
299
300Expected<IntOrString> RCParser::readTypeOrName() {
301 // We suggest that the correct resource name or type should be either an
302 // identifier or an integer. The original RC tool is much more liberal.
303 if (!isNextTokenKind(TokenKind: Kind::Identifier) && !isNextTokenKind(TokenKind: Kind::Int))
304 return getExpectedError(Message: "int or identifier");
305 return IntOrString(read());
306}
307
308Error RCParser::consumeType(Kind TokenKind) {
309 if (isNextTokenKind(TokenKind)) {
310 consume();
311 return Error::success();
312 }
313
314 switch (TokenKind) {
315#define TOKEN(TokenName) \
316 case Kind::TokenName: \
317 return getExpectedError(#TokenName);
318#define SHORT_TOKEN(TokenName, TokenCh) \
319 case Kind::TokenName: \
320 return getExpectedError(#TokenCh);
321#include "ResourceScriptTokenList.def"
322 }
323
324 llvm_unreachable("All case options exhausted.");
325}
326
327bool RCParser::consumeOptionalType(Kind TokenKind) {
328 if (isNextTokenKind(TokenKind)) {
329 consume();
330 return true;
331 }
332
333 return false;
334}
335
336Expected<SmallVector<RCInt, 8>> RCParser::readIntsWithCommas(size_t MinCount,
337 size_t MaxCount) {
338 assert(MinCount <= MaxCount);
339
340 SmallVector<RCInt, 8> Result;
341
342 auto FailureHandler =
343 [&](llvm::Error Err) -> Expected<SmallVector<RCInt, 8>> {
344 if (Result.size() < MinCount)
345 return std::move(Err);
346 consumeError(Err: std::move(Err));
347 return Result;
348 };
349
350 for (size_t i = 0; i < MaxCount; ++i) {
351 // Try to read a comma unless we read the first token.
352 // Sometimes RC tool requires them and sometimes not. We decide to
353 // always require them.
354 if (i >= 1) {
355 if (auto CommaError = consumeType(TokenKind: Kind::Comma))
356 return FailureHandler(std::move(CommaError));
357 }
358
359 if (auto IntResult = readInt())
360 Result.push_back(Elt: *IntResult);
361 else
362 return FailureHandler(IntResult.takeError());
363 }
364
365 return std::move(Result);
366}
367
368Expected<uint32_t> RCParser::parseFlags(ArrayRef<StringRef> FlagDesc,
369 ArrayRef<uint32_t> FlagValues) {
370 assert(!FlagDesc.empty());
371 assert(FlagDesc.size() == FlagValues.size());
372
373 uint32_t Result = 0;
374 while (isNextTokenKind(TokenKind: Kind::Comma)) {
375 consume();
376 ASSIGN_OR_RETURN(FlagResult, readIdentifier());
377 bool FoundFlag = false;
378
379 for (size_t FlagId = 0; FlagId < FlagDesc.size(); ++FlagId) {
380 if (!FlagResult->equals_insensitive(RHS: FlagDesc[FlagId]))
381 continue;
382
383 Result |= FlagValues[FlagId];
384 FoundFlag = true;
385 break;
386 }
387
388 if (!FoundFlag)
389 return getExpectedError(Message: join(R&: FlagDesc, Separator: "/"), IsAlreadyRead: true);
390 }
391
392 return Result;
393}
394
395uint16_t RCParser::parseMemoryFlags(uint16_t Flags) {
396 while (!isEof()) {
397 const RCToken &Token = look();
398 if (Token.kind() != Kind::Identifier)
399 return Flags;
400 const StringRef Ident = Token.value();
401 if (Ident.equals_insensitive(RHS: "PRELOAD"))
402 Flags |= MfPreload;
403 else if (Ident.equals_insensitive(RHS: "LOADONCALL"))
404 Flags &= ~MfPreload;
405 else if (Ident.equals_insensitive(RHS: "FIXED"))
406 Flags &= ~(MfMoveable | MfDiscardable);
407 else if (Ident.equals_insensitive(RHS: "MOVEABLE"))
408 Flags |= MfMoveable;
409 else if (Ident.equals_insensitive(RHS: "DISCARDABLE"))
410 Flags |= MfDiscardable | MfMoveable | MfPure;
411 else if (Ident.equals_insensitive(RHS: "PURE"))
412 Flags |= MfPure;
413 else if (Ident.equals_insensitive(RHS: "IMPURE"))
414 Flags &= ~(MfPure | MfDiscardable);
415 else if (Ident.equals_insensitive(RHS: "SHARED"))
416 Flags |= MfPure;
417 else if (Ident.equals_insensitive(RHS: "NONSHARED"))
418 Flags &= ~(MfPure | MfDiscardable);
419 else
420 return Flags;
421 consume();
422 }
423 return Flags;
424}
425
426Expected<OptionalStmtList>
427RCParser::parseOptionalStatements(OptStmtType StmtsType) {
428 OptionalStmtList Result;
429
430 // The last statement is always followed by the start of the block.
431 while (!isNextTokenKind(TokenKind: Kind::BlockBegin)) {
432 ASSIGN_OR_RETURN(SingleParse, parseSingleOptionalStatement(StmtsType));
433 Result.addStmt(Stmt: std::move(*SingleParse));
434 }
435
436 return std::move(Result);
437}
438
439Expected<std::unique_ptr<OptionalStmt>>
440RCParser::parseSingleOptionalStatement(OptStmtType StmtsType) {
441 ASSIGN_OR_RETURN(TypeToken, readIdentifier());
442 if (TypeToken->equals_insensitive(RHS: "CHARACTERISTICS"))
443 return parseCharacteristicsStmt();
444 if (TypeToken->equals_insensitive(RHS: "LANGUAGE"))
445 return parseLanguageStmt();
446 if (TypeToken->equals_insensitive(RHS: "VERSION"))
447 return parseVersionStmt();
448
449 if (StmtsType != OptStmtType::BasicStmt) {
450 if (TypeToken->equals_insensitive(RHS: "CAPTION"))
451 return parseCaptionStmt();
452 if (TypeToken->equals_insensitive(RHS: "CLASS"))
453 return parseClassStmt();
454 if (TypeToken->equals_insensitive(RHS: "EXSTYLE"))
455 return parseExStyleStmt();
456 if (TypeToken->equals_insensitive(RHS: "FONT"))
457 return parseFontStmt(DialogType: StmtsType);
458 if (TypeToken->equals_insensitive(RHS: "STYLE"))
459 return parseStyleStmt();
460 if (TypeToken->equals_insensitive(RHS: "MENU"))
461 return parseMenuStmt();
462 }
463
464 return getExpectedError(Message: "optional statement type, BEGIN or '{'",
465 /* IsAlreadyRead = */ true);
466}
467
468RCParser::ParseType RCParser::parseLanguageResource() {
469 // Read LANGUAGE as an optional statement. If it's read correctly, we can
470 // upcast it to RCResource.
471 return parseLanguageStmt();
472}
473
474RCParser::ParseType RCParser::parseAcceleratorsResource() {
475 uint16_t MemoryFlags =
476 parseMemoryFlags(Flags: AcceleratorsResource::getDefaultMemoryFlags());
477 ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());
478 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
479
480 auto Accels = std::make_unique<AcceleratorsResource>(
481 args: std::move(*OptStatements), args&: MemoryFlags);
482
483 while (!consumeOptionalType(TokenKind: Kind::BlockEnd)) {
484 ASSIGN_OR_RETURN(EventResult, readIntOrString());
485 RETURN_IF_ERROR(consumeType(Kind::Comma));
486 ASSIGN_OR_RETURN(IDResult, readInt());
487 ASSIGN_OR_RETURN(
488 FlagsResult,
489 parseFlags(AcceleratorsResource::Accelerator::OptionsStr,
490 AcceleratorsResource::Accelerator::OptionsFlags));
491 Accels->addAccelerator(Event: *EventResult, Id: *IDResult, Flags: *FlagsResult);
492 }
493
494 return std::move(Accels);
495}
496
497RCParser::ParseType RCParser::parseCursorResource() {
498 uint16_t MemoryFlags =
499 parseMemoryFlags(Flags: CursorResource::getDefaultMemoryFlags());
500 ASSIGN_OR_RETURN(Arg, readFilename());
501 return std::make_unique<CursorResource>(args&: *Arg, args&: MemoryFlags);
502}
503
504RCParser::ParseType RCParser::parseDialogResource(bool IsExtended) {
505 uint16_t MemoryFlags =
506 parseMemoryFlags(Flags: DialogResource::getDefaultMemoryFlags());
507 // Dialog resources have the following format of the arguments:
508 // DIALOG: x, y, width, height [opt stmts...] {controls...}
509 // DIALOGEX: x, y, width, height [, helpID] [opt stmts...] {controls...}
510 // These are very similar, so we parse them together.
511 ASSIGN_OR_RETURN(LocResult, readIntsWithCommas(4, 4));
512
513 uint32_t HelpID = 0; // When HelpID is unset, it's assumed to be 0.
514 if (IsExtended && consumeOptionalType(TokenKind: Kind::Comma)) {
515 ASSIGN_OR_RETURN(HelpIDResult, readInt());
516 HelpID = *HelpIDResult;
517 }
518
519 ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements(
520 IsExtended ? OptStmtType::DialogExStmt
521 : OptStmtType::DialogStmt));
522
523 assert(isNextTokenKind(Kind::BlockBegin) &&
524 "parseOptionalStatements, when successful, halts on BlockBegin.");
525 consume();
526
527 auto Dialog = std::make_unique<DialogResource>(
528 args&: (*LocResult)[0], args&: (*LocResult)[1], args&: (*LocResult)[2], args&: (*LocResult)[3],
529 args&: HelpID, args: std::move(*OptStatements), args&: IsExtended, args&: MemoryFlags);
530
531 while (!consumeOptionalType(TokenKind: Kind::BlockEnd)) {
532 ASSIGN_OR_RETURN(ControlDefResult, parseControl());
533 Dialog->addControl(Ctl: std::move(*ControlDefResult));
534 }
535
536 return std::move(Dialog);
537}
538
539RCParser::ParseType RCParser::parseUserDefinedResource(IntOrString Type) {
540 uint16_t MemoryFlags =
541 parseMemoryFlags(Flags: UserDefinedResource::getDefaultMemoryFlags());
542 if (isEof())
543 return getExpectedError(Message: "filename, '{' or BEGIN");
544
545 // Check if this is a file resource.
546 switch (look().kind()) {
547 case Kind::String:
548 case Kind::Identifier: {
549 ASSIGN_OR_RETURN(Filename, readFilename());
550 return std::make_unique<UserDefinedResource>(args&: Type, args&: *Filename, args&: MemoryFlags);
551 }
552 default:
553 break;
554 }
555
556 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
557 std::vector<IntOrString> Data;
558
559 while (!consumeOptionalType(TokenKind: Kind::BlockEnd)) {
560 ASSIGN_OR_RETURN(Item, readIntOrString());
561 Data.push_back(x: *Item);
562
563 // There can be zero or more commas after each token (but not before
564 // the first one).
565 while (consumeOptionalType(TokenKind: Kind::Comma)) {
566 }
567 }
568
569 return std::make_unique<UserDefinedResource>(args&: Type, args: std::move(Data),
570 args&: MemoryFlags);
571}
572
573RCParser::ParseType RCParser::parseVersionInfoResource() {
574 uint16_t MemoryFlags =
575 parseMemoryFlags(Flags: VersionInfoResource::getDefaultMemoryFlags());
576 ASSIGN_OR_RETURN(FixedResult, parseVersionInfoFixed());
577 ASSIGN_OR_RETURN(BlockResult, parseVersionInfoBlockContents(StringRef()));
578 return std::make_unique<VersionInfoResource>(
579 args: std::move(**BlockResult), args: std::move(*FixedResult), args&: MemoryFlags);
580}
581
582Expected<Control> RCParser::parseControl() {
583 // Each control definition (except CONTROL) follows one of the schemes below
584 // depending on the control class:
585 // [class] text, id, x, y, width, height [, style] [, exstyle] [, helpID]
586 // [class] id, x, y, width, height [, style] [, exstyle] [, helpID]
587 // Note that control ids must be integers.
588 // Text might be either a string or an integer pointing to resource ID.
589 ASSIGN_OR_RETURN(ClassResult, readIdentifier());
590 std::string ClassUpper = ClassResult->upper();
591 auto CtlInfo = Control::SupportedCtls.find(Key: ClassUpper);
592 if (CtlInfo == Control::SupportedCtls.end())
593 return getExpectedError(Message: "control type, END or '}'", IsAlreadyRead: true);
594
595 // Read caption if necessary.
596 IntOrString Caption{StringRef()};
597 if (CtlInfo->getValue().HasTitle) {
598 ASSIGN_OR_RETURN(CaptionResult, readIntOrString());
599 RETURN_IF_ERROR(consumeType(Kind::Comma));
600 Caption = *CaptionResult;
601 }
602
603 ASSIGN_OR_RETURN(ID, readInt());
604 RETURN_IF_ERROR(consumeType(Kind::Comma));
605
606 IntOrString Class;
607 std::optional<IntWithNotMask> Style;
608 if (ClassUpper == "CONTROL") {
609 // CONTROL text, id, class, style, x, y, width, height [, exstyle] [,
610 // helpID]
611 ASSIGN_OR_RETURN(ClassStr, readString());
612 RETURN_IF_ERROR(consumeType(Kind::Comma));
613 Class = *ClassStr;
614 ASSIGN_OR_RETURN(StyleVal, parseIntExpr1());
615 RETURN_IF_ERROR(consumeType(Kind::Comma));
616 Style = *StyleVal;
617 } else {
618 Class = CtlInfo->getValue().CtlClass;
619 }
620
621 // x, y, width, height
622 ASSIGN_OR_RETURN(Args, readIntsWithCommas(4, 4));
623
624 if (ClassUpper != "CONTROL") {
625 if (consumeOptionalType(TokenKind: Kind::Comma)) {
626 ASSIGN_OR_RETURN(Val, parseIntExpr1());
627 Style = *Val;
628 }
629 }
630
631 std::optional<uint32_t> ExStyle;
632 if (consumeOptionalType(TokenKind: Kind::Comma)) {
633 ASSIGN_OR_RETURN(Val, readInt());
634 ExStyle = *Val;
635 }
636 std::optional<uint32_t> HelpID;
637 if (consumeOptionalType(TokenKind: Kind::Comma)) {
638 ASSIGN_OR_RETURN(Val, readInt());
639 HelpID = *Val;
640 }
641
642 return Control(*ClassResult, Caption, *ID, (*Args)[0], (*Args)[1], (*Args)[2],
643 (*Args)[3], Style, ExStyle, HelpID, Class);
644}
645
646RCParser::ParseType RCParser::parseBitmapResource() {
647 uint16_t MemoryFlags =
648 parseMemoryFlags(Flags: BitmapResource::getDefaultMemoryFlags());
649 ASSIGN_OR_RETURN(Arg, readFilename());
650 return std::make_unique<BitmapResource>(args&: *Arg, args&: MemoryFlags);
651}
652
653RCParser::ParseType RCParser::parseIconResource() {
654 uint16_t MemoryFlags =
655 parseMemoryFlags(Flags: IconResource::getDefaultMemoryFlags());
656 ASSIGN_OR_RETURN(Arg, readFilename());
657 return std::make_unique<IconResource>(args&: *Arg, args&: MemoryFlags);
658}
659
660RCParser::ParseType RCParser::parseHTMLResource() {
661 uint16_t MemoryFlags =
662 parseMemoryFlags(Flags: HTMLResource::getDefaultMemoryFlags());
663 ASSIGN_OR_RETURN(Arg, readFilename());
664 return std::make_unique<HTMLResource>(args&: *Arg, args&: MemoryFlags);
665}
666
667RCParser::ParseType RCParser::parseMenuResource() {
668 uint16_t MemoryFlags =
669 parseMemoryFlags(Flags: MenuResource::getDefaultMemoryFlags());
670 ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());
671 ASSIGN_OR_RETURN(Items, parseMenuItemsList());
672 return std::make_unique<MenuResource>(args: std::move(*OptStatements),
673 args: std::move(*Items), args&: MemoryFlags);
674}
675
676RCParser::ParseType RCParser::parseMenuExResource() {
677 uint16_t MemoryFlags =
678 parseMemoryFlags(Flags: MenuExResource::getDefaultMemoryFlags());
679 ASSIGN_OR_RETURN(Items, parseMenuExItemsList());
680 return std::make_unique<MenuExResource>(args: std::move(*Items), args&: MemoryFlags);
681}
682
683Expected<MenuDefinitionList> RCParser::parseMenuItemsList() {
684 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
685
686 MenuDefinitionList List;
687
688 // Read a set of items. Each item is of one of three kinds:
689 // MENUITEM SEPARATOR
690 // MENUITEM caption:String, result:Int [, menu flags]...
691 // POPUP caption:String [, menu flags]... { items... }
692 while (!consumeOptionalType(TokenKind: Kind::BlockEnd)) {
693 ASSIGN_OR_RETURN(ItemTypeResult, readIdentifier());
694
695 bool IsMenuItem = ItemTypeResult->equals_insensitive(RHS: "MENUITEM");
696 bool IsPopup = ItemTypeResult->equals_insensitive(RHS: "POPUP");
697 if (!IsMenuItem && !IsPopup)
698 return getExpectedError(Message: "MENUITEM, POPUP, END or '}'", IsAlreadyRead: true);
699
700 if (IsMenuItem && isNextTokenKind(TokenKind: Kind::Identifier)) {
701 // Now, expecting SEPARATOR.
702 ASSIGN_OR_RETURN(SeparatorResult, readIdentifier());
703 if (SeparatorResult->equals_insensitive(RHS: "SEPARATOR")) {
704 List.addDefinition(Def: std::make_unique<MenuSeparator>());
705 continue;
706 }
707
708 return getExpectedError(Message: "SEPARATOR or string", IsAlreadyRead: true);
709 }
710
711 // Not a separator. Read the caption.
712 ASSIGN_OR_RETURN(CaptionResult, readString());
713
714 // If MENUITEM, expect also a comma and an integer.
715 uint32_t MenuResult = -1;
716
717 if (IsMenuItem) {
718 RETURN_IF_ERROR(consumeType(Kind::Comma));
719 ASSIGN_OR_RETURN(IntResult, readInt());
720 MenuResult = *IntResult;
721 }
722
723 ASSIGN_OR_RETURN(FlagsResult, parseFlags(MenuDefinition::OptionsStr,
724 MenuDefinition::OptionsFlags));
725
726 if (IsPopup) {
727 // If POPUP, read submenu items recursively.
728 ASSIGN_OR_RETURN(SubMenuResult, parseMenuItemsList());
729 List.addDefinition(Def: std::make_unique<PopupItem>(
730 args&: *CaptionResult, args&: *FlagsResult, args: std::move(*SubMenuResult)));
731 continue;
732 }
733
734 assert(IsMenuItem);
735 List.addDefinition(
736 Def: std::make_unique<MenuItem>(args&: *CaptionResult, args&: MenuResult, args&: *FlagsResult));
737 }
738
739 return std::move(List);
740}
741
742Expected<MenuDefinitionList> RCParser::parseMenuExItemsList() {
743 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
744
745 MenuDefinitionList List;
746
747 // Read a set of items. Each item is of one of two kinds:
748 // MENUITEM caption:String [,[id][, [type][, state]]]]
749 // POPUP caption:String [,[id][, [type][, [state][, helpID]]]] { popupBody }
750 while (!consumeOptionalType(TokenKind: Kind::BlockEnd)) {
751 ASSIGN_OR_RETURN(ItemTypeResult, readIdentifier());
752
753 bool IsMenuItem = ItemTypeResult->equals_insensitive(RHS: "MENUITEM");
754 bool IsPopup = ItemTypeResult->equals_insensitive(RHS: "POPUP");
755 if (!IsMenuItem && !IsPopup)
756 return getExpectedError(Message: "MENUITEM, POPUP, END or '}'", IsAlreadyRead: true);
757
758 // Not a separator. Read the caption.
759 ASSIGN_OR_RETURN(CaptionResult, readString());
760
761 // If MENUITEM, expect [,[id][, [type][, state]]]]
762 if (IsMenuItem) {
763 uint32_t MenuId = 0;
764 uint32_t MenuType = 0;
765 uint32_t MenuState = 0;
766
767 if (consumeOptionalType(TokenKind: Kind::Comma)) {
768 auto IntId = readInt();
769 if (IntId) {
770 MenuId = *IntId;
771 }
772 if (consumeOptionalType(TokenKind: Kind::Comma)) {
773 auto IntType = readInt();
774 if (IntType) {
775 MenuType = *IntType;
776 }
777 if (consumeOptionalType(TokenKind: Kind::Comma)) {
778 auto IntState = readInt();
779 if (IntState) {
780 MenuState = *IntState;
781 }
782 }
783 }
784 }
785 List.addDefinition(Def: std::make_unique<MenuExItem>(args&: *CaptionResult, args&: MenuId,
786 args&: MenuType, args&: MenuState));
787 continue;
788 }
789
790 assert(IsPopup);
791
792 uint32_t PopupId = 0;
793 uint32_t PopupType = 0;
794 uint32_t PopupState = 0;
795 uint32_t PopupHelpID = 0;
796
797 if (consumeOptionalType(TokenKind: Kind::Comma)) {
798 auto IntId = readInt();
799 if (IntId) {
800 PopupId = *IntId;
801 }
802 if (consumeOptionalType(TokenKind: Kind::Comma)) {
803 auto IntType = readInt();
804 if (IntType) {
805 PopupType = *IntType;
806 }
807 if (consumeOptionalType(TokenKind: Kind::Comma)) {
808 auto IntState = readInt();
809 if (IntState) {
810 PopupState = *IntState;
811 }
812 if (consumeOptionalType(TokenKind: Kind::Comma)) {
813 auto IntHelpID = readInt();
814 if (IntHelpID) {
815 PopupHelpID = *IntHelpID;
816 }
817 }
818 }
819 }
820 }
821 // If POPUP, read submenu items recursively.
822 ASSIGN_OR_RETURN(SubMenuResult, parseMenuExItemsList());
823 List.addDefinition(Def: std::make_unique<PopupExItem>(
824 args&: *CaptionResult, args&: PopupId, args&: PopupType, args&: PopupState, args&: PopupHelpID,
825 args: std::move(*SubMenuResult)));
826 }
827
828 return std::move(List);
829}
830
831RCParser::ParseType RCParser::parseStringTableResource() {
832 uint16_t MemoryFlags =
833 parseMemoryFlags(Flags: StringTableResource::getDefaultMemoryFlags());
834 ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());
835 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
836
837 auto Table = std::make_unique<StringTableResource>(args: std::move(*OptStatements),
838 args&: MemoryFlags);
839
840 // Read strings until we reach the end of the block.
841 while (!consumeOptionalType(TokenKind: Kind::BlockEnd)) {
842 // Each definition consists of string's ID (an integer) and a string.
843 // Some examples in documentation suggest that there might be a comma in
844 // between, however we strictly adhere to the single statement definition.
845 ASSIGN_OR_RETURN(IDResult, readInt());
846 consumeOptionalType(TokenKind: Kind::Comma);
847
848 std::vector<StringRef> Strings;
849 ASSIGN_OR_RETURN(StrResult, readString());
850 Strings.push_back(x: *StrResult);
851 while (isNextTokenKind(TokenKind: Kind::String))
852 Strings.push_back(x: read().value());
853
854 Table->addStrings(ID: *IDResult, Strings: std::move(Strings));
855 }
856
857 return std::move(Table);
858}
859
860Expected<std::unique_ptr<VersionInfoBlock>>
861RCParser::parseVersionInfoBlockContents(StringRef BlockName) {
862 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
863
864 auto Contents = std::make_unique<VersionInfoBlock>(args&: BlockName);
865
866 while (!isNextTokenKind(TokenKind: Kind::BlockEnd)) {
867 ASSIGN_OR_RETURN(Stmt, parseVersionInfoStmt());
868 Contents->addStmt(Stmt: std::move(*Stmt));
869 }
870
871 consume(); // Consume BlockEnd.
872
873 return std::move(Contents);
874}
875
876Expected<std::unique_ptr<VersionInfoStmt>> RCParser::parseVersionInfoStmt() {
877 // Expect either BLOCK or VALUE, then a name or a key (a string).
878 ASSIGN_OR_RETURN(TypeResult, readIdentifier());
879
880 if (TypeResult->equals_insensitive(RHS: "BLOCK")) {
881 ASSIGN_OR_RETURN(NameResult, readString());
882 return parseVersionInfoBlockContents(BlockName: *NameResult);
883 }
884
885 if (TypeResult->equals_insensitive(RHS: "VALUE")) {
886 ASSIGN_OR_RETURN(KeyResult, readString());
887 // Read a non-empty list of strings and/or ints, each
888 // possibly preceded by a comma. Unfortunately, the tool behavior depends
889 // on them existing or not, so we need to memorize where we found them.
890 std::vector<IntOrString> Values;
891 BitVector PrecedingCommas;
892 RETURN_IF_ERROR(consumeType(Kind::Comma));
893 while (!isNextTokenKind(TokenKind: Kind::Identifier) &&
894 !isNextTokenKind(TokenKind: Kind::BlockEnd)) {
895 // Try to eat a comma if it's not the first statement.
896 bool HadComma = Values.size() > 0 && consumeOptionalType(TokenKind: Kind::Comma);
897 ASSIGN_OR_RETURN(ValueResult, readIntOrString());
898 Values.push_back(x: *ValueResult);
899 PrecedingCommas.push_back(Val: HadComma);
900 }
901 return std::make_unique<VersionInfoValue>(args&: *KeyResult, args: std::move(Values),
902 args: std::move(PrecedingCommas));
903 }
904
905 return getExpectedError(Message: "BLOCK or VALUE", IsAlreadyRead: true);
906}
907
908Expected<VersionInfoResource::VersionInfoFixed>
909RCParser::parseVersionInfoFixed() {
910 using RetType = VersionInfoResource::VersionInfoFixed;
911 RetType Result;
912
913 // Read until the beginning of the block.
914 while (!isNextTokenKind(TokenKind: Kind::BlockBegin)) {
915 ASSIGN_OR_RETURN(TypeResult, readIdentifier());
916 auto FixedType = RetType::getFixedType(Type: *TypeResult);
917
918 if (!RetType::isTypeSupported(Type: FixedType))
919 return getExpectedError(Message: "fixed VERSIONINFO statement type", IsAlreadyRead: true);
920 if (Result.IsTypePresent[FixedType])
921 return getExpectedError(Message: "yet unread fixed VERSIONINFO statement type",
922 IsAlreadyRead: true);
923
924 // VERSION variations take multiple integers.
925 size_t NumInts = RetType::isVersionType(Type: FixedType) ? 4 : 1;
926 ASSIGN_OR_RETURN(ArgsResult, readIntsWithCommas(1, NumInts));
927 SmallVector<uint32_t, 4> ArgInts(ArgsResult->begin(), ArgsResult->end());
928 while (ArgInts.size() < NumInts)
929 ArgInts.push_back(Elt: 0);
930 Result.setValue(Type: FixedType, Value: ArgInts);
931 }
932
933 return Result;
934}
935
936RCParser::ParseOptionType RCParser::parseLanguageStmt() {
937 ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 2, /* max = */ 2));
938 return std::make_unique<LanguageResource>(args&: (*Args)[0], args&: (*Args)[1]);
939}
940
941RCParser::ParseOptionType RCParser::parseCharacteristicsStmt() {
942 ASSIGN_OR_RETURN(Arg, readInt());
943 return std::make_unique<CharacteristicsStmt>(args&: *Arg);
944}
945
946RCParser::ParseOptionType RCParser::parseVersionStmt() {
947 ASSIGN_OR_RETURN(Arg, readInt());
948 return std::make_unique<VersionStmt>(args&: *Arg);
949}
950
951RCParser::ParseOptionType RCParser::parseCaptionStmt() {
952 ASSIGN_OR_RETURN(Arg, readString());
953 return std::make_unique<CaptionStmt>(args&: *Arg);
954}
955
956RCParser::ParseOptionType RCParser::parseClassStmt() {
957 ASSIGN_OR_RETURN(Arg, readIntOrString());
958 return std::make_unique<ClassStmt>(args&: *Arg);
959}
960
961RCParser::ParseOptionType RCParser::parseFontStmt(OptStmtType DialogType) {
962 assert(DialogType != OptStmtType::BasicStmt);
963
964 ASSIGN_OR_RETURN(SizeResult, readInt());
965 RETURN_IF_ERROR(consumeType(Kind::Comma));
966 ASSIGN_OR_RETURN(NameResult, readString());
967
968 // Default values for the optional arguments.
969 uint32_t FontWeight = 0;
970 bool FontItalic = false;
971 uint32_t FontCharset = 1;
972 if (DialogType == OptStmtType::DialogExStmt) {
973 if (consumeOptionalType(TokenKind: Kind::Comma)) {
974 ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 0, /* max = */ 3));
975 if (Args->size() >= 1)
976 FontWeight = (*Args)[0];
977 if (Args->size() >= 2)
978 FontItalic = (*Args)[1] != 0;
979 if (Args->size() >= 3)
980 FontCharset = (*Args)[2];
981 }
982 }
983 return std::make_unique<FontStmt>(args&: *SizeResult, args&: *NameResult, args&: FontWeight,
984 args&: FontItalic, args&: FontCharset);
985}
986
987RCParser::ParseOptionType RCParser::parseStyleStmt() {
988 ASSIGN_OR_RETURN(Arg, readInt());
989 return std::make_unique<StyleStmt>(args&: *Arg);
990}
991
992RCParser::ParseOptionType RCParser::parseExStyleStmt() {
993 ASSIGN_OR_RETURN(Arg, readInt());
994 return std::make_unique<ExStyleStmt>(args&: *Arg);
995}
996
997RCParser::ParseOptionType RCParser::parseMenuStmt() {
998 ASSIGN_OR_RETURN(Arg, readIntOrString());
999 return std::make_unique<MenuStmt>(args&: *Arg);
1000}
1001
1002Error RCParser::getExpectedError(const Twine &Message, bool IsAlreadyRead) {
1003 return make_error<ParserError>(
1004 Args: Message, Args: IsAlreadyRead ? std::prev(x: CurLoc) : CurLoc, Args: End);
1005}
1006
1007} // namespace rc
1008} // namespace llvm
1009