[llvm-rc] Add DIALOG(EX) parsing ability (parser, pt 5/8).

This extends the set of resources parsed by llvm-rc by DIALOG and
DIALOGEX.

Additionally, three optional resource statements specific to these two
resources are added: CAPTION, FONT, and STYLE.

Thanks for Nico Weber for his original work in this area.

Differential Revision: https://reviews.llvm.org/D36905

llvm-svn: 312009
This commit is contained in:
Marek Sokolowski 2017-08-29 16:49:59 +00:00
parent c6f6aa441b
commit 4ac54d9302
11 changed files with 310 additions and 6 deletions

View File

@ -56,3 +56,25 @@ LANGUAGE 4, 1
MENUITEM "&Word", 502 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 {}

View File

@ -0,0 +1 @@
3 DIALOG 1, 2, 3, 4, 500 {}

View File

@ -0,0 +1,3 @@
1 DIALOG 1, 2, 3, 4 {
LTEXT "Too short", 1, 2, 3
}

View File

@ -0,0 +1,3 @@
1 DIALOGEX 1, 2, 3, 4 {
LTEXT "Too long", 1, 2, 3, 4, 5, 6, 7, 8, 9
}

View File

@ -0,0 +1,3 @@
1 DIALOGEX 1, 2, 3, 4 {
UNKNOWN 1, 2, 3, 4, 5, 6, 7, 8
}

View File

@ -0,0 +1,3 @@
1 DIALOGEX 1, 2, 3, 4 {
EDITTEXT "This shouldn't be here", 1, 2, 3, 4
}

View File

@ -49,6 +49,21 @@
; PGOOD-NEXT: MenuItem ("&Word"), ID = 502 ; PGOOD-NEXT: MenuItem ("&Word"), ID = 502
; PGOOD-NEXT: Menu list ends ; PGOOD-NEXT: Menu list ends
; 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>&1 | FileCheck %s --check-prefix PSTRINGTABLE1 ; RUN: not llvm-rc /V %p/Inputs/parser-stringtable-no-string.rc 2>&1 | FileCheck %s --check-prefix PSTRINGTABLE1
@ -144,3 +159,28 @@
; RUN: not llvm-rc /V %p/Inputs/parser-menu-misspelled-separator.rc 2>&1 | FileCheck %s --check-prefix PMENU4 ; RUN: not llvm-rc /V %p/Inputs/parser-menu-misspelled-separator.rc 2>&1 | FileCheck %s --check-prefix PMENU4
; PMENU4: llvm-rc: Error parsing file: expected SEPARATOR or string, got NOTSEPARATOR ; 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>&1 | FileCheck %s --check-prefix PDIALOG1
; PDIALOG1: llvm-rc: Error parsing file: expected identifier, got ,
; RUN: not llvm-rc /V %p/Inputs/parser-dialog-too-few-args.rc 2>&1 | FileCheck %s --check-prefix PDIALOG2
; PDIALOG2: llvm-rc: Error parsing file: expected ',', got }
; RUN: not llvm-rc /V %p/Inputs/parser-dialog-too-many-args.rc 2>&1 | FileCheck %s --check-prefix PDIALOG3
; PDIALOG3: llvm-rc: Error parsing file: expected identifier, got ,
; RUN: not llvm-rc /V %p/Inputs/parser-dialog-unknown-type.rc 2>&1 | FileCheck %s --check-prefix PDIALOG4
; 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>&1 | FileCheck %s --check-prefix PDIALOG5
; PDIALOG5: llvm-rc: Error parsing file: expected integer, got "This shouldn't be here"

View File

@ -67,6 +67,10 @@ RCParser::ParseType RCParser::parseSingleResource() {
Result = parseAcceleratorsResource(); Result = parseAcceleratorsResource();
else if (TypeToken->equalsLower("CURSOR")) else if (TypeToken->equalsLower("CURSOR"))
Result = parseCursorResource(); Result = parseCursorResource();
else if (TypeToken->equalsLower("DIALOG"))
Result = parseDialogResource(false);
else if (TypeToken->equalsLower("DIALOGEX"))
Result = parseDialogResource(true);
else if (TypeToken->equalsLower("ICON")) else if (TypeToken->equalsLower("ICON"))
Result = parseIconResource(); Result = parseIconResource();
else if (TypeToken->equalsLower("HTML")) else if (TypeToken->equalsLower("HTML"))
@ -235,17 +239,26 @@ Expected<OptionalStmtList> RCParser::parseOptionalStatements(bool IsExtended) {
} }
Expected<std::unique_ptr<OptionalStmt>> Expected<std::unique_ptr<OptionalStmt>>
RCParser::parseSingleOptionalStatement(bool) { RCParser::parseSingleOptionalStatement(bool IsExtended) {
ASSIGN_OR_RETURN(TypeToken, readIdentifier()); ASSIGN_OR_RETURN(TypeToken, readIdentifier());
if (TypeToken->equals_lower("CHARACTERISTICS")) if (TypeToken->equals_lower("CHARACTERISTICS"))
return parseCharacteristicsStmt(); return parseCharacteristicsStmt();
else if (TypeToken->equals_lower("LANGUAGE")) if (TypeToken->equals_lower("LANGUAGE"))
return parseLanguageStmt(); return parseLanguageStmt();
else if (TypeToken->equals_lower("VERSION")) if (TypeToken->equals_lower("VERSION"))
return parseVersionStmt(); return parseVersionStmt();
else
return getExpectedError("optional statement type, BEGIN or '{'", if (IsExtended) {
/* IsAlreadyRead = */ true); if (TypeToken->equals_lower("CAPTION"))
return parseCaptionStmt();
if (TypeToken->equals_lower("FONT"))
return parseFontStmt();
if (TypeToken->equals_lower("STYLE"))
return parseStyleStmt();
}
return getExpectedError("optional statement type, BEGIN or '{'",
/* IsAlreadyRead = */ true);
} }
RCParser::ParseType RCParser::parseLanguageResource() { RCParser::ParseType RCParser::parseLanguageResource() {
@ -277,6 +290,68 @@ RCParser::ParseType RCParser::parseCursorResource() {
return make_unique<CursorResource>(*Arg); return make_unique<CursorResource>(*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<DialogResource>(
(*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<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.
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<uint32_t> {
return Args->size() > Id ? (*Args)[Id] : Optional<uint32_t>();
};
return Control(*ClassResult, Caption, (*Args)[0], (*Args)[1], (*Args)[2],
(*Args)[3], (*Args)[4], TakeOptArg(5), TakeOptArg(6),
TakeOptArg(7));
}
RCParser::ParseType RCParser::parseIconResource() { RCParser::ParseType RCParser::parseIconResource() {
ASSIGN_OR_RETURN(Arg, readString()); ASSIGN_OR_RETURN(Arg, readString());
return make_unique<IconResource>(*Arg); return make_unique<IconResource>(*Arg);
@ -386,6 +461,23 @@ RCParser::ParseOptionType RCParser::parseVersionStmt() {
return make_unique<VersionStmt>(*Arg); return make_unique<VersionStmt>(*Arg);
} }
RCParser::ParseOptionType RCParser::parseCaptionStmt() {
ASSIGN_OR_RETURN(Arg, readString());
return make_unique<CaptionStmt>(*Arg);
}
RCParser::ParseOptionType RCParser::parseFontStmt() {
ASSIGN_OR_RETURN(SizeResult, readInt());
RETURN_IF_ERROR(consumeType(Kind::Comma));
ASSIGN_OR_RETURN(NameResult, readString());
return make_unique<FontStmt>(*SizeResult, *NameResult);
}
RCParser::ParseOptionType RCParser::parseStyleStmt() {
ASSIGN_OR_RETURN(Arg, readInt());
return make_unique<StyleStmt>(*Arg);
}
Error RCParser::getExpectedError(const Twine Message, bool IsAlreadyRead) { Error RCParser::getExpectedError(const Twine Message, bool IsAlreadyRead) {
return make_error<ParserError>( return make_error<ParserError>(
Message, IsAlreadyRead ? std::prev(CurLoc) : CurLoc, End); Message, IsAlreadyRead ? std::prev(CurLoc) : CurLoc, End);

View File

@ -128,11 +128,15 @@ private:
ParseType parseLanguageResource(); ParseType parseLanguageResource();
ParseType parseAcceleratorsResource(); ParseType parseAcceleratorsResource();
ParseType parseCursorResource(); ParseType parseCursorResource();
ParseType parseDialogResource(bool IsExtended);
ParseType parseIconResource(); ParseType parseIconResource();
ParseType parseHTMLResource(); ParseType parseHTMLResource();
ParseType parseMenuResource(); ParseType parseMenuResource();
ParseType parseStringTableResource(); ParseType parseStringTableResource();
// Helper DIALOG parser - a single control.
Expected<Control> parseControl();
// Helper MENU parser. // Helper MENU parser.
Expected<MenuDefinitionList> parseMenuItemsList(); Expected<MenuDefinitionList> parseMenuItemsList();
@ -140,6 +144,9 @@ private:
ParseOptionType parseLanguageStmt(); ParseOptionType parseLanguageStmt();
ParseOptionType parseCharacteristicsStmt(); ParseOptionType parseCharacteristicsStmt();
ParseOptionType parseVersionStmt(); ParseOptionType parseVersionStmt();
ParseOptionType parseCaptionStmt();
ParseOptionType parseFontStmt();
ParseOptionType parseStyleStmt();
// Raises an error. If IsAlreadyRead = false (default), this complains about // 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 // the token that couldn't be parsed. If the flag is on, this complains about

View File

@ -113,6 +113,35 @@ raw_ostream &StringTableResource::log(raw_ostream &OS) const {
return OS; 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 { raw_ostream &CharacteristicsStmt::log(raw_ostream &OS) const {
return OS << "Characteristics: " << Value << "\n"; return OS << "Characteristics: " << Value << "\n";
} }
@ -121,5 +150,17 @@ raw_ostream &VersionStmt::log(raw_ostream &OS) const {
return OS << "Version: " << Value << "\n"; 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 rc
} // namespace llvm } // namespace llvm

View File

@ -16,6 +16,8 @@
#include "ResourceScriptToken.h" #include "ResourceScriptToken.h"
#include "llvm/ADT/StringSet.h"
namespace llvm { namespace llvm {
namespace rc { namespace rc {
@ -268,6 +270,55 @@ public:
raw_ostream &log(raw_ostream &) const override; 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<uint32_t> 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<uint32_t> ItemStyle, Optional<uint32_t> ExtItemStyle,
Optional<uint32_t> 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<Control> 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. // CHARACTERISTICS optional statement.
// //
// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380872(v=vs.85).aspx // Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380872(v=vs.85).aspx
@ -290,6 +341,44 @@ public:
raw_ostream &log(raw_ostream &) const override; 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 rc
} // namespace llvm } // namespace llvm