1 //===-- ResourceScriptParser.cpp --------------------------------*- C++-*-===//
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
7 //===---------------------------------------------------------------------===//
9 // This implements the parser defined in ResourceScriptParser.h.
11 //===---------------------------------------------------------------------===//
13 #include "ResourceScriptParser.h"
14 #include "llvm/Option/ArgList.h"
15 #include "llvm/Support/FileSystem.h"
16 #include "llvm/Support/Path.h"
17 #include "llvm/Support/Process.h"
19 // Take an expression returning llvm::Error and forward the error if it exists.
20 #define RETURN_IF_ERROR(Expr) \
21 if (auto Err = (Expr)) \
22 return std::move(Err);
24 // Take an expression returning llvm::Expected<T> and assign it to Var or
25 // forward the error out of the function.
26 #define ASSIGN_OR_RETURN(Var, Expr) \
29 return Var.takeError();
34 RCParser::ParserError::ParserError(const Twine
&Expected
, const LocIter CurLoc
,
36 : ErrorLoc(CurLoc
), FileEnd(End
) {
37 CurMessage
= "Error parsing file: expected " + Expected
.str() + ", got " +
38 (CurLoc
== End
? "<EOF>" : CurLoc
->value()).str();
41 char RCParser::ParserError::ID
= 0;
43 RCParser::RCParser(std::vector
<RCToken
> TokenList
)
44 : Tokens(std::move(TokenList
)), CurLoc(Tokens
.begin()), End(Tokens
.end()) {}
46 bool RCParser::isEof() const { return CurLoc
== End
; }
48 RCParser::ParseType
RCParser::parseSingleResource() {
49 // The first thing we read is usually a resource's name. However, in some
50 // cases (LANGUAGE and STRINGTABLE) the resources don't have their names
51 // and the first token to be read is the type.
52 ASSIGN_OR_RETURN(NameToken
, readTypeOrName());
54 if (NameToken
->equalsLower("LANGUAGE"))
55 return parseLanguageResource();
56 else if (NameToken
->equalsLower("STRINGTABLE"))
57 return parseStringTableResource();
59 // If it's not an unnamed resource, what we've just read is a name. Now,
60 // read resource type;
61 ASSIGN_OR_RETURN(TypeToken
, readTypeOrName());
63 ParseType Result
= std::unique_ptr
<RCResource
>();
66 if (TypeToken
->equalsLower("ACCELERATORS"))
67 Result
= parseAcceleratorsResource();
68 else if (TypeToken
->equalsLower("BITMAP"))
69 Result
= parseBitmapResource();
70 else if (TypeToken
->equalsLower("CURSOR"))
71 Result
= parseCursorResource();
72 else if (TypeToken
->equalsLower("DIALOG"))
73 Result
= parseDialogResource(false);
74 else if (TypeToken
->equalsLower("DIALOGEX"))
75 Result
= parseDialogResource(true);
76 else if (TypeToken
->equalsLower("HTML"))
77 Result
= parseHTMLResource();
78 else if (TypeToken
->equalsLower("ICON"))
79 Result
= parseIconResource();
80 else if (TypeToken
->equalsLower("MENU"))
81 Result
= parseMenuResource();
82 else if (TypeToken
->equalsLower("RCDATA"))
83 Result
= parseUserDefinedResource(RkRcData
);
84 else if (TypeToken
->equalsLower("VERSIONINFO"))
85 Result
= parseVersionInfoResource();
87 Result
= parseUserDefinedResource(*TypeToken
);
90 (*Result
)->setName(*NameToken
);
95 bool RCParser::isNextTokenKind(Kind TokenKind
) const {
96 return !isEof() && look().kind() == TokenKind
;
99 const RCToken
&RCParser::look() const {
104 const RCToken
&RCParser::read() {
109 void RCParser::consume() {
114 // An integer description might consist of a single integer or
115 // an arithmetic expression evaluating to the integer. The expressions
116 // can contain the following tokens: <int> ( ) + - | & ~ not. Their meaning
117 // is the same as in C++ except for 'not' expression.
118 // The operators in the original RC implementation have the following
120 // 1) Unary operators (- ~ not),
121 // 2) Binary operators (+ - & |), with no precedence.
123 // 'not' expression is mostly useful for style values. It evaluates to 0,
124 // but value given to the operator is stored separately from integer value.
125 // It's mostly useful for control style expressions and causes bits from
126 // default control style to be excluded from generated style. For binary
127 // operators the mask from the right operand is applied to the left operand
128 // and masks from both operands are combined in operator result.
130 // The following grammar is used to parse the expressions Exp1:
131 // Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2
132 // Exp2 ::= -Exp2 || ~Exp2 || not Expr2 || Int || (Exp1).
133 // (More conveniently, Exp1 is a non-empty sequence of Exp2 expressions,
134 // separated by binary operators.)
136 // Expressions of type Exp1 are read by parseIntExpr1(Inner) method, while Exp2
137 // is read by parseIntExpr2().
139 // The original Microsoft tool handles multiple unary operators incorrectly.
140 // For example, in 16-bit little-endian integers:
141 // 1 => 01 00, -1 => ff ff, --1 => ff ff, ---1 => 01 00;
142 // 1 => 01 00, ~1 => fe ff, ~~1 => fd ff, ~~~1 => fc ff.
143 // Our implementation differs from the original one and handles these
144 // operators correctly:
145 // 1 => 01 00, -1 => ff ff, --1 => 01 00, ---1 => ff ff;
146 // 1 => 01 00, ~1 => fe ff, ~~1 => 01 00, ~~~1 => fe ff.
148 Expected
<RCInt
> RCParser::readInt() {
149 ASSIGN_OR_RETURN(Value
, parseIntExpr1());
150 return (*Value
).getValue();
153 Expected
<IntWithNotMask
> RCParser::parseIntExpr1() {
154 // Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2.
155 ASSIGN_OR_RETURN(FirstResult
, parseIntExpr2());
156 IntWithNotMask Result
= *FirstResult
;
158 while (!isEof() && look().isBinaryOp()) {
159 auto OpToken
= read();
160 ASSIGN_OR_RETURN(NextResult
, parseIntExpr2());
162 switch (OpToken
.kind()) {
164 Result
+= *NextResult
;
168 Result
-= *NextResult
;
172 Result
|= *NextResult
;
176 Result
&= *NextResult
;
180 llvm_unreachable("Already processed all binary ops.");
187 Expected
<IntWithNotMask
> RCParser::parseIntExpr2() {
188 // Exp2 ::= -Exp2 || ~Exp2 || not Expr2 || Int || (Exp1).
189 static const char ErrorMsg
[] = "'-', '~', integer or '('";
192 return getExpectedError(ErrorMsg
);
194 switch (look().kind()) {
197 ASSIGN_OR_RETURN(Result
, parseIntExpr2());
203 ASSIGN_OR_RETURN(Result
, parseIntExpr2());
208 return RCInt(read());
210 case Kind::LeftParen
: {
212 ASSIGN_OR_RETURN(Result
, parseIntExpr1());
213 RETURN_IF_ERROR(consumeType(Kind::RightParen
));
217 case Kind::Identifier
: {
218 if (!read().value().equals_lower("not"))
219 return getExpectedError(ErrorMsg
, true);
220 ASSIGN_OR_RETURN(Result
, parseIntExpr2());
221 return IntWithNotMask(0, (*Result
).getValue());
225 return getExpectedError(ErrorMsg
);
229 Expected
<StringRef
> RCParser::readString() {
230 if (!isNextTokenKind(Kind::String
))
231 return getExpectedError("string");
232 return read().value();
235 Expected
<StringRef
> RCParser::readFilename() {
236 if (!isNextTokenKind(Kind::String
) && !isNextTokenKind(Kind::Identifier
))
237 return getExpectedError("string");
238 return read().value();
241 Expected
<StringRef
> RCParser::readIdentifier() {
242 if (!isNextTokenKind(Kind::Identifier
))
243 return getExpectedError("identifier");
244 return read().value();
247 Expected
<IntOrString
> RCParser::readIntOrString() {
248 if (!isNextTokenKind(Kind::Int
) && !isNextTokenKind(Kind::String
))
249 return getExpectedError("int or string");
250 return IntOrString(read());
253 Expected
<IntOrString
> RCParser::readTypeOrName() {
254 // We suggest that the correct resource name or type should be either an
255 // identifier or an integer. The original RC tool is much more liberal.
256 if (!isNextTokenKind(Kind::Identifier
) && !isNextTokenKind(Kind::Int
))
257 return getExpectedError("int or identifier");
258 return IntOrString(read());
261 Error
RCParser::consumeType(Kind TokenKind
) {
262 if (isNextTokenKind(TokenKind
)) {
264 return Error::success();
268 #define TOKEN(TokenName) \
269 case Kind::TokenName: \
270 return getExpectedError(#TokenName);
271 #define SHORT_TOKEN(TokenName, TokenCh) \
272 case Kind::TokenName: \
273 return getExpectedError(#TokenCh);
274 #include "ResourceScriptTokenList.def"
277 llvm_unreachable("All case options exhausted.");
280 bool RCParser::consumeOptionalType(Kind TokenKind
) {
281 if (isNextTokenKind(TokenKind
)) {
289 Expected
<SmallVector
<RCInt
, 8>> RCParser::readIntsWithCommas(size_t MinCount
,
291 assert(MinCount
<= MaxCount
);
293 SmallVector
<RCInt
, 8> Result
;
295 auto FailureHandler
=
296 [&](llvm::Error Err
) -> Expected
<SmallVector
<RCInt
, 8>> {
297 if (Result
.size() < MinCount
)
298 return std::move(Err
);
299 consumeError(std::move(Err
));
303 for (size_t i
= 0; i
< MaxCount
; ++i
) {
304 // Try to read a comma unless we read the first token.
305 // Sometimes RC tool requires them and sometimes not. We decide to
306 // always require them.
308 if (auto CommaError
= consumeType(Kind::Comma
))
309 return FailureHandler(std::move(CommaError
));
312 if (auto IntResult
= readInt())
313 Result
.push_back(*IntResult
);
315 return FailureHandler(IntResult
.takeError());
318 return std::move(Result
);
321 Expected
<uint32_t> RCParser::parseFlags(ArrayRef
<StringRef
> FlagDesc
,
322 ArrayRef
<uint32_t> FlagValues
) {
323 assert(!FlagDesc
.empty());
324 assert(FlagDesc
.size() == FlagValues
.size());
327 while (isNextTokenKind(Kind::Comma
)) {
329 ASSIGN_OR_RETURN(FlagResult
, readIdentifier());
330 bool FoundFlag
= false;
332 for (size_t FlagId
= 0; FlagId
< FlagDesc
.size(); ++FlagId
) {
333 if (!FlagResult
->equals_lower(FlagDesc
[FlagId
]))
336 Result
|= FlagValues
[FlagId
];
342 return getExpectedError(join(FlagDesc
, "/"), true);
348 uint16_t RCParser::parseMemoryFlags(uint16_t Flags
) {
350 const RCToken
&Token
= look();
351 if (Token
.kind() != Kind::Identifier
)
353 const StringRef Ident
= Token
.value();
354 if (Ident
.equals_lower("PRELOAD"))
356 else if (Ident
.equals_lower("LOADONCALL"))
358 else if (Ident
.equals_lower("FIXED"))
359 Flags
&= ~(MfMoveable
| MfDiscardable
);
360 else if (Ident
.equals_lower("MOVEABLE"))
362 else if (Ident
.equals_lower("DISCARDABLE"))
363 Flags
|= MfDiscardable
| MfMoveable
| MfPure
;
364 else if (Ident
.equals_lower("PURE"))
366 else if (Ident
.equals_lower("IMPURE"))
367 Flags
&= ~(MfPure
| MfDiscardable
);
368 else if (Ident
.equals_lower("SHARED"))
370 else if (Ident
.equals_lower("NONSHARED"))
371 Flags
&= ~(MfPure
| MfDiscardable
);
379 Expected
<OptionalStmtList
>
380 RCParser::parseOptionalStatements(OptStmtType StmtsType
) {
381 OptionalStmtList Result
;
383 // The last statement is always followed by the start of the block.
384 while (!isNextTokenKind(Kind::BlockBegin
)) {
385 ASSIGN_OR_RETURN(SingleParse
, parseSingleOptionalStatement(StmtsType
));
386 Result
.addStmt(std::move(*SingleParse
));
389 return std::move(Result
);
392 Expected
<std::unique_ptr
<OptionalStmt
>>
393 RCParser::parseSingleOptionalStatement(OptStmtType StmtsType
) {
394 ASSIGN_OR_RETURN(TypeToken
, readIdentifier());
395 if (TypeToken
->equals_lower("CHARACTERISTICS"))
396 return parseCharacteristicsStmt();
397 if (TypeToken
->equals_lower("LANGUAGE"))
398 return parseLanguageStmt();
399 if (TypeToken
->equals_lower("VERSION"))
400 return parseVersionStmt();
402 if (StmtsType
!= OptStmtType::BasicStmt
) {
403 if (TypeToken
->equals_lower("CAPTION"))
404 return parseCaptionStmt();
405 if (TypeToken
->equals_lower("CLASS"))
406 return parseClassStmt();
407 if (TypeToken
->equals_lower("EXSTYLE"))
408 return parseExStyleStmt();
409 if (TypeToken
->equals_lower("FONT"))
410 return parseFontStmt(StmtsType
);
411 if (TypeToken
->equals_lower("STYLE"))
412 return parseStyleStmt();
415 return getExpectedError("optional statement type, BEGIN or '{'",
416 /* IsAlreadyRead = */ true);
419 RCParser::ParseType
RCParser::parseLanguageResource() {
420 // Read LANGUAGE as an optional statement. If it's read correctly, we can
421 // upcast it to RCResource.
422 return parseLanguageStmt();
425 RCParser::ParseType
RCParser::parseAcceleratorsResource() {
426 uint16_t MemoryFlags
=
427 parseMemoryFlags(AcceleratorsResource::getDefaultMemoryFlags());
428 ASSIGN_OR_RETURN(OptStatements
, parseOptionalStatements());
429 RETURN_IF_ERROR(consumeType(Kind::BlockBegin
));
431 auto Accels
= std::make_unique
<AcceleratorsResource
>(
432 std::move(*OptStatements
), MemoryFlags
);
434 while (!consumeOptionalType(Kind::BlockEnd
)) {
435 ASSIGN_OR_RETURN(EventResult
, readIntOrString());
436 RETURN_IF_ERROR(consumeType(Kind::Comma
));
437 ASSIGN_OR_RETURN(IDResult
, readInt());
440 parseFlags(AcceleratorsResource::Accelerator::OptionsStr
,
441 AcceleratorsResource::Accelerator::OptionsFlags
));
442 Accels
->addAccelerator(*EventResult
, *IDResult
, *FlagsResult
);
445 return std::move(Accels
);
448 RCParser::ParseType
RCParser::parseCursorResource() {
449 uint16_t MemoryFlags
=
450 parseMemoryFlags(CursorResource::getDefaultMemoryFlags());
451 ASSIGN_OR_RETURN(Arg
, readFilename());
452 return std::make_unique
<CursorResource
>(*Arg
, MemoryFlags
);
455 RCParser::ParseType
RCParser::parseDialogResource(bool IsExtended
) {
456 uint16_t MemoryFlags
=
457 parseMemoryFlags(DialogResource::getDefaultMemoryFlags());
458 // Dialog resources have the following format of the arguments:
459 // DIALOG: x, y, width, height [opt stmts...] {controls...}
460 // DIALOGEX: x, y, width, height [, helpID] [opt stmts...] {controls...}
461 // These are very similar, so we parse them together.
462 ASSIGN_OR_RETURN(LocResult
, readIntsWithCommas(4, 4));
464 uint32_t HelpID
= 0; // When HelpID is unset, it's assumed to be 0.
465 if (IsExtended
&& consumeOptionalType(Kind::Comma
)) {
466 ASSIGN_OR_RETURN(HelpIDResult
, readInt());
467 HelpID
= *HelpIDResult
;
470 ASSIGN_OR_RETURN(OptStatements
, parseOptionalStatements(
471 IsExtended
? OptStmtType::DialogExStmt
472 : OptStmtType::DialogStmt
));
474 assert(isNextTokenKind(Kind::BlockBegin
) &&
475 "parseOptionalStatements, when successful, halts on BlockBegin.");
478 auto Dialog
= std::make_unique
<DialogResource
>(
479 (*LocResult
)[0], (*LocResult
)[1], (*LocResult
)[2], (*LocResult
)[3],
480 HelpID
, std::move(*OptStatements
), IsExtended
, MemoryFlags
);
482 while (!consumeOptionalType(Kind::BlockEnd
)) {
483 ASSIGN_OR_RETURN(ControlDefResult
, parseControl());
484 Dialog
->addControl(std::move(*ControlDefResult
));
487 return std::move(Dialog
);
490 RCParser::ParseType
RCParser::parseUserDefinedResource(IntOrString Type
) {
491 uint16_t MemoryFlags
=
492 parseMemoryFlags(UserDefinedResource::getDefaultMemoryFlags());
494 return getExpectedError("filename, '{' or BEGIN");
496 // Check if this is a file resource.
497 switch (look().kind()) {
499 case Kind::Identifier
:
500 return std::make_unique
<UserDefinedResource
>(Type
, read().value(),
506 RETURN_IF_ERROR(consumeType(Kind::BlockBegin
));
507 std::vector
<IntOrString
> Data
;
509 // Consume comma before each consecutive token except the first one.
510 bool ConsumeComma
= false;
511 while (!consumeOptionalType(Kind::BlockEnd
)) {
513 RETURN_IF_ERROR(consumeType(Kind::Comma
));
516 ASSIGN_OR_RETURN(Item
, readIntOrString());
517 Data
.push_back(*Item
);
520 return std::make_unique
<UserDefinedResource
>(Type
, std::move(Data
),
524 RCParser::ParseType
RCParser::parseVersionInfoResource() {
525 uint16_t MemoryFlags
=
526 parseMemoryFlags(VersionInfoResource::getDefaultMemoryFlags());
527 ASSIGN_OR_RETURN(FixedResult
, parseVersionInfoFixed());
528 ASSIGN_OR_RETURN(BlockResult
, parseVersionInfoBlockContents(StringRef()));
529 return std::make_unique
<VersionInfoResource
>(
530 std::move(**BlockResult
), std::move(*FixedResult
), MemoryFlags
);
533 Expected
<Control
> RCParser::parseControl() {
534 // Each control definition (except CONTROL) follows one of the schemes below
535 // depending on the control class:
536 // [class] text, id, x, y, width, height [, style] [, exstyle] [, helpID]
537 // [class] id, x, y, width, height [, style] [, exstyle] [, helpID]
538 // Note that control ids must be integers.
539 // Text might be either a string or an integer pointing to resource ID.
540 ASSIGN_OR_RETURN(ClassResult
, readIdentifier());
541 std::string ClassUpper
= ClassResult
->upper();
542 auto CtlInfo
= Control::SupportedCtls
.find(ClassUpper
);
543 if (CtlInfo
== Control::SupportedCtls
.end())
544 return getExpectedError("control type, END or '}'", true);
546 // Read caption if necessary.
547 IntOrString Caption
{StringRef()};
548 if (CtlInfo
->getValue().HasTitle
) {
549 ASSIGN_OR_RETURN(CaptionResult
, readIntOrString());
550 RETURN_IF_ERROR(consumeType(Kind::Comma
));
551 Caption
= *CaptionResult
;
554 ASSIGN_OR_RETURN(ID
, readInt());
555 RETURN_IF_ERROR(consumeType(Kind::Comma
));
558 Optional
<IntWithNotMask
> Style
;
559 if (ClassUpper
== "CONTROL") {
560 // CONTROL text, id, class, style, x, y, width, height [, exstyle] [, helpID]
561 ASSIGN_OR_RETURN(ClassStr
, readString());
562 RETURN_IF_ERROR(consumeType(Kind::Comma
));
564 ASSIGN_OR_RETURN(StyleVal
, parseIntExpr1());
565 RETURN_IF_ERROR(consumeType(Kind::Comma
));
568 Class
= CtlInfo
->getValue().CtlClass
;
571 // x, y, width, height
572 ASSIGN_OR_RETURN(Args
, readIntsWithCommas(4, 4));
574 if (ClassUpper
!= "CONTROL") {
575 if (consumeOptionalType(Kind::Comma
)) {
576 ASSIGN_OR_RETURN(Val
, parseIntExpr1());
581 Optional
<uint32_t> ExStyle
;
582 if (consumeOptionalType(Kind::Comma
)) {
583 ASSIGN_OR_RETURN(Val
, readInt());
586 Optional
<uint32_t> HelpID
;
587 if (consumeOptionalType(Kind::Comma
)) {
588 ASSIGN_OR_RETURN(Val
, readInt());
592 return Control(*ClassResult
, Caption
, *ID
, (*Args
)[0], (*Args
)[1],
593 (*Args
)[2], (*Args
)[3], Style
, ExStyle
, HelpID
, Class
);
596 RCParser::ParseType
RCParser::parseBitmapResource() {
597 uint16_t MemoryFlags
=
598 parseMemoryFlags(BitmapResource::getDefaultMemoryFlags());
599 ASSIGN_OR_RETURN(Arg
, readFilename());
600 return std::make_unique
<BitmapResource
>(*Arg
, MemoryFlags
);
603 RCParser::ParseType
RCParser::parseIconResource() {
604 uint16_t MemoryFlags
=
605 parseMemoryFlags(IconResource::getDefaultMemoryFlags());
606 ASSIGN_OR_RETURN(Arg
, readFilename());
607 return std::make_unique
<IconResource
>(*Arg
, MemoryFlags
);
610 RCParser::ParseType
RCParser::parseHTMLResource() {
611 uint16_t MemoryFlags
=
612 parseMemoryFlags(HTMLResource::getDefaultMemoryFlags());
613 ASSIGN_OR_RETURN(Arg
, readFilename());
614 return std::make_unique
<HTMLResource
>(*Arg
, MemoryFlags
);
617 RCParser::ParseType
RCParser::parseMenuResource() {
618 uint16_t MemoryFlags
=
619 parseMemoryFlags(MenuResource::getDefaultMemoryFlags());
620 ASSIGN_OR_RETURN(OptStatements
, parseOptionalStatements());
621 ASSIGN_OR_RETURN(Items
, parseMenuItemsList());
622 return std::make_unique
<MenuResource
>(std::move(*OptStatements
),
623 std::move(*Items
), MemoryFlags
);
626 Expected
<MenuDefinitionList
> RCParser::parseMenuItemsList() {
627 RETURN_IF_ERROR(consumeType(Kind::BlockBegin
));
629 MenuDefinitionList List
;
631 // Read a set of items. Each item is of one of three kinds:
632 // MENUITEM SEPARATOR
633 // MENUITEM caption:String, result:Int [, menu flags]...
634 // POPUP caption:String [, menu flags]... { items... }
635 while (!consumeOptionalType(Kind::BlockEnd
)) {
636 ASSIGN_OR_RETURN(ItemTypeResult
, readIdentifier());
638 bool IsMenuItem
= ItemTypeResult
->equals_lower("MENUITEM");
639 bool IsPopup
= ItemTypeResult
->equals_lower("POPUP");
640 if (!IsMenuItem
&& !IsPopup
)
641 return getExpectedError("MENUITEM, POPUP, END or '}'", true);
643 if (IsMenuItem
&& isNextTokenKind(Kind::Identifier
)) {
644 // Now, expecting SEPARATOR.
645 ASSIGN_OR_RETURN(SeparatorResult
, readIdentifier());
646 if (SeparatorResult
->equals_lower("SEPARATOR")) {
647 List
.addDefinition(std::make_unique
<MenuSeparator
>());
651 return getExpectedError("SEPARATOR or string", true);
654 // Not a separator. Read the caption.
655 ASSIGN_OR_RETURN(CaptionResult
, readString());
657 // If MENUITEM, expect also a comma and an integer.
658 uint32_t MenuResult
= -1;
661 RETURN_IF_ERROR(consumeType(Kind::Comma
));
662 ASSIGN_OR_RETURN(IntResult
, readInt());
663 MenuResult
= *IntResult
;
666 ASSIGN_OR_RETURN(FlagsResult
, parseFlags(MenuDefinition::OptionsStr
,
667 MenuDefinition::OptionsFlags
));
670 // If POPUP, read submenu items recursively.
671 ASSIGN_OR_RETURN(SubMenuResult
, parseMenuItemsList());
672 List
.addDefinition(std::make_unique
<PopupItem
>(
673 *CaptionResult
, *FlagsResult
, std::move(*SubMenuResult
)));
679 std::make_unique
<MenuItem
>(*CaptionResult
, MenuResult
, *FlagsResult
));
682 return std::move(List
);
685 RCParser::ParseType
RCParser::parseStringTableResource() {
686 uint16_t MemoryFlags
=
687 parseMemoryFlags(StringTableResource::getDefaultMemoryFlags());
688 ASSIGN_OR_RETURN(OptStatements
, parseOptionalStatements());
689 RETURN_IF_ERROR(consumeType(Kind::BlockBegin
));
691 auto Table
= std::make_unique
<StringTableResource
>(std::move(*OptStatements
),
694 // Read strings until we reach the end of the block.
695 while (!consumeOptionalType(Kind::BlockEnd
)) {
696 // Each definition consists of string's ID (an integer) and a string.
697 // Some examples in documentation suggest that there might be a comma in
698 // between, however we strictly adhere to the single statement definition.
699 ASSIGN_OR_RETURN(IDResult
, readInt());
700 consumeOptionalType(Kind::Comma
);
701 ASSIGN_OR_RETURN(StrResult
, readString());
702 Table
->addString(*IDResult
, *StrResult
);
705 return std::move(Table
);
708 Expected
<std::unique_ptr
<VersionInfoBlock
>>
709 RCParser::parseVersionInfoBlockContents(StringRef BlockName
) {
710 RETURN_IF_ERROR(consumeType(Kind::BlockBegin
));
712 auto Contents
= std::make_unique
<VersionInfoBlock
>(BlockName
);
714 while (!isNextTokenKind(Kind::BlockEnd
)) {
715 ASSIGN_OR_RETURN(Stmt
, parseVersionInfoStmt());
716 Contents
->addStmt(std::move(*Stmt
));
719 consume(); // Consume BlockEnd.
721 return std::move(Contents
);
724 Expected
<std::unique_ptr
<VersionInfoStmt
>> RCParser::parseVersionInfoStmt() {
725 // Expect either BLOCK or VALUE, then a name or a key (a string).
726 ASSIGN_OR_RETURN(TypeResult
, readIdentifier());
728 if (TypeResult
->equals_lower("BLOCK")) {
729 ASSIGN_OR_RETURN(NameResult
, readString());
730 return parseVersionInfoBlockContents(*NameResult
);
733 if (TypeResult
->equals_lower("VALUE")) {
734 ASSIGN_OR_RETURN(KeyResult
, readString());
735 // Read a non-empty list of strings and/or ints, each
736 // possibly preceded by a comma. Unfortunately, the tool behavior depends
737 // on them existing or not, so we need to memorize where we found them.
738 std::vector
<IntOrString
> Values
;
739 std::vector
<bool> PrecedingCommas
;
740 RETURN_IF_ERROR(consumeType(Kind::Comma
));
741 while (!isNextTokenKind(Kind::Identifier
) &&
742 !isNextTokenKind(Kind::BlockEnd
)) {
743 // Try to eat a comma if it's not the first statement.
744 bool HadComma
= Values
.size() > 0 && consumeOptionalType(Kind::Comma
);
745 ASSIGN_OR_RETURN(ValueResult
, readIntOrString());
746 Values
.push_back(*ValueResult
);
747 PrecedingCommas
.push_back(HadComma
);
749 return std::make_unique
<VersionInfoValue
>(*KeyResult
, std::move(Values
),
750 std::move(PrecedingCommas
));
753 return getExpectedError("BLOCK or VALUE", true);
756 Expected
<VersionInfoResource::VersionInfoFixed
>
757 RCParser::parseVersionInfoFixed() {
758 using RetType
= VersionInfoResource::VersionInfoFixed
;
761 // Read until the beginning of the block.
762 while (!isNextTokenKind(Kind::BlockBegin
)) {
763 ASSIGN_OR_RETURN(TypeResult
, readIdentifier());
764 auto FixedType
= RetType::getFixedType(*TypeResult
);
766 if (!RetType::isTypeSupported(FixedType
))
767 return getExpectedError("fixed VERSIONINFO statement type", true);
768 if (Result
.IsTypePresent
[FixedType
])
769 return getExpectedError("yet unread fixed VERSIONINFO statement type",
772 // VERSION variations take multiple integers.
773 size_t NumInts
= RetType::isVersionType(FixedType
) ? 4 : 1;
774 ASSIGN_OR_RETURN(ArgsResult
, readIntsWithCommas(NumInts
, NumInts
));
775 SmallVector
<uint32_t, 4> ArgInts(ArgsResult
->begin(), ArgsResult
->end());
776 Result
.setValue(FixedType
, ArgInts
);
782 RCParser::ParseOptionType
RCParser::parseLanguageStmt() {
783 ASSIGN_OR_RETURN(Args
, readIntsWithCommas(/* min = */ 2, /* max = */ 2));
784 return std::make_unique
<LanguageResource
>((*Args
)[0], (*Args
)[1]);
787 RCParser::ParseOptionType
RCParser::parseCharacteristicsStmt() {
788 ASSIGN_OR_RETURN(Arg
, readInt());
789 return std::make_unique
<CharacteristicsStmt
>(*Arg
);
792 RCParser::ParseOptionType
RCParser::parseVersionStmt() {
793 ASSIGN_OR_RETURN(Arg
, readInt());
794 return std::make_unique
<VersionStmt
>(*Arg
);
797 RCParser::ParseOptionType
RCParser::parseCaptionStmt() {
798 ASSIGN_OR_RETURN(Arg
, readString());
799 return std::make_unique
<CaptionStmt
>(*Arg
);
802 RCParser::ParseOptionType
RCParser::parseClassStmt() {
803 ASSIGN_OR_RETURN(Arg
, readIntOrString());
804 return std::make_unique
<ClassStmt
>(*Arg
);
807 RCParser::ParseOptionType
RCParser::parseFontStmt(OptStmtType DialogType
) {
808 assert(DialogType
!= OptStmtType::BasicStmt
);
810 ASSIGN_OR_RETURN(SizeResult
, readInt());
811 RETURN_IF_ERROR(consumeType(Kind::Comma
));
812 ASSIGN_OR_RETURN(NameResult
, readString());
814 // Default values for the optional arguments.
815 uint32_t FontWeight
= 0;
816 bool FontItalic
= false;
817 uint32_t FontCharset
= 1;
818 if (DialogType
== OptStmtType::DialogExStmt
) {
819 if (consumeOptionalType(Kind::Comma
)) {
820 ASSIGN_OR_RETURN(Args
, readIntsWithCommas(/* min = */ 0, /* max = */ 3));
821 if (Args
->size() >= 1)
822 FontWeight
= (*Args
)[0];
823 if (Args
->size() >= 2)
824 FontItalic
= (*Args
)[1] != 0;
825 if (Args
->size() >= 3)
826 FontCharset
= (*Args
)[2];
829 return std::make_unique
<FontStmt
>(*SizeResult
, *NameResult
, FontWeight
,
830 FontItalic
, FontCharset
);
833 RCParser::ParseOptionType
RCParser::parseStyleStmt() {
834 ASSIGN_OR_RETURN(Arg
, readInt());
835 return std::make_unique
<StyleStmt
>(*Arg
);
838 RCParser::ParseOptionType
RCParser::parseExStyleStmt() {
839 ASSIGN_OR_RETURN(Arg
, readInt());
840 return std::make_unique
<ExStyleStmt
>(*Arg
);
843 Error
RCParser::getExpectedError(const Twine
&Message
, bool IsAlreadyRead
) {
844 return make_error
<ParserError
>(
845 Message
, IsAlreadyRead
? std::prev(CurLoc
) : CurLoc
, End
);