aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptParser.cpp
diff options
context:
space:
mode:
authororivej <orivej@yandex-team.ru>2022-02-10 16:44:49 +0300
committerDaniil Cherednik <dcherednik@yandex-team.ru>2022-02-10 16:44:49 +0300
commit718c552901d703c502ccbefdfc3c9028d608b947 (patch)
tree46534a98bbefcd7b1f3faa5b52c138ab27db75b7 /contrib/libs/llvm12/tools/llvm-rc/ResourceScriptParser.cpp
parente9656aae26e0358d5378e5b63dcac5c8dbe0e4d0 (diff)
downloadydb-718c552901d703c502ccbefdfc3c9028d608b947.tar.gz
Restoring authorship annotation for <orivej@yandex-team.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/libs/llvm12/tools/llvm-rc/ResourceScriptParser.cpp')
-rw-r--r--contrib/libs/llvm12/tools/llvm-rc/ResourceScriptParser.cpp1708
1 files changed, 854 insertions, 854 deletions
diff --git a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptParser.cpp b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptParser.cpp
index 5141ac0c38..1dbfc5e8bf 100644
--- a/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptParser.cpp
+++ b/contrib/libs/llvm12/tools/llvm-rc/ResourceScriptParser.cpp
@@ -1,857 +1,857 @@
-//===-- ResourceScriptParser.cpp --------------------------------*- C++-*-===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===---------------------------------------------------------------------===//
-//
-// This implements the parser defined in ResourceScriptParser.h.
-//
-//===---------------------------------------------------------------------===//
-
-#include "ResourceScriptParser.h"
-#include "llvm/Option/ArgList.h"
-#include "llvm/Support/FileSystem.h"
-#include "llvm/Support/Path.h"
-#include "llvm/Support/Process.h"
-
-// Take an expression returning llvm::Error and forward the error if it exists.
-#define RETURN_IF_ERROR(Expr) \
- if (auto Err = (Expr)) \
- return std::move(Err);
-
-// Take an expression returning llvm::Expected<T> and assign it to Var or
-// forward the error out of the function.
-#define ASSIGN_OR_RETURN(Var, Expr) \
- auto Var = (Expr); \
- if (!Var) \
- return Var.takeError();
-
-namespace llvm {
-namespace rc {
-
-RCParser::ParserError::ParserError(const Twine &Expected, const LocIter CurLoc,
- const LocIter End)
- : ErrorLoc(CurLoc), FileEnd(End) {
- CurMessage = "Error parsing file: expected " + Expected.str() + ", got " +
- (CurLoc == End ? "<EOF>" : CurLoc->value()).str();
-}
-
-char RCParser::ParserError::ID = 0;
-
-RCParser::RCParser(std::vector<RCToken> TokenList)
- : Tokens(std::move(TokenList)), CurLoc(Tokens.begin()), End(Tokens.end()) {}
-
-bool RCParser::isEof() const { return CurLoc == End; }
-
-RCParser::ParseType RCParser::parseSingleResource() {
- // The first thing we read is usually a resource's name. However, in some
- // cases (LANGUAGE and STRINGTABLE) the resources don't have their names
- // and the first token to be read is the type.
- ASSIGN_OR_RETURN(NameToken, readTypeOrName());
-
- if (NameToken->equalsLower("LANGUAGE"))
- return parseLanguageResource();
- else if (NameToken->equalsLower("STRINGTABLE"))
- return parseStringTableResource();
-
- // If it's not an unnamed resource, what we've just read is a name. Now,
- // read resource type;
- ASSIGN_OR_RETURN(TypeToken, readTypeOrName());
-
- ParseType Result = std::unique_ptr<RCResource>();
- (void)!Result;
-
- if (TypeToken->equalsLower("ACCELERATORS"))
- Result = parseAcceleratorsResource();
- else if (TypeToken->equalsLower("BITMAP"))
- Result = parseBitmapResource();
- else if (TypeToken->equalsLower("CURSOR"))
- Result = parseCursorResource();
- else if (TypeToken->equalsLower("DIALOG"))
- Result = parseDialogResource(false);
- else if (TypeToken->equalsLower("DIALOGEX"))
- Result = parseDialogResource(true);
- else if (TypeToken->equalsLower("HTML"))
- Result = parseHTMLResource();
- else if (TypeToken->equalsLower("ICON"))
- Result = parseIconResource();
- else if (TypeToken->equalsLower("MENU"))
- Result = parseMenuResource();
- else if (TypeToken->equalsLower("RCDATA"))
- Result = parseUserDefinedResource(RkRcData);
- else if (TypeToken->equalsLower("VERSIONINFO"))
- Result = parseVersionInfoResource();
- else
- Result = parseUserDefinedResource(*TypeToken);
-
- if (Result)
- (*Result)->setName(*NameToken);
-
- return Result;
-}
-
-bool RCParser::isNextTokenKind(Kind TokenKind) const {
- return !isEof() && look().kind() == TokenKind;
-}
-
-const RCToken &RCParser::look() const {
- assert(!isEof());
- return *CurLoc;
-}
-
-const RCToken &RCParser::read() {
- assert(!isEof());
- return *CurLoc++;
-}
-
-void RCParser::consume() {
- assert(!isEof());
- CurLoc++;
-}
-
-// An integer description might consist of a single integer or
-// an arithmetic expression evaluating to the integer. The expressions
-// can contain the following tokens: <int> ( ) + - | & ~ not. Their meaning
-// is the same as in C++ except for 'not' expression.
-// The operators in the original RC implementation have the following
-// precedence:
-// 1) Unary operators (- ~ not),
-// 2) Binary operators (+ - & |), with no precedence.
-//
-// 'not' expression is mostly useful for style values. It evaluates to 0,
-// but value given to the operator is stored separately from integer value.
-// It's mostly useful for control style expressions and causes bits from
-// default control style to be excluded from generated style. For binary
-// operators the mask from the right operand is applied to the left operand
-// and masks from both operands are combined in operator result.
-//
-// The following grammar is used to parse the expressions Exp1:
-// Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2
-// Exp2 ::= -Exp2 || ~Exp2 || not Expr2 || Int || (Exp1).
-// (More conveniently, Exp1 is a non-empty sequence of Exp2 expressions,
-// separated by binary operators.)
-//
-// Expressions of type Exp1 are read by parseIntExpr1(Inner) method, while Exp2
-// is read by parseIntExpr2().
-//
-// The original Microsoft tool handles multiple unary operators incorrectly.
-// For example, in 16-bit little-endian integers:
-// 1 => 01 00, -1 => ff ff, --1 => ff ff, ---1 => 01 00;
-// 1 => 01 00, ~1 => fe ff, ~~1 => fd ff, ~~~1 => fc ff.
-// Our implementation differs from the original one and handles these
-// operators correctly:
-// 1 => 01 00, -1 => ff ff, --1 => 01 00, ---1 => ff ff;
-// 1 => 01 00, ~1 => fe ff, ~~1 => 01 00, ~~~1 => fe ff.
-
-Expected<RCInt> RCParser::readInt() {
- ASSIGN_OR_RETURN(Value, parseIntExpr1());
- return (*Value).getValue();
-}
-
-Expected<IntWithNotMask> RCParser::parseIntExpr1() {
- // Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2.
- ASSIGN_OR_RETURN(FirstResult, parseIntExpr2());
- IntWithNotMask Result = *FirstResult;
-
- while (!isEof() && look().isBinaryOp()) {
- auto OpToken = read();
- ASSIGN_OR_RETURN(NextResult, parseIntExpr2());
-
- switch (OpToken.kind()) {
- case Kind::Plus:
- Result += *NextResult;
- break;
-
- case Kind::Minus:
- Result -= *NextResult;
- break;
-
- case Kind::Pipe:
- Result |= *NextResult;
- break;
-
- case Kind::Amp:
- Result &= *NextResult;
- break;
-
- default:
- llvm_unreachable("Already processed all binary ops.");
- }
- }
-
- return Result;
-}
-
-Expected<IntWithNotMask> RCParser::parseIntExpr2() {
- // Exp2 ::= -Exp2 || ~Exp2 || not Expr2 || Int || (Exp1).
- static const char ErrorMsg[] = "'-', '~', integer or '('";
-
- if (isEof())
- return getExpectedError(ErrorMsg);
-
- switch (look().kind()) {
- case Kind::Minus: {
- consume();
- ASSIGN_OR_RETURN(Result, parseIntExpr2());
- return -(*Result);
- }
-
- case Kind::Tilde: {
- consume();
- ASSIGN_OR_RETURN(Result, parseIntExpr2());
- return ~(*Result);
- }
-
- case Kind::Int:
- return RCInt(read());
-
- case Kind::LeftParen: {
- consume();
- ASSIGN_OR_RETURN(Result, parseIntExpr1());
- RETURN_IF_ERROR(consumeType(Kind::RightParen));
- return *Result;
- }
-
- case Kind::Identifier: {
- if (!read().value().equals_lower("not"))
- return getExpectedError(ErrorMsg, true);
- ASSIGN_OR_RETURN(Result, parseIntExpr2());
- return IntWithNotMask(0, (*Result).getValue());
- }
-
- default:
- return getExpectedError(ErrorMsg);
- }
-}
-
-Expected<StringRef> RCParser::readString() {
- if (!isNextTokenKind(Kind::String))
- return getExpectedError("string");
- return read().value();
-}
-
-Expected<StringRef> RCParser::readFilename() {
- if (!isNextTokenKind(Kind::String) && !isNextTokenKind(Kind::Identifier))
- return getExpectedError("string");
- return read().value();
-}
-
-Expected<StringRef> RCParser::readIdentifier() {
- if (!isNextTokenKind(Kind::Identifier))
- return getExpectedError("identifier");
- return read().value();
-}
-
-Expected<IntOrString> RCParser::readIntOrString() {
- if (!isNextTokenKind(Kind::Int) && !isNextTokenKind(Kind::String))
- return getExpectedError("int or string");
- return IntOrString(read());
-}
-
-Expected<IntOrString> RCParser::readTypeOrName() {
- // We suggest that the correct resource name or type should be either an
- // identifier or an integer. The original RC tool is much more liberal.
- if (!isNextTokenKind(Kind::Identifier) && !isNextTokenKind(Kind::Int))
- return getExpectedError("int or identifier");
- return IntOrString(read());
-}
-
-Error RCParser::consumeType(Kind TokenKind) {
- if (isNextTokenKind(TokenKind)) {
- consume();
- return Error::success();
- }
-
- switch (TokenKind) {
-#define TOKEN(TokenName) \
- case Kind::TokenName: \
- return getExpectedError(#TokenName);
-#define SHORT_TOKEN(TokenName, TokenCh) \
- case Kind::TokenName: \
- return getExpectedError(#TokenCh);
-#include "ResourceScriptTokenList.def"
- }
-
- llvm_unreachable("All case options exhausted.");
-}
-
-bool RCParser::consumeOptionalType(Kind TokenKind) {
- if (isNextTokenKind(TokenKind)) {
- consume();
- return true;
- }
-
- return false;
-}
-
-Expected<SmallVector<RCInt, 8>> RCParser::readIntsWithCommas(size_t MinCount,
- size_t MaxCount) {
- assert(MinCount <= MaxCount);
-
- SmallVector<RCInt, 8> Result;
-
- auto FailureHandler =
- [&](llvm::Error Err) -> Expected<SmallVector<RCInt, 8>> {
- if (Result.size() < MinCount)
- return std::move(Err);
- consumeError(std::move(Err));
- return Result;
- };
-
- for (size_t i = 0; i < MaxCount; ++i) {
- // Try to read a comma unless we read the first token.
- // Sometimes RC tool requires them and sometimes not. We decide to
- // always require them.
- if (i >= 1) {
- if (auto CommaError = consumeType(Kind::Comma))
- return FailureHandler(std::move(CommaError));
- }
-
- if (auto IntResult = readInt())
- Result.push_back(*IntResult);
- else
- return FailureHandler(IntResult.takeError());
- }
-
- return std::move(Result);
-}
-
-Expected<uint32_t> RCParser::parseFlags(ArrayRef<StringRef> FlagDesc,
- ArrayRef<uint32_t> FlagValues) {
- assert(!FlagDesc.empty());
- assert(FlagDesc.size() == FlagValues.size());
-
- uint32_t Result = 0;
- while (isNextTokenKind(Kind::Comma)) {
- consume();
- ASSIGN_OR_RETURN(FlagResult, readIdentifier());
- bool FoundFlag = false;
-
- for (size_t FlagId = 0; FlagId < FlagDesc.size(); ++FlagId) {
- if (!FlagResult->equals_lower(FlagDesc[FlagId]))
- continue;
-
- Result |= FlagValues[FlagId];
- FoundFlag = true;
- break;
- }
-
- if (!FoundFlag)
- return getExpectedError(join(FlagDesc, "/"), true);
- }
-
- return Result;
-}
-
-uint16_t RCParser::parseMemoryFlags(uint16_t Flags) {
- while (!isEof()) {
- const RCToken &Token = look();
- if (Token.kind() != Kind::Identifier)
- return Flags;
- const StringRef Ident = Token.value();
- if (Ident.equals_lower("PRELOAD"))
- Flags |= MfPreload;
- else if (Ident.equals_lower("LOADONCALL"))
- Flags &= ~MfPreload;
- else if (Ident.equals_lower("FIXED"))
- Flags &= ~(MfMoveable | MfDiscardable);
- else if (Ident.equals_lower("MOVEABLE"))
- Flags |= MfMoveable;
- else if (Ident.equals_lower("DISCARDABLE"))
- Flags |= MfDiscardable | MfMoveable | MfPure;
- else if (Ident.equals_lower("PURE"))
- Flags |= MfPure;
- else if (Ident.equals_lower("IMPURE"))
- Flags &= ~(MfPure | MfDiscardable);
- else if (Ident.equals_lower("SHARED"))
- Flags |= MfPure;
- else if (Ident.equals_lower("NONSHARED"))
- Flags &= ~(MfPure | MfDiscardable);
- else
- return Flags;
- consume();
- }
- return Flags;
-}
-
-Expected<OptionalStmtList>
-RCParser::parseOptionalStatements(OptStmtType StmtsType) {
- OptionalStmtList Result;
-
- // The last statement is always followed by the start of the block.
- while (!isNextTokenKind(Kind::BlockBegin)) {
- ASSIGN_OR_RETURN(SingleParse, parseSingleOptionalStatement(StmtsType));
- Result.addStmt(std::move(*SingleParse));
- }
-
- return std::move(Result);
-}
-
-Expected<std::unique_ptr<OptionalStmt>>
-RCParser::parseSingleOptionalStatement(OptStmtType StmtsType) {
- ASSIGN_OR_RETURN(TypeToken, readIdentifier());
- if (TypeToken->equals_lower("CHARACTERISTICS"))
- return parseCharacteristicsStmt();
- if (TypeToken->equals_lower("LANGUAGE"))
- return parseLanguageStmt();
- if (TypeToken->equals_lower("VERSION"))
- return parseVersionStmt();
-
- if (StmtsType != OptStmtType::BasicStmt) {
- if (TypeToken->equals_lower("CAPTION"))
- return parseCaptionStmt();
- if (TypeToken->equals_lower("CLASS"))
- return parseClassStmt();
- if (TypeToken->equals_lower("EXSTYLE"))
- return parseExStyleStmt();
- if (TypeToken->equals_lower("FONT"))
- return parseFontStmt(StmtsType);
- if (TypeToken->equals_lower("STYLE"))
- return parseStyleStmt();
- }
-
- return getExpectedError("optional statement type, BEGIN or '{'",
- /* IsAlreadyRead = */ true);
-}
-
-RCParser::ParseType RCParser::parseLanguageResource() {
- // Read LANGUAGE as an optional statement. If it's read correctly, we can
- // upcast it to RCResource.
- return parseLanguageStmt();
-}
-
-RCParser::ParseType RCParser::parseAcceleratorsResource() {
- uint16_t MemoryFlags =
- parseMemoryFlags(AcceleratorsResource::getDefaultMemoryFlags());
- ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());
- RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
-
- auto Accels = std::make_unique<AcceleratorsResource>(
- std::move(*OptStatements), MemoryFlags);
-
- while (!consumeOptionalType(Kind::BlockEnd)) {
- ASSIGN_OR_RETURN(EventResult, readIntOrString());
- RETURN_IF_ERROR(consumeType(Kind::Comma));
- ASSIGN_OR_RETURN(IDResult, readInt());
- ASSIGN_OR_RETURN(
- FlagsResult,
- parseFlags(AcceleratorsResource::Accelerator::OptionsStr,
- AcceleratorsResource::Accelerator::OptionsFlags));
- Accels->addAccelerator(*EventResult, *IDResult, *FlagsResult);
- }
-
- return std::move(Accels);
-}
-
-RCParser::ParseType RCParser::parseCursorResource() {
- uint16_t MemoryFlags =
- parseMemoryFlags(CursorResource::getDefaultMemoryFlags());
- ASSIGN_OR_RETURN(Arg, readFilename());
- return std::make_unique<CursorResource>(*Arg, MemoryFlags);
-}
-
-RCParser::ParseType RCParser::parseDialogResource(bool IsExtended) {
- uint16_t MemoryFlags =
- parseMemoryFlags(DialogResource::getDefaultMemoryFlags());
- // Dialog resources have the following format of the arguments:
- // DIALOG: x, y, width, height [opt stmts...] {controls...}
- // DIALOGEX: x, y, width, height [, helpID] [opt stmts...] {controls...}
- // These are very similar, so we parse them together.
- ASSIGN_OR_RETURN(LocResult, readIntsWithCommas(4, 4));
-
- uint32_t HelpID = 0; // When HelpID is unset, it's assumed to be 0.
- if (IsExtended && consumeOptionalType(Kind::Comma)) {
- ASSIGN_OR_RETURN(HelpIDResult, readInt());
- HelpID = *HelpIDResult;
- }
-
- ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements(
- IsExtended ? OptStmtType::DialogExStmt
- : OptStmtType::DialogStmt));
-
- assert(isNextTokenKind(Kind::BlockBegin) &&
- "parseOptionalStatements, when successful, halts on BlockBegin.");
- consume();
-
- auto Dialog = std::make_unique<DialogResource>(
- (*LocResult)[0], (*LocResult)[1], (*LocResult)[2], (*LocResult)[3],
- HelpID, std::move(*OptStatements), IsExtended, MemoryFlags);
-
- while (!consumeOptionalType(Kind::BlockEnd)) {
- ASSIGN_OR_RETURN(ControlDefResult, parseControl());
- Dialog->addControl(std::move(*ControlDefResult));
- }
-
- return std::move(Dialog);
-}
-
-RCParser::ParseType RCParser::parseUserDefinedResource(IntOrString Type) {
- uint16_t MemoryFlags =
- parseMemoryFlags(UserDefinedResource::getDefaultMemoryFlags());
- if (isEof())
- return getExpectedError("filename, '{' or BEGIN");
-
- // Check if this is a file resource.
- switch (look().kind()) {
- case Kind::String:
- case Kind::Identifier:
- return std::make_unique<UserDefinedResource>(Type, read().value(),
- MemoryFlags);
- default:
- break;
- }
-
- RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
- std::vector<IntOrString> Data;
-
- // Consume comma before each consecutive token except the first one.
- bool ConsumeComma = false;
- while (!consumeOptionalType(Kind::BlockEnd)) {
- if (ConsumeComma)
- RETURN_IF_ERROR(consumeType(Kind::Comma));
- ConsumeComma = true;
-
- ASSIGN_OR_RETURN(Item, readIntOrString());
- Data.push_back(*Item);
- }
-
- return std::make_unique<UserDefinedResource>(Type, std::move(Data),
- MemoryFlags);
-}
-
-RCParser::ParseType RCParser::parseVersionInfoResource() {
- uint16_t MemoryFlags =
- parseMemoryFlags(VersionInfoResource::getDefaultMemoryFlags());
- ASSIGN_OR_RETURN(FixedResult, parseVersionInfoFixed());
- ASSIGN_OR_RETURN(BlockResult, parseVersionInfoBlockContents(StringRef()));
- return std::make_unique<VersionInfoResource>(
- std::move(**BlockResult), std::move(*FixedResult), MemoryFlags);
-}
-
-Expected<Control> RCParser::parseControl() {
- // Each control definition (except CONTROL) follows one of the schemes below
- // depending on the control class:
- // [class] text, id, x, y, width, height [, style] [, exstyle] [, helpID]
- // [class] id, x, y, width, height [, style] [, exstyle] [, helpID]
- // Note that control ids must be integers.
- // Text might be either a string or an integer pointing to resource ID.
- ASSIGN_OR_RETURN(ClassResult, readIdentifier());
- std::string ClassUpper = ClassResult->upper();
- auto CtlInfo = Control::SupportedCtls.find(ClassUpper);
- if (CtlInfo == Control::SupportedCtls.end())
- return getExpectedError("control type, END or '}'", true);
-
- // Read caption if necessary.
- IntOrString Caption{StringRef()};
- if (CtlInfo->getValue().HasTitle) {
- ASSIGN_OR_RETURN(CaptionResult, readIntOrString());
- RETURN_IF_ERROR(consumeType(Kind::Comma));
- Caption = *CaptionResult;
- }
-
- ASSIGN_OR_RETURN(ID, readInt());
- RETURN_IF_ERROR(consumeType(Kind::Comma));
-
- IntOrString Class;
- Optional<IntWithNotMask> Style;
- if (ClassUpper == "CONTROL") {
- // CONTROL text, id, class, style, x, y, width, height [, exstyle] [, helpID]
- ASSIGN_OR_RETURN(ClassStr, readString());
- RETURN_IF_ERROR(consumeType(Kind::Comma));
- Class = *ClassStr;
- ASSIGN_OR_RETURN(StyleVal, parseIntExpr1());
- RETURN_IF_ERROR(consumeType(Kind::Comma));
- Style = *StyleVal;
- } else {
- Class = CtlInfo->getValue().CtlClass;
- }
-
- // x, y, width, height
- ASSIGN_OR_RETURN(Args, readIntsWithCommas(4, 4));
-
- if (ClassUpper != "CONTROL") {
- if (consumeOptionalType(Kind::Comma)) {
- ASSIGN_OR_RETURN(Val, parseIntExpr1());
- Style = *Val;
- }
- }
-
- Optional<uint32_t> ExStyle;
- if (consumeOptionalType(Kind::Comma)) {
- ASSIGN_OR_RETURN(Val, readInt());
- ExStyle = *Val;
- }
- Optional<uint32_t> HelpID;
- if (consumeOptionalType(Kind::Comma)) {
- ASSIGN_OR_RETURN(Val, readInt());
- HelpID = *Val;
- }
-
- return Control(*ClassResult, Caption, *ID, (*Args)[0], (*Args)[1],
- (*Args)[2], (*Args)[3], Style, ExStyle, HelpID, Class);
-}
-
-RCParser::ParseType RCParser::parseBitmapResource() {
- uint16_t MemoryFlags =
- parseMemoryFlags(BitmapResource::getDefaultMemoryFlags());
- ASSIGN_OR_RETURN(Arg, readFilename());
- return std::make_unique<BitmapResource>(*Arg, MemoryFlags);
-}
-
-RCParser::ParseType RCParser::parseIconResource() {
- uint16_t MemoryFlags =
- parseMemoryFlags(IconResource::getDefaultMemoryFlags());
- ASSIGN_OR_RETURN(Arg, readFilename());
- return std::make_unique<IconResource>(*Arg, MemoryFlags);
-}
-
-RCParser::ParseType RCParser::parseHTMLResource() {
- uint16_t MemoryFlags =
- parseMemoryFlags(HTMLResource::getDefaultMemoryFlags());
- ASSIGN_OR_RETURN(Arg, readFilename());
- return std::make_unique<HTMLResource>(*Arg, MemoryFlags);
-}
-
-RCParser::ParseType RCParser::parseMenuResource() {
- uint16_t MemoryFlags =
- parseMemoryFlags(MenuResource::getDefaultMemoryFlags());
- ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());
- ASSIGN_OR_RETURN(Items, parseMenuItemsList());
- return std::make_unique<MenuResource>(std::move(*OptStatements),
- std::move(*Items), MemoryFlags);
-}
-
-Expected<MenuDefinitionList> RCParser::parseMenuItemsList() {
- RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
-
- MenuDefinitionList List;
-
- // Read a set of items. Each item is of one of three kinds:
- // MENUITEM SEPARATOR
- // MENUITEM caption:String, result:Int [, menu flags]...
- // POPUP caption:String [, menu flags]... { items... }
- while (!consumeOptionalType(Kind::BlockEnd)) {
- ASSIGN_OR_RETURN(ItemTypeResult, readIdentifier());
-
- bool IsMenuItem = ItemTypeResult->equals_lower("MENUITEM");
- bool IsPopup = ItemTypeResult->equals_lower("POPUP");
- if (!IsMenuItem && !IsPopup)
- return getExpectedError("MENUITEM, POPUP, END or '}'", true);
-
- if (IsMenuItem && isNextTokenKind(Kind::Identifier)) {
- // Now, expecting SEPARATOR.
- ASSIGN_OR_RETURN(SeparatorResult, readIdentifier());
- if (SeparatorResult->equals_lower("SEPARATOR")) {
- List.addDefinition(std::make_unique<MenuSeparator>());
- continue;
- }
-
- return getExpectedError("SEPARATOR or string", true);
- }
-
- // Not a separator. Read the caption.
- ASSIGN_OR_RETURN(CaptionResult, readString());
-
- // If MENUITEM, expect also a comma and an integer.
- uint32_t MenuResult = -1;
-
- if (IsMenuItem) {
- RETURN_IF_ERROR(consumeType(Kind::Comma));
- ASSIGN_OR_RETURN(IntResult, readInt());
- MenuResult = *IntResult;
- }
-
- ASSIGN_OR_RETURN(FlagsResult, parseFlags(MenuDefinition::OptionsStr,
- MenuDefinition::OptionsFlags));
-
- if (IsPopup) {
- // If POPUP, read submenu items recursively.
- ASSIGN_OR_RETURN(SubMenuResult, parseMenuItemsList());
- List.addDefinition(std::make_unique<PopupItem>(
- *CaptionResult, *FlagsResult, std::move(*SubMenuResult)));
- continue;
- }
-
- assert(IsMenuItem);
- List.addDefinition(
- std::make_unique<MenuItem>(*CaptionResult, MenuResult, *FlagsResult));
- }
-
- return std::move(List);
-}
-
-RCParser::ParseType RCParser::parseStringTableResource() {
- uint16_t MemoryFlags =
- parseMemoryFlags(StringTableResource::getDefaultMemoryFlags());
- ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());
- RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
-
- auto Table = std::make_unique<StringTableResource>(std::move(*OptStatements),
- MemoryFlags);
-
- // Read strings until we reach the end of the block.
- while (!consumeOptionalType(Kind::BlockEnd)) {
- // Each definition consists of string's ID (an integer) and a string.
- // Some examples in documentation suggest that there might be a comma in
- // between, however we strictly adhere to the single statement definition.
- ASSIGN_OR_RETURN(IDResult, readInt());
- consumeOptionalType(Kind::Comma);
-
- std::vector<StringRef> Strings;
- ASSIGN_OR_RETURN(StrResult, readString());
- Strings.push_back(*StrResult);
- while (isNextTokenKind(Kind::String))
- Strings.push_back(read().value());
-
- Table->addStrings(*IDResult, std::move(Strings));
- }
-
- return std::move(Table);
-}
-
-Expected<std::unique_ptr<VersionInfoBlock>>
-RCParser::parseVersionInfoBlockContents(StringRef BlockName) {
- RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
-
- auto Contents = std::make_unique<VersionInfoBlock>(BlockName);
-
- while (!isNextTokenKind(Kind::BlockEnd)) {
- ASSIGN_OR_RETURN(Stmt, parseVersionInfoStmt());
- Contents->addStmt(std::move(*Stmt));
- }
-
- consume(); // Consume BlockEnd.
-
- return std::move(Contents);
-}
-
-Expected<std::unique_ptr<VersionInfoStmt>> RCParser::parseVersionInfoStmt() {
- // Expect either BLOCK or VALUE, then a name or a key (a string).
- ASSIGN_OR_RETURN(TypeResult, readIdentifier());
-
- if (TypeResult->equals_lower("BLOCK")) {
- ASSIGN_OR_RETURN(NameResult, readString());
- return parseVersionInfoBlockContents(*NameResult);
- }
-
- if (TypeResult->equals_lower("VALUE")) {
- ASSIGN_OR_RETURN(KeyResult, readString());
- // Read a non-empty list of strings and/or ints, each
- // possibly preceded by a comma. Unfortunately, the tool behavior depends
- // on them existing or not, so we need to memorize where we found them.
- std::vector<IntOrString> Values;
- std::vector<bool> PrecedingCommas;
- RETURN_IF_ERROR(consumeType(Kind::Comma));
- while (!isNextTokenKind(Kind::Identifier) &&
- !isNextTokenKind(Kind::BlockEnd)) {
- // Try to eat a comma if it's not the first statement.
- bool HadComma = Values.size() > 0 && consumeOptionalType(Kind::Comma);
- ASSIGN_OR_RETURN(ValueResult, readIntOrString());
- Values.push_back(*ValueResult);
- PrecedingCommas.push_back(HadComma);
- }
- return std::make_unique<VersionInfoValue>(*KeyResult, std::move(Values),
- std::move(PrecedingCommas));
- }
-
- return getExpectedError("BLOCK or VALUE", true);
-}
-
-Expected<VersionInfoResource::VersionInfoFixed>
-RCParser::parseVersionInfoFixed() {
- using RetType = VersionInfoResource::VersionInfoFixed;
- RetType Result;
-
- // Read until the beginning of the block.
- while (!isNextTokenKind(Kind::BlockBegin)) {
- ASSIGN_OR_RETURN(TypeResult, readIdentifier());
- auto FixedType = RetType::getFixedType(*TypeResult);
-
- if (!RetType::isTypeSupported(FixedType))
- return getExpectedError("fixed VERSIONINFO statement type", true);
- if (Result.IsTypePresent[FixedType])
- return getExpectedError("yet unread fixed VERSIONINFO statement type",
- true);
-
- // VERSION variations take multiple integers.
- size_t NumInts = RetType::isVersionType(FixedType) ? 4 : 1;
+//===-- ResourceScriptParser.cpp --------------------------------*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===---------------------------------------------------------------------===//
+//
+// This implements the parser defined in ResourceScriptParser.h.
+//
+//===---------------------------------------------------------------------===//
+
+#include "ResourceScriptParser.h"
+#include "llvm/Option/ArgList.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Process.h"
+
+// Take an expression returning llvm::Error and forward the error if it exists.
+#define RETURN_IF_ERROR(Expr) \
+ if (auto Err = (Expr)) \
+ return std::move(Err);
+
+// Take an expression returning llvm::Expected<T> and assign it to Var or
+// forward the error out of the function.
+#define ASSIGN_OR_RETURN(Var, Expr) \
+ auto Var = (Expr); \
+ if (!Var) \
+ return Var.takeError();
+
+namespace llvm {
+namespace rc {
+
+RCParser::ParserError::ParserError(const Twine &Expected, const LocIter CurLoc,
+ const LocIter End)
+ : ErrorLoc(CurLoc), FileEnd(End) {
+ CurMessage = "Error parsing file: expected " + Expected.str() + ", got " +
+ (CurLoc == End ? "<EOF>" : CurLoc->value()).str();
+}
+
+char RCParser::ParserError::ID = 0;
+
+RCParser::RCParser(std::vector<RCToken> TokenList)
+ : Tokens(std::move(TokenList)), CurLoc(Tokens.begin()), End(Tokens.end()) {}
+
+bool RCParser::isEof() const { return CurLoc == End; }
+
+RCParser::ParseType RCParser::parseSingleResource() {
+ // The first thing we read is usually a resource's name. However, in some
+ // cases (LANGUAGE and STRINGTABLE) the resources don't have their names
+ // and the first token to be read is the type.
+ ASSIGN_OR_RETURN(NameToken, readTypeOrName());
+
+ if (NameToken->equalsLower("LANGUAGE"))
+ return parseLanguageResource();
+ else if (NameToken->equalsLower("STRINGTABLE"))
+ return parseStringTableResource();
+
+ // If it's not an unnamed resource, what we've just read is a name. Now,
+ // read resource type;
+ ASSIGN_OR_RETURN(TypeToken, readTypeOrName());
+
+ ParseType Result = std::unique_ptr<RCResource>();
+ (void)!Result;
+
+ if (TypeToken->equalsLower("ACCELERATORS"))
+ Result = parseAcceleratorsResource();
+ else if (TypeToken->equalsLower("BITMAP"))
+ Result = parseBitmapResource();
+ else if (TypeToken->equalsLower("CURSOR"))
+ Result = parseCursorResource();
+ else if (TypeToken->equalsLower("DIALOG"))
+ Result = parseDialogResource(false);
+ else if (TypeToken->equalsLower("DIALOGEX"))
+ Result = parseDialogResource(true);
+ else if (TypeToken->equalsLower("HTML"))
+ Result = parseHTMLResource();
+ else if (TypeToken->equalsLower("ICON"))
+ Result = parseIconResource();
+ else if (TypeToken->equalsLower("MENU"))
+ Result = parseMenuResource();
+ else if (TypeToken->equalsLower("RCDATA"))
+ Result = parseUserDefinedResource(RkRcData);
+ else if (TypeToken->equalsLower("VERSIONINFO"))
+ Result = parseVersionInfoResource();
+ else
+ Result = parseUserDefinedResource(*TypeToken);
+
+ if (Result)
+ (*Result)->setName(*NameToken);
+
+ return Result;
+}
+
+bool RCParser::isNextTokenKind(Kind TokenKind) const {
+ return !isEof() && look().kind() == TokenKind;
+}
+
+const RCToken &RCParser::look() const {
+ assert(!isEof());
+ return *CurLoc;
+}
+
+const RCToken &RCParser::read() {
+ assert(!isEof());
+ return *CurLoc++;
+}
+
+void RCParser::consume() {
+ assert(!isEof());
+ CurLoc++;
+}
+
+// An integer description might consist of a single integer or
+// an arithmetic expression evaluating to the integer. The expressions
+// can contain the following tokens: <int> ( ) + - | & ~ not. Their meaning
+// is the same as in C++ except for 'not' expression.
+// The operators in the original RC implementation have the following
+// precedence:
+// 1) Unary operators (- ~ not),
+// 2) Binary operators (+ - & |), with no precedence.
+//
+// 'not' expression is mostly useful for style values. It evaluates to 0,
+// but value given to the operator is stored separately from integer value.
+// It's mostly useful for control style expressions and causes bits from
+// default control style to be excluded from generated style. For binary
+// operators the mask from the right operand is applied to the left operand
+// and masks from both operands are combined in operator result.
+//
+// The following grammar is used to parse the expressions Exp1:
+// Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2
+// Exp2 ::= -Exp2 || ~Exp2 || not Expr2 || Int || (Exp1).
+// (More conveniently, Exp1 is a non-empty sequence of Exp2 expressions,
+// separated by binary operators.)
+//
+// Expressions of type Exp1 are read by parseIntExpr1(Inner) method, while Exp2
+// is read by parseIntExpr2().
+//
+// The original Microsoft tool handles multiple unary operators incorrectly.
+// For example, in 16-bit little-endian integers:
+// 1 => 01 00, -1 => ff ff, --1 => ff ff, ---1 => 01 00;
+// 1 => 01 00, ~1 => fe ff, ~~1 => fd ff, ~~~1 => fc ff.
+// Our implementation differs from the original one and handles these
+// operators correctly:
+// 1 => 01 00, -1 => ff ff, --1 => 01 00, ---1 => ff ff;
+// 1 => 01 00, ~1 => fe ff, ~~1 => 01 00, ~~~1 => fe ff.
+
+Expected<RCInt> RCParser::readInt() {
+ ASSIGN_OR_RETURN(Value, parseIntExpr1());
+ return (*Value).getValue();
+}
+
+Expected<IntWithNotMask> RCParser::parseIntExpr1() {
+ // Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2.
+ ASSIGN_OR_RETURN(FirstResult, parseIntExpr2());
+ IntWithNotMask Result = *FirstResult;
+
+ while (!isEof() && look().isBinaryOp()) {
+ auto OpToken = read();
+ ASSIGN_OR_RETURN(NextResult, parseIntExpr2());
+
+ switch (OpToken.kind()) {
+ case Kind::Plus:
+ Result += *NextResult;
+ break;
+
+ case Kind::Minus:
+ Result -= *NextResult;
+ break;
+
+ case Kind::Pipe:
+ Result |= *NextResult;
+ break;
+
+ case Kind::Amp:
+ Result &= *NextResult;
+ break;
+
+ default:
+ llvm_unreachable("Already processed all binary ops.");
+ }
+ }
+
+ return Result;
+}
+
+Expected<IntWithNotMask> RCParser::parseIntExpr2() {
+ // Exp2 ::= -Exp2 || ~Exp2 || not Expr2 || Int || (Exp1).
+ static const char ErrorMsg[] = "'-', '~', integer or '('";
+
+ if (isEof())
+ return getExpectedError(ErrorMsg);
+
+ switch (look().kind()) {
+ case Kind::Minus: {
+ consume();
+ ASSIGN_OR_RETURN(Result, parseIntExpr2());
+ return -(*Result);
+ }
+
+ case Kind::Tilde: {
+ consume();
+ ASSIGN_OR_RETURN(Result, parseIntExpr2());
+ return ~(*Result);
+ }
+
+ case Kind::Int:
+ return RCInt(read());
+
+ case Kind::LeftParen: {
+ consume();
+ ASSIGN_OR_RETURN(Result, parseIntExpr1());
+ RETURN_IF_ERROR(consumeType(Kind::RightParen));
+ return *Result;
+ }
+
+ case Kind::Identifier: {
+ if (!read().value().equals_lower("not"))
+ return getExpectedError(ErrorMsg, true);
+ ASSIGN_OR_RETURN(Result, parseIntExpr2());
+ return IntWithNotMask(0, (*Result).getValue());
+ }
+
+ default:
+ return getExpectedError(ErrorMsg);
+ }
+}
+
+Expected<StringRef> RCParser::readString() {
+ if (!isNextTokenKind(Kind::String))
+ return getExpectedError("string");
+ return read().value();
+}
+
+Expected<StringRef> RCParser::readFilename() {
+ if (!isNextTokenKind(Kind::String) && !isNextTokenKind(Kind::Identifier))
+ return getExpectedError("string");
+ return read().value();
+}
+
+Expected<StringRef> RCParser::readIdentifier() {
+ if (!isNextTokenKind(Kind::Identifier))
+ return getExpectedError("identifier");
+ return read().value();
+}
+
+Expected<IntOrString> RCParser::readIntOrString() {
+ if (!isNextTokenKind(Kind::Int) && !isNextTokenKind(Kind::String))
+ return getExpectedError("int or string");
+ return IntOrString(read());
+}
+
+Expected<IntOrString> RCParser::readTypeOrName() {
+ // We suggest that the correct resource name or type should be either an
+ // identifier or an integer. The original RC tool is much more liberal.
+ if (!isNextTokenKind(Kind::Identifier) && !isNextTokenKind(Kind::Int))
+ return getExpectedError("int or identifier");
+ return IntOrString(read());
+}
+
+Error RCParser::consumeType(Kind TokenKind) {
+ if (isNextTokenKind(TokenKind)) {
+ consume();
+ return Error::success();
+ }
+
+ switch (TokenKind) {
+#define TOKEN(TokenName) \
+ case Kind::TokenName: \
+ return getExpectedError(#TokenName);
+#define SHORT_TOKEN(TokenName, TokenCh) \
+ case Kind::TokenName: \
+ return getExpectedError(#TokenCh);
+#include "ResourceScriptTokenList.def"
+ }
+
+ llvm_unreachable("All case options exhausted.");
+}
+
+bool RCParser::consumeOptionalType(Kind TokenKind) {
+ if (isNextTokenKind(TokenKind)) {
+ consume();
+ return true;
+ }
+
+ return false;
+}
+
+Expected<SmallVector<RCInt, 8>> RCParser::readIntsWithCommas(size_t MinCount,
+ size_t MaxCount) {
+ assert(MinCount <= MaxCount);
+
+ SmallVector<RCInt, 8> Result;
+
+ auto FailureHandler =
+ [&](llvm::Error Err) -> Expected<SmallVector<RCInt, 8>> {
+ if (Result.size() < MinCount)
+ return std::move(Err);
+ consumeError(std::move(Err));
+ return Result;
+ };
+
+ for (size_t i = 0; i < MaxCount; ++i) {
+ // Try to read a comma unless we read the first token.
+ // Sometimes RC tool requires them and sometimes not. We decide to
+ // always require them.
+ if (i >= 1) {
+ if (auto CommaError = consumeType(Kind::Comma))
+ return FailureHandler(std::move(CommaError));
+ }
+
+ if (auto IntResult = readInt())
+ Result.push_back(*IntResult);
+ else
+ return FailureHandler(IntResult.takeError());
+ }
+
+ return std::move(Result);
+}
+
+Expected<uint32_t> RCParser::parseFlags(ArrayRef<StringRef> FlagDesc,
+ ArrayRef<uint32_t> FlagValues) {
+ assert(!FlagDesc.empty());
+ assert(FlagDesc.size() == FlagValues.size());
+
+ uint32_t Result = 0;
+ while (isNextTokenKind(Kind::Comma)) {
+ consume();
+ ASSIGN_OR_RETURN(FlagResult, readIdentifier());
+ bool FoundFlag = false;
+
+ for (size_t FlagId = 0; FlagId < FlagDesc.size(); ++FlagId) {
+ if (!FlagResult->equals_lower(FlagDesc[FlagId]))
+ continue;
+
+ Result |= FlagValues[FlagId];
+ FoundFlag = true;
+ break;
+ }
+
+ if (!FoundFlag)
+ return getExpectedError(join(FlagDesc, "/"), true);
+ }
+
+ return Result;
+}
+
+uint16_t RCParser::parseMemoryFlags(uint16_t Flags) {
+ while (!isEof()) {
+ const RCToken &Token = look();
+ if (Token.kind() != Kind::Identifier)
+ return Flags;
+ const StringRef Ident = Token.value();
+ if (Ident.equals_lower("PRELOAD"))
+ Flags |= MfPreload;
+ else if (Ident.equals_lower("LOADONCALL"))
+ Flags &= ~MfPreload;
+ else if (Ident.equals_lower("FIXED"))
+ Flags &= ~(MfMoveable | MfDiscardable);
+ else if (Ident.equals_lower("MOVEABLE"))
+ Flags |= MfMoveable;
+ else if (Ident.equals_lower("DISCARDABLE"))
+ Flags |= MfDiscardable | MfMoveable | MfPure;
+ else if (Ident.equals_lower("PURE"))
+ Flags |= MfPure;
+ else if (Ident.equals_lower("IMPURE"))
+ Flags &= ~(MfPure | MfDiscardable);
+ else if (Ident.equals_lower("SHARED"))
+ Flags |= MfPure;
+ else if (Ident.equals_lower("NONSHARED"))
+ Flags &= ~(MfPure | MfDiscardable);
+ else
+ return Flags;
+ consume();
+ }
+ return Flags;
+}
+
+Expected<OptionalStmtList>
+RCParser::parseOptionalStatements(OptStmtType StmtsType) {
+ OptionalStmtList Result;
+
+ // The last statement is always followed by the start of the block.
+ while (!isNextTokenKind(Kind::BlockBegin)) {
+ ASSIGN_OR_RETURN(SingleParse, parseSingleOptionalStatement(StmtsType));
+ Result.addStmt(std::move(*SingleParse));
+ }
+
+ return std::move(Result);
+}
+
+Expected<std::unique_ptr<OptionalStmt>>
+RCParser::parseSingleOptionalStatement(OptStmtType StmtsType) {
+ ASSIGN_OR_RETURN(TypeToken, readIdentifier());
+ if (TypeToken->equals_lower("CHARACTERISTICS"))
+ return parseCharacteristicsStmt();
+ if (TypeToken->equals_lower("LANGUAGE"))
+ return parseLanguageStmt();
+ if (TypeToken->equals_lower("VERSION"))
+ return parseVersionStmt();
+
+ if (StmtsType != OptStmtType::BasicStmt) {
+ if (TypeToken->equals_lower("CAPTION"))
+ return parseCaptionStmt();
+ if (TypeToken->equals_lower("CLASS"))
+ return parseClassStmt();
+ if (TypeToken->equals_lower("EXSTYLE"))
+ return parseExStyleStmt();
+ if (TypeToken->equals_lower("FONT"))
+ return parseFontStmt(StmtsType);
+ if (TypeToken->equals_lower("STYLE"))
+ return parseStyleStmt();
+ }
+
+ return getExpectedError("optional statement type, BEGIN or '{'",
+ /* IsAlreadyRead = */ true);
+}
+
+RCParser::ParseType RCParser::parseLanguageResource() {
+ // Read LANGUAGE as an optional statement. If it's read correctly, we can
+ // upcast it to RCResource.
+ return parseLanguageStmt();
+}
+
+RCParser::ParseType RCParser::parseAcceleratorsResource() {
+ uint16_t MemoryFlags =
+ parseMemoryFlags(AcceleratorsResource::getDefaultMemoryFlags());
+ ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());
+ RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
+
+ auto Accels = std::make_unique<AcceleratorsResource>(
+ std::move(*OptStatements), MemoryFlags);
+
+ while (!consumeOptionalType(Kind::BlockEnd)) {
+ ASSIGN_OR_RETURN(EventResult, readIntOrString());
+ RETURN_IF_ERROR(consumeType(Kind::Comma));
+ ASSIGN_OR_RETURN(IDResult, readInt());
+ ASSIGN_OR_RETURN(
+ FlagsResult,
+ parseFlags(AcceleratorsResource::Accelerator::OptionsStr,
+ AcceleratorsResource::Accelerator::OptionsFlags));
+ Accels->addAccelerator(*EventResult, *IDResult, *FlagsResult);
+ }
+
+ return std::move(Accels);
+}
+
+RCParser::ParseType RCParser::parseCursorResource() {
+ uint16_t MemoryFlags =
+ parseMemoryFlags(CursorResource::getDefaultMemoryFlags());
+ ASSIGN_OR_RETURN(Arg, readFilename());
+ return std::make_unique<CursorResource>(*Arg, MemoryFlags);
+}
+
+RCParser::ParseType RCParser::parseDialogResource(bool IsExtended) {
+ uint16_t MemoryFlags =
+ parseMemoryFlags(DialogResource::getDefaultMemoryFlags());
+ // Dialog resources have the following format of the arguments:
+ // DIALOG: x, y, width, height [opt stmts...] {controls...}
+ // DIALOGEX: x, y, width, height [, helpID] [opt stmts...] {controls...}
+ // These are very similar, so we parse them together.
+ ASSIGN_OR_RETURN(LocResult, readIntsWithCommas(4, 4));
+
+ uint32_t HelpID = 0; // When HelpID is unset, it's assumed to be 0.
+ if (IsExtended && consumeOptionalType(Kind::Comma)) {
+ ASSIGN_OR_RETURN(HelpIDResult, readInt());
+ HelpID = *HelpIDResult;
+ }
+
+ ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements(
+ IsExtended ? OptStmtType::DialogExStmt
+ : OptStmtType::DialogStmt));
+
+ assert(isNextTokenKind(Kind::BlockBegin) &&
+ "parseOptionalStatements, when successful, halts on BlockBegin.");
+ consume();
+
+ auto Dialog = std::make_unique<DialogResource>(
+ (*LocResult)[0], (*LocResult)[1], (*LocResult)[2], (*LocResult)[3],
+ HelpID, std::move(*OptStatements), IsExtended, MemoryFlags);
+
+ while (!consumeOptionalType(Kind::BlockEnd)) {
+ ASSIGN_OR_RETURN(ControlDefResult, parseControl());
+ Dialog->addControl(std::move(*ControlDefResult));
+ }
+
+ return std::move(Dialog);
+}
+
+RCParser::ParseType RCParser::parseUserDefinedResource(IntOrString Type) {
+ uint16_t MemoryFlags =
+ parseMemoryFlags(UserDefinedResource::getDefaultMemoryFlags());
+ if (isEof())
+ return getExpectedError("filename, '{' or BEGIN");
+
+ // Check if this is a file resource.
+ switch (look().kind()) {
+ case Kind::String:
+ case Kind::Identifier:
+ return std::make_unique<UserDefinedResource>(Type, read().value(),
+ MemoryFlags);
+ default:
+ break;
+ }
+
+ RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
+ std::vector<IntOrString> Data;
+
+ // Consume comma before each consecutive token except the first one.
+ bool ConsumeComma = false;
+ while (!consumeOptionalType(Kind::BlockEnd)) {
+ if (ConsumeComma)
+ RETURN_IF_ERROR(consumeType(Kind::Comma));
+ ConsumeComma = true;
+
+ ASSIGN_OR_RETURN(Item, readIntOrString());
+ Data.push_back(*Item);
+ }
+
+ return std::make_unique<UserDefinedResource>(Type, std::move(Data),
+ MemoryFlags);
+}
+
+RCParser::ParseType RCParser::parseVersionInfoResource() {
+ uint16_t MemoryFlags =
+ parseMemoryFlags(VersionInfoResource::getDefaultMemoryFlags());
+ ASSIGN_OR_RETURN(FixedResult, parseVersionInfoFixed());
+ ASSIGN_OR_RETURN(BlockResult, parseVersionInfoBlockContents(StringRef()));
+ return std::make_unique<VersionInfoResource>(
+ std::move(**BlockResult), std::move(*FixedResult), MemoryFlags);
+}
+
+Expected<Control> RCParser::parseControl() {
+ // Each control definition (except CONTROL) follows one of the schemes below
+ // depending on the control class:
+ // [class] text, id, x, y, width, height [, style] [, exstyle] [, helpID]
+ // [class] id, x, y, width, height [, style] [, exstyle] [, helpID]
+ // Note that control ids must be integers.
+ // Text might be either a string or an integer pointing to resource ID.
+ ASSIGN_OR_RETURN(ClassResult, readIdentifier());
+ std::string ClassUpper = ClassResult->upper();
+ auto CtlInfo = Control::SupportedCtls.find(ClassUpper);
+ if (CtlInfo == Control::SupportedCtls.end())
+ return getExpectedError("control type, END or '}'", true);
+
+ // Read caption if necessary.
+ IntOrString Caption{StringRef()};
+ if (CtlInfo->getValue().HasTitle) {
+ ASSIGN_OR_RETURN(CaptionResult, readIntOrString());
+ RETURN_IF_ERROR(consumeType(Kind::Comma));
+ Caption = *CaptionResult;
+ }
+
+ ASSIGN_OR_RETURN(ID, readInt());
+ RETURN_IF_ERROR(consumeType(Kind::Comma));
+
+ IntOrString Class;
+ Optional<IntWithNotMask> Style;
+ if (ClassUpper == "CONTROL") {
+ // CONTROL text, id, class, style, x, y, width, height [, exstyle] [, helpID]
+ ASSIGN_OR_RETURN(ClassStr, readString());
+ RETURN_IF_ERROR(consumeType(Kind::Comma));
+ Class = *ClassStr;
+ ASSIGN_OR_RETURN(StyleVal, parseIntExpr1());
+ RETURN_IF_ERROR(consumeType(Kind::Comma));
+ Style = *StyleVal;
+ } else {
+ Class = CtlInfo->getValue().CtlClass;
+ }
+
+ // x, y, width, height
+ ASSIGN_OR_RETURN(Args, readIntsWithCommas(4, 4));
+
+ if (ClassUpper != "CONTROL") {
+ if (consumeOptionalType(Kind::Comma)) {
+ ASSIGN_OR_RETURN(Val, parseIntExpr1());
+ Style = *Val;
+ }
+ }
+
+ Optional<uint32_t> ExStyle;
+ if (consumeOptionalType(Kind::Comma)) {
+ ASSIGN_OR_RETURN(Val, readInt());
+ ExStyle = *Val;
+ }
+ Optional<uint32_t> HelpID;
+ if (consumeOptionalType(Kind::Comma)) {
+ ASSIGN_OR_RETURN(Val, readInt());
+ HelpID = *Val;
+ }
+
+ return Control(*ClassResult, Caption, *ID, (*Args)[0], (*Args)[1],
+ (*Args)[2], (*Args)[3], Style, ExStyle, HelpID, Class);
+}
+
+RCParser::ParseType RCParser::parseBitmapResource() {
+ uint16_t MemoryFlags =
+ parseMemoryFlags(BitmapResource::getDefaultMemoryFlags());
+ ASSIGN_OR_RETURN(Arg, readFilename());
+ return std::make_unique<BitmapResource>(*Arg, MemoryFlags);
+}
+
+RCParser::ParseType RCParser::parseIconResource() {
+ uint16_t MemoryFlags =
+ parseMemoryFlags(IconResource::getDefaultMemoryFlags());
+ ASSIGN_OR_RETURN(Arg, readFilename());
+ return std::make_unique<IconResource>(*Arg, MemoryFlags);
+}
+
+RCParser::ParseType RCParser::parseHTMLResource() {
+ uint16_t MemoryFlags =
+ parseMemoryFlags(HTMLResource::getDefaultMemoryFlags());
+ ASSIGN_OR_RETURN(Arg, readFilename());
+ return std::make_unique<HTMLResource>(*Arg, MemoryFlags);
+}
+
+RCParser::ParseType RCParser::parseMenuResource() {
+ uint16_t MemoryFlags =
+ parseMemoryFlags(MenuResource::getDefaultMemoryFlags());
+ ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());
+ ASSIGN_OR_RETURN(Items, parseMenuItemsList());
+ return std::make_unique<MenuResource>(std::move(*OptStatements),
+ std::move(*Items), MemoryFlags);
+}
+
+Expected<MenuDefinitionList> RCParser::parseMenuItemsList() {
+ RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
+
+ MenuDefinitionList List;
+
+ // Read a set of items. Each item is of one of three kinds:
+ // MENUITEM SEPARATOR
+ // MENUITEM caption:String, result:Int [, menu flags]...
+ // POPUP caption:String [, menu flags]... { items... }
+ while (!consumeOptionalType(Kind::BlockEnd)) {
+ ASSIGN_OR_RETURN(ItemTypeResult, readIdentifier());
+
+ bool IsMenuItem = ItemTypeResult->equals_lower("MENUITEM");
+ bool IsPopup = ItemTypeResult->equals_lower("POPUP");
+ if (!IsMenuItem && !IsPopup)
+ return getExpectedError("MENUITEM, POPUP, END or '}'", true);
+
+ if (IsMenuItem && isNextTokenKind(Kind::Identifier)) {
+ // Now, expecting SEPARATOR.
+ ASSIGN_OR_RETURN(SeparatorResult, readIdentifier());
+ if (SeparatorResult->equals_lower("SEPARATOR")) {
+ List.addDefinition(std::make_unique<MenuSeparator>());
+ continue;
+ }
+
+ return getExpectedError("SEPARATOR or string", true);
+ }
+
+ // Not a separator. Read the caption.
+ ASSIGN_OR_RETURN(CaptionResult, readString());
+
+ // If MENUITEM, expect also a comma and an integer.
+ uint32_t MenuResult = -1;
+
+ if (IsMenuItem) {
+ RETURN_IF_ERROR(consumeType(Kind::Comma));
+ ASSIGN_OR_RETURN(IntResult, readInt());
+ MenuResult = *IntResult;
+ }
+
+ ASSIGN_OR_RETURN(FlagsResult, parseFlags(MenuDefinition::OptionsStr,
+ MenuDefinition::OptionsFlags));
+
+ if (IsPopup) {
+ // If POPUP, read submenu items recursively.
+ ASSIGN_OR_RETURN(SubMenuResult, parseMenuItemsList());
+ List.addDefinition(std::make_unique<PopupItem>(
+ *CaptionResult, *FlagsResult, std::move(*SubMenuResult)));
+ continue;
+ }
+
+ assert(IsMenuItem);
+ List.addDefinition(
+ std::make_unique<MenuItem>(*CaptionResult, MenuResult, *FlagsResult));
+ }
+
+ return std::move(List);
+}
+
+RCParser::ParseType RCParser::parseStringTableResource() {
+ uint16_t MemoryFlags =
+ parseMemoryFlags(StringTableResource::getDefaultMemoryFlags());
+ ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());
+ RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
+
+ auto Table = std::make_unique<StringTableResource>(std::move(*OptStatements),
+ MemoryFlags);
+
+ // Read strings until we reach the end of the block.
+ while (!consumeOptionalType(Kind::BlockEnd)) {
+ // Each definition consists of string's ID (an integer) and a string.
+ // Some examples in documentation suggest that there might be a comma in
+ // between, however we strictly adhere to the single statement definition.
+ ASSIGN_OR_RETURN(IDResult, readInt());
+ consumeOptionalType(Kind::Comma);
+
+ std::vector<StringRef> Strings;
+ ASSIGN_OR_RETURN(StrResult, readString());
+ Strings.push_back(*StrResult);
+ while (isNextTokenKind(Kind::String))
+ Strings.push_back(read().value());
+
+ Table->addStrings(*IDResult, std::move(Strings));
+ }
+
+ return std::move(Table);
+}
+
+Expected<std::unique_ptr<VersionInfoBlock>>
+RCParser::parseVersionInfoBlockContents(StringRef BlockName) {
+ RETURN_IF_ERROR(consumeType(Kind::BlockBegin));
+
+ auto Contents = std::make_unique<VersionInfoBlock>(BlockName);
+
+ while (!isNextTokenKind(Kind::BlockEnd)) {
+ ASSIGN_OR_RETURN(Stmt, parseVersionInfoStmt());
+ Contents->addStmt(std::move(*Stmt));
+ }
+
+ consume(); // Consume BlockEnd.
+
+ return std::move(Contents);
+}
+
+Expected<std::unique_ptr<VersionInfoStmt>> RCParser::parseVersionInfoStmt() {
+ // Expect either BLOCK or VALUE, then a name or a key (a string).
+ ASSIGN_OR_RETURN(TypeResult, readIdentifier());
+
+ if (TypeResult->equals_lower("BLOCK")) {
+ ASSIGN_OR_RETURN(NameResult, readString());
+ return parseVersionInfoBlockContents(*NameResult);
+ }
+
+ if (TypeResult->equals_lower("VALUE")) {
+ ASSIGN_OR_RETURN(KeyResult, readString());
+ // Read a non-empty list of strings and/or ints, each
+ // possibly preceded by a comma. Unfortunately, the tool behavior depends
+ // on them existing or not, so we need to memorize where we found them.
+ std::vector<IntOrString> Values;
+ std::vector<bool> PrecedingCommas;
+ RETURN_IF_ERROR(consumeType(Kind::Comma));
+ while (!isNextTokenKind(Kind::Identifier) &&
+ !isNextTokenKind(Kind::BlockEnd)) {
+ // Try to eat a comma if it's not the first statement.
+ bool HadComma = Values.size() > 0 && consumeOptionalType(Kind::Comma);
+ ASSIGN_OR_RETURN(ValueResult, readIntOrString());
+ Values.push_back(*ValueResult);
+ PrecedingCommas.push_back(HadComma);
+ }
+ return std::make_unique<VersionInfoValue>(*KeyResult, std::move(Values),
+ std::move(PrecedingCommas));
+ }
+
+ return getExpectedError("BLOCK or VALUE", true);
+}
+
+Expected<VersionInfoResource::VersionInfoFixed>
+RCParser::parseVersionInfoFixed() {
+ using RetType = VersionInfoResource::VersionInfoFixed;
+ RetType Result;
+
+ // Read until the beginning of the block.
+ while (!isNextTokenKind(Kind::BlockBegin)) {
+ ASSIGN_OR_RETURN(TypeResult, readIdentifier());
+ auto FixedType = RetType::getFixedType(*TypeResult);
+
+ if (!RetType::isTypeSupported(FixedType))
+ return getExpectedError("fixed VERSIONINFO statement type", true);
+ if (Result.IsTypePresent[FixedType])
+ return getExpectedError("yet unread fixed VERSIONINFO statement type",
+ true);
+
+ // VERSION variations take multiple integers.
+ size_t NumInts = RetType::isVersionType(FixedType) ? 4 : 1;
ASSIGN_OR_RETURN(ArgsResult, readIntsWithCommas(1, NumInts));
- SmallVector<uint32_t, 4> ArgInts(ArgsResult->begin(), ArgsResult->end());
+ SmallVector<uint32_t, 4> ArgInts(ArgsResult->begin(), ArgsResult->end());
while (ArgInts.size() < NumInts)
ArgInts.push_back(0);
- Result.setValue(FixedType, ArgInts);
- }
-
- return Result;
-}
-
-RCParser::ParseOptionType RCParser::parseLanguageStmt() {
- ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 2, /* max = */ 2));
- return std::make_unique<LanguageResource>((*Args)[0], (*Args)[1]);
-}
-
-RCParser::ParseOptionType RCParser::parseCharacteristicsStmt() {
- ASSIGN_OR_RETURN(Arg, readInt());
- return std::make_unique<CharacteristicsStmt>(*Arg);
-}
-
-RCParser::ParseOptionType RCParser::parseVersionStmt() {
- ASSIGN_OR_RETURN(Arg, readInt());
- return std::make_unique<VersionStmt>(*Arg);
-}
-
-RCParser::ParseOptionType RCParser::parseCaptionStmt() {
- ASSIGN_OR_RETURN(Arg, readString());
- return std::make_unique<CaptionStmt>(*Arg);
-}
-
-RCParser::ParseOptionType RCParser::parseClassStmt() {
- ASSIGN_OR_RETURN(Arg, readIntOrString());
- return std::make_unique<ClassStmt>(*Arg);
-}
-
-RCParser::ParseOptionType RCParser::parseFontStmt(OptStmtType DialogType) {
- assert(DialogType != OptStmtType::BasicStmt);
-
- ASSIGN_OR_RETURN(SizeResult, readInt());
- RETURN_IF_ERROR(consumeType(Kind::Comma));
- ASSIGN_OR_RETURN(NameResult, readString());
-
- // Default values for the optional arguments.
- uint32_t FontWeight = 0;
- bool FontItalic = false;
- uint32_t FontCharset = 1;
- if (DialogType == OptStmtType::DialogExStmt) {
- if (consumeOptionalType(Kind::Comma)) {
- ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 0, /* max = */ 3));
- if (Args->size() >= 1)
- FontWeight = (*Args)[0];
- if (Args->size() >= 2)
- FontItalic = (*Args)[1] != 0;
- if (Args->size() >= 3)
- FontCharset = (*Args)[2];
- }
- }
- return std::make_unique<FontStmt>(*SizeResult, *NameResult, FontWeight,
- FontItalic, FontCharset);
-}
-
-RCParser::ParseOptionType RCParser::parseStyleStmt() {
- ASSIGN_OR_RETURN(Arg, readInt());
- return std::make_unique<StyleStmt>(*Arg);
-}
-
-RCParser::ParseOptionType RCParser::parseExStyleStmt() {
- ASSIGN_OR_RETURN(Arg, readInt());
- return std::make_unique<ExStyleStmt>(*Arg);
-}
-
-Error RCParser::getExpectedError(const Twine &Message, bool IsAlreadyRead) {
- return make_error<ParserError>(
- Message, IsAlreadyRead ? std::prev(CurLoc) : CurLoc, End);
-}
-
-} // namespace rc
-} // namespace llvm
+ Result.setValue(FixedType, ArgInts);
+ }
+
+ return Result;
+}
+
+RCParser::ParseOptionType RCParser::parseLanguageStmt() {
+ ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 2, /* max = */ 2));
+ return std::make_unique<LanguageResource>((*Args)[0], (*Args)[1]);
+}
+
+RCParser::ParseOptionType RCParser::parseCharacteristicsStmt() {
+ ASSIGN_OR_RETURN(Arg, readInt());
+ return std::make_unique<CharacteristicsStmt>(*Arg);
+}
+
+RCParser::ParseOptionType RCParser::parseVersionStmt() {
+ ASSIGN_OR_RETURN(Arg, readInt());
+ return std::make_unique<VersionStmt>(*Arg);
+}
+
+RCParser::ParseOptionType RCParser::parseCaptionStmt() {
+ ASSIGN_OR_RETURN(Arg, readString());
+ return std::make_unique<CaptionStmt>(*Arg);
+}
+
+RCParser::ParseOptionType RCParser::parseClassStmt() {
+ ASSIGN_OR_RETURN(Arg, readIntOrString());
+ return std::make_unique<ClassStmt>(*Arg);
+}
+
+RCParser::ParseOptionType RCParser::parseFontStmt(OptStmtType DialogType) {
+ assert(DialogType != OptStmtType::BasicStmt);
+
+ ASSIGN_OR_RETURN(SizeResult, readInt());
+ RETURN_IF_ERROR(consumeType(Kind::Comma));
+ ASSIGN_OR_RETURN(NameResult, readString());
+
+ // Default values for the optional arguments.
+ uint32_t FontWeight = 0;
+ bool FontItalic = false;
+ uint32_t FontCharset = 1;
+ if (DialogType == OptStmtType::DialogExStmt) {
+ if (consumeOptionalType(Kind::Comma)) {
+ ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 0, /* max = */ 3));
+ if (Args->size() >= 1)
+ FontWeight = (*Args)[0];
+ if (Args->size() >= 2)
+ FontItalic = (*Args)[1] != 0;
+ if (Args->size() >= 3)
+ FontCharset = (*Args)[2];
+ }
+ }
+ return std::make_unique<FontStmt>(*SizeResult, *NameResult, FontWeight,
+ FontItalic, FontCharset);
+}
+
+RCParser::ParseOptionType RCParser::parseStyleStmt() {
+ ASSIGN_OR_RETURN(Arg, readInt());
+ return std::make_unique<StyleStmt>(*Arg);
+}
+
+RCParser::ParseOptionType RCParser::parseExStyleStmt() {
+ ASSIGN_OR_RETURN(Arg, readInt());
+ return std::make_unique<ExStyleStmt>(*Arg);
+}
+
+Error RCParser::getExpectedError(const Twine &Message, bool IsAlreadyRead) {
+ return make_error<ParserError>(
+ Message, IsAlreadyRead ? std::prev(CurLoc) : CurLoc, End);
+}
+
+} // namespace rc
+} // namespace llvm