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 @@ -29,3 +29,30 @@ 2, 2, CONTROL, VIRTKEY 3, 3, ALT, CONTROL, SHIFT, NOINVERT, ASCII, VIRTKEY } + +LLVMTest MENU +LANGUAGE 4, 1 +{ + POPUP "&OneMenu" + { + POPUP "Menu&1" + { + MENUITEM "Item&1", 301, MENUBREAK, CHECKED + MENUITEM "Item&2", 302, CHECKED, MENUBARBREAK + MENUITEM "Item&3", 303, MENUBREAK, INACTIVE, HELP + MENUITEM "Item&4", 304, GRAYED + } + POPUP "Menu&2" + { + MENUITEM "&A", 401 + MENUITEM "&B", 402 + } + } + POPUP "&Items" + { + MENUITEM "&Row", 500 + MENUITEM "&Column", 501, CHECKED + MENUITEM SEPARATOR + MENUITEM "&Word", 502 + } +} Index: llvm/test/tools/llvm-rc/Inputs/parser-menu-bad-flag.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/parser-menu-bad-flag.rc @@ -0,0 +1,3 @@ +1 MENU { + MENUITEM "&Item", 500, MENUBREAK, ERRONEOUS, HELP +} Index: llvm/test/tools/llvm-rc/Inputs/parser-menu-bad-id.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/parser-menu-bad-id.rc @@ -0,0 +1,3 @@ +1 MENU { + MENUITEM "Hello", A +} Index: llvm/test/tools/llvm-rc/Inputs/parser-menu-missing-block.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/parser-menu-missing-block.rc @@ -0,0 +1,4 @@ +1 MENU { + POPUP "1" + POPUP "2" {} +} Index: llvm/test/tools/llvm-rc/Inputs/parser-menu-misspelled-separator.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/parser-menu-misspelled-separator.rc @@ -0,0 +1,3 @@ +1 MENU { + MENUITEM NOTSEPARATOR +} Index: llvm/test/tools/llvm-rc/parser.test =================================================================== --- llvm/test/tools/llvm-rc/parser.test +++ llvm/test/tools/llvm-rc/parser.test @@ -23,6 +23,32 @@ ; 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 +; PGOOD-NEXT: Menu (LLVMTest): +; PGOOD-NEXT: Option: Language: 4, Sublanguage: 1 +; PGOOD-NEXT: Menu list starts +; PGOOD-NEXT: Popup ("&OneMenu"): +; PGOOD-NEXT: Menu list starts +; PGOOD-NEXT: Popup ("Menu&1"): +; PGOOD-NEXT: Menu list starts +; PGOOD-NEXT: MenuItem ("Item&1"), ID = 301 CHECKED MENUBREAK +; PGOOD-NEXT: MenuItem ("Item&2"), ID = 302 CHECKED MENUBARBREAK +; PGOOD-NEXT: MenuItem ("Item&3"), ID = 303 HELP INACTIVE MENUBREAK +; PGOOD-NEXT: MenuItem ("Item&4"), ID = 304 GRAYED +; PGOOD-NEXT: Menu list ends +; PGOOD-NEXT: Popup ("Menu&2"): +; PGOOD-NEXT: Menu list starts +; PGOOD-NEXT: MenuItem ("&A"), ID = 401 +; PGOOD-NEXT: MenuItem ("&B"), ID = 402 +; PGOOD-NEXT: Menu list ends +; PGOOD-NEXT: Menu list ends +; PGOOD-NEXT: Popup ("&Items"): +; PGOOD-NEXT: Menu list starts +; PGOOD-NEXT: MenuItem ("&Row"), ID = 500 +; PGOOD-NEXT: MenuItem ("&Column"), ID = 501 CHECKED +; PGOOD-NEXT: Menu separator +; PGOOD-NEXT: MenuItem ("&Word"), ID = 502 +; PGOOD-NEXT: Menu list ends +; PGOOD-NEXT: Menu list ends ; RUN: not llvm-rc /V %p/Inputs/parser-stringtable-no-string.rc 2> %t2 @@ -89,3 +115,27 @@ ; RUN: FileCheck %s --check-prefix PHTML2 --input-file %t12 ; PHTML2: llvm-rc: Error parsing file: expected string, got , + + +; RUN: not llvm-rc /V %p/Inputs/parser-menu-bad-id.rc 2> %t13 +; RUN: FileCheck %s --check-prefix PMENU1 --input-file %t13 + +; PMENU1: llvm-rc: Error parsing file: expected integer, got A + + +; RUN: not llvm-rc /V %p/Inputs/parser-menu-bad-flag.rc 2> %t14 +; RUN: FileCheck %s --check-prefix PMENU2 --input-file %t14 + +; PMENU2: llvm-rc: Error parsing file: expected CHECKED/GRAYED/HELP/INACTIVE/MENUBARBREAK/MENUBREAK, got ERRONEOUS + + +; RUN: not llvm-rc /V %p/Inputs/parser-menu-missing-block.rc 2> %t15 +; RUN: FileCheck %s --check-prefix PMENU3 --input-file %t15 + +; PMENU3: llvm-rc: Error parsing file: expected '{', got POPUP + + +; RUN: not llvm-rc /V %p/Inputs/parser-menu-misspelled-separator.rc 2> %t16 +; RUN: FileCheck %s --check-prefix PMENU4 --input-file %t16 + +; PMENU4: llvm-rc: Error parsing file: expected SEPARATOR or string, got NOTSEPARATOR \ No newline at end of file Index: llvm/tools/llvm-rc/ResourceScriptParser.h =================================================================== --- llvm/tools/llvm-rc/ResourceScriptParser.h +++ llvm/tools/llvm-rc/ResourceScriptParser.h @@ -130,8 +130,12 @@ ParseType parseCursorResource(); ParseType parseIconResource(); ParseType parseHTMLResource(); + ParseType parseMenuResource(); ParseType parseStringTableResource(); + // Helper MENU parser. + Expected parseMenuItemsList(); + // Optional statement parsers. ParseOptionType parseLanguageStmt(); ParseOptionType parseCharacteristicsStmt(); Index: llvm/tools/llvm-rc/ResourceScriptParser.cpp =================================================================== --- llvm/tools/llvm-rc/ResourceScriptParser.cpp +++ llvm/tools/llvm-rc/ResourceScriptParser.cpp @@ -71,6 +71,8 @@ Result = parseIconResource(); else if (TypeToken->equalsLower("HTML")) Result = parseHTMLResource(); + else if (TypeToken->equalsLower("MENU")) + Result = parseMenuResource(); else return getExpectedError("resource type", /* IsAlreadyRead = */ true); @@ -291,6 +293,69 @@ return make_unique(*Arg); } +RCParser::ParseType RCParser::parseMenuResource() { + ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements()); + ASSIGN_OR_RETURN(Items, parseMenuItemsList()); + return make_unique(std::move(*OptStatements), + std::move(*Items)); +} + +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"), + IsPopup = ItemTypeResult->equals_lower("POPUP"); + if (!IsMenuItem && !IsPopup) + return getExpectedError("MENUITEM, POPUP, END or '}'", true); + + if (IsMenuItem && isNextTokenKind(Kind::Identifier)) { + // Now, expecting SEPARATOR. + auto SeparatorResult = readIdentifier(); + (void)!SeparatorResult; // We know it's an identifier. + if (SeparatorResult->equals_lower("SEPARATOR")) + List.addDefinition(make_unique()); + else + return getExpectedError("SEPARATOR or string", true); + } else { + // 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::NumFlags)); + + if (IsPopup) { + // If POPUP, read submenu items recursively. + ASSIGN_OR_RETURN(SubMenuResult, parseMenuItemsList()); + List.addDefinition(make_unique(*CaptionResult, *FlagsResult, + std::move(*SubMenuResult))); + } else { + List.addDefinition( + make_unique(*CaptionResult, MenuResult, *FlagsResult)); + } + } + } + + return std::move(List); +} + RCParser::ParseType RCParser::parseStringTableResource() { ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements()); RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); Index: llvm/tools/llvm-rc/ResourceScriptStmt.h =================================================================== --- llvm/tools/llvm-rc/ResourceScriptStmt.h +++ llvm/tools/llvm-rc/ResourceScriptStmt.h @@ -166,6 +166,92 @@ raw_ostream &log(raw_ostream &) const override; }; +// -- MENU resource and its helper classes -- +// This resource describes the contents of an application menu +// (usually located in the upper part of the dialog.) +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381025(v=vs.85).aspx + +// Description of a single submenu item. +class MenuDefinition { +public: + enum Options { + CHECKED = (1 << 0), + GRAYED = (1 << 1), + HELP = (1 << 2), + INACTIVE = (1 << 3), + MENUBARBREAK = (1 << 4), + MENUBREAK = (1 << 5) + }; + + static constexpr size_t NumFlags = 6; + static StringRef OptionsStr[NumFlags]; + static raw_ostream &logFlags(raw_ostream &, uint8_t Flags); + virtual raw_ostream &log(raw_ostream &OS) const { + return OS << "Base menu definition\n"; + } + virtual ~MenuDefinition() {} +}; + +// Recursive description of a whole submenu. +class MenuDefinitionList : public MenuDefinition { + std::vector> Definitions; + +public: + void addDefinition(std::unique_ptr Def) { + Definitions.push_back(std::move(Def)); + } + raw_ostream &log(raw_ostream &) const override; +}; + +// Separator in MENU definition (MENUITEM SEPARATOR). +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381024(v=vs.85).aspx +class MenuSeparator : public MenuDefinition { +public: + raw_ostream &log(raw_ostream &) const override; +}; + +// MENUITEM statement definition. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381024(v=vs.85).aspx +class MenuItem : public MenuDefinition { + StringRef Name; + uint32_t Id; + uint8_t Flags; + +public: + MenuItem(StringRef Caption, uint32_t ItemId, uint8_t ItemFlags) + : Name(Caption), Id(ItemId), Flags(ItemFlags) {} + raw_ostream &log(raw_ostream &) const override; +}; + +// POPUP statement definition. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381030(v=vs.85).aspx +class PopupItem : public MenuDefinition { + StringRef Name; + uint8_t Flags; + MenuDefinitionList SubItems; + +public: + PopupItem(StringRef Caption, uint8_t ItemFlags, + MenuDefinitionList &&SubItemsList) + : Name(Caption), Flags(ItemFlags), SubItems(std::move(SubItemsList)) {} + raw_ostream &log(raw_ostream &) const override; +}; + +// Menu resource definition. +class MenuResource : public RCResource { + OptionalStmtList OptStatements; + MenuDefinitionList Elements; + +public: + MenuResource(OptionalStmtList &&OptStmts, MenuDefinitionList &&Items) + : OptStatements(std::move(OptStmts)), Elements(std::move(Items)) {} + raw_ostream &log(raw_ostream &) const override; +}; + // STRINGTABLE resource. Contains a list of strings, each having its unique ID. // // Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381050(v=vs.85).aspx Index: llvm/tools/llvm-rc/ResourceScriptStmt.cpp =================================================================== --- llvm/tools/llvm-rc/ResourceScriptStmt.cpp +++ llvm/tools/llvm-rc/ResourceScriptStmt.cpp @@ -65,6 +65,46 @@ return OS << "HTML (" << ResName << "): " << HTMLLoc << "\n"; } +StringRef MenuDefinition::OptionsStr[MenuDefinition::NumFlags] = { + "CHECKED", "GRAYED", "HELP", "INACTIVE", "MENUBARBREAK", "MENUBREAK"}; + +raw_ostream &MenuDefinition::logFlags(raw_ostream &OS, uint8_t Flags) { + for (size_t i = 0; i < NumFlags; ++i) + if (Flags & (1U << i)) + OS << " " << OptionsStr[i]; + return OS; +} + +raw_ostream &MenuDefinitionList::log(raw_ostream &OS) const { + OS << " Menu list starts\n"; + for (auto &Item : Definitions) + Item->log(OS); + return OS << " Menu list ends\n"; +} + +raw_ostream &MenuItem::log(raw_ostream &OS) const { + OS << " MenuItem (" << Name << "), ID = " << Id; + logFlags(OS, Flags); + return OS << "\n"; +} + +raw_ostream &MenuSeparator::log(raw_ostream &OS) const { + return OS << " Menu separator\n"; +} + +raw_ostream &PopupItem::log(raw_ostream &OS) const { + OS << " Popup (" << Name << ")"; + logFlags(OS, Flags); + OS << ":\n"; + return SubItems.log(OS); +} + +raw_ostream &MenuResource::log(raw_ostream &OS) const { + OS << "Menu (" << ResName << "):\n"; + OptStatements.log(OS); + return Elements.log(OS); +} + raw_ostream &StringTableResource::log(raw_ostream &OS) const { OS << "StringTable:\n"; OptStatements.log(OS);