]> granicus.if.org Git - llvm/commitdiff
[llvm-rc] Add DIALOG(EX) parsing ability (parser, pt 5/8).
authorMarek Sokolowski <mnbvmar@gmail.com>
Tue, 29 Aug 2017 16:49:59 +0000 (16:49 +0000)
committerMarek Sokolowski <mnbvmar@gmail.com>
Tue, 29 Aug 2017 16:49:59 +0000 (16:49 +0000)
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

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@312009 91177308-0d34-0410-b5e6-96231b3b80d8

test/tools/llvm-rc/Inputs/parser-correct-everything.rc
test/tools/llvm-rc/Inputs/parser-dialog-cant-give-helpid.rc [new file with mode: 0644]
test/tools/llvm-rc/Inputs/parser-dialog-too-few-args.rc [new file with mode: 0644]
test/tools/llvm-rc/Inputs/parser-dialog-too-many-args.rc [new file with mode: 0644]
test/tools/llvm-rc/Inputs/parser-dialog-unknown-type.rc [new file with mode: 0644]
test/tools/llvm-rc/Inputs/parser-dialog-unnecessary-string.rc [new file with mode: 0644]
test/tools/llvm-rc/parser.test
tools/llvm-rc/ResourceScriptParser.cpp
tools/llvm-rc/ResourceScriptParser.h
tools/llvm-rc/ResourceScriptStmt.cpp
tools/llvm-rc/ResourceScriptStmt.h

index 103dfe25f0ab2b31414d8241fca56812384d647a..4c7ce4025dde85b49a35addf580fe14e95ec65c0 100644 (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 {}
diff --git a/test/tools/llvm-rc/Inputs/parser-dialog-cant-give-helpid.rc b/test/tools/llvm-rc/Inputs/parser-dialog-cant-give-helpid.rc
new file mode 100644 (file)
index 0000000..74986a7
--- /dev/null
@@ -0,0 +1 @@
+3 DIALOG 1, 2, 3, 4, 500 {}
diff --git a/test/tools/llvm-rc/Inputs/parser-dialog-too-few-args.rc b/test/tools/llvm-rc/Inputs/parser-dialog-too-few-args.rc
new file mode 100644 (file)
index 0000000..907720b
--- /dev/null
@@ -0,0 +1,3 @@
+1 DIALOG 1, 2, 3, 4 {
+  LTEXT "Too short", 1, 2, 3
+}
diff --git a/test/tools/llvm-rc/Inputs/parser-dialog-too-many-args.rc b/test/tools/llvm-rc/Inputs/parser-dialog-too-many-args.rc
new file mode 100644 (file)
index 0000000..0b76ee3
--- /dev/null
@@ -0,0 +1,3 @@
+1 DIALOGEX 1, 2, 3, 4 {
+  LTEXT "Too long", 1, 2, 3, 4, 5, 6, 7, 8, 9
+}
diff --git a/test/tools/llvm-rc/Inputs/parser-dialog-unknown-type.rc b/test/tools/llvm-rc/Inputs/parser-dialog-unknown-type.rc
new file mode 100644 (file)
index 0000000..ab0c06e
--- /dev/null
@@ -0,0 +1,3 @@
+1 DIALOGEX 1, 2, 3, 4 {
+  UNKNOWN 1, 2, 3, 4, 5, 6, 7, 8
+}
diff --git a/test/tools/llvm-rc/Inputs/parser-dialog-unnecessary-string.rc b/test/tools/llvm-rc/Inputs/parser-dialog-unnecessary-string.rc
new file mode 100644 (file)
index 0000000..6af387d
--- /dev/null
@@ -0,0 +1,3 @@
+1 DIALOGEX 1, 2, 3, 4 {
+  EDITTEXT "This shouldn't be here", 1, 2, 3, 4
+}
index 710a02df8ca7bda11db4223ad8f1b9ad4c056318..bc7f20325d64278c52904cb8166605ead614cf19 100644 (file)
 ; 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
 ; 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"
index 5cc171c49afb2b2514d4a1af3f99284e5b4e9c19..499d0af83ad572ae232b49351772760039036b21 100644 (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);
index 3fd1c27d928919134eb9d27f1e112e9e94c21103..bce2e0b544e2046d8fbf43ee25876cb35ff4d5bc 100644 (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
index 9f61ce451de5f78cfb6b07be1afe12ba1463b1c9..cfbd2f8f7a388b91773c6b6f617f8d6abd09ed95 100644 (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
index 7166138a517170605813604a66ce0c1ef59f220e..0812c263b98397d56c308aef468b4942e1cb9c10 100644 (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