//===-- 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 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 ? "" : CurLoc->value()).str(); } char RCParser::ParserError::ID = 0; RCParser::RCParser(std::vector 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(); (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: ( ) + - | & ~ 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 RCParser::readInt() { ASSIGN_OR_RETURN(Value, parseIntExpr1()); return (*Value).getValue(); } Expected 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 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 RCParser::readString() { if (!isNextTokenKind(Kind::String)) return getExpectedError("string"); return read().value(); } Expected RCParser::readFilename() { if (!isNextTokenKind(Kind::String) && !isNextTokenKind(Kind::Identifier)) return getExpectedError("string"); return read().value(); } Expected RCParser::readIdentifier() { if (!isNextTokenKind(Kind::Identifier)) return getExpectedError("identifier"); return read().value(); } Expected RCParser::readIntOrString() { if (!isNextTokenKind(Kind::Int) && !isNextTokenKind(Kind::String)) return getExpectedError("int or string"); return IntOrString(read()); } Expected 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> RCParser::readIntsWithCommas(size_t MinCount, size_t MaxCount) { assert(MinCount <= MaxCount); SmallVector Result; auto FailureHandler = [&](llvm::Error Err) -> Expected> { 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 RCParser::parseFlags(ArrayRef FlagDesc, ArrayRef 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 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> 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 = llvm::make_unique( 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 llvm::make_unique(*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 = llvm::make_unique( (*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 llvm::make_unique(Type, read().value(), MemoryFlags); default: break; } RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); std::vector 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 llvm::make_unique(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 llvm::make_unique( std::move(**BlockResult), std::move(*FixedResult), MemoryFlags); } Expected 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 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 ExStyle; if (consumeOptionalType(Kind::Comma)) { ASSIGN_OR_RETURN(Val, readInt()); ExStyle = *Val; } Optional 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 llvm::make_unique(*Arg, MemoryFlags); } RCParser::ParseType RCParser::parseIconResource() { uint16_t MemoryFlags = parseMemoryFlags(IconResource::getDefaultMemoryFlags()); ASSIGN_OR_RETURN(Arg, readFilename()); return llvm::make_unique(*Arg, MemoryFlags); } RCParser::ParseType RCParser::parseHTMLResource() { uint16_t MemoryFlags = parseMemoryFlags(HTMLResource::getDefaultMemoryFlags()); ASSIGN_OR_RETURN(Arg, readFilename()); return llvm::make_unique(*Arg, MemoryFlags); } RCParser::ParseType RCParser::parseMenuResource() { uint16_t MemoryFlags = parseMemoryFlags(MenuResource::getDefaultMemoryFlags()); ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements()); ASSIGN_OR_RETURN(Items, parseMenuItemsList()); return llvm::make_unique(std::move(*OptStatements), std::move(*Items), MemoryFlags); } Expected 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(llvm::make_unique()); 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(llvm::make_unique( *CaptionResult, *FlagsResult, std::move(*SubMenuResult))); continue; } assert(IsMenuItem); List.addDefinition( llvm::make_unique(*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 = llvm::make_unique(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); ASSIGN_OR_RETURN(StrResult, readString()); Table->addString(*IDResult, *StrResult); } return std::move(Table); } Expected> RCParser::parseVersionInfoBlockContents(StringRef BlockName) { RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); auto Contents = llvm::make_unique(BlockName); while (!isNextTokenKind(Kind::BlockEnd)) { ASSIGN_OR_RETURN(Stmt, parseVersionInfoStmt()); Contents->addStmt(std::move(*Stmt)); } consume(); // Consume BlockEnd. return std::move(Contents); } Expected> 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 Values; std::vector 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 llvm::make_unique(*KeyResult, std::move(Values), std::move(PrecedingCommas)); } return getExpectedError("BLOCK or VALUE", true); } Expected 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(NumInts, NumInts)); SmallVector ArgInts(ArgsResult->begin(), ArgsResult->end()); Result.setValue(FixedType, ArgInts); } return Result; } RCParser::ParseOptionType RCParser::parseLanguageStmt() { ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 2, /* max = */ 2)); return llvm::make_unique((*Args)[0], (*Args)[1]); } RCParser::ParseOptionType RCParser::parseCharacteristicsStmt() { ASSIGN_OR_RETURN(Arg, readInt()); return llvm::make_unique(*Arg); } RCParser::ParseOptionType RCParser::parseVersionStmt() { ASSIGN_OR_RETURN(Arg, readInt()); return llvm::make_unique(*Arg); } RCParser::ParseOptionType RCParser::parseCaptionStmt() { ASSIGN_OR_RETURN(Arg, readString()); return llvm::make_unique(*Arg); } RCParser::ParseOptionType RCParser::parseClassStmt() { ASSIGN_OR_RETURN(Arg, readIntOrString()); return llvm::make_unique(*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 llvm::make_unique(*SizeResult, *NameResult, FontWeight, FontItalic, FontCharset); } RCParser::ParseOptionType RCParser::parseStyleStmt() { ASSIGN_OR_RETURN(Arg, readInt()); return llvm::make_unique(*Arg); } RCParser::ParseOptionType RCParser::parseExStyleStmt() { ASSIGN_OR_RETURN(Arg, readInt()); return llvm::make_unique(*Arg); } Error RCParser::getExpectedError(const Twine &Message, bool IsAlreadyRead) { return make_error( Message, IsAlreadyRead ? std::prev(CurLoc) : CurLoc, End); } } // namespace rc } // namespace llvm