Index: llvm/test/tools/llvm-rc/Inputs/parser-accelerators-bad-flag.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/parser-accelerators-bad-flag.rc @@ -0,0 +1,3 @@ +1 ACCELERATORS { + 5, 7, ALT, CONTROL, HELLO, WORLD +} Index: llvm/test/tools/llvm-rc/Inputs/parser-accelerators-bad-int-or-string.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/parser-accelerators-bad-int-or-string.rc @@ -0,0 +1,3 @@ +1 ACCELERATORS { + NotIntOrString, 7 +} Index: llvm/test/tools/llvm-rc/Inputs/parser-accelerators-no-comma-2.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/parser-accelerators-no-comma-2.rc @@ -0,0 +1,3 @@ +1 ACCELERATORS { + "^C" 10 +} Index: llvm/test/tools/llvm-rc/Inputs/parser-accelerators-no-comma.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/parser-accelerators-no-comma.rc @@ -0,0 +1,3 @@ +1 ACCELERATORS { + 5, 10, ASCII CONTROL +} Index: llvm/test/tools/llvm-rc/Inputs/parser-correct-everything.rc =================================================================== --- llvm/test/tools/llvm-rc/Inputs/parser-correct-everything.rc +++ llvm/test/tools/llvm-rc/Inputs/parser-correct-everything.rc @@ -16,3 +16,16 @@ 500 HTML "index.html" Name Cursor "hello.ico" + +12 ACCELERATORS +VERSION 5000 +LANGUAGE 0, 2 +{ + "^C", 10 + 14, 11 + 5, 12, VIRTKEY + 0, 0, ASCII + 1, 1, VIRTKEY, CONTROL + 2, 2, CONTROL, VIRTKEY + 3, 3, ALT, CONTROL, SHIFT, NOINVERT, ASCII, VIRTKEY +} Index: llvm/test/tools/llvm-rc/parser.test =================================================================== --- llvm/test/tools/llvm-rc/parser.test +++ llvm/test/tools/llvm-rc/parser.test @@ -13,6 +13,16 @@ ; PGOOD-NEXT: StringTable: ; PGOOD-NEXT: HTML (500): "index.html" ; PGOOD-NEXT: Cursor (Name): "hello.ico" +; PGOOD-NEXT: Accelerators (12): +; PGOOD-NEXT: Option: Version: 5000 +; PGOOD-NEXT: Option: Language: 0, Sublanguage: 2 +; PGOOD-NEXT: Accelerator: "^C" 10 +; PGOOD-NEXT: Accelerator: 14 11 +; PGOOD-NEXT: Accelerator: 5 12 VIRTKEY +; PGOOD-NEXT: Accelerator: 0 0 ASCII +; PGOOD-NEXT: Accelerator: 1 1 VIRTKEY CONTROL +; PGOOD-NEXT: Accelerator: 2 2 VIRTKEY CONTROL +; PGOOD-NEXT: Accelerator: 3 3 ASCII VIRTKEY NOINVERT ALT SHIFT CONTROL ; RUN: not llvm-rc /V %p/Inputs/parser-stringtable-no-string.rc 2>&1 | FileCheck %s --check-prefix PSTRINGTABLE1 @@ -68,3 +78,23 @@ ; RUN: not llvm-rc /V %p/Inputs/parser-html-extra-comma.rc 2>&1 | FileCheck %s --check-prefix PHTML2 ; PHTML2: llvm-rc: Error parsing file: expected string, got , + + +; RUN: not llvm-rc /V %p/Inputs/parser-accelerators-bad-flag.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS1 + +; PACCELERATORS1: llvm-rc: Error parsing file: expected ASCII/VIRTKEY/NOINVERT/ALT/SHIFT/CONTROL, got HELLO + + +; RUN: not llvm-rc /V %p/Inputs/parser-accelerators-bad-int-or-string.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS2 + +; PACCELERATORS2: llvm-rc: Error parsing file: expected int or string, got NotIntOrString + + +; RUN: not llvm-rc /V %p/Inputs/parser-accelerators-no-comma.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS3 + +; PACCELERATORS3: llvm-rc: Error parsing file: expected int or string, got CONTROL + + +; RUN: not llvm-rc /V %p/Inputs/parser-accelerators-no-comma-2.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS4 + +; PACCELERATORS4: llvm-rc: Error parsing file: expected ',', got 10 Index: llvm/tools/llvm-rc/ResourceScriptParser.h =================================================================== --- llvm/tools/llvm-rc/ResourceScriptParser.h +++ llvm/tools/llvm-rc/ResourceScriptParser.h @@ -77,10 +77,11 @@ // The following methods try to read a single token, check if it has the // correct type and then parse it. - Expected readInt(); // Parse an integer. - Expected readString(); // Parse a string. - Expected readIdentifier(); // Parse an identifier. - Expected readTypeOrName(); // Parse an integer or an identifier. + Expected readInt(); // Parse an integer. + Expected readString(); // Parse a string. + Expected readIdentifier(); // Parse an identifier. + Expected readIntOrString(); // Parse an integer or a string. + Expected readTypeOrName(); // Parse an integer or an identifier. // Advance the state by one, discarding the current token. // If the discarded token had an incorrect type, fail. @@ -97,6 +98,13 @@ Expected> readIntsWithCommas(size_t MinCount, size_t MaxCount); + // Read an unknown number of flags preceded by commas. Each correct flag + // has an entry in FlagDesc array of length NumFlags. In case i-th + // flag (0-based) has been read, the i-th bit of the result is set. + // As long as parser has a comma to read, it expects to be fed with + // a correct flag afterwards. + Expected parseFlags(ArrayRef FlagDesc); + // Reads a set of optional statements. These can change the behavior of // a number of resource types (e.g. STRINGTABLE, MENU or DIALOG) if provided // before the main block with the contents of the resource. @@ -118,6 +126,7 @@ // Top-level resource parsers. ParseType parseLanguageResource(); + ParseType parseAcceleratorsResource(); ParseType parseCursorResource(); ParseType parseIconResource(); ParseType parseHTMLResource(); Index: llvm/tools/llvm-rc/ResourceScriptParser.cpp =================================================================== --- llvm/tools/llvm-rc/ResourceScriptParser.cpp +++ llvm/tools/llvm-rc/ResourceScriptParser.cpp @@ -63,7 +63,9 @@ ParseType Result = std::unique_ptr(); (void)!Result; - if (TypeToken->equalsLower("CURSOR")) + if (TypeToken->equalsLower("ACCELERATORS")) + Result = parseAcceleratorsResource(); + else if (TypeToken->equalsLower("CURSOR")) Result = parseCursorResource(); else if (TypeToken->equalsLower("ICON")) Result = parseIconResource(); @@ -115,17 +117,18 @@ 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"); - - const RCToken &Tok = read(); - if (Tok.kind() == Kind::Int) - return IntOrString(Tok.intValue()); - else - return IntOrString(Tok.value()); + return IntOrString(read()); } Error RCParser::consumeType(Kind TokenKind) { @@ -190,6 +193,32 @@ return std::move(Result); } +Expected RCParser::parseFlags(ArrayRef FlagDesc) { + assert(FlagDesc.size() <= 32 && "More than 32 flags won't fit in result."); + assert(!FlagDesc.empty()); + + 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 |= (1U << FlagId); + FoundFlag = true; + break; + } + + if (!FoundFlag) + return getExpectedError(join(FlagDesc, "/"), true); + } + + return Result; +} + // As for now, we ignore the extended set of statements. Expected RCParser::parseOptionalStatements(bool IsExtended) { OptionalStmtList Result; @@ -223,6 +252,24 @@ return parseLanguageStmt(); } +RCParser::ParseType RCParser::parseAcceleratorsResource() { + ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements()); + RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); + + auto Accels = make_unique(std::move(*OptStatements)); + + 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)); + Accels->addAccelerator(*EventResult, *IDResult, *FlagsResult); + } + + return std::move(Accels); +} + RCParser::ParseType RCParser::parseCursorResource() { ASSIGN_OR_RETURN(Arg, readString()); return make_unique(*Arg); Index: llvm/tools/llvm-rc/ResourceScriptStmt.h =================================================================== --- llvm/tools/llvm-rc/ResourceScriptStmt.h +++ llvm/tools/llvm-rc/ResourceScriptStmt.h @@ -27,7 +27,12 @@ StringRef String; Data(uint32_t Value) : Int(Value) {} Data(const StringRef Value) : String(Value) {} - Data(const RCToken &Token); + Data(const RCToken &Token) { + if (Token.kind() == RCToken::Kind::Int) + Int = Token.intValue(); + else + String = Token.value(); + } } Data; bool IsInt; @@ -90,6 +95,42 @@ raw_ostream &log(raw_ostream &) const override; }; +// ACCELERATORS resource. Defines a named table of accelerators for the app. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380610(v=vs.85).aspx +class AcceleratorsResource : public RCResource { +public: + class Accelerator { + public: + IntOrString Event; + uint32_t Id; + uint8_t Flags; + + enum Options { + ASCII = (1 << 0), + VIRTKEY = (1 << 1), + NOINVERT = (1 << 2), + ALT = (1 << 3), + SHIFT = (1 << 4), + CONTROL = (1 << 5) + }; + + static constexpr size_t NumFlags = 6; + static StringRef OptionsStr[NumFlags]; + }; + + AcceleratorsResource(OptionalStmtList &&OptStmts) + : OptStatements(std::move(OptStmts)) {} + void addAccelerator(IntOrString Event, uint32_t Id, uint8_t Flags) { + Accelerators.push_back(Accelerator{Event, Id, Flags}); + } + raw_ostream &log(raw_ostream &) const override; + +private: + std::vector Accelerators; + OptionalStmtList OptStatements; +}; + // CURSOR resource. Represents a single cursor (".cur") file. // // Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380920(v=vs.85).aspx Index: llvm/tools/llvm-rc/ResourceScriptStmt.cpp =================================================================== --- llvm/tools/llvm-rc/ResourceScriptStmt.cpp +++ llvm/tools/llvm-rc/ResourceScriptStmt.cpp @@ -36,6 +36,23 @@ return OS << "Language: " << Lang << ", Sublanguage: " << SubLang << "\n"; } +StringRef AcceleratorsResource::Accelerator::OptionsStr + [AcceleratorsResource::Accelerator::NumFlags] = { + "ASCII", "VIRTKEY", "NOINVERT", "ALT", "SHIFT", "CONTROL"}; + +raw_ostream &AcceleratorsResource::log(raw_ostream &OS) const { + OS << "Accelerators (" << ResName << "): \n"; + OptStatements.log(OS); + for (const auto &Acc : Accelerators) { + OS << " Accelerator: " << Acc.Event << " " << Acc.Id; + for (size_t i = 0; i < Accelerator::NumFlags; ++i) + if (Acc.Flags & (1U << i)) + OS << " " << Accelerator::OptionsStr[i]; + OS << "\n"; + } + return OS; +} + raw_ostream &CursorResource::log(raw_ostream &OS) const { return OS << "Cursor (" << ResName << "): " << CursorLoc << "\n"; }