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/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"
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);
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) \
30 return Var.takeError();
35 RCParser::ParserError::ParserError(const Twine
&Expected
, const LocIter CurLoc
,
37 : ErrorLoc(CurLoc
), FileEnd(End
) {
38 CurMessage
= "Error parsing file: expected " + Expected
.str() + ", got " +
39 (CurLoc
== End
? "<EOF>" : CurLoc
->value()).str();
42 char RCParser::ParserError::ID
= 0;
44 RCParser::RCParser(std::vector
<RCToken
> TokenList
)
45 : Tokens(std::move(TokenList
)), CurLoc(Tokens
.begin()), End(Tokens
.end()) {}
47 bool RCParser::isEof() const { return CurLoc
== End
; }
49 RCParser::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());
55 if (NameToken
->equalsLower("LANGUAGE"))
56 return parseLanguageResource();
57 else if (NameToken
->equalsLower("STRINGTABLE"))
58 return parseStringTableResource();
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());
64 ParseType Result
= std::unique_ptr
<RCResource
>();
67 if (TypeToken
->equalsLower("ACCELERATORS"))
68 Result
= parseAcceleratorsResource();
69 else if (TypeToken
->equalsLower("BITMAP"))
70 Result
= parseBitmapResource();
71 else if (TypeToken
->equalsLower("CURSOR"))
72 Result
= parseCursorResource();
73 else if (TypeToken
->equalsLower("DIALOG"))
74 Result
= parseDialogResource(false);
75 else if (TypeToken
->equalsLower("DIALOGEX"))
76 Result
= parseDialogResource(true);
77 else if (TypeToken
->equalsLower("HTML"))
78 Result
= parseHTMLResource();
79 else if (TypeToken
->equalsLower("ICON"))
80 Result
= parseIconResource();
81 else if (TypeToken
->equalsLower("MENU"))
82 Result
= parseMenuResource();
83 else if (TypeToken
->equalsLower("MENUEX"))
84 Result
= parseMenuExResource();
85 else if (TypeToken
->equalsLower("RCDATA"))
86 Result
= parseUserDefinedResource(RkRcData
);
87 else if (TypeToken
->equalsLower("VERSIONINFO"))
88 Result
= parseVersionInfoResource();
90 Result
= parseUserDefinedResource(*TypeToken
);
93 (*Result
)->setName(*NameToken
);
98 bool RCParser::isNextTokenKind(Kind TokenKind
) const {
99 return !isEof() && look().kind() == TokenKind
;
102 const RCToken
&RCParser::look() const {
107 const RCToken
&RCParser::read() {
112 void RCParser::consume() {
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
123 // 1) Unary operators (- ~ not),
124 // 2) Binary operators (+ - & |), with no precedence.
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.
133 // The following grammar is used to parse the expressions Exp1:
134 // Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2
135 // Exp2 ::= -Exp2 || ~Exp2 || not Expr2 || Int || (Exp1).
136 // (More conveniently, Exp1 is a non-empty sequence of Exp2 expressions,
137 // separated by binary operators.)
139 // Expressions of type Exp1 are read by parseIntExpr1(Inner) method, while Exp2
140 // is read by parseIntExpr2().
142 // The original Microsoft tool handles multiple unary operators incorrectly.
143 // For example, in 16-bit little-endian integers:
144 // 1 => 01 00, -1 => ff ff, --1 => ff ff, ---1 => 01 00;
145 // 1 => 01 00, ~1 => fe ff, ~~1 => fd ff, ~~~1 => fc ff.
146 // Our implementation differs from the original one and handles these
147 // operators correctly:
148 // 1 => 01 00, -1 => ff ff, --1 => 01 00, ---1 => ff ff;
149 // 1 => 01 00, ~1 => fe ff, ~~1 => 01 00, ~~~1 => fe ff.
151 Expected
<RCInt
> RCParser::readInt() {
152 ASSIGN_OR_RETURN(Value
, parseIntExpr1());
153 return (*Value
).getValue();
156 Expected
<IntWithNotMask
> RCParser::parseIntExpr1() {
157 // Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2.
158 ASSIGN_OR_RETURN(FirstResult
, parseIntExpr2());
159 IntWithNotMask Result
= *FirstResult
;
161 while (!isEof() && look().isBinaryOp()) {
162 auto OpToken
= read();
163 ASSIGN_OR_RETURN(NextResult
, parseIntExpr2());
165 switch (OpToken
.kind()) {
167 Result
+= *NextResult
;
171 Result
-= *NextResult
;
175 Result
|= *NextResult
;
179 Result
&= *NextResult
;
183 llvm_unreachable("Already processed all binary ops.");
190 Expected
<IntWithNotMask
> RCParser::parseIntExpr2() {
191 // Exp2 ::= -Exp2 || ~Exp2 || not Expr2 || Int || (Exp1).
192 static const char ErrorMsg
[] = "'-', '~', integer or '('";
195 return getExpectedError(ErrorMsg
);
197 switch (look().kind()) {
200 ASSIGN_OR_RETURN(Result
, parseIntExpr2());
206 ASSIGN_OR_RETURN(Result
, parseIntExpr2());
211 return RCInt(read());
213 case Kind::LeftParen
: {
215 ASSIGN_OR_RETURN(Result
, parseIntExpr1());
216 RETURN_IF_ERROR(consumeType(Kind::RightParen
));
220 case Kind::Identifier
: {
221 if (!read().value().equals_insensitive("not"))
222 return getExpectedError(ErrorMsg
, true);
223 ASSIGN_OR_RETURN(Result
, parseIntExpr2());
224 return IntWithNotMask(0, (*Result
).getValue());
228 return getExpectedError(ErrorMsg
);
232 Expected
<StringRef
> RCParser::readString() {
233 if (!isNextTokenKind(Kind::String
))
234 return getExpectedError("string");
235 return read().value();
238 Expected
<StringRef
> RCParser::readFilename() {
239 if (!isNextTokenKind(Kind::String
) && !isNextTokenKind(Kind::Identifier
))
240 return getExpectedError("string");
241 const RCToken
&Token
= read();
242 StringRef Str
= Token
.value();
243 if (Token
.kind() != Kind::String
)
245 while (isNextTokenKind(Kind::String
)) {
246 const RCToken
&NextToken
= read();
247 StringRef Next
= NextToken
.value();
248 bool IsWide
= Str
.consume_front_insensitive("L");
249 Next
.consume_front_insensitive("L");
250 bool StrUnquoted
= Str
.consume_front("\"") && Str
.consume_back("\"");
251 bool NextUnquoted
= Next
.consume_front("\"") && Next
.consume_back("\"");
252 assert(StrUnquoted
&& NextUnquoted
);
256 Str
= Saver
.save(Twine(IsWide
? "L" : "") + "\"" + Str
+ Next
+ "\"");
261 Expected
<StringRef
> RCParser::readIdentifier() {
262 if (!isNextTokenKind(Kind::Identifier
))
263 return getExpectedError("identifier");
264 return read().value();
267 Expected
<IntOrString
> RCParser::readIntOrString() {
268 if (!isNextTokenKind(Kind::Int
) && !isNextTokenKind(Kind::String
))
269 return getExpectedError("int or string");
270 return IntOrString(read());
273 Expected
<IntOrString
> RCParser::readTypeOrName() {
274 // We suggest that the correct resource name or type should be either an
275 // identifier or an integer. The original RC tool is much more liberal.
276 if (!isNextTokenKind(Kind::Identifier
) && !isNextTokenKind(Kind::Int
))
277 return getExpectedError("int or identifier");
278 return IntOrString(read());
281 Error
RCParser::consumeType(Kind TokenKind
) {
282 if (isNextTokenKind(TokenKind
)) {
284 return Error::success();
288 #define TOKEN(TokenName) \
289 case Kind::TokenName: \
290 return getExpectedError(#TokenName);
291 #define SHORT_TOKEN(TokenName, TokenCh) \
292 case Kind::TokenName: \
293 return getExpectedError(#TokenCh);
294 #include "ResourceScriptTokenList.def"
297 llvm_unreachable("All case options exhausted.");
300 bool RCParser::consumeOptionalType(Kind TokenKind
) {
301 if (isNextTokenKind(TokenKind
)) {
309 Expected
<SmallVector
<RCInt
, 8>> RCParser::readIntsWithCommas(size_t MinCount
,
311 assert(MinCount
<= MaxCount
);
313 SmallVector
<RCInt
, 8> Result
;
315 auto FailureHandler
=
316 [&](llvm::Error Err
) -> Expected
<SmallVector
<RCInt
, 8>> {
317 if (Result
.size() < MinCount
)
318 return std::move(Err
);
319 consumeError(std::move(Err
));
323 for (size_t i
= 0; i
< MaxCount
; ++i
) {
324 // Try to read a comma unless we read the first token.
325 // Sometimes RC tool requires them and sometimes not. We decide to
326 // always require them.
328 if (auto CommaError
= consumeType(Kind::Comma
))
329 return FailureHandler(std::move(CommaError
));
332 if (auto IntResult
= readInt())
333 Result
.push_back(*IntResult
);
335 return FailureHandler(IntResult
.takeError());
338 return std::move(Result
);
341 Expected
<uint32_t> RCParser::parseFlags(ArrayRef
<StringRef
> FlagDesc
,
342 ArrayRef
<uint32_t> FlagValues
) {
343 assert(!FlagDesc
.empty());
344 assert(FlagDesc
.size() == FlagValues
.size());
347 while (isNextTokenKind(Kind::Comma
)) {
349 ASSIGN_OR_RETURN(FlagResult
, readIdentifier());
350 bool FoundFlag
= false;
352 for (size_t FlagId
= 0; FlagId
< FlagDesc
.size(); ++FlagId
) {
353 if (!FlagResult
->equals_insensitive(FlagDesc
[FlagId
]))
356 Result
|= FlagValues
[FlagId
];
362 return getExpectedError(join(FlagDesc
, "/"), true);
368 uint16_t RCParser::parseMemoryFlags(uint16_t Flags
) {
370 const RCToken
&Token
= look();
371 if (Token
.kind() != Kind::Identifier
)
373 const StringRef Ident
= Token
.value();
374 if (Ident
.equals_insensitive("PRELOAD"))
376 else if (Ident
.equals_insensitive("LOADONCALL"))
378 else if (Ident
.equals_insensitive("FIXED"))
379 Flags
&= ~(MfMoveable
| MfDiscardable
);
380 else if (Ident
.equals_insensitive("MOVEABLE"))
382 else if (Ident
.equals_insensitive("DISCARDABLE"))
383 Flags
|= MfDiscardable
| MfMoveable
| MfPure
;
384 else if (Ident
.equals_insensitive("PURE"))
386 else if (Ident
.equals_insensitive("IMPURE"))
387 Flags
&= ~(MfPure
| MfDiscardable
);
388 else if (Ident
.equals_insensitive("SHARED"))
390 else if (Ident
.equals_insensitive("NONSHARED"))
391 Flags
&= ~(MfPure
| MfDiscardable
);
399 Expected
<OptionalStmtList
>
400 RCParser::parseOptionalStatements(OptStmtType StmtsType
) {
401 OptionalStmtList Result
;
403 // The last statement is always followed by the start of the block.
404 while (!isNextTokenKind(Kind::BlockBegin
)) {
405 ASSIGN_OR_RETURN(SingleParse
, parseSingleOptionalStatement(StmtsType
));
406 Result
.addStmt(std::move(*SingleParse
));
409 return std::move(Result
);
412 Expected
<std::unique_ptr
<OptionalStmt
>>
413 RCParser::parseSingleOptionalStatement(OptStmtType StmtsType
) {
414 ASSIGN_OR_RETURN(TypeToken
, readIdentifier());
415 if (TypeToken
->equals_insensitive("CHARACTERISTICS"))
416 return parseCharacteristicsStmt();
417 if (TypeToken
->equals_insensitive("LANGUAGE"))
418 return parseLanguageStmt();
419 if (TypeToken
->equals_insensitive("VERSION"))
420 return parseVersionStmt();
422 if (StmtsType
!= OptStmtType::BasicStmt
) {
423 if (TypeToken
->equals_insensitive("CAPTION"))
424 return parseCaptionStmt();
425 if (TypeToken
->equals_insensitive("CLASS"))
426 return parseClassStmt();
427 if (TypeToken
->equals_insensitive("EXSTYLE"))
428 return parseExStyleStmt();
429 if (TypeToken
->equals_insensitive("FONT"))
430 return parseFontStmt(StmtsType
);
431 if (TypeToken
->equals_insensitive("STYLE"))
432 return parseStyleStmt();
435 return getExpectedError("optional statement type, BEGIN or '{'",
436 /* IsAlreadyRead = */ true);
439 RCParser::ParseType
RCParser::parseLanguageResource() {
440 // Read LANGUAGE as an optional statement. If it's read correctly, we can
441 // upcast it to RCResource.
442 return parseLanguageStmt();
445 RCParser::ParseType
RCParser::parseAcceleratorsResource() {
446 uint16_t MemoryFlags
=
447 parseMemoryFlags(AcceleratorsResource::getDefaultMemoryFlags());
448 ASSIGN_OR_RETURN(OptStatements
, parseOptionalStatements());
449 RETURN_IF_ERROR(consumeType(Kind::BlockBegin
));
451 auto Accels
= std::make_unique
<AcceleratorsResource
>(
452 std::move(*OptStatements
), MemoryFlags
);
454 while (!consumeOptionalType(Kind::BlockEnd
)) {
455 ASSIGN_OR_RETURN(EventResult
, readIntOrString());
456 RETURN_IF_ERROR(consumeType(Kind::Comma
));
457 ASSIGN_OR_RETURN(IDResult
, readInt());
460 parseFlags(AcceleratorsResource::Accelerator::OptionsStr
,
461 AcceleratorsResource::Accelerator::OptionsFlags
));
462 Accels
->addAccelerator(*EventResult
, *IDResult
, *FlagsResult
);
465 return std::move(Accels
);
468 RCParser::ParseType
RCParser::parseCursorResource() {
469 uint16_t MemoryFlags
=
470 parseMemoryFlags(CursorResource::getDefaultMemoryFlags());
471 ASSIGN_OR_RETURN(Arg
, readFilename());
472 return std::make_unique
<CursorResource
>(*Arg
, MemoryFlags
);
475 RCParser::ParseType
RCParser::parseDialogResource(bool IsExtended
) {
476 uint16_t MemoryFlags
=
477 parseMemoryFlags(DialogResource::getDefaultMemoryFlags());
478 // Dialog resources have the following format of the arguments:
479 // DIALOG: x, y, width, height [opt stmts...] {controls...}
480 // DIALOGEX: x, y, width, height [, helpID] [opt stmts...] {controls...}
481 // These are very similar, so we parse them together.
482 ASSIGN_OR_RETURN(LocResult
, readIntsWithCommas(4, 4));
484 uint32_t HelpID
= 0; // When HelpID is unset, it's assumed to be 0.
485 if (IsExtended
&& consumeOptionalType(Kind::Comma
)) {
486 ASSIGN_OR_RETURN(HelpIDResult
, readInt());
487 HelpID
= *HelpIDResult
;
490 ASSIGN_OR_RETURN(OptStatements
, parseOptionalStatements(
491 IsExtended
? OptStmtType::DialogExStmt
492 : OptStmtType::DialogStmt
));
494 assert(isNextTokenKind(Kind::BlockBegin
) &&
495 "parseOptionalStatements, when successful, halts on BlockBegin.");
498 auto Dialog
= std::make_unique
<DialogResource
>(
499 (*LocResult
)[0], (*LocResult
)[1], (*LocResult
)[2], (*LocResult
)[3],
500 HelpID
, std::move(*OptStatements
), IsExtended
, MemoryFlags
);
502 while (!consumeOptionalType(Kind::BlockEnd
)) {
503 ASSIGN_OR_RETURN(ControlDefResult
, parseControl());
504 Dialog
->addControl(std::move(*ControlDefResult
));
507 return std::move(Dialog
);
510 RCParser::ParseType
RCParser::parseUserDefinedResource(IntOrString Type
) {
511 uint16_t MemoryFlags
=
512 parseMemoryFlags(UserDefinedResource::getDefaultMemoryFlags());
514 return getExpectedError("filename, '{' or BEGIN");
516 // Check if this is a file resource.
517 switch (look().kind()) {
519 case Kind::Identifier
: {
520 ASSIGN_OR_RETURN(Filename
, readFilename());
521 return std::make_unique
<UserDefinedResource
>(Type
, *Filename
, MemoryFlags
);
527 RETURN_IF_ERROR(consumeType(Kind::BlockBegin
));
528 std::vector
<IntOrString
> Data
;
530 while (!consumeOptionalType(Kind::BlockEnd
)) {
531 ASSIGN_OR_RETURN(Item
, readIntOrString());
532 Data
.push_back(*Item
);
534 // There can be zero or more commas after each token (but not before
536 while (consumeOptionalType(Kind::Comma
)) {
540 return std::make_unique
<UserDefinedResource
>(Type
, std::move(Data
),
544 RCParser::ParseType
RCParser::parseVersionInfoResource() {
545 uint16_t MemoryFlags
=
546 parseMemoryFlags(VersionInfoResource::getDefaultMemoryFlags());
547 ASSIGN_OR_RETURN(FixedResult
, parseVersionInfoFixed());
548 ASSIGN_OR_RETURN(BlockResult
, parseVersionInfoBlockContents(StringRef()));
549 return std::make_unique
<VersionInfoResource
>(
550 std::move(**BlockResult
), std::move(*FixedResult
), MemoryFlags
);
553 Expected
<Control
> RCParser::parseControl() {
554 // Each control definition (except CONTROL) follows one of the schemes below
555 // depending on the control class:
556 // [class] text, id, x, y, width, height [, style] [, exstyle] [, helpID]
557 // [class] id, x, y, width, height [, style] [, exstyle] [, helpID]
558 // Note that control ids must be integers.
559 // Text might be either a string or an integer pointing to resource ID.
560 ASSIGN_OR_RETURN(ClassResult
, readIdentifier());
561 std::string ClassUpper
= ClassResult
->upper();
562 auto CtlInfo
= Control::SupportedCtls
.find(ClassUpper
);
563 if (CtlInfo
== Control::SupportedCtls
.end())
564 return getExpectedError("control type, END or '}'", true);
566 // Read caption if necessary.
567 IntOrString Caption
{StringRef()};
568 if (CtlInfo
->getValue().HasTitle
) {
569 ASSIGN_OR_RETURN(CaptionResult
, readIntOrString());
570 RETURN_IF_ERROR(consumeType(Kind::Comma
));
571 Caption
= *CaptionResult
;
574 ASSIGN_OR_RETURN(ID
, readInt());
575 RETURN_IF_ERROR(consumeType(Kind::Comma
));
578 std::optional
<IntWithNotMask
> Style
;
579 if (ClassUpper
== "CONTROL") {
580 // CONTROL text, id, class, style, x, y, width, height [, exstyle] [,
582 ASSIGN_OR_RETURN(ClassStr
, readString());
583 RETURN_IF_ERROR(consumeType(Kind::Comma
));
585 ASSIGN_OR_RETURN(StyleVal
, parseIntExpr1());
586 RETURN_IF_ERROR(consumeType(Kind::Comma
));
589 Class
= CtlInfo
->getValue().CtlClass
;
592 // x, y, width, height
593 ASSIGN_OR_RETURN(Args
, readIntsWithCommas(4, 4));
595 if (ClassUpper
!= "CONTROL") {
596 if (consumeOptionalType(Kind::Comma
)) {
597 ASSIGN_OR_RETURN(Val
, parseIntExpr1());
602 std::optional
<uint32_t> ExStyle
;
603 if (consumeOptionalType(Kind::Comma
)) {
604 ASSIGN_OR_RETURN(Val
, readInt());
607 std::optional
<uint32_t> HelpID
;
608 if (consumeOptionalType(Kind::Comma
)) {
609 ASSIGN_OR_RETURN(Val
, readInt());
613 return Control(*ClassResult
, Caption
, *ID
, (*Args
)[0], (*Args
)[1], (*Args
)[2],
614 (*Args
)[3], Style
, ExStyle
, HelpID
, Class
);
617 RCParser::ParseType
RCParser::parseBitmapResource() {
618 uint16_t MemoryFlags
=
619 parseMemoryFlags(BitmapResource::getDefaultMemoryFlags());
620 ASSIGN_OR_RETURN(Arg
, readFilename());
621 return std::make_unique
<BitmapResource
>(*Arg
, MemoryFlags
);
624 RCParser::ParseType
RCParser::parseIconResource() {
625 uint16_t MemoryFlags
=
626 parseMemoryFlags(IconResource::getDefaultMemoryFlags());
627 ASSIGN_OR_RETURN(Arg
, readFilename());
628 return std::make_unique
<IconResource
>(*Arg
, MemoryFlags
);
631 RCParser::ParseType
RCParser::parseHTMLResource() {
632 uint16_t MemoryFlags
=
633 parseMemoryFlags(HTMLResource::getDefaultMemoryFlags());
634 ASSIGN_OR_RETURN(Arg
, readFilename());
635 return std::make_unique
<HTMLResource
>(*Arg
, MemoryFlags
);
638 RCParser::ParseType
RCParser::parseMenuResource() {
639 uint16_t MemoryFlags
=
640 parseMemoryFlags(MenuResource::getDefaultMemoryFlags());
641 ASSIGN_OR_RETURN(OptStatements
, parseOptionalStatements());
642 ASSIGN_OR_RETURN(Items
, parseMenuItemsList());
643 return std::make_unique
<MenuResource
>(std::move(*OptStatements
),
644 std::move(*Items
), MemoryFlags
);
647 RCParser::ParseType
RCParser::parseMenuExResource() {
648 uint16_t MemoryFlags
=
649 parseMemoryFlags(MenuExResource::getDefaultMemoryFlags());
650 ASSIGN_OR_RETURN(Items
, parseMenuExItemsList());
651 return std::make_unique
<MenuExResource
>(std::move(*Items
), MemoryFlags
);
654 Expected
<MenuDefinitionList
> RCParser::parseMenuItemsList() {
655 RETURN_IF_ERROR(consumeType(Kind::BlockBegin
));
657 MenuDefinitionList List
;
659 // Read a set of items. Each item is of one of three kinds:
660 // MENUITEM SEPARATOR
661 // MENUITEM caption:String, result:Int [, menu flags]...
662 // POPUP caption:String [, menu flags]... { items... }
663 while (!consumeOptionalType(Kind::BlockEnd
)) {
664 ASSIGN_OR_RETURN(ItemTypeResult
, readIdentifier());
666 bool IsMenuItem
= ItemTypeResult
->equals_insensitive("MENUITEM");
667 bool IsPopup
= ItemTypeResult
->equals_insensitive("POPUP");
668 if (!IsMenuItem
&& !IsPopup
)
669 return getExpectedError("MENUITEM, POPUP, END or '}'", true);
671 if (IsMenuItem
&& isNextTokenKind(Kind::Identifier
)) {
672 // Now, expecting SEPARATOR.
673 ASSIGN_OR_RETURN(SeparatorResult
, readIdentifier());
674 if (SeparatorResult
->equals_insensitive("SEPARATOR")) {
675 List
.addDefinition(std::make_unique
<MenuSeparator
>());
679 return getExpectedError("SEPARATOR or string", true);
682 // Not a separator. Read the caption.
683 ASSIGN_OR_RETURN(CaptionResult
, readString());
685 // If MENUITEM, expect also a comma and an integer.
686 uint32_t MenuResult
= -1;
689 RETURN_IF_ERROR(consumeType(Kind::Comma
));
690 ASSIGN_OR_RETURN(IntResult
, readInt());
691 MenuResult
= *IntResult
;
694 ASSIGN_OR_RETURN(FlagsResult
, parseFlags(MenuDefinition::OptionsStr
,
695 MenuDefinition::OptionsFlags
));
698 // If POPUP, read submenu items recursively.
699 ASSIGN_OR_RETURN(SubMenuResult
, parseMenuItemsList());
700 List
.addDefinition(std::make_unique
<PopupItem
>(
701 *CaptionResult
, *FlagsResult
, std::move(*SubMenuResult
)));
707 std::make_unique
<MenuItem
>(*CaptionResult
, MenuResult
, *FlagsResult
));
710 return std::move(List
);
713 Expected
<MenuDefinitionList
> RCParser::parseMenuExItemsList() {
714 RETURN_IF_ERROR(consumeType(Kind::BlockBegin
));
716 MenuDefinitionList List
;
718 // Read a set of items. Each item is of one of two kinds:
719 // MENUITEM caption:String [,[id][, [type][, state]]]]
720 // POPUP caption:String [,[id][, [type][, [state][, helpID]]]] { popupBody }
721 while (!consumeOptionalType(Kind::BlockEnd
)) {
722 ASSIGN_OR_RETURN(ItemTypeResult
, readIdentifier());
724 bool IsMenuItem
= ItemTypeResult
->equals_insensitive("MENUITEM");
725 bool IsPopup
= ItemTypeResult
->equals_insensitive("POPUP");
726 if (!IsMenuItem
&& !IsPopup
)
727 return getExpectedError("MENUITEM, POPUP, END or '}'", true);
729 // Not a separator. Read the caption.
730 ASSIGN_OR_RETURN(CaptionResult
, readString());
732 // If MENUITEM, expect [,[id][, [type][, state]]]]
735 uint32_t MenuType
= 0;
736 uint32_t MenuState
= 0;
738 if (consumeOptionalType(Kind::Comma
)) {
739 auto IntId
= readInt();
743 if (consumeOptionalType(Kind::Comma
)) {
744 auto IntType
= readInt();
748 if (consumeOptionalType(Kind::Comma
)) {
749 auto IntState
= readInt();
751 MenuState
= *IntState
;
756 List
.addDefinition(std::make_unique
<MenuExItem
>(*CaptionResult
, MenuId
,
757 MenuType
, MenuState
));
763 uint32_t PopupId
= 0;
764 uint32_t PopupType
= 0;
765 uint32_t PopupState
= 0;
766 uint32_t PopupHelpID
= 0;
768 if (consumeOptionalType(Kind::Comma
)) {
769 auto IntId
= readInt();
773 if (consumeOptionalType(Kind::Comma
)) {
774 auto IntType
= readInt();
776 PopupType
= *IntType
;
778 if (consumeOptionalType(Kind::Comma
)) {
779 auto IntState
= readInt();
781 PopupState
= *IntState
;
783 if (consumeOptionalType(Kind::Comma
)) {
784 auto IntHelpID
= readInt();
786 PopupHelpID
= *IntHelpID
;
792 // If POPUP, read submenu items recursively.
793 ASSIGN_OR_RETURN(SubMenuResult
, parseMenuExItemsList());
794 List
.addDefinition(std::make_unique
<PopupExItem
>(
795 *CaptionResult
, PopupId
, PopupType
, PopupState
, PopupHelpID
,
796 std::move(*SubMenuResult
)));
799 return std::move(List
);
802 RCParser::ParseType
RCParser::parseStringTableResource() {
803 uint16_t MemoryFlags
=
804 parseMemoryFlags(StringTableResource::getDefaultMemoryFlags());
805 ASSIGN_OR_RETURN(OptStatements
, parseOptionalStatements());
806 RETURN_IF_ERROR(consumeType(Kind::BlockBegin
));
808 auto Table
= std::make_unique
<StringTableResource
>(std::move(*OptStatements
),
811 // Read strings until we reach the end of the block.
812 while (!consumeOptionalType(Kind::BlockEnd
)) {
813 // Each definition consists of string's ID (an integer) and a string.
814 // Some examples in documentation suggest that there might be a comma in
815 // between, however we strictly adhere to the single statement definition.
816 ASSIGN_OR_RETURN(IDResult
, readInt());
817 consumeOptionalType(Kind::Comma
);
819 std::vector
<StringRef
> Strings
;
820 ASSIGN_OR_RETURN(StrResult
, readString());
821 Strings
.push_back(*StrResult
);
822 while (isNextTokenKind(Kind::String
))
823 Strings
.push_back(read().value());
825 Table
->addStrings(*IDResult
, std::move(Strings
));
828 return std::move(Table
);
831 Expected
<std::unique_ptr
<VersionInfoBlock
>>
832 RCParser::parseVersionInfoBlockContents(StringRef BlockName
) {
833 RETURN_IF_ERROR(consumeType(Kind::BlockBegin
));
835 auto Contents
= std::make_unique
<VersionInfoBlock
>(BlockName
);
837 while (!isNextTokenKind(Kind::BlockEnd
)) {
838 ASSIGN_OR_RETURN(Stmt
, parseVersionInfoStmt());
839 Contents
->addStmt(std::move(*Stmt
));
842 consume(); // Consume BlockEnd.
844 return std::move(Contents
);
847 Expected
<std::unique_ptr
<VersionInfoStmt
>> RCParser::parseVersionInfoStmt() {
848 // Expect either BLOCK or VALUE, then a name or a key (a string).
849 ASSIGN_OR_RETURN(TypeResult
, readIdentifier());
851 if (TypeResult
->equals_insensitive("BLOCK")) {
852 ASSIGN_OR_RETURN(NameResult
, readString());
853 return parseVersionInfoBlockContents(*NameResult
);
856 if (TypeResult
->equals_insensitive("VALUE")) {
857 ASSIGN_OR_RETURN(KeyResult
, readString());
858 // Read a non-empty list of strings and/or ints, each
859 // possibly preceded by a comma. Unfortunately, the tool behavior depends
860 // on them existing or not, so we need to memorize where we found them.
861 std::vector
<IntOrString
> Values
;
862 BitVector PrecedingCommas
;
863 RETURN_IF_ERROR(consumeType(Kind::Comma
));
864 while (!isNextTokenKind(Kind::Identifier
) &&
865 !isNextTokenKind(Kind::BlockEnd
)) {
866 // Try to eat a comma if it's not the first statement.
867 bool HadComma
= Values
.size() > 0 && consumeOptionalType(Kind::Comma
);
868 ASSIGN_OR_RETURN(ValueResult
, readIntOrString());
869 Values
.push_back(*ValueResult
);
870 PrecedingCommas
.push_back(HadComma
);
872 return std::make_unique
<VersionInfoValue
>(*KeyResult
, std::move(Values
),
873 std::move(PrecedingCommas
));
876 return getExpectedError("BLOCK or VALUE", true);
879 Expected
<VersionInfoResource::VersionInfoFixed
>
880 RCParser::parseVersionInfoFixed() {
881 using RetType
= VersionInfoResource::VersionInfoFixed
;
884 // Read until the beginning of the block.
885 while (!isNextTokenKind(Kind::BlockBegin
)) {
886 ASSIGN_OR_RETURN(TypeResult
, readIdentifier());
887 auto FixedType
= RetType::getFixedType(*TypeResult
);
889 if (!RetType::isTypeSupported(FixedType
))
890 return getExpectedError("fixed VERSIONINFO statement type", true);
891 if (Result
.IsTypePresent
[FixedType
])
892 return getExpectedError("yet unread fixed VERSIONINFO statement type",
895 // VERSION variations take multiple integers.
896 size_t NumInts
= RetType::isVersionType(FixedType
) ? 4 : 1;
897 ASSIGN_OR_RETURN(ArgsResult
, readIntsWithCommas(1, NumInts
));
898 SmallVector
<uint32_t, 4> ArgInts(ArgsResult
->begin(), ArgsResult
->end());
899 while (ArgInts
.size() < NumInts
)
900 ArgInts
.push_back(0);
901 Result
.setValue(FixedType
, ArgInts
);
907 RCParser::ParseOptionType
RCParser::parseLanguageStmt() {
908 ASSIGN_OR_RETURN(Args
, readIntsWithCommas(/* min = */ 2, /* max = */ 2));
909 return std::make_unique
<LanguageResource
>((*Args
)[0], (*Args
)[1]);
912 RCParser::ParseOptionType
RCParser::parseCharacteristicsStmt() {
913 ASSIGN_OR_RETURN(Arg
, readInt());
914 return std::make_unique
<CharacteristicsStmt
>(*Arg
);
917 RCParser::ParseOptionType
RCParser::parseVersionStmt() {
918 ASSIGN_OR_RETURN(Arg
, readInt());
919 return std::make_unique
<VersionStmt
>(*Arg
);
922 RCParser::ParseOptionType
RCParser::parseCaptionStmt() {
923 ASSIGN_OR_RETURN(Arg
, readString());
924 return std::make_unique
<CaptionStmt
>(*Arg
);
927 RCParser::ParseOptionType
RCParser::parseClassStmt() {
928 ASSIGN_OR_RETURN(Arg
, readIntOrString());
929 return std::make_unique
<ClassStmt
>(*Arg
);
932 RCParser::ParseOptionType
RCParser::parseFontStmt(OptStmtType DialogType
) {
933 assert(DialogType
!= OptStmtType::BasicStmt
);
935 ASSIGN_OR_RETURN(SizeResult
, readInt());
936 RETURN_IF_ERROR(consumeType(Kind::Comma
));
937 ASSIGN_OR_RETURN(NameResult
, readString());
939 // Default values for the optional arguments.
940 uint32_t FontWeight
= 0;
941 bool FontItalic
= false;
942 uint32_t FontCharset
= 1;
943 if (DialogType
== OptStmtType::DialogExStmt
) {
944 if (consumeOptionalType(Kind::Comma
)) {
945 ASSIGN_OR_RETURN(Args
, readIntsWithCommas(/* min = */ 0, /* max = */ 3));
946 if (Args
->size() >= 1)
947 FontWeight
= (*Args
)[0];
948 if (Args
->size() >= 2)
949 FontItalic
= (*Args
)[1] != 0;
950 if (Args
->size() >= 3)
951 FontCharset
= (*Args
)[2];
954 return std::make_unique
<FontStmt
>(*SizeResult
, *NameResult
, FontWeight
,
955 FontItalic
, FontCharset
);
958 RCParser::ParseOptionType
RCParser::parseStyleStmt() {
959 ASSIGN_OR_RETURN(Arg
, readInt());
960 return std::make_unique
<StyleStmt
>(*Arg
);
963 RCParser::ParseOptionType
RCParser::parseExStyleStmt() {
964 ASSIGN_OR_RETURN(Arg
, readInt());
965 return std::make_unique
<ExStyleStmt
>(*Arg
);
968 Error
RCParser::getExpectedError(const Twine
&Message
, bool IsAlreadyRead
) {
969 return make_error
<ParserError
>(
970 Message
, IsAlreadyRead
? std::prev(CurLoc
) : CurLoc
, End
);