forked from OSchip/llvm-project
[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:
parent
c6f6aa441b
commit
4ac54d9302
|
@ -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 {}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
3 DIALOG 1, 2, 3, 4, 500 {}
|
|
@ -0,0 +1,3 @@
|
|||
1 DIALOG 1, 2, 3, 4 {
|
||||
LTEXT "Too short", 1, 2, 3
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
1 DIALOGEX 1, 2, 3, 4 {
|
||||
LTEXT "Too long", 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
1 DIALOGEX 1, 2, 3, 4 {
|
||||
UNKNOWN 1, 2, 3, 4, 5, 6, 7, 8
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
1 DIALOGEX 1, 2, 3, 4 {
|
||||
EDITTEXT "This shouldn't be here", 1, 2, 3, 4
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue