[llvm-rc] Serialize ACCELERATORS to .res files (serialization, pt 2).

This allows llvm-rc to serialize ACCELERATORS resources.

Additionally, as this is the first type of resource to support basic
optional resource statements (LANGUAGE, CHARACTERISTICS, VERSION),

ACCELERATORS statement documentation:
msdn.microsoft.com/en-us/library/windows/desktop/aa380610.aspx
Accelerator table structure documentation:
msdn.microsoft.com/en-us/library/windows/desktop/ms648010.aspx
Optional resource statement fields are described in:
msdn.microsoft.com/en-us/library/windows/desktop/ms648027.aspx

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

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

llvm-svn: 314549
diff --git a/llvm/tools/llvm-rc/ResourceFileWriter.cpp b/llvm/tools/llvm-rc/ResourceFileWriter.cpp
index 642d6ef..f8ba2a7 100644
--- a/llvm/tools/llvm-rc/ResourceFileWriter.cpp
+++ b/llvm/tools/llvm-rc/ResourceFileWriter.cpp
@@ -28,6 +28,19 @@
 namespace llvm {
 namespace rc {
 
+// Class that employs RAII to save the current serializator object state
+// and revert to it as soon as we leave the scope. This is useful if resources
+// declare their own resource-local statements.
+class ContextKeeper {
+  ResourceFileWriter *FileWriter;
+  ResourceFileWriter::ObjectInfo SavedInfo;
+
+public:
+  ContextKeeper(ResourceFileWriter *V)
+      : FileWriter(V), SavedInfo(V->ObjectData) {}
+  ~ContextKeeper() { FileWriter->ObjectData = SavedInfo; }
+};
+
 static Error createError(Twine Message,
                          std::errc Type = std::errc::invalid_argument) {
   return make_error<StringError>(Message, std::make_error_code(Type));
@@ -184,10 +197,20 @@
   return writeResource(Res, &ResourceFileWriter::writeNullBody);
 }
 
+Error ResourceFileWriter::visitAcceleratorsResource(const RCResource *Res) {
+  return writeResource(Res, &ResourceFileWriter::writeAcceleratorsBody);
+}
+
 Error ResourceFileWriter::visitHTMLResource(const RCResource *Res) {
   return writeResource(Res, &ResourceFileWriter::writeHTMLBody);
 }
 
+Error ResourceFileWriter::visitCharacteristicsStmt(
+    const CharacteristicsStmt *Stmt) {
+  ObjectData.Characteristics = Stmt->Value;
+  return Error::success();
+}
+
 Error ResourceFileWriter::visitLanguageStmt(const LanguageResource *Stmt) {
   RETURN_IF_ERROR(checkNumberFits(Stmt->Lang, 10, "Primary language ID"));
   RETURN_IF_ERROR(checkNumberFits(Stmt->SubLang, 6, "Sublanguage ID"));
@@ -195,6 +218,11 @@
   return Error::success();
 }
 
+Error ResourceFileWriter::visitVersionStmt(const VersionStmt *Stmt) {
+  ObjectData.VersionInfo = Stmt->Value;
+  return Error::success();
+}
+
 Error ResourceFileWriter::writeResource(
     const RCResource *Res,
     Error (ResourceFileWriter::*BodyWriter)(const RCResource *)) {
@@ -208,12 +236,16 @@
   RETURN_IF_ERROR(handleError(writeIdentifier(ResType), Res));
   RETURN_IF_ERROR(handleError(writeIdentifier(Res->ResName), Res));
 
+  // Apply the resource-local optional statements.
+  ContextKeeper RAII(this);
+  RETURN_IF_ERROR(handleError(Res->applyStmts(this), Res));
+
   padStream(sizeof(uint32_t));
   object::WinResHeaderSuffix HeaderSuffix{
       ulittle32_t(0), // DataVersion; seems to always be 0
       ulittle16_t(Res->getMemoryFlags()), ulittle16_t(ObjectData.LanguageInfo),
-      ulittle32_t(0),  // VersionInfo
-      ulittle32_t(0)}; // Characteristics
+      ulittle32_t(ObjectData.VersionInfo),
+      ulittle32_t(ObjectData.Characteristics)};
   writeObject(HeaderSuffix);
 
   uint64_t DataLoc = tell();
@@ -229,10 +261,123 @@
   return Error::success();
 }
 
+// --- NullResource helpers. --- //
+
 Error ResourceFileWriter::writeNullBody(const RCResource *) {
   return Error::success();
 }
 
+// --- AcceleratorsResource helpers. --- //
+
+Error ResourceFileWriter::writeSingleAccelerator(
+    const AcceleratorsResource::Accelerator &Obj, bool IsLastItem) {
+  using Accelerator = AcceleratorsResource::Accelerator;
+  using Opt = Accelerator::Options;
+
+  struct AccelTableEntry {
+    ulittle16_t Flags;
+    ulittle16_t ANSICode;
+    ulittle16_t Id;
+    uint16_t Padding;
+  } Entry{ulittle16_t(0), ulittle16_t(0), ulittle16_t(0), 0};
+
+  bool IsASCII = Obj.Flags & Opt::ASCII, IsVirtKey = Obj.Flags & Opt::VIRTKEY;
+
+  // Remove ASCII flags (which doesn't occur in .res files).
+  Entry.Flags = Obj.Flags & ~Opt::ASCII;
+
+  if (IsLastItem)
+    Entry.Flags |= 0x80;
+
+  RETURN_IF_ERROR(checkNumberFits<uint16_t>(Obj.Id, "ACCELERATORS entry ID"));
+  Entry.Id = ulittle16_t(Obj.Id);
+
+  auto createAccError = [&Obj](const char *Msg) {
+    return createError("Accelerator ID " + Twine(Obj.Id) + ": " + Msg);
+  };
+
+  if (IsASCII && IsVirtKey)
+    return createAccError("Accelerator can't be both ASCII and VIRTKEY");
+
+  if (!IsVirtKey && (Obj.Flags & (Opt::ALT | Opt::SHIFT | Opt::CONTROL)))
+    return createAccError("Can only apply ALT, SHIFT or CONTROL to VIRTKEY"
+                          " accelerators");
+
+  if (Obj.Event.isInt()) {
+    if (!IsASCII && !IsVirtKey)
+      return createAccError(
+          "Accelerator with a numeric event must be either ASCII"
+          " or VIRTKEY");
+
+    uint32_t EventVal = Obj.Event.getInt();
+    RETURN_IF_ERROR(
+        checkNumberFits<uint16_t>(EventVal, "Numeric event key ID"));
+    Entry.ANSICode = ulittle16_t(EventVal);
+    writeObject(Entry);
+    return Error::success();
+  }
+
+  StringRef Str = Obj.Event.getString();
+  bool IsWide;
+  stripQuotes(Str, IsWide);
+
+  if (Str.size() == 0 || Str.size() > 2)
+    return createAccError(
+        "Accelerator string events should have length 1 or 2");
+
+  if (Str[0] == '^') {
+    if (Str.size() == 1)
+      return createAccError("No character following '^' in accelerator event");
+    if (IsVirtKey)
+      return createAccError(
+          "VIRTKEY accelerator events can't be preceded by '^'");
+
+    char Ch = Str[1];
+    if (Ch >= 'a' && Ch <= 'z')
+      Entry.ANSICode = ulittle16_t(Ch - 'a' + 1);
+    else if (Ch >= 'A' && Ch <= 'Z')
+      Entry.ANSICode = ulittle16_t(Ch - 'A' + 1);
+    else
+      return createAccError("Control character accelerator event should be"
+                            " alphabetic");
+
+    writeObject(Entry);
+    return Error::success();
+  }
+
+  if (Str.size() == 2)
+    return createAccError("Event string should be one-character, possibly"
+                          " preceded by '^'");
+
+  uint8_t EventCh = Str[0];
+  // The original tool just warns in this situation. We chose to fail.
+  if (IsVirtKey && !isalnum(EventCh))
+    return createAccError("Non-alphanumeric characters cannot describe virtual"
+                          " keys");
+  if (EventCh > 0x7F)
+    return createAccError("Non-ASCII description of accelerator");
+
+  if (IsVirtKey)
+    EventCh = toupper(EventCh);
+  Entry.ANSICode = ulittle16_t(EventCh);
+  writeObject(Entry);
+  return Error::success();
+}
+
+Error ResourceFileWriter::writeAcceleratorsBody(const RCResource *Base) {
+  auto *Res = cast<AcceleratorsResource>(Base);
+  size_t AcceleratorId = 0;
+  for (auto &Acc : Res->Accelerators) {
+    ++AcceleratorId;
+    RETURN_IF_ERROR(
+        writeSingleAccelerator(Acc, AcceleratorId == Res->Accelerators.size()));
+  }
+  return Error::success();
+}
+
+// --- HTMLResource helpers. --- //
+
+
 Error ResourceFileWriter::writeHTMLBody(const RCResource *Base) {
   return appendFile(cast<HTMLResource>(Base)->HTMLLoc);
 }