[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
}
}
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: 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
@ -144,3 +159,28 @@
; 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
; 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();
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"))
@ -235,17 +239,26 @@ Expected<OptionalStmtList> RCParser::parseOptionalStatements(bool IsExtended) {
}
Expected<std::unique_ptr<OptionalStmt>>
RCParser::parseSingleOptionalStatement(bool) {
RCParser::parseSingleOptionalStatement(bool IsExtended) {
ASSIGN_OR_RETURN(TypeToken, readIdentifier());
if (TypeToken->equals_lower("CHARACTERISTICS"))
return parseCharacteristicsStmt();
else if (TypeToken->equals_lower("LANGUAGE"))
if (TypeToken->equals_lower("LANGUAGE"))
return parseLanguageStmt();
else if (TypeToken->equals_lower("VERSION"))
if (TypeToken->equals_lower("VERSION"))
return parseVersionStmt();
else
return getExpectedError("optional statement type, BEGIN or '{'",
/* IsAlreadyRead = */ true);
if (IsExtended) {
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() {
@ -277,6 +290,68 @@ RCParser::ParseType RCParser::parseCursorResource() {
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() {
ASSIGN_OR_RETURN(Arg, readString());
return make_unique<IconResource>(*Arg);
@ -386,6 +461,23 @@ RCParser::ParseOptionType RCParser::parseVersionStmt() {
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) {
return make_error<ParserError>(
Message, IsAlreadyRead ? std::prev(CurLoc) : CurLoc, End);

View File

@ -128,11 +128,15 @@ private:
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<Control> parseControl();
// Helper MENU parser.
Expected<MenuDefinitionList> parseMenuItemsList();
@ -140,6 +144,9 @@ private:
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

View File

@ -113,6 +113,35 @@ raw_ostream &StringTableResource::log(raw_ostream &OS) const {
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 @@ raw_ostream &VersionStmt::log(raw_ostream &OS) const {
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

View File

@ -16,6 +16,8 @@
#include "ResourceScriptToken.h"
#include "llvm/ADT/StringSet.h"
namespace llvm {
namespace rc {
@ -268,6 +270,55 @@ public:
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.
//
// 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;
};
// 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