1009 lines · cpp
1//===-- ResourceScriptParser.cpp --------------------------------*- C++-*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===---------------------------------------------------------------------===//8//9// This implements the parser defined in ResourceScriptParser.h.10//11//===---------------------------------------------------------------------===//12 13#include "ResourceScriptParser.h"14#include "llvm/ADT/StringExtras.h"15#include "llvm/Option/ArgList.h"16#include "llvm/Support/FileSystem.h"17#include "llvm/Support/Path.h"18#include "llvm/Support/Process.h"19 20// Take an expression returning llvm::Error and forward the error if it exists.21#define RETURN_IF_ERROR(Expr) \22 if (auto Err = (Expr)) \23 return std::move(Err);24 25// Take an expression returning llvm::Expected<T> and assign it to Var or26// forward the error out of the function.27#define ASSIGN_OR_RETURN(Var, Expr) \28 auto Var = (Expr); \29 if (!Var) \30 return Var.takeError();31 32namespace llvm {33namespace rc {34 35RCParser::ParserError::ParserError(const Twine &Expected, const LocIter CurLoc,36 const LocIter End)37 : ErrorLoc(CurLoc), FileEnd(End) {38 CurMessage = "Error parsing file: expected " + Expected.str() + ", got " +39 (CurLoc == End ? "<EOF>" : CurLoc->value()).str();40}41 42char RCParser::ParserError::ID = 0;43 44RCParser::RCParser(std::vector<RCToken> TokenList)45 : Tokens(std::move(TokenList)), CurLoc(Tokens.begin()), End(Tokens.end()) {}46 47bool RCParser::isEof() const { return CurLoc == End; }48 49RCParser::ParseType RCParser::parseSingleResource() {50 // The first thing we read is usually a resource's name. However, in some51 // cases (LANGUAGE and STRINGTABLE) the resources don't have their names52 // and the first token to be read is the type.53 ASSIGN_OR_RETURN(NameToken, readTypeOrName());54 55 if (NameToken->equalsLower("LANGUAGE"))56 return parseLanguageResource();57 else if (NameToken->equalsLower("STRINGTABLE"))58 return parseStringTableResource();59 60 // If it's not an unnamed resource, what we've just read is a name. Now,61 // read resource type;62 ASSIGN_OR_RETURN(TypeToken, readTypeOrName());63 64 ParseType Result = std::unique_ptr<RCResource>();65 (void)!Result;66 67 if (TypeToken->equalsLower("ACCELERATORS"))68 Result = parseAcceleratorsResource();69 else if (TypeToken->equalsLower("BITMAP"))70 Result = parseBitmapResource();71 else if (TypeToken->equalsLower("CURSOR"))72 Result = parseCursorResource();73 else if (TypeToken->equalsLower("DIALOG"))74 Result = parseDialogResource(false);75 else if (TypeToken->equalsLower("DIALOGEX"))76 Result = parseDialogResource(true);77 else if (TypeToken->equalsLower("HTML"))78 Result = parseHTMLResource();79 else if (TypeToken->equalsLower("ICON"))80 Result = parseIconResource();81 else if (TypeToken->equalsLower("MENU"))82 Result = parseMenuResource();83 else if (TypeToken->equalsLower("MENUEX"))84 Result = parseMenuExResource();85 else if (TypeToken->equalsLower("RCDATA"))86 Result = parseUserDefinedResource(RkRcData);87 else if (TypeToken->equalsLower("VERSIONINFO"))88 Result = parseVersionInfoResource();89 else90 Result = parseUserDefinedResource(*TypeToken);91 92 if (Result)93 (*Result)->setName(*NameToken);94 95 return Result;96}97 98bool RCParser::isNextTokenKind(Kind TokenKind) const {99 return !isEof() && look().kind() == TokenKind;100}101 102const RCToken &RCParser::look() const {103 assert(!isEof());104 return *CurLoc;105}106 107const RCToken &RCParser::read() {108 assert(!isEof());109 return *CurLoc++;110}111 112void RCParser::consume() {113 assert(!isEof());114 CurLoc++;115}116 117// An integer description might consist of a single integer or118// an arithmetic expression evaluating to the integer. The expressions119// can contain the following tokens: <int> ( ) + - | & ~ not. Their meaning120// is the same as in C++ except for 'not' expression.121// The operators in the original RC implementation have the following122// precedence:123// 1) Unary operators (- ~ not),124// 2) Binary operators (+ - & |), with no precedence.125//126// 'not' expression is mostly useful for style values. It evaluates to 0,127// but value given to the operator is stored separately from integer value.128// It's mostly useful for control style expressions and causes bits from129// default control style to be excluded from generated style. For binary130// operators the mask from the right operand is applied to the left operand131// and masks from both operands are combined in operator result.132//133// The following grammar is used to parse the expressions Exp1:134// Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2135// Exp2 ::= Exp3 || Exp3 * Exp3 || Exp3 / Exp3136// Exp3 ::= -Exp3 || ~Exp3 || not Expr3 || Int || (Exp1)137// (More conveniently, Exp1 and Exp2 are non-empty sequences of Exp3138// expressions, separated by binary operators.)139//140// Expressions of type Exp1 are read by parseIntExpr1(Inner) method, Exp2141// is read by parseIntExpr2() and Exp3 is read by parseIntExpr3().142//143// The original Microsoft tool handles multiple unary operators incorrectly.144// For example, in 16-bit little-endian integers:145// 1 => 01 00, -1 => ff ff, --1 => ff ff, ---1 => 01 00;146// 1 => 01 00, ~1 => fe ff, ~~1 => fd ff, ~~~1 => fc ff.147// Our implementation differs from the original one and handles these148// operators correctly:149// 1 => 01 00, -1 => ff ff, --1 => 01 00, ---1 => ff ff;150// 1 => 01 00, ~1 => fe ff, ~~1 => 01 00, ~~~1 => fe ff.151 152Expected<RCInt> RCParser::readInt() {153 ASSIGN_OR_RETURN(Value, parseIntExpr1());154 return (*Value).getValue();155}156 157Expected<IntWithNotMask> RCParser::parseIntExpr1() {158 // Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2.159 ASSIGN_OR_RETURN(FirstResult, parseIntExpr2());160 IntWithNotMask Result = *FirstResult;161 162 while (!isEof() && look().isLowPrecedenceBinaryOp()) {163 auto OpToken = read();164 ASSIGN_OR_RETURN(NextResult, parseIntExpr2());165 166 switch (OpToken.kind()) {167 case Kind::Plus:168 Result += *NextResult;169 break;170 171 case Kind::Minus:172 Result -= *NextResult;173 break;174 175 case Kind::Pipe:176 Result |= *NextResult;177 break;178 179 case Kind::Amp:180 Result &= *NextResult;181 break;182 183 default:184 llvm_unreachable("Already processed all low precedence binary ops.");185 }186 }187 188 return Result;189}190 191Expected<IntWithNotMask> RCParser::parseIntExpr2() {192 // Exp2 ::= Exp3 || Exp3 * Exp3 || Exp3 / Exp3.193 ASSIGN_OR_RETURN(FirstResult, parseIntExpr3());194 IntWithNotMask Result = *FirstResult;195 196 while (!isEof() && look().isHighPrecedenceBinaryOp()) {197 auto OpToken = read();198 ASSIGN_OR_RETURN(NextResult, parseIntExpr3());199 200 switch (OpToken.kind()) {201 case Kind::Asterisk:202 Result *= *NextResult;203 break;204 205 case Kind::Slash:206 Result /= *NextResult;207 break;208 209 default:210 llvm_unreachable("Already processed all high precedence binary ops.");211 }212 }213 214 return Result;215}216 217Expected<IntWithNotMask> RCParser::parseIntExpr3() {218 // Exp3 ::= -Exp3 || ~Exp3 || not Expr3 || Int || (Exp1).219 static const char ErrorMsg[] = "'-', '~', integer or '('";220 221 if (isEof())222 return getExpectedError(ErrorMsg);223 224 switch (look().kind()) {225 case Kind::Minus: {226 consume();227 ASSIGN_OR_RETURN(Result, parseIntExpr3());228 return -(*Result);229 }230 231 case Kind::Tilde: {232 consume();233 ASSIGN_OR_RETURN(Result, parseIntExpr3());234 return ~(*Result);235 }236 237 case Kind::Int:238 return RCInt(read());239 240 case Kind::LeftParen: {241 consume();242 ASSIGN_OR_RETURN(Result, parseIntExpr1());243 RETURN_IF_ERROR(consumeType(Kind::RightParen));244 return *Result;245 }246 247 case Kind::Identifier: {248 if (!read().value().equals_insensitive("not"))249 return getExpectedError(ErrorMsg, true);250 ASSIGN_OR_RETURN(Result, parseIntExpr3());251 return IntWithNotMask(0, (*Result).getValue());252 }253 254 default:255 return getExpectedError(ErrorMsg);256 }257}258 259Expected<StringRef> RCParser::readString() {260 if (!isNextTokenKind(Kind::String))261 return getExpectedError("string");262 return read().value();263}264 265Expected<StringRef> RCParser::readFilename() {266 if (!isNextTokenKind(Kind::String) && !isNextTokenKind(Kind::Identifier))267 return getExpectedError("string");268 const RCToken &Token = read();269 StringRef Str = Token.value();270 if (Token.kind() != Kind::String)271 return Str;272 while (isNextTokenKind(Kind::String)) {273 const RCToken &NextToken = read();274 StringRef Next = NextToken.value();275 bool IsWide = Str.consume_front_insensitive("L");276 Next.consume_front_insensitive("L");277 bool StrUnquoted = Str.consume_front("\"") && Str.consume_back("\"");278 bool NextUnquoted = Next.consume_front("\"") && Next.consume_back("\"");279 assert(StrUnquoted && NextUnquoted);280 (void)StrUnquoted;281 (void)NextUnquoted;282 283 Str = Saver.save(Twine(IsWide ? "L" : "") + "\"" + Str + Next + "\"");284 }285 return Str;286}287 288Expected<StringRef> RCParser::readIdentifier() {289 if (!isNextTokenKind(Kind::Identifier))290 return getExpectedError("identifier");291 return read().value();292}293 294Expected<IntOrString> RCParser::readIntOrString() {295 if (!isNextTokenKind(Kind::Int) && !isNextTokenKind(Kind::String))296 return getExpectedError("int or string");297 return IntOrString(read());298}299 300Expected<IntOrString> RCParser::readTypeOrName() {301 // We suggest that the correct resource name or type should be either an302 // identifier or an integer. The original RC tool is much more liberal.303 if (!isNextTokenKind(Kind::Identifier) && !isNextTokenKind(Kind::Int))304 return getExpectedError("int or identifier");305 return IntOrString(read());306}307 308Error RCParser::consumeType(Kind TokenKind) {309 if (isNextTokenKind(TokenKind)) {310 consume();311 return Error::success();312 }313 314 switch (TokenKind) {315#define TOKEN(TokenName) \316 case Kind::TokenName: \317 return getExpectedError(#TokenName);318#define SHORT_TOKEN(TokenName, TokenCh) \319 case Kind::TokenName: \320 return getExpectedError(#TokenCh);321#include "ResourceScriptTokenList.def"322 }323 324 llvm_unreachable("All case options exhausted.");325}326 327bool RCParser::consumeOptionalType(Kind TokenKind) {328 if (isNextTokenKind(TokenKind)) {329 consume();330 return true;331 }332 333 return false;334}335 336Expected<SmallVector<RCInt, 8>> RCParser::readIntsWithCommas(size_t MinCount,337 size_t MaxCount) {338 assert(MinCount <= MaxCount);339 340 SmallVector<RCInt, 8> Result;341 342 auto FailureHandler =343 [&](llvm::Error Err) -> Expected<SmallVector<RCInt, 8>> {344 if (Result.size() < MinCount)345 return std::move(Err);346 consumeError(std::move(Err));347 return Result;348 };349 350 for (size_t i = 0; i < MaxCount; ++i) {351 // Try to read a comma unless we read the first token.352 // Sometimes RC tool requires them and sometimes not. We decide to353 // always require them.354 if (i >= 1) {355 if (auto CommaError = consumeType(Kind::Comma))356 return FailureHandler(std::move(CommaError));357 }358 359 if (auto IntResult = readInt())360 Result.push_back(*IntResult);361 else362 return FailureHandler(IntResult.takeError());363 }364 365 return std::move(Result);366}367 368Expected<uint32_t> RCParser::parseFlags(ArrayRef<StringRef> FlagDesc,369 ArrayRef<uint32_t> FlagValues) {370 assert(!FlagDesc.empty());371 assert(FlagDesc.size() == FlagValues.size());372 373 uint32_t Result = 0;374 while (isNextTokenKind(Kind::Comma)) {375 consume();376 ASSIGN_OR_RETURN(FlagResult, readIdentifier());377 bool FoundFlag = false;378 379 for (size_t FlagId = 0; FlagId < FlagDesc.size(); ++FlagId) {380 if (!FlagResult->equals_insensitive(FlagDesc[FlagId]))381 continue;382 383 Result |= FlagValues[FlagId];384 FoundFlag = true;385 break;386 }387 388 if (!FoundFlag)389 return getExpectedError(join(FlagDesc, "/"), true);390 }391 392 return Result;393}394 395uint16_t RCParser::parseMemoryFlags(uint16_t Flags) {396 while (!isEof()) {397 const RCToken &Token = look();398 if (Token.kind() != Kind::Identifier)399 return Flags;400 const StringRef Ident = Token.value();401 if (Ident.equals_insensitive("PRELOAD"))402 Flags |= MfPreload;403 else if (Ident.equals_insensitive("LOADONCALL"))404 Flags &= ~MfPreload;405 else if (Ident.equals_insensitive("FIXED"))406 Flags &= ~(MfMoveable | MfDiscardable);407 else if (Ident.equals_insensitive("MOVEABLE"))408 Flags |= MfMoveable;409 else if (Ident.equals_insensitive("DISCARDABLE"))410 Flags |= MfDiscardable | MfMoveable | MfPure;411 else if (Ident.equals_insensitive("PURE"))412 Flags |= MfPure;413 else if (Ident.equals_insensitive("IMPURE"))414 Flags &= ~(MfPure | MfDiscardable);415 else if (Ident.equals_insensitive("SHARED"))416 Flags |= MfPure;417 else if (Ident.equals_insensitive("NONSHARED"))418 Flags &= ~(MfPure | MfDiscardable);419 else420 return Flags;421 consume();422 }423 return Flags;424}425 426Expected<OptionalStmtList>427RCParser::parseOptionalStatements(OptStmtType StmtsType) {428 OptionalStmtList Result;429 430 // The last statement is always followed by the start of the block.431 while (!isNextTokenKind(Kind::BlockBegin)) {432 ASSIGN_OR_RETURN(SingleParse, parseSingleOptionalStatement(StmtsType));433 Result.addStmt(std::move(*SingleParse));434 }435 436 return std::move(Result);437}438 439Expected<std::unique_ptr<OptionalStmt>>440RCParser::parseSingleOptionalStatement(OptStmtType StmtsType) {441 ASSIGN_OR_RETURN(TypeToken, readIdentifier());442 if (TypeToken->equals_insensitive("CHARACTERISTICS"))443 return parseCharacteristicsStmt();444 if (TypeToken->equals_insensitive("LANGUAGE"))445 return parseLanguageStmt();446 if (TypeToken->equals_insensitive("VERSION"))447 return parseVersionStmt();448 449 if (StmtsType != OptStmtType::BasicStmt) {450 if (TypeToken->equals_insensitive("CAPTION"))451 return parseCaptionStmt();452 if (TypeToken->equals_insensitive("CLASS"))453 return parseClassStmt();454 if (TypeToken->equals_insensitive("EXSTYLE"))455 return parseExStyleStmt();456 if (TypeToken->equals_insensitive("FONT"))457 return parseFontStmt(StmtsType);458 if (TypeToken->equals_insensitive("STYLE"))459 return parseStyleStmt();460 if (TypeToken->equals_insensitive("MENU"))461 return parseMenuStmt();462 }463 464 return getExpectedError("optional statement type, BEGIN or '{'",465 /* IsAlreadyRead = */ true);466}467 468RCParser::ParseType RCParser::parseLanguageResource() {469 // Read LANGUAGE as an optional statement. If it's read correctly, we can470 // upcast it to RCResource.471 return parseLanguageStmt();472}473 474RCParser::ParseType RCParser::parseAcceleratorsResource() {475 uint16_t MemoryFlags =476 parseMemoryFlags(AcceleratorsResource::getDefaultMemoryFlags());477 ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());478 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));479 480 auto Accels = std::make_unique<AcceleratorsResource>(481 std::move(*OptStatements), MemoryFlags);482 483 while (!consumeOptionalType(Kind::BlockEnd)) {484 ASSIGN_OR_RETURN(EventResult, readIntOrString());485 RETURN_IF_ERROR(consumeType(Kind::Comma));486 ASSIGN_OR_RETURN(IDResult, readInt());487 ASSIGN_OR_RETURN(488 FlagsResult,489 parseFlags(AcceleratorsResource::Accelerator::OptionsStr,490 AcceleratorsResource::Accelerator::OptionsFlags));491 Accels->addAccelerator(*EventResult, *IDResult, *FlagsResult);492 }493 494 return std::move(Accels);495}496 497RCParser::ParseType RCParser::parseCursorResource() {498 uint16_t MemoryFlags =499 parseMemoryFlags(CursorResource::getDefaultMemoryFlags());500 ASSIGN_OR_RETURN(Arg, readFilename());501 return std::make_unique<CursorResource>(*Arg, MemoryFlags);502}503 504RCParser::ParseType RCParser::parseDialogResource(bool IsExtended) {505 uint16_t MemoryFlags =506 parseMemoryFlags(DialogResource::getDefaultMemoryFlags());507 // Dialog resources have the following format of the arguments:508 // DIALOG: x, y, width, height [opt stmts...] {controls...}509 // DIALOGEX: x, y, width, height [, helpID] [opt stmts...] {controls...}510 // These are very similar, so we parse them together.511 ASSIGN_OR_RETURN(LocResult, readIntsWithCommas(4, 4));512 513 uint32_t HelpID = 0; // When HelpID is unset, it's assumed to be 0.514 if (IsExtended && consumeOptionalType(Kind::Comma)) {515 ASSIGN_OR_RETURN(HelpIDResult, readInt());516 HelpID = *HelpIDResult;517 }518 519 ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements(520 IsExtended ? OptStmtType::DialogExStmt521 : OptStmtType::DialogStmt));522 523 assert(isNextTokenKind(Kind::BlockBegin) &&524 "parseOptionalStatements, when successful, halts on BlockBegin.");525 consume();526 527 auto Dialog = std::make_unique<DialogResource>(528 (*LocResult)[0], (*LocResult)[1], (*LocResult)[2], (*LocResult)[3],529 HelpID, std::move(*OptStatements), IsExtended, MemoryFlags);530 531 while (!consumeOptionalType(Kind::BlockEnd)) {532 ASSIGN_OR_RETURN(ControlDefResult, parseControl());533 Dialog->addControl(std::move(*ControlDefResult));534 }535 536 return std::move(Dialog);537}538 539RCParser::ParseType RCParser::parseUserDefinedResource(IntOrString Type) {540 uint16_t MemoryFlags =541 parseMemoryFlags(UserDefinedResource::getDefaultMemoryFlags());542 if (isEof())543 return getExpectedError("filename, '{' or BEGIN");544 545 // Check if this is a file resource.546 switch (look().kind()) {547 case Kind::String:548 case Kind::Identifier: {549 ASSIGN_OR_RETURN(Filename, readFilename());550 return std::make_unique<UserDefinedResource>(Type, *Filename, MemoryFlags);551 }552 default:553 break;554 }555 556 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));557 std::vector<IntOrString> Data;558 559 while (!consumeOptionalType(Kind::BlockEnd)) {560 ASSIGN_OR_RETURN(Item, readIntOrString());561 Data.push_back(*Item);562 563 // There can be zero or more commas after each token (but not before564 // the first one).565 while (consumeOptionalType(Kind::Comma)) {566 }567 }568 569 return std::make_unique<UserDefinedResource>(Type, std::move(Data),570 MemoryFlags);571}572 573RCParser::ParseType RCParser::parseVersionInfoResource() {574 uint16_t MemoryFlags =575 parseMemoryFlags(VersionInfoResource::getDefaultMemoryFlags());576 ASSIGN_OR_RETURN(FixedResult, parseVersionInfoFixed());577 ASSIGN_OR_RETURN(BlockResult, parseVersionInfoBlockContents(StringRef()));578 return std::make_unique<VersionInfoResource>(579 std::move(**BlockResult), std::move(*FixedResult), MemoryFlags);580}581 582Expected<Control> RCParser::parseControl() {583 // Each control definition (except CONTROL) follows one of the schemes below584 // depending on the control class:585 // [class] text, id, x, y, width, height [, style] [, exstyle] [, helpID]586 // [class] id, x, y, width, height [, style] [, exstyle] [, helpID]587 // Note that control ids must be integers.588 // Text might be either a string or an integer pointing to resource ID.589 ASSIGN_OR_RETURN(ClassResult, readIdentifier());590 std::string ClassUpper = ClassResult->upper();591 auto CtlInfo = Control::SupportedCtls.find(ClassUpper);592 if (CtlInfo == Control::SupportedCtls.end())593 return getExpectedError("control type, END or '}'", true);594 595 // Read caption if necessary.596 IntOrString Caption{StringRef()};597 if (CtlInfo->getValue().HasTitle) {598 ASSIGN_OR_RETURN(CaptionResult, readIntOrString());599 RETURN_IF_ERROR(consumeType(Kind::Comma));600 Caption = *CaptionResult;601 }602 603 ASSIGN_OR_RETURN(ID, readInt());604 RETURN_IF_ERROR(consumeType(Kind::Comma));605 606 IntOrString Class;607 std::optional<IntWithNotMask> Style;608 if (ClassUpper == "CONTROL") {609 // CONTROL text, id, class, style, x, y, width, height [, exstyle] [,610 // helpID]611 ASSIGN_OR_RETURN(ClassStr, readString());612 RETURN_IF_ERROR(consumeType(Kind::Comma));613 Class = *ClassStr;614 ASSIGN_OR_RETURN(StyleVal, parseIntExpr1());615 RETURN_IF_ERROR(consumeType(Kind::Comma));616 Style = *StyleVal;617 } else {618 Class = CtlInfo->getValue().CtlClass;619 }620 621 // x, y, width, height622 ASSIGN_OR_RETURN(Args, readIntsWithCommas(4, 4));623 624 if (ClassUpper != "CONTROL") {625 if (consumeOptionalType(Kind::Comma)) {626 ASSIGN_OR_RETURN(Val, parseIntExpr1());627 Style = *Val;628 }629 }630 631 std::optional<uint32_t> ExStyle;632 if (consumeOptionalType(Kind::Comma)) {633 ASSIGN_OR_RETURN(Val, readInt());634 ExStyle = *Val;635 }636 std::optional<uint32_t> HelpID;637 if (consumeOptionalType(Kind::Comma)) {638 ASSIGN_OR_RETURN(Val, readInt());639 HelpID = *Val;640 }641 642 return Control(*ClassResult, Caption, *ID, (*Args)[0], (*Args)[1], (*Args)[2],643 (*Args)[3], Style, ExStyle, HelpID, Class);644}645 646RCParser::ParseType RCParser::parseBitmapResource() {647 uint16_t MemoryFlags =648 parseMemoryFlags(BitmapResource::getDefaultMemoryFlags());649 ASSIGN_OR_RETURN(Arg, readFilename());650 return std::make_unique<BitmapResource>(*Arg, MemoryFlags);651}652 653RCParser::ParseType RCParser::parseIconResource() {654 uint16_t MemoryFlags =655 parseMemoryFlags(IconResource::getDefaultMemoryFlags());656 ASSIGN_OR_RETURN(Arg, readFilename());657 return std::make_unique<IconResource>(*Arg, MemoryFlags);658}659 660RCParser::ParseType RCParser::parseHTMLResource() {661 uint16_t MemoryFlags =662 parseMemoryFlags(HTMLResource::getDefaultMemoryFlags());663 ASSIGN_OR_RETURN(Arg, readFilename());664 return std::make_unique<HTMLResource>(*Arg, MemoryFlags);665}666 667RCParser::ParseType RCParser::parseMenuResource() {668 uint16_t MemoryFlags =669 parseMemoryFlags(MenuResource::getDefaultMemoryFlags());670 ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());671 ASSIGN_OR_RETURN(Items, parseMenuItemsList());672 return std::make_unique<MenuResource>(std::move(*OptStatements),673 std::move(*Items), MemoryFlags);674}675 676RCParser::ParseType RCParser::parseMenuExResource() {677 uint16_t MemoryFlags =678 parseMemoryFlags(MenuExResource::getDefaultMemoryFlags());679 ASSIGN_OR_RETURN(Items, parseMenuExItemsList());680 return std::make_unique<MenuExResource>(std::move(*Items), MemoryFlags);681}682 683Expected<MenuDefinitionList> RCParser::parseMenuItemsList() {684 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));685 686 MenuDefinitionList List;687 688 // Read a set of items. Each item is of one of three kinds:689 // MENUITEM SEPARATOR690 // MENUITEM caption:String, result:Int [, menu flags]...691 // POPUP caption:String [, menu flags]... { items... }692 while (!consumeOptionalType(Kind::BlockEnd)) {693 ASSIGN_OR_RETURN(ItemTypeResult, readIdentifier());694 695 bool IsMenuItem = ItemTypeResult->equals_insensitive("MENUITEM");696 bool IsPopup = ItemTypeResult->equals_insensitive("POPUP");697 if (!IsMenuItem && !IsPopup)698 return getExpectedError("MENUITEM, POPUP, END or '}'", true);699 700 if (IsMenuItem && isNextTokenKind(Kind::Identifier)) {701 // Now, expecting SEPARATOR.702 ASSIGN_OR_RETURN(SeparatorResult, readIdentifier());703 if (SeparatorResult->equals_insensitive("SEPARATOR")) {704 List.addDefinition(std::make_unique<MenuSeparator>());705 continue;706 }707 708 return getExpectedError("SEPARATOR or string", true);709 }710 711 // Not a separator. Read the caption.712 ASSIGN_OR_RETURN(CaptionResult, readString());713 714 // If MENUITEM, expect also a comma and an integer.715 uint32_t MenuResult = -1;716 717 if (IsMenuItem) {718 RETURN_IF_ERROR(consumeType(Kind::Comma));719 ASSIGN_OR_RETURN(IntResult, readInt());720 MenuResult = *IntResult;721 }722 723 ASSIGN_OR_RETURN(FlagsResult, parseFlags(MenuDefinition::OptionsStr,724 MenuDefinition::OptionsFlags));725 726 if (IsPopup) {727 // If POPUP, read submenu items recursively.728 ASSIGN_OR_RETURN(SubMenuResult, parseMenuItemsList());729 List.addDefinition(std::make_unique<PopupItem>(730 *CaptionResult, *FlagsResult, std::move(*SubMenuResult)));731 continue;732 }733 734 assert(IsMenuItem);735 List.addDefinition(736 std::make_unique<MenuItem>(*CaptionResult, MenuResult, *FlagsResult));737 }738 739 return std::move(List);740}741 742Expected<MenuDefinitionList> RCParser::parseMenuExItemsList() {743 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));744 745 MenuDefinitionList List;746 747 // Read a set of items. Each item is of one of two kinds:748 // MENUITEM caption:String [,[id][, [type][, state]]]]749 // POPUP caption:String [,[id][, [type][, [state][, helpID]]]] { popupBody }750 while (!consumeOptionalType(Kind::BlockEnd)) {751 ASSIGN_OR_RETURN(ItemTypeResult, readIdentifier());752 753 bool IsMenuItem = ItemTypeResult->equals_insensitive("MENUITEM");754 bool IsPopup = ItemTypeResult->equals_insensitive("POPUP");755 if (!IsMenuItem && !IsPopup)756 return getExpectedError("MENUITEM, POPUP, END or '}'", true);757 758 // Not a separator. Read the caption.759 ASSIGN_OR_RETURN(CaptionResult, readString());760 761 // If MENUITEM, expect [,[id][, [type][, state]]]]762 if (IsMenuItem) {763 uint32_t MenuId = 0;764 uint32_t MenuType = 0;765 uint32_t MenuState = 0;766 767 if (consumeOptionalType(Kind::Comma)) {768 auto IntId = readInt();769 if (IntId) {770 MenuId = *IntId;771 }772 if (consumeOptionalType(Kind::Comma)) {773 auto IntType = readInt();774 if (IntType) {775 MenuType = *IntType;776 }777 if (consumeOptionalType(Kind::Comma)) {778 auto IntState = readInt();779 if (IntState) {780 MenuState = *IntState;781 }782 }783 }784 }785 List.addDefinition(std::make_unique<MenuExItem>(*CaptionResult, MenuId,786 MenuType, MenuState));787 continue;788 }789 790 assert(IsPopup);791 792 uint32_t PopupId = 0;793 uint32_t PopupType = 0;794 uint32_t PopupState = 0;795 uint32_t PopupHelpID = 0;796 797 if (consumeOptionalType(Kind::Comma)) {798 auto IntId = readInt();799 if (IntId) {800 PopupId = *IntId;801 }802 if (consumeOptionalType(Kind::Comma)) {803 auto IntType = readInt();804 if (IntType) {805 PopupType = *IntType;806 }807 if (consumeOptionalType(Kind::Comma)) {808 auto IntState = readInt();809 if (IntState) {810 PopupState = *IntState;811 }812 if (consumeOptionalType(Kind::Comma)) {813 auto IntHelpID = readInt();814 if (IntHelpID) {815 PopupHelpID = *IntHelpID;816 }817 }818 }819 }820 }821 // If POPUP, read submenu items recursively.822 ASSIGN_OR_RETURN(SubMenuResult, parseMenuExItemsList());823 List.addDefinition(std::make_unique<PopupExItem>(824 *CaptionResult, PopupId, PopupType, PopupState, PopupHelpID,825 std::move(*SubMenuResult)));826 }827 828 return std::move(List);829}830 831RCParser::ParseType RCParser::parseStringTableResource() {832 uint16_t MemoryFlags =833 parseMemoryFlags(StringTableResource::getDefaultMemoryFlags());834 ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements());835 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));836 837 auto Table = std::make_unique<StringTableResource>(std::move(*OptStatements),838 MemoryFlags);839 840 // Read strings until we reach the end of the block.841 while (!consumeOptionalType(Kind::BlockEnd)) {842 // Each definition consists of string's ID (an integer) and a string.843 // Some examples in documentation suggest that there might be a comma in844 // between, however we strictly adhere to the single statement definition.845 ASSIGN_OR_RETURN(IDResult, readInt());846 consumeOptionalType(Kind::Comma);847 848 std::vector<StringRef> Strings;849 ASSIGN_OR_RETURN(StrResult, readString());850 Strings.push_back(*StrResult);851 while (isNextTokenKind(Kind::String))852 Strings.push_back(read().value());853 854 Table->addStrings(*IDResult, std::move(Strings));855 }856 857 return std::move(Table);858}859 860Expected<std::unique_ptr<VersionInfoBlock>>861RCParser::parseVersionInfoBlockContents(StringRef BlockName) {862 RETURN_IF_ERROR(consumeType(Kind::BlockBegin));863 864 auto Contents = std::make_unique<VersionInfoBlock>(BlockName);865 866 while (!isNextTokenKind(Kind::BlockEnd)) {867 ASSIGN_OR_RETURN(Stmt, parseVersionInfoStmt());868 Contents->addStmt(std::move(*Stmt));869 }870 871 consume(); // Consume BlockEnd.872 873 return std::move(Contents);874}875 876Expected<std::unique_ptr<VersionInfoStmt>> RCParser::parseVersionInfoStmt() {877 // Expect either BLOCK or VALUE, then a name or a key (a string).878 ASSIGN_OR_RETURN(TypeResult, readIdentifier());879 880 if (TypeResult->equals_insensitive("BLOCK")) {881 ASSIGN_OR_RETURN(NameResult, readString());882 return parseVersionInfoBlockContents(*NameResult);883 }884 885 if (TypeResult->equals_insensitive("VALUE")) {886 ASSIGN_OR_RETURN(KeyResult, readString());887 // Read a non-empty list of strings and/or ints, each888 // possibly preceded by a comma. Unfortunately, the tool behavior depends889 // on them existing or not, so we need to memorize where we found them.890 std::vector<IntOrString> Values;891 BitVector PrecedingCommas;892 RETURN_IF_ERROR(consumeType(Kind::Comma));893 while (!isNextTokenKind(Kind::Identifier) &&894 !isNextTokenKind(Kind::BlockEnd)) {895 // Try to eat a comma if it's not the first statement.896 bool HadComma = Values.size() > 0 && consumeOptionalType(Kind::Comma);897 ASSIGN_OR_RETURN(ValueResult, readIntOrString());898 Values.push_back(*ValueResult);899 PrecedingCommas.push_back(HadComma);900 }901 return std::make_unique<VersionInfoValue>(*KeyResult, std::move(Values),902 std::move(PrecedingCommas));903 }904 905 return getExpectedError("BLOCK or VALUE", true);906}907 908Expected<VersionInfoResource::VersionInfoFixed>909RCParser::parseVersionInfoFixed() {910 using RetType = VersionInfoResource::VersionInfoFixed;911 RetType Result;912 913 // Read until the beginning of the block.914 while (!isNextTokenKind(Kind::BlockBegin)) {915 ASSIGN_OR_RETURN(TypeResult, readIdentifier());916 auto FixedType = RetType::getFixedType(*TypeResult);917 918 if (!RetType::isTypeSupported(FixedType))919 return getExpectedError("fixed VERSIONINFO statement type", true);920 if (Result.IsTypePresent[FixedType])921 return getExpectedError("yet unread fixed VERSIONINFO statement type",922 true);923 924 // VERSION variations take multiple integers.925 size_t NumInts = RetType::isVersionType(FixedType) ? 4 : 1;926 ASSIGN_OR_RETURN(ArgsResult, readIntsWithCommas(1, NumInts));927 SmallVector<uint32_t, 4> ArgInts(ArgsResult->begin(), ArgsResult->end());928 while (ArgInts.size() < NumInts)929 ArgInts.push_back(0);930 Result.setValue(FixedType, ArgInts);931 }932 933 return Result;934}935 936RCParser::ParseOptionType RCParser::parseLanguageStmt() {937 ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 2, /* max = */ 2));938 return std::make_unique<LanguageResource>((*Args)[0], (*Args)[1]);939}940 941RCParser::ParseOptionType RCParser::parseCharacteristicsStmt() {942 ASSIGN_OR_RETURN(Arg, readInt());943 return std::make_unique<CharacteristicsStmt>(*Arg);944}945 946RCParser::ParseOptionType RCParser::parseVersionStmt() {947 ASSIGN_OR_RETURN(Arg, readInt());948 return std::make_unique<VersionStmt>(*Arg);949}950 951RCParser::ParseOptionType RCParser::parseCaptionStmt() {952 ASSIGN_OR_RETURN(Arg, readString());953 return std::make_unique<CaptionStmt>(*Arg);954}955 956RCParser::ParseOptionType RCParser::parseClassStmt() {957 ASSIGN_OR_RETURN(Arg, readIntOrString());958 return std::make_unique<ClassStmt>(*Arg);959}960 961RCParser::ParseOptionType RCParser::parseFontStmt(OptStmtType DialogType) {962 assert(DialogType != OptStmtType::BasicStmt);963 964 ASSIGN_OR_RETURN(SizeResult, readInt());965 RETURN_IF_ERROR(consumeType(Kind::Comma));966 ASSIGN_OR_RETURN(NameResult, readString());967 968 // Default values for the optional arguments.969 uint32_t FontWeight = 0;970 bool FontItalic = false;971 uint32_t FontCharset = 1;972 if (DialogType == OptStmtType::DialogExStmt) {973 if (consumeOptionalType(Kind::Comma)) {974 ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 0, /* max = */ 3));975 if (Args->size() >= 1)976 FontWeight = (*Args)[0];977 if (Args->size() >= 2)978 FontItalic = (*Args)[1] != 0;979 if (Args->size() >= 3)980 FontCharset = (*Args)[2];981 }982 }983 return std::make_unique<FontStmt>(*SizeResult, *NameResult, FontWeight,984 FontItalic, FontCharset);985}986 987RCParser::ParseOptionType RCParser::parseStyleStmt() {988 ASSIGN_OR_RETURN(Arg, readInt());989 return std::make_unique<StyleStmt>(*Arg);990}991 992RCParser::ParseOptionType RCParser::parseExStyleStmt() {993 ASSIGN_OR_RETURN(Arg, readInt());994 return std::make_unique<ExStyleStmt>(*Arg);995}996 997RCParser::ParseOptionType RCParser::parseMenuStmt() {998 ASSIGN_OR_RETURN(Arg, readIntOrString());999 return std::make_unique<MenuStmt>(*Arg);1000}1001 1002Error RCParser::getExpectedError(const Twine &Message, bool IsAlreadyRead) {1003 return make_error<ParserError>(1004 Message, IsAlreadyRead ? std::prev(CurLoc) : CurLoc, End);1005}1006 1007} // namespace rc1008} // namespace llvm1009