forked from OSchip/llvm-project
				
			
		
			
				
	
	
		
			850 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			850 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===-- 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);
 | |
|     ASSIGN_OR_RETURN(StrResult, readString());
 | |
|     Table->addString(*IDResult, *StrResult);
 | |
|   }
 | |
| 
 | |
|   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(NumInts, NumInts));
 | |
|     SmallVector<uint32_t, 4> 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 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
 |