[flang] Support localizable formatted messages.
authorpeter klausler <pklausler@nvidia.com>
Wed, 21 Feb 2018 20:12:52 +0000 (12:12 -0800)
committerpeter klausler <pklausler@nvidia.com>
Wed, 21 Feb 2018 20:12:52 +0000 (12:12 -0800)
Original-commit: flang-compiler/f18@e974321494b4fcecdb9bd0a5f82f0624c1a33696
Reviewed-on: https://github.com/flang-compiler/f18/pull/14

flang/lib/parser/message.cc
flang/lib/parser/message.h
flang/lib/parser/parse-state.h
flang/lib/parser/preprocessor.cc
flang/lib/parser/prescan.cc
flang/lib/parser/prescan.h
flang/lib/parser/token-parsers.h

index 67591c2..e3d4927 100644 (file)
@@ -1,4 +1,7 @@
 #include "message.h"
+#include <cstdarg>
+#include <cstdio>
+#include <cstring>
 
 namespace Fortran {
 namespace parser {
@@ -10,12 +13,63 @@ std::ostream &operator<<(std::ostream &o, const MessageFixedText &t) {
   return o;
 }
 
+std::string MessageFixedText::ToString() const {
+  return std::string{str_, /*not in std::*/ strnlen(str_, bytes_)};
+}
+
+MessageFormattedText::MessageFormattedText(MessageFixedText text, ...) {
+  const char *p{text.str()};
+  std::string asString;
+  if (p[text.size()] != '\0') {
+    // not NUL-terminated
+    asString = text.ToString();
+    p = asString.data();
+  }
+  char buffer[256];
+  va_list ap;
+  va_start(ap, text);
+  vsnprintf(buffer, sizeof buffer, p, ap);
+  va_end(ap);
+  string_ = buffer;
+}
+
+MessageFixedText MessageExpectedText::AsMessageFixedText() const {
+  if (str_ != nullptr) {
+    return {str_, bytes_};
+  }
+  static char chars[256];
+  if (chars[1] == '\0') {
+    // one-time initialization of array used for permanant single-byte string
+    // pointers
+    for (size_t j{0}; j < sizeof chars; ++j) {
+      chars[j] = j;
+    }
+  }
+  return {&chars[static_cast<unsigned char>(singleton_)], 1};
+}
+
 Provenance Message::Emit(
     std::ostream &o, const AllSources &sources, bool echoSourceLine) const {
   if (!context_ || context_->Emit(o, sources, false) != provenance_) {
     sources.Identify(o, provenance_, "", echoSourceLine);
   }
-  o << "   " << text_ << extra_ << '\n';
+  o << "   ";
+  if (string_.empty()) {
+    if (isExpectedText_) {
+      std::string goal{text_.ToString()};
+      if (goal == "\n") {
+        o << "expected end of line"_en_US;
+      } else {
+        o << MessageFormattedText("expected '%s'"_en_US, goal.data())
+                 .MoveString();
+      }
+    } else {
+      o << text_;
+    }
+  } else {
+    o << string_;
+  }
+  o << '\n';
   return provenance_;
 }
 
index abefb60..aa28151 100644 (file)
@@ -30,7 +30,7 @@ public:
   size_t size() const { return bytes_; }
   bool empty() const { return bytes_ == 0; }
 
-  std::string ToString() const { return std::string(str_, bytes_); }
+  std::string ToString() const;
 
 private:
   const char *str_{nullptr};
@@ -43,6 +43,28 @@ constexpr MessageFixedText operator""_en_US(const char str[], size_t n) {
 
 std::ostream &operator<<(std::ostream &, const MessageFixedText &);
 
+class MessageFormattedText {
+public:
+  MessageFormattedText(MessageFixedText, ...);
+  std::string MoveString() { return std::move(string_); }
+
+private:
+  std::string string_;
+};
+
+// Represents a formatted rendition of "expected '%s'"_en_US on a constant text.
+class MessageExpectedText {
+public:
+  MessageExpectedText(const char *s, size_t n) : str_{s}, bytes_{n} {}
+  explicit MessageExpectedText(char ch) : singleton_{ch} {}
+  MessageFixedText AsMessageFixedText() const;
+
+private:
+  const char *str_{nullptr};
+  char singleton_;
+  size_t bytes_{1};
+};
+
 class Message;
 using MessageContext = std::shared_ptr<Message>;
 
@@ -52,6 +74,11 @@ public:
   Message(const Message &) = default;
   Message(Provenance p, MessageFixedText t, MessageContext c = nullptr)
     : provenance_{p}, text_{t}, context_{c} {}
+  Message(Provenance p, MessageFormattedText &&s, MessageContext c = nullptr)
+    : provenance_{p}, string_{s.MoveString()}, context_{c} {}
+  Message(Provenance p, MessageExpectedText t, MessageContext c = nullptr)
+    : provenance_{p}, text_{t.AsMessageFixedText()},
+      isExpectedText_{true}, context_{c} {}
   Message(Message &&) = default;
   Message &operator=(const Message &that) = default;
   Message &operator=(Message &&that) = default;
@@ -61,26 +88,16 @@ public:
   }
 
   Provenance provenance() const { return provenance_; }
-  MessageFixedText text() const { return text_; }
-  std::string extra() const { return extra_; }
   MessageContext context() const { return context_; }
 
-  Message &operator+=(std::string s) {
-    extra_ += s;
-    return *this;
-  }
-  Message &operator+=(char ch) {
-    extra_ += ch;
-    return *this;
-  }
-
   Provenance Emit(
       std::ostream &, const AllSources &, bool echoSourceLine = true) const;
 
 private:
   Provenance provenance_;
   MessageFixedText text_;
-  std::string extra_;
+  bool isExpectedText_{false};  // implies "expected '%s'"_en_US
+  std::string string_;
   MessageContext context_;
 };
 
index e6a79d3..14f7cf3 100644 (file)
@@ -127,12 +127,30 @@ public:
   }
 
   Message &PutMessage(MessageFixedText t) { return PutMessage(p_, t); }
+  Message &PutMessage(MessageFormattedText &&t) {
+    return PutMessage(p_, std::move(t));
+  }
+  Message &PutMessage(MessageExpectedText &&t) {
+    return PutMessage(p_, std::move(t));
+  }
   Message &PutMessage(const char *at, MessageFixedText t) {
     return PutMessage(GetProvenance(at), t);
   }
+  Message &PutMessage(const char *at, MessageFormattedText &&t) {
+    return PutMessage(GetProvenance(at), std::move(t));
+  }
+  Message &PutMessage(const char *at, MessageExpectedText &&t) {
+    return PutMessage(GetProvenance(at), std::move(t));
+  }
   Message &PutMessage(Provenance at, MessageFixedText t) {
     return messages_.Put(Message{at, t, context_});
   }
+  Message &PutMessage(Provenance at, MessageFormattedText &&t) {
+    return messages_.Put(Message{at, std::move(t), context_});
+  }
+  Message &PutMessage(Provenance at, MessageExpectedText &&t) {
+    return messages_.Put(Message{at, std::move(t), context_});
+  }
 
   bool IsAtEnd() const { return p_ >= limit_; }
 
index d07be68..f7461d1 100644 (file)
@@ -466,13 +466,14 @@ bool Preprocessor::Directive(const TokenSequence &dir, Prescanner *prescanner) {
   }
   if (dirName == "ifdef" || dirName == "ifndef") {
     if (nameToken.empty()) {
-      prescanner->Complain("#"_en_US) += dirName + ": missing name";
+      prescanner->Complain(
+          MessageFormattedText("#%s: missing name"_en_US, dirName.data()));
       return false;
     }
     j = SkipBlanks(dir, j + 1, tokens);
     if (j != tokens) {
-      prescanner->Complain("#"_en_US) +=
-          dirName + ": excess tokens at end of directive";
+      prescanner->Complain(MessageFormattedText(
+          "#%s: excess tokens at end of directive"_en_US, dirName.data()));
       return false;
     }
     if (IsNameDefined(nameToken) == (dirName == "ifdef")) {
@@ -533,11 +534,13 @@ bool Preprocessor::Directive(const TokenSequence &dir, Prescanner *prescanner) {
     return true;
   }
   if (dirName == "error") {
-    prescanner->Complain("#error: "_en_US) += dir.ToString();
+    prescanner->Complain(
+        MessageFormattedText("#error: %s"_en_US, dir.ToString().data()));
     return false;
   }
   if (dirName == "warning") {
-    prescanner->Complain("#warning: "_en_US) += dir.ToString();
+    prescanner->Complain(
+        MessageFormattedText("#warning: %s"_en_US, dir.ToString().data()));
     return true;
   }
   if (dirName == "include") {
@@ -569,15 +572,16 @@ bool Preprocessor::Directive(const TokenSequence &dir, Prescanner *prescanner) {
     std::stringstream error;
     const SourceFile *included{allSources_->Open(include, &error)};
     if (included == nullptr) {
-      prescanner->Complain("#include: "_en_US) += error.str();
+      prescanner->Complain(
+          MessageFormattedText("#include: %s"_en_US, error.str().data()));
       return false;
     }
     ProvenanceRange fileRange{
         allSources_->AddIncludedFile(*included, dir.GetProvenanceRange())};
     return Prescanner{*prescanner}.Prescan(fileRange);
   }
-  prescanner->Complain("#"_en_US) +=
-      dirName + ": unknown or unimplemented directive";
+  prescanner->Complain(MessageFormattedText(
+      "#%s: unknown or unimplemented directive"_en_US, dirName.data()));
   return false;
 }
 
@@ -615,7 +619,8 @@ bool Preprocessor::SkipDisabledConditionalCode(const std::string &dirName,
       }
     }
   }
-  prescanner->Complain("#"_en_US) += dirName + ": missing #endif";
+  prescanner->Complain(
+      MessageFormattedText("#%s: missing #endif"_en_US, dirName.data()));
   return false;
 }
 
index 0e93d36..76c6488 100644 (file)
@@ -92,6 +92,10 @@ Message &Prescanner::Complain(MessageFixedText text) {
   return messages_->Put({GetCurrentProvenance(), text});
 }
 
+Message &Prescanner::Complain(MessageFormattedText &&text) {
+  return messages_->Put({GetCurrentProvenance(), std::move(text)});
+}
+
 void Prescanner::NextLine() {
   void *vstart{static_cast<void *>(const_cast<char *>(lineStart_))};
   void *v{std::memchr(vstart, '\n', limit_ - lineStart_)};
@@ -436,7 +440,8 @@ bool Prescanner::IncludeLine(const char *p) {
     allSources->PopSearchPathDirectory();
   }
   if (included == nullptr) {
-    messages_->Put({provenance, "INCLUDE: "_en_US}) += error.str();
+    messages_->Put({provenance,
+        MessageFormattedText("INCLUDE: %s"_en_US, error.str().data())});
     anyFatalErrors_ = true;
     return true;
   }
index eb66194..50198f0 100644 (file)
@@ -50,6 +50,7 @@ public:
   std::optional<TokenSequence> NextTokenizedLine();
   Provenance GetCurrentProvenance() const { return GetProvenance(at_); }
   Message &Complain(MessageFixedText);
+  Message &Complain(MessageFormattedText &&);
 
 private:
   void BeginSourceLine(const char *at) {
index 4b8ea9f..9e7135f 100644 (file)
@@ -72,11 +72,7 @@ public:
       result.reset();
     }
     if (!result) {
-      if (good == '\n') {
-        state->PutMessage(at, "expected end of line"_en_US);
-      } else {
-        state->PutMessage(at, "expected '"_en_US) += std::string{good} + '\'';
-      }
+      state->PutMessage(at, MessageExpectedText{good});
     }
     return {result};
   }
@@ -103,7 +99,7 @@ public:
   using resultType = Success;
   constexpr TokenStringMatch(const TokenStringMatch &) = default;
   constexpr TokenStringMatch(const char *str, size_t n)
-    : str_{str}, length_{n} {}
+    : str_{str}, bytes_{n} {}
   constexpr TokenStringMatch(const char *str) : str_{str} {}
   std::optional<Success> Parse(ParseState *state) const {
     auto at = state->GetLocation();
@@ -112,10 +108,10 @@ public:
     }
     const char *p{str_};
     std::optional<char> ch;  // initially empty
-    for (size_t j{0}; j < length_ && *p != '\0'; ++j, ++p) {
+    for (size_t j{0}; j < bytes_ && *p != '\0'; ++j, ++p) {
       const auto spaceSkipping{*p == ' '};
       if (spaceSkipping) {
-        if (j + 1 == length_ || p[1] == ' ' || p[1] == '\0') {
+        if (j + 1 == bytes_ || p[1] == ' ' || p[1] == '\0') {
           continue;  // redundant; ignore
         }
       }
@@ -133,7 +129,7 @@ public:
       } else if (*ch == tolower(*p)) {
         ch.reset();
       } else {
-        (state->PutMessage(at, "expected '"_en_US) += str_) += '\'';
+        state->PutMessage(at, MessageExpectedText{str_, bytes_});
         return {};
       }
     }
@@ -142,7 +138,7 @@ public:
 
 private:
   const char *const str_;
-  const size_t length_{std::numeric_limits<size_t>::max()};
+  const size_t bytes_{std::numeric_limits<size_t>::max()};
 };
 
 constexpr TokenStringMatch operator""_tok(const char str[], size_t n) {