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 @@ -56,3 +56,25 @@ MENUITEM "&Word", 502 } } + +14 DIALOGEX 50, 60, 10, 20, 500 +LANGUAGE 1, 2 +CHARACTERISTICS 50 +VERSION 100 +FONT 12, "Arial" +CAPTION "RC parser dialog" +STYLE 0x51234 +BEGIN + LTEXT "Hello world!", 14, 20, 20, 50, 50 + RTEXT "Heh", 50, 51, 52, 53, 54, 55, 56 + CTEXT "Muuuu", 1, 2, 3, 4, 5, 6, 7, 8 + PUSHBUTTON "Muuuu", 1, 2, 3, 4, 5, 6, 7, 8 + DEFPUSHBUTTON "Muuuu", 1, 2, 3, 4, 5, 6 + EDITTEXT 5, 1, 2, 4, 7, 8 +END + +25 DIALOG 1, 2, 3, 4 +BEGIN +END + +26 DIALOGEX 1, 2, 3, 4 {} Index: llvm/test/tools/llvm-rc/Inputs/parser-dialog-cant-give-helpid.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/parser-dialog-cant-give-helpid.rc @@ -0,0 +1 @@ +3 DIALOG 1, 2, 3, 4, 500 {} Index: llvm/test/tools/llvm-rc/Inputs/parser-dialog-too-few-args.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/parser-dialog-too-few-args.rc @@ -0,0 +1,3 @@ +1 DIALOG 1, 2, 3, 4 { + LTEXT "Too short", 1, 2, 3 +} Index: llvm/test/tools/llvm-rc/Inputs/parser-dialog-too-many-args.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/parser-dialog-too-many-args.rc @@ -0,0 +1,3 @@ +1 DIALOGEX 1, 2, 3, 4 { + LTEXT "Too long", 1, 2, 3, 4, 5, 6, 7, 8, 9 +} Index: llvm/test/tools/llvm-rc/Inputs/parser-dialog-unknown-type.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/parser-dialog-unknown-type.rc @@ -0,0 +1,3 @@ +1 DIALOGEX 1, 2, 3, 4 { + UNKNOWN 1, 2, 3, 4, 5, 6, 7, 8 +} Index: llvm/test/tools/llvm-rc/Inputs/parser-dialog-unnecessary-string.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/parser-dialog-unnecessary-string.rc @@ -0,0 +1,3 @@ +1 DIALOGEX 1, 2, 3, 4 { + EDITTEXT "This shouldn't be here", 1, 2, 3, 4 +} Index: llvm/test/tools/llvm-rc/parser.test =================================================================== --- llvm/test/tools/llvm-rc/parser.test +++ llvm/test/tools/llvm-rc/parser.test @@ -49,7 +49,21 @@ ; PGOOD-NEXT: MenuItem ("&Word"), ID = 502 ; PGOOD-NEXT: Menu list ends ; PGOOD-NEXT: Menu list ends - +; PGOOD-NEXT: DialogEx (14): loc: (50, 60), size: [10, 20], help ID: 500 +; PGOOD-NEXT: Option: Language: 1, Sublanguage: 2 +; PGOOD-NEXT: Option: Characteristics: 50 +; PGOOD-NEXT: Option: Version: 100 +; PGOOD-NEXT: Option: Font: size = 12, face = "Arial" +; PGOOD-NEXT: Option: Caption: "RC parser dialog" +; PGOOD-NEXT: Option: Style: 332340 +; PGOOD-NEXT: Control (14): LTEXT, title: "Hello world!", loc: (20, 20), size: [50, 50] +; PGOOD-NEXT: Control (50): RTEXT, title: "Heh", loc: (51, 52), size: [53, 54], style: 55, ext. style: 56 +; PGOOD-NEXT: Control (1): CTEXT, title: "Muuuu", loc: (2, 3), size: [4, 5], style: 6, ext. style: 7, help ID: 8 +; PGOOD-NEXT: Control (1): PUSHBUTTON, title: "Muuuu", loc: (2, 3), size: [4, 5], style: 6, ext. style: 7, help ID: 8 +; PGOOD-NEXT: Control (1): DEFPUSHBUTTON, title: "Muuuu", loc: (2, 3), size: [4, 5], style: 6 +; PGOOD-NEXT: Control (5): EDITTEXT, title: , loc: (1, 2), size: [4, 7], style: 8 +; PGOOD-NEXT: Dialog (25): loc: (1, 2), size: [3, 4], help ID: 0 +; PGOOD-NEXT: DialogEx (26): loc: (1, 2), size: [3, 4], help ID: 0 ; RUN: not llvm-rc /V %p/Inputs/parser-stringtable-no-string.rc 2> %t2 ; RUN: FileCheck %s --check-prefix PSTRINGTABLE1 --input-file %t2 @@ -138,4 +152,34 @@ ; 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 +; PMENU4: llvm-rc: Error parsing file: expected SEPARATOR or string, got NOTSEPARATOR + + +; RUN: not llvm-rc /V %p/Inputs/parser-dialog-cant-give-helpid.rc 2> %t17 +; RUN: FileCheck %s --check-prefix PDIALOG1 --input-file %t17 + +; PDIALOG1: llvm-rc: Error parsing file: expected identifier, got , + + +; RUN: not llvm-rc /V %p/Inputs/parser-dialog-too-few-args.rc 2> %t18 +; RUN: FileCheck %s --check-prefix PDIALOG2 --input-file %t18 + +; PDIALOG2: llvm-rc: Error parsing file: expected ',', got } + + +; RUN: not llvm-rc /V %p/Inputs/parser-dialog-too-many-args.rc 2> %t19 +; RUN: FileCheck %s --check-prefix PDIALOG3 --input-file %t19 + +; PDIALOG3: llvm-rc: Error parsing file: expected identifier, got , + + +; RUN: not llvm-rc /V %p/Inputs/parser-dialog-unknown-type.rc 2> %t20 +; RUN: FileCheck %s --check-prefix PDIALOG4 --input-file %t20 + +; PDIALOG4: llvm-rc: Error parsing file: expected control type, END or '}', got UNKNOWN + + +; RUN: not llvm-rc /V %p/Inputs/parser-dialog-unnecessary-string.rc 2> %t21 +; RUN: FileCheck %s --check-prefix PDIALOG5 --input-file %t21 + +; PDIALOG5: llvm-rc: Error parsing file: expected integer, got "This shouldn't be here" Index: llvm/tools/llvm-rc/ResourceScriptParser.h =================================================================== --- llvm/tools/llvm-rc/ResourceScriptParser.h +++ llvm/tools/llvm-rc/ResourceScriptParser.h @@ -128,11 +128,15 @@ ParseType parseLanguageResource(); ParseType parseAcceleratorsResource(); ParseType parseCursorResource(); + ParseType parseDialogResource(bool IsExtended); ParseType parseIconResource(); ParseType parseHTMLResource(); ParseType parseMenuResource(); ParseType parseStringTableResource(); + // Helper DIALOG parser - a single control. + Expected parseControl(); + // Helper MENU parser. Expected parseMenuItemsList(); @@ -140,6 +144,9 @@ ParseOptionType parseLanguageStmt(); ParseOptionType parseCharacteristicsStmt(); ParseOptionType parseVersionStmt(); + ParseOptionType parseCaptionStmt(); + ParseOptionType parseFontStmt(); + ParseOptionType parseStyleStmt(); // Raises an error. If IsAlreadyRead = false (default), this complains about // the token that couldn't be parsed. If the flag is on, this complains about Index: llvm/tools/llvm-rc/ResourceScriptParser.cpp =================================================================== --- llvm/tools/llvm-rc/ResourceScriptParser.cpp +++ llvm/tools/llvm-rc/ResourceScriptParser.cpp @@ -67,6 +67,10 @@ Result = parseAcceleratorsResource(); 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("ICON")) Result = parseIconResource(); else if (TypeToken->equalsLower("HTML")) @@ -240,7 +244,7 @@ } Expected> -RCParser::parseSingleOptionalStatement(bool) { +RCParser::parseSingleOptionalStatement(bool IsExtended) { ASSIGN_OR_RETURN(TypeToken, readIdentifier()); if (TypeToken->equals_lower("CHARACTERISTICS")) return parseCharacteristicsStmt(); @@ -248,9 +252,16 @@ return parseLanguageStmt(); else if (TypeToken->equals_lower("VERSION")) return parseVersionStmt(); - else - return getExpectedError("optional statement type, BEGIN or '{'", - /* IsAlreadyRead = */ true); + else if (IsExtended) { + if (TypeToken->equals_lower("CAPTION")) + return parseCaptionStmt(); + else if (TypeToken->equals_lower("FONT")) + return parseFontStmt(); + else if (TypeToken->equals_lower("STYLE")) + return parseStyleStmt(); + } + return getExpectedError("optional statement type, BEGIN or '{'", + /* IsAlreadyRead = */ true); } RCParser::ParseType RCParser::parseLanguageResource() { @@ -283,6 +294,68 @@ return make_unique(*Arg); } +RCParser::ParseType RCParser::parseDialogResource(bool IsExtended) { + // 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(/*UseExtendedStmts = */ true)); + + assert(isNextTokenKind(Kind::BlockBegin) && + "parseOptionalStatements, when successful, halts on BlockBegin."); + consume(); + + auto Dialog = make_unique( + (*LocResult)[0], (*LocResult)[1], (*LocResult)[2], (*LocResult)[3], + HelpID, std::move(*OptStatements), IsExtended); + + while (!consumeOptionalType(Kind::BlockEnd)) { + ASSIGN_OR_RETURN(ControlDefResult, parseControl()); + Dialog->addControl(std::move(*ControlDefResult)); + } + + return std::move(Dialog); +} + +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. + ASSIGN_OR_RETURN(ClassResult, readIdentifier()); + StringRef ClassUpper = ClassResult->upper(); + if (Control::SupportedCtls.find(ClassUpper) == Control::SupportedCtls.end()) + return getExpectedError("control type, END or '}'", true); + + // Read caption if necessary. + StringRef Caption; + if (Control::CtlsWithTitle.find(ClassUpper) != Control::CtlsWithTitle.end()) { + ASSIGN_OR_RETURN(CaptionResult, readString()); + RETURN_IF_ERROR(consumeType(Kind::Comma)); + Caption = *CaptionResult; + } + + ASSIGN_OR_RETURN(Args, readIntsWithCommas(5, 8)); + + auto TakeOptArg = [&Args](size_t Id) -> Optional { + return Args->size() > Id ? (*Args)[Id] : Optional(); + }; + + return Control(*ClassResult, Caption, (*Args)[0], (*Args)[1], (*Args)[2], + (*Args)[3], (*Args)[4], TakeOptArg(5), TakeOptArg(6), + TakeOptArg(7)); +} + RCParser::ParseType RCParser::parseIconResource() { ASSIGN_OR_RETURN(Arg, readString()); return make_unique(*Arg); @@ -390,6 +463,23 @@ return make_unique(*Arg); } +RCParser::ParseOptionType RCParser::parseCaptionStmt() { + ASSIGN_OR_RETURN(Arg, readString()); + return make_unique(*Arg); +} + +RCParser::ParseOptionType RCParser::parseFontStmt() { + ASSIGN_OR_RETURN(SizeResult, readInt()); + RETURN_IF_ERROR(consumeType(Kind::Comma)); + ASSIGN_OR_RETURN(NameResult, readString()); + return make_unique(*SizeResult, *NameResult); +} + +RCParser::ParseOptionType RCParser::parseStyleStmt() { + ASSIGN_OR_RETURN(Arg, readInt()); + return make_unique(*Arg); +} + Error RCParser::getExpectedError(const Twine Message, bool IsAlreadyRead) { return make_error( Message, IsAlreadyRead ? std::prev(CurLoc) : CurLoc, End); Index: llvm/tools/llvm-rc/ResourceScriptStmt.h =================================================================== --- llvm/tools/llvm-rc/ResourceScriptStmt.h +++ llvm/tools/llvm-rc/ResourceScriptStmt.h @@ -16,6 +16,8 @@ #include "ResourceScriptToken.h" +#include "llvm/ADT/StringSet.h" + namespace llvm { namespace rc { @@ -268,6 +270,55 @@ raw_ostream &log(raw_ostream &) const override; }; +// -- DIALOG(EX) resource and its helper classes -- +// +// This resource describes dialog boxes and controls residing inside them. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381003(v=vs.85).aspx +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381002(v=vs.85).aspx + +// Single control definition. +class Control { + StringRef Type, Title; + uint32_t ID, X, Y, Width, Height; + Optional Style, ExtStyle, HelpID; + +public: + Control(StringRef CtlType, StringRef CtlTitle, uint32_t CtlID, uint32_t PosX, + uint32_t PosY, uint32_t ItemWidth, uint32_t ItemHeight, + Optional ItemStyle, Optional ExtItemStyle, + Optional CtlHelpID) + : Type(CtlType), Title(CtlTitle), ID(CtlID), X(PosX), Y(PosY), + Width(ItemWidth), Height(ItemHeight), Style(ItemStyle), + ExtStyle(ExtItemStyle), HelpID(CtlHelpID) {} + + static const StringSet<> SupportedCtls; + static const StringSet<> CtlsWithTitle; + + raw_ostream &log(raw_ostream &) const; +}; + +// Single dialog definition. We don't create distinct classes for DIALOG and +// DIALOGEX because of their being too similar to each other. We only have a +// flag determining the type of the dialog box. +class DialogResource : public RCResource { + uint32_t X, Y, Width, Height, HelpID; + OptionalStmtList OptStatements; + std::vector Controls; + bool IsExtended; + +public: + DialogResource(uint32_t PosX, uint32_t PosY, uint32_t DlgWidth, + uint32_t DlgHeight, uint32_t DlgHelpID, + OptionalStmtList &&OptStmts, bool IsDialogEx) + : X(PosX), Y(PosY), Width(DlgWidth), Height(DlgHeight), HelpID(DlgHelpID), + OptStatements(std::move(OptStmts)), IsExtended(IsDialogEx) {} + + void addControl(Control &&Ctl) { Controls.push_back(std::move(Ctl)); } + + raw_ostream &log(raw_ostream &) const override; +}; + // CHARACTERISTICS optional statement. // // Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380872(v=vs.85).aspx @@ -290,6 +341,44 @@ raw_ostream &log(raw_ostream &) const override; }; +// CAPTION optional statement. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380778(v=vs.85).aspx +class CaptionStmt : public OptionalStmt { + StringRef Value; + +public: + CaptionStmt(StringRef Caption) : Value(Caption) {} + raw_ostream &log(raw_ostream &) const override; +}; + +// FONT optional statement. +// Note that the documentation is inaccurate: it expects five arguments to be +// given, however the example provides only two. In fact, the original tool +// expects two arguments - point size and name of the typeface. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381013(v=vs.85).aspx +class FontStmt : public OptionalStmt { + uint32_t Size; + StringRef Typeface; + +public: + FontStmt(uint32_t FontSize, StringRef FontName) + : Size(FontSize), Typeface(FontName) {} + raw_ostream &log(raw_ostream &) const override; +}; + +// STYLE optional statement. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381051(v=vs.85).aspx +class StyleStmt : public OptionalStmt { + uint32_t Value; + +public: + StyleStmt(uint32_t Style) : Value(Style) {} + raw_ostream &log(raw_ostream &) const override; +}; + } // namespace rc } // namespace llvm Index: llvm/tools/llvm-rc/ResourceScriptStmt.cpp =================================================================== --- llvm/tools/llvm-rc/ResourceScriptStmt.cpp +++ llvm/tools/llvm-rc/ResourceScriptStmt.cpp @@ -113,6 +113,35 @@ return OS; } +const StringSet<> Control::SupportedCtls = { + "LTEXT", "RTEXT", "CTEXT", "PUSHBUTTON", "DEFPUSHBUTTON", "EDITTEXT"}; + +const StringSet<> Control::CtlsWithTitle = {"LTEXT", "RTEXT", "CTEXT", + "PUSHBUTTON", "DEFPUSHBUTTON"}; + +raw_ostream &Control::log(raw_ostream &OS) const { + OS << " Control (" << ID << "): " << Type << ", title: " << Title + << ", loc: (" << X << ", " << Y << "), size: [" << Width << ", " << Height + << "]"; + if (Style) + OS << ", style: " << *Style; + if (ExtStyle) + OS << ", ext. style: " << *ExtStyle; + if (HelpID) + OS << ", help ID: " << *HelpID; + return OS << "\n"; +} + +raw_ostream &DialogResource::log(raw_ostream &OS) const { + OS << "Dialog" << (IsExtended ? "Ex" : "") << " (" << ResName << "): loc: (" + << X << ", " << Y << "), size: [" << Width << ", " << Height + << "], help ID: " << HelpID << "\n"; + OptStatements.log(OS); + for (auto &Ctl : Controls) + Ctl.log(OS); + return OS; +} + raw_ostream &CharacteristicsStmt::log(raw_ostream &OS) const { return OS << "Characteristics: " << Value << "\n"; } @@ -121,5 +150,17 @@ return OS << "Version: " << Value << "\n"; } +raw_ostream &CaptionStmt::log(raw_ostream &OS) const { + return OS << "Caption: " << Value << "\n"; +} + +raw_ostream &FontStmt::log(raw_ostream &OS) const { + return OS << "Font: size = " << Size << ", face = " << Typeface << "\n"; +} + +raw_ostream &StyleStmt::log(raw_ostream &OS) const { + return OS << "Style: " << Value << "\n"; +} + } // namespace rc } // namespace llvm