[flang] Variadic macros, __VA_ARG__ and __VA_OPT__, and use
authorpeter klausler <pklausler@nvidia.com>
Mon, 5 Feb 2018 20:48:09 +0000 (12:48 -0800)
committerpeter klausler <pklausler@nvidia.com>
Mon, 5 Feb 2018 20:48:09 +0000 (12:48 -0800)
"default: CRASH_NO_CASE;" in place of prior macro.

Original-commit: flang-compiler/f18@21088961554347d28e88e14f9c82eeb0987867af

flang/idioms.cc
flang/idioms.h
flang/parse-tree.cc
flang/preprocessor.cc
flang/preprocessor.h
flang/prescan.cc

index 6510e19..cfdda03 100644 (file)
@@ -5,7 +5,7 @@
 
 namespace Fortran {
 
-void die(const char *msg, ...) {
+[[noreturn]] void die(const char *msg, ...) {
   va_list ap;
   va_start(ap, msg);
   std::fputs("\nfatal internal error: ", stderr);
index b455c1f..887d3b9 100644 (file)
@@ -52,7 +52,7 @@ template<typename... LAMBDAS>
 visitors(LAMBDAS... x) -> visitors<LAMBDAS...>;
 
 // Calls std::fprintf(stderr, ...), then abort().
-void die(const char *, ...);
+[[noreturn]] void die(const char *, ...);
 
 // Treat operator! as if it were a Boolean context, i.e. like if() and ? :,
 // when its operand is std::optional<>.
@@ -62,8 +62,8 @@ template<typename A> bool operator!(const std::optional<A> &x) {
 }  // namespace Fortran
 
 // For switch statements without default: labels.
-#define DEFAULT_CRASH \
-  default: die("no case at " __FILE__ "(%d)", __LINE__)
+#define CRASH_NO_CASE \
+  die("no case at " __FILE__ "(%d)", __LINE__)
 
 // For cheap assertions that should be applied in production.
 #define CHECK(x) \
index 52f950e..aa3dfce 100644 (file)
@@ -517,7 +517,7 @@ std::ostream &operator<<(std::ostream &o,
   case DefinedOperator::IntrinsicOperator::OR: return o << "OR";
   case DefinedOperator::IntrinsicOperator::EQV: return o << "EQV";
   case DefinedOperator::IntrinsicOperator::NEQV: return o << "NEQV";
-  DEFAULT_CRASH;
+  default: CRASH_NO_CASE;
   }
   return o;
 }
@@ -560,7 +560,7 @@ std::ostream &operator<<(std::ostream &o, Sign x) {
   switch (x) {
   case Sign::Positive: return o << "Positive";
   case Sign::Negative: return o << "Negative";
-  DEFAULT_CRASH;
+  default: CRASH_NO_CASE;
   }
   return o;
 }
@@ -617,7 +617,7 @@ std::ostream &operator<<(std::ostream &o,
   switch (x) {
   case TypeParamDefStmt::KindOrLength::Kind: o << "Kind"; break;
   case TypeParamDefStmt::KindOrLength::Length: o << "Length"; break;
-  DEFAULT_CRASH;
+  default: CRASH_NO_CASE;
   }
   return o;
 }
@@ -645,7 +645,7 @@ std::ostream &operator<<(std::ostream &o, const AccessSpec &x) {
   switch (x.v) {
   case AccessSpec::Kind::Public: return o << "(Public)";
   case AccessSpec::Kind::Private: return o << "(Private)";
-  DEFAULT_CRASH;
+  default: CRASH_NO_CASE;
   }
   return o;
 }
@@ -656,7 +656,7 @@ std::ostream &operator<<(std::ostream &o, const IntentSpec &x) {
   case IntentSpec::Intent::In: return o << "(Intent In)";
   case IntentSpec::Intent::Out: return o << "(Intent Out)";
   case IntentSpec::Intent::InOut: return o << "(Intent InOut)";
-  DEFAULT_CRASH;
+  default: CRASH_NO_CASE;
   }
   return o;
 }
@@ -678,7 +678,7 @@ std::ostream &operator<<(std::ostream &o,
   switch (x) {
   case ImplicitStmt::ImplicitNoneNameSpec::External: return o << "External";
   case ImplicitStmt::ImplicitNoneNameSpec::Type: return o << "Type";
-  DEFAULT_CRASH;
+  default: CRASH_NO_CASE;
   }
   return o;
 }
@@ -700,7 +700,7 @@ std::ostream &operator<<(std::ostream &o, const ImportStmt &x) {
     return o << "None)";
   case ImportStmt::Kind::All:
     return o << "All)";
-  DEFAULT_CRASH;
+  default: CRASH_NO_CASE;
   }
   return o;
 }
@@ -895,7 +895,7 @@ std::ostream &operator<<(std::ostream &o, const ConnectSpec::CharExpr::Kind x) {
   case ConnectSpec::CharExpr::Kind::Round: o << "Round"; break;
   case ConnectSpec::CharExpr::Kind::Sign: o << "Sign"; break;
   case ConnectSpec::CharExpr::Kind::Dispose: o << "Dispose"; break;
-  DEFAULT_CRASH;
+  default: CRASH_NO_CASE;
   }
   return o;
 }
@@ -910,7 +910,7 @@ std::ostream &operator<<(std::ostream &o, IoControlSpec::CharExpr::Kind x) {
   case IoControlSpec::CharExpr::Kind::Pad: o << "Pad"; break;
   case IoControlSpec::CharExpr::Kind::Round: o << "Round"; break;
   case IoControlSpec::CharExpr::Kind::Sign: o << "Sign"; break;
-  DEFAULT_CRASH;
+  default: CRASH_NO_CASE;
   }
   return o;
 }
@@ -939,7 +939,7 @@ std::ostream &operator<<(std::ostream &o, const InquireSpec::CharVar::Kind x) {
   case InquireSpec::CharVar::Kind::Stream: o << "Stream"; break;
   case InquireSpec::CharVar::Kind::Status: o << "Status"; break;
   case InquireSpec::CharVar::Kind::Write: o << "Write"; break;
-  DEFAULT_CRASH;
+  default: CRASH_NO_CASE;
   }
   return o;
 }
@@ -952,7 +952,7 @@ std::ostream &operator<<(std::ostream &o, const InquireSpec::IntVar::Kind x) {
   case InquireSpec::IntVar::Kind::Pos: o << "Pos"; break;
   case InquireSpec::IntVar::Kind::Recl: o << "Recl"; break;
   case InquireSpec::IntVar::Kind::Size: o << "Size"; break;
-  DEFAULT_CRASH;
+  default: CRASH_NO_CASE;
   }
   return o;
 }
@@ -963,7 +963,7 @@ std::ostream &operator<<(std::ostream &o, const InquireSpec::LogVar::Kind x) {
   case InquireSpec::LogVar::Kind::Named: o << "Named"; break;
   case InquireSpec::LogVar::Kind::Opened: o << "Opened"; break;
   case InquireSpec::LogVar::Kind::Pending: o << "Pending"; break;
-  DEFAULT_CRASH;
+  default: CRASH_NO_CASE;
   }
   return o;
 }
@@ -985,7 +985,7 @@ std::ostream &operator<<(std::ostream &o, const IntrinsicTypeDataEditDesc &x) {
   case IntrinsicTypeDataEditDesc::Kind::L: o << "L "; break;
   case IntrinsicTypeDataEditDesc::Kind::A: o << "A "; break;
   case IntrinsicTypeDataEditDesc::Kind::D: o << "D "; break;
-  DEFAULT_CRASH;
+  default: CRASH_NO_CASE;
   }
   return o << x.width << ' ' << x.digits << ' ' << x.exponentWidth << ')';
 }
@@ -995,7 +995,7 @@ std::ostream &operator<<(std::ostream &o, StopStmt::Kind x) {
   switch (x) {
   case StopStmt::Kind::Stop: o << "Stop"; break;
   case StopStmt::Kind::ErrorStop: o << "ErrorStop"; break;
-  DEFAULT_CRASH;
+  default: CRASH_NO_CASE;
   }
   return o;
 }
@@ -1041,7 +1041,7 @@ std::ostream &operator<<(std::ostream &o, const ControlEditDesc &x) {
   case ControlEditDesc::Kind::RP: o << "RP "; break;
   case ControlEditDesc::Kind::DC: o << "DC "; break;
   case ControlEditDesc::Kind::DP: o << "DP "; break;
-  DEFAULT_CRASH;
+  default: CRASH_NO_CASE;
   }
   return o << x.count << ')';
 }
@@ -1064,7 +1064,7 @@ std::ostream &operator<<(std::ostream &o, const UseStmt &x) {
     case UseStmt::ModuleNature::Non_Intrinsic:
       o << "Non_Intrinsic";
       break;
-    DEFAULT_CRASH;
+    default: CRASH_NO_CASE;
     }
   } else {
     o << "()";
@@ -1082,7 +1082,7 @@ std::ostream &operator<<(std::ostream &o, const ProcedureStmt::Kind &x) {
   switch (x) {
   case ProcedureStmt::Kind::ModuleProcedure: return o << "ModuleProcedure";
   case ProcedureStmt::Kind::Procedure: return o << "Procedure";
-  DEFAULT_CRASH;
+  default: CRASH_NO_CASE;
   }
   return o;
 }
index 9fe3fff..8c4882e 100644 (file)
@@ -57,14 +57,50 @@ std::string TokenSequence::ToString() const {
   return {&char_[0], char_.size()};
 }
 
+void TokenSequence::clear() {
+  start_.clear();
+  nextStart_ = 0;
+  char_.clear();
+}
+
+void TokenSequence::push_back(const char *s, size_t bytes) {
+  for (size_t j{0}; j < bytes; ++j) {
+    AddChar(s[j]);
+  }
+  EndToken();
+}
+
+void TokenSequence::push_back(const CharPointerWithLength &t) {
+  push_back(&t[0], t.size());
+}
+void TokenSequence::push_back(const std::string &s) {
+  push_back(s.data(), s.size());
+}
+
+void TokenSequence::push_back(const std::stringstream &ss) {
+  push_back(ss.str());
+}
+
+void TokenSequence::pop_back() {
+  nextStart_ = start_.back();
+  start_.pop_back();
+  char_.resize(nextStart_);
+}
+
+void TokenSequence::shrink_to_fit() {
+  start_.shrink_to_fit();
+  char_.shrink_to_fit();
+}
+
 Definition::Definition(const TokenSequence &repl, size_t firstToken,
                        size_t tokens)
   : replacement_{Tokenize({}, repl, firstToken, tokens)} {}
 
 Definition::Definition(const std::vector<std::string> &argNames,
                        const TokenSequence &repl, size_t firstToken,
-                       size_t tokens)
+                       size_t tokens, bool isVariadic)
   : isFunctionLike_{true}, argumentCount_(argNames.size()),
+    isVariadic_{isVariadic},
     replacement_{Tokenize(argNames, repl, firstToken, tokens)} {}
 
 Definition::Definition(const std::string &predefined)
@@ -95,19 +131,15 @@ TokenSequence Definition::Tokenize(const std::vector<std::string> &argNames,
   }
   TokenSequence result;
   for (size_t j{0}; j < tokens; ++j) {
-    size_t bytes{token[firstToken + j].size()};
-    if (bytes == 0) {
-      continue;
-    }
-    const char *text{token[firstToken + j].data()};
-    if (bytes > 0 && IsIdentifierFirstCharacter(*text)) {
-      auto it = args.find(token[firstToken + j].ToString());
+    CharPointerWithLength tok{token[firstToken + j]};
+    if (IsIdentifierFirstCharacter(tok)) {
+      auto it = args.find(tok.ToString());
       if (it != args.end()) {
         result.push_back(it->second);
         continue;
       }
     }
-    result.push_back(text, bytes);
+    result.push_back(tok);
   }
   return result;
 }
@@ -115,13 +147,24 @@ TokenSequence Definition::Tokenize(const std::vector<std::string> &argNames,
 TokenSequence Definition::Apply(const std::vector<TokenSequence> &args) {
   TokenSequence result;
   bool pasting{false};
+  bool skipping{false};
+  int parenthesesNesting{0};
   size_t tokens{replacement_.size()};
   for (size_t j{0}; j < tokens; ++j) {
     const CharPointerWithLength &token{replacement_[j]};
     size_t bytes{token.size()};
-    const char *text{token.data()};
-    if (bytes == 2 && *text == '~') {
-      size_t index = text[1] - 'A';
+    if (skipping) {
+      if (bytes == 1) {
+        if (token[0] == '(') {
+           ++parenthesesNesting;
+        } else if (token[0] == ')') {
+          skipping = --parenthesesNesting > 0;
+        }
+      }
+      continue;
+    }
+    if (bytes == 2 && token[0] == '~') {
+      size_t index = token[1] - 'A';
       if (index >= args.size()) {
         continue;
       }
@@ -138,8 +181,8 @@ TokenSequence Definition::Apply(const std::vector<TokenSequence> &args) {
         }
         std::string strung{'"'};
         for (size_t k{0}; k < argTokens; ++k) {
+          const CharPointerWithLength &arg{args[index][k]};
           size_t argBytes{args[index][k].size()};
-          const char *arg{args[index][k].data()};
           for (size_t n{0}; n < argBytes; ++n) {
             char ch{arg[n]};
             if (ch == '"' || ch == '\\') {
@@ -159,7 +202,7 @@ TokenSequence Definition::Apply(const std::vector<TokenSequence> &args) {
           }
         }
       }
-    } else if (bytes == 2 && text[0] == '#' && text[1] == '#') {
+    } else if (bytes == 2 && token[0] == '#' && token[1] == '#') {
       // Token pasting operator in body (not expanded argument); discard any
       // immediately preceding white space, then reopen the last token.
       while (!result.empty() && result[result.size() - 1].IsBlank()) {
@@ -171,7 +214,33 @@ TokenSequence Definition::Apply(const std::vector<TokenSequence> &args) {
       }
     } else if (pasting && token.IsBlank()) {
       // Delete whitespace immediately following ## in the body.
+    } else if (bytes == 11 && isVariadic_ &&
+               token.ToString() == "__VA_ARGS__") {
+      for (size_t k{argumentCount_}; k < args.size(); ++k) {
+        if (k > argumentCount_) {
+          result.push_back(","s);
+        }
+        for (size_t n{0}; n < args[k].size(); ++n) {
+          result.push_back(args[k][n]);
+        }
+      }
+    } else if (bytes == 10 && isVariadic_ &&
+               token.ToString() == "__VA_OPT__" &&
+               j + 2 < tokens &&
+               replacement_[j + 1].ToString() == "(" &&
+               parenthesesNesting == 0) {
+      parenthesesNesting = 1;
+      skipping = args.size() == argumentCount_;
+      ++j;
     } else {
+      if (bytes == 1 && parenthesesNesting > 0 && token[0] == '(') {
+        ++parenthesesNesting;
+      } else if (bytes == 1 && parenthesesNesting > 0 && token[0] == ')') {
+        if (--parenthesesNesting == 0) {
+          skipping = false;
+          continue;
+        }
+      }
       result.push_back(token);
     }
   }
@@ -285,7 +354,8 @@ bool Preprocessor::MacroReplacement(const TokenSequence &input,
       }
     }
     if (k >= tokens ||
-        argStart.size() != def.argumentCount()) {
+        argStart.size() < def.argumentCount() ||
+        (argStart.size() > def.argumentCount() && !def.isVariadic())) {
       result->push_back(token);
       continue;
     }
@@ -296,7 +366,7 @@ bool Preprocessor::MacroReplacement(const TokenSequence &input,
       size_t count{(k + 1 == argStart.size() ? j : argStart[k+1] - 1) - at};
       TokenSequence actual;
       for (; count-- > 0; ++at) {
-        actual.push_back(input[at].data(), input[at].size());
+        actual.push_back(input[at]);
       }
       args.emplace_back(std::move(actual));
     }
@@ -393,14 +463,19 @@ bool Preprocessor::Directive(const TokenSequence &dir) {
     if (++j < tokens && dir[j].size() == 1 && dir[j][0] == '(') {
       j = SkipBlanks(dir, j + 1, tokens);
       std::vector<std::string> argName;
+      bool isVariadic{false};
       if (dir[j].ToString() != ")") {
         while (true) {
           std::string an{dir[j].ToString()};
-          if (an.empty() || !IsIdentifierFirstCharacter(an[0])) {
-            Complain("#define: missing or invalid argument name");
-            return false;
+          if (an == "...") {
+            isVariadic = true;
+          } else {
+            if (an.empty() || !IsIdentifierFirstCharacter(an[0])) {
+              Complain("#define: missing or invalid argument name");
+              return false;
+            }
+            argName.push_back(an);
           }
-          argName.push_back(an);
           j = SkipBlanks(dir, j + 1, tokens);
           if (j == tokens) {
             Complain("#define: malformed argument list");
@@ -415,7 +490,7 @@ bool Preprocessor::Directive(const TokenSequence &dir) {
             return false;
           }
           j = SkipBlanks(dir, j + 1, tokens);
-          if (j == tokens) {
+          if (j == tokens || isVariadic) {
             Complain("#define: malformed argument list");
             return false;
           }
@@ -428,7 +503,8 @@ bool Preprocessor::Directive(const TokenSequence &dir) {
       }
       j = SkipBlanks(dir, j + 1, tokens);
       definitions_.emplace(
-        std::make_pair(nameToken, Definition{argName, dir, j, tokens - j}));
+        std::make_pair(nameToken,
+                       Definition{argName, dir, j, tokens - j, isVariadic}));
     } else {
       definitions_.emplace(
         std::make_pair(nameToken, Definition{dir, j, tokens - j}));
@@ -560,21 +636,22 @@ void Preprocessor::Complain(const std::string &message) {
 }
 
 // Precedence level codes used here to accommodate mixed Fortran and C:
-// 13: parentheses and constants, logical !, bitwise ~
-// 12: unary + and -
-// 11: **
-// 10: *, /, % (modulus)
-//  9: + and -
-//  8: << and >>
-//  7: bitwise &
-//  6: bitwise ^
-//  5: bitwise |
-//  4: relations (.EQ., ==, &c.)
-//  3: .NOT.
-//  2: .AND., &&
-//  1: .OR., ||
-//  0: .EQV. and .NEQV. / .XOR.
-// TODO: Ternary and comma operators?
+// 15: parentheses and constants, logical !, bitwise ~
+// 14: unary + and -
+// 13: **
+// 12: *, /, % (modulus)
+// 11: + and -
+//  0: << and >>
+//  9: bitwise &
+//  8: bitwise ^
+//  7: bitwise |
+//  6: relations (.EQ., ==, &c.)
+//  5: .NOT.
+//  4: .AND., &&
+//  3: .OR., ||
+//  2: .EQV. and .NEQV. / .XOR.
+//  1: ? :
+//  0: ,
 static std::int64_t ExpressionValue(const TokenSequence &token,
                                     int minimumPrecedence,
                                     size_t *atToken, std::string *errors) {
@@ -583,23 +660,26 @@ static std::int64_t ExpressionValue(const TokenSequence &token,
     TIMES, DIVIDE, MODULUS, ADD, SUBTRACT, LEFTSHIFT, RIGHTSHIFT,
     BITAND, BITXOR, BITOR,
     LT, LE, EQ, NE, GE, GT,
-    NOT, AND, OR, EQV, NEQV
+    NOT, AND, OR, EQV, NEQV,
+    SELECT, COMMA
   };
   static const int precedence[]{
-    13, 13, 13, 13,  // (), 0, !, ~
-    12, 12,  // unary +, -
-    11, 10, 10, 10, 9, 9, 8, 8,  // **, *, /, %, +, -, <<, >>
-    7, 6, 5,  // &, ^, |
-    4, 4, 4, 4, 4, 4,  // relations
-    3, 2, 1, 0, 0  // .NOT., .AND., .OR., .EQV., .NEQV.
+    15, 15, 15, 15,  // (), 0, !, ~
+    14, 14,  // unary +, -
+    13, 12, 12, 12, 11, 11, 10, 10,  // **, *, /, %, +, -, <<, >>
+    9, 8, 7,  // &, ^, |
+    6, 6, 6, 6, 6, 6,  // relations
+    5, 4, 3, 2, 2,  // .NOT., .AND., .OR., .EQV., .NEQV.
+    1, 0  // ?: and ,
   };
   static const int operandPrecedence[]{
-    0, -1, 13, 13,
-    13, 13,
-    11, 10, 10, 10, 9, 9, 9, 9,
-    7, 6, 5,
-    5, 5, 5, 5, 5, 5,
-    4, 2, 1, 1, 1
+    0, -1, 15, 15,
+    15, 15,
+    13, 12, 12, 12, 11, 11, 11, 11,
+    9, 8, 7,
+    7, 7, 7, 7, 7, 7,
+    6, 4, 3, 3, 3,
+    1, 0
   };
 
   static std::map<std::string, enum Operator> opNameMap;
@@ -629,6 +709,8 @@ static std::int64_t ExpressionValue(const TokenSequence &token,
     opNameMap[".or."] = opNameMap[".o."] = opNameMap["||"] = OR;
     opNameMap[".eqv."] = EQV;
     opNameMap[".neqv."] = opNameMap[".xor."] = opNameMap[".x."] = NEQV;
+    opNameMap["?"] = SELECT;
+    opNameMap[","] = COMMA;
   }
 
   size_t tokens{token.size()};
@@ -700,7 +782,7 @@ static std::int64_t ExpressionValue(const TokenSequence &token,
     case NOT:
       left = -!left;
       break;
-    DEFAULT_CRASH;
+    default: CRASH_NO_CASE;
     }
   }
   if (!errors->empty() || *atToken >= tokens) {
@@ -813,7 +895,19 @@ static std::int64_t ExpressionValue(const TokenSequence &token,
     return -(!left == !right);
   case NEQV:
     return -(!left != !right);
-  DEFAULT_CRASH;
+  case SELECT:
+    if (*atToken >= tokens || token[*atToken].ToString() != ":") {
+      *errors = "':' required in selection expression";
+      return left;
+    } else {
+      ++*atToken;
+      std::int64_t third{ExpressionValue(token, operandPrecedence[op],
+                                         atToken, errors)};
+      return left != 0 ? right : third;
+    }
+  case COMMA:
+    return right;
+  default: CRASH_NO_CASE;
   }
   return 0;  // silence compiler warning
 }
index 29f8d1b..6c473b0 100644 (file)
@@ -41,7 +41,6 @@ class CharPointerWithLength {
 
   bool empty() const { return bytes_ == 0; }
   size_t size() const { return bytes_; }
-  const char *data() const { return data_; }
   const char &operator[](size_t j) const { return data_[j]; }
 
   bool IsBlank() const;
@@ -56,10 +55,9 @@ class CharPointerWithLength {
 // Specializations to enable std::unordered_map<CharPointerWithLength, ...>
 template<> struct std::hash<Fortran::CharPointerWithLength> {
   size_t operator()(const Fortran::CharPointerWithLength &x) const {
-    size_t hash{0};
-    const char *p{x.data()}, *limit{p + x.size()};
-    for (; p < limit; ++p) {
-      hash = (hash * 31) ^ *p;
+    size_t hash{0}, bytes{x.size()};
+    for (size_t j{0}; j < bytes; ++j) {
+      hash = (hash * 31) ^ x[j];
     }
     return hash;
   }
@@ -69,8 +67,8 @@ template<> struct std::equal_to<Fortran::CharPointerWithLength> {
   bool operator()(const Fortran::CharPointerWithLength &x,
                   const Fortran::CharPointerWithLength &y) const {
     return x.size() == y.size() &&
-           std::memcmp(static_cast<const void *>(x.data()),
-                       static_cast<const void *>(y.data()),
+           std::memcmp(static_cast<const void *>(&x[0]),
+                       static_cast<const void *>(&y[0]),
                        x.size()) == 0;
   }
 };
@@ -128,36 +126,13 @@ class TokenSequence {
   bool empty() const { return start_.empty(); }
   size_t size() const { return start_.size(); }
   const char *data() const { return &char_[0]; }
-
-  void clear() {
-    start_.clear();
-    nextStart_ = 0;
-    char_.clear();
-  }
-
-  void push_back(const char *s, size_t bytes) {
-    for (size_t j{0}; j < bytes; ++j) {
-      AddChar(s[j]);
-    }
-    EndToken();
-  }
-
-  void push_back(const CharPointerWithLength &t) {
-    push_back(t.data(), t.size());
-  }
-
-  void push_back(const std::stringstream &ss) { push_back(ss.str()); }
-
-  void pop_back() {
-    nextStart_ = start_.back();
-    start_.pop_back();
-    char_.resize(nextStart_);
-  }
-
-  void shrink_to_fit() {
-    start_.shrink_to_fit();
-    char_.shrink_to_fit();
-  }
+  void clear();
+  void push_back(const char *, size_t);
+  void push_back(const CharPointerWithLength &);
+  void push_back(const std::string &);
+  void push_back(const std::stringstream &);
+  void pop_back();
+  void shrink_to_fit();
 
  private:
   std::vector<int> start_;
@@ -170,7 +145,7 @@ class Definition {
  public:
   Definition(const TokenSequence &, size_t firstToken, size_t tokens);
   Definition(const std::vector<std::string> &argNames, const TokenSequence &,
-             size_t firstToken, size_t tokens);
+             size_t firstToken, size_t tokens, bool isVariadic = false);
   explicit Definition(const std::string &predefined);
 
   bool isFunctionLike() const { return isFunctionLike_; }
index 9d1a0d9..f554889 100644 (file)
@@ -216,10 +216,13 @@ bool Prescanner::NextToken(TokenSequence *tokens) {
     }
     preventHollerith_ = false;
   } else if (*at_ == '.') {
-    if (isdigit(EmitCharAndAdvance(tokens, '.'))) {
+    char nch{EmitCharAndAdvance(tokens, '.')};
+    if (isdigit(nch)) {
       while (isdigit(EmitCharAndAdvance(tokens, *at_))) {
       }
       ExponentAndKind(tokens);
+    } else if (nch == '.' && EmitCharAndAdvance(tokens, '.') == '.') {
+      EmitCharAndAdvance(tokens, '.');  // variadic macro definition ellipsis
     }
     preventHollerith_ = false;
   } else if (IsNameChar(*at_)) {