[flang] More precise source locations on error messages. Anticipate module file...
authorpeter klausler <pklausler@nvidia.com>
Thu, 15 Feb 2018 18:42:36 +0000 (10:42 -0800)
committerGitHub <noreply@github.com>
Thu, 15 Feb 2018 23:58:44 +0000 (15:58 -0800)
Original-commit: flang-compiler/f18@f4faadd3897fa90f705c50e432e1e7c7343f90cd
Reviewed-on: https://github.com/flang-compiler/f18/pull/9
Tree-same-pre-rewrite: false

13 files changed:
flang/CMakeLists.txt
flang/lib/parser/debug-parser.h
flang/lib/parser/grammar.h
flang/lib/parser/idioms.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/provenance.cc
flang/lib/parser/provenance.h
flang/lib/parser/token-sequence.cc
flang/lib/parser/token-sequence.h
flang/tools/f18/f18.cc

index 833fac5..8dc9180 100644 (file)
@@ -6,6 +6,9 @@ set(CMAKE_INSTALL_RPATH "${GCC}/lib64")
 set(CMAKE_BUILD_WITH_INSTALL_RPATH true)
 
 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -std=c++17")
+set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DDEBUG")
+set(CMAKE_CXX_FLAGS_MINSIZEREL "-O2 '-DCHECK=(void)'")
+set(CMAKE_CXX_FLAGS_DEBUG "-g -DDEBUG")
 
 set(SOURCES_F18
   tools/f18/f18.cc
index 76daaf6..3696a1c 100644 (file)
@@ -21,9 +21,9 @@ public:
   constexpr DebugParser(const char *str, size_t n) : str_{str}, length_{n} {}
   std::optional<Success> Parse(ParseState *state) const {
     if (auto context = state->context()) {
-      std::cout << *context;
+      context->Emit(std::cout, *state->cooked().allSources());
     }
-    state->cooked().allSources().Identify(
+    state->cooked().allSources()->Identify(
         std::cout, state->GetProvenance(), "");
     std::cout << "   parser debug: " << std::string{str_, length_} << '\n';
     return {Success{}};
index 24e53e6..b15b693 100644 (file)
@@ -184,11 +184,11 @@ template<typename PA> inline constexpr auto unterminatedStatement(const PA &p) {
                                    maybe(label), isLabelOk, spaces >> p);
 }
 
-constexpr auto endOfLine =
-    CharMatch<'\n'>{} || fail<char>("expected end of line");
+constexpr auto endOfLine = CharMatch<'\n'>{} / skipMany("\n"_tok) ||
+    fail<char>("expected end of line");
 
-constexpr auto
-    endOfStmt = spaces >> CharMatch<';'>{} / skipMany(";"_tok) || endOfLine;
+constexpr auto endOfStmt = spaces >>
+    (CharMatch<';'>{} / skipMany(";"_tok) / maybe(endOfLine) || endOfLine);
 
 template<typename PA> inline constexpr auto statement(const PA &p) {
   return unterminatedStatement(p) / endOfStmt;
@@ -372,8 +372,10 @@ struct StartNewSubprogram {
 } startNewSubprogram;
 
 TYPE_PARSER(construct<Program>{}(
-                some(startNewSubprogram >> Parser<ProgramUnit>{} / endOfLine)) /
-    skipMany(endOfLine) / consumedAllInput)
+    // statements consume only trailing noise; consume leading noise here.
+    skipMany("\n"_tok) >>
+    some(startNewSubprogram >> Parser<ProgramUnit>{} / endOfLine) /
+        consumedAllInput))
 
 // R502 program-unit ->
 //        main-program | external-subprogram | module | submodule | block-data
index b9587b9..7283cbd 100644 (file)
@@ -66,7 +66,7 @@ template<typename A> bool operator!(const std::optional<A> &x) {
   Fortran::parser::die("no case at " __FILE__ "(%d)", __LINE__)
 
 // For cheap assertions that should be applied in production.
-// To disable all CHECK tests, compile with -DCHECK.
+// To disable, compile with '-DCHECK=(void)'
 #ifndef CHECK
 #define CHECK(x) \
   ((x) || \
index 7840822..3f49da9 100644 (file)
@@ -23,11 +23,12 @@ class UserState;
 
 class ParseState {
 public:
+  // TODO: Add a constructor for parsing a normalized module file.
   ParseState(const CookedSource &cooked)
-    : cooked_{cooked}, p_{&cooked[0]},
-      remaining_{cooked.size()}, messages_{*cooked.allSources()} {}
+    : cooked_{cooked}, p_{&cooked[0]}, limit_{p_ + cooked.size()},
+      messages_{*cooked.allSources()} {}
   ParseState(const ParseState &that)
-    : cooked_{that.cooked_}, p_{that.p_}, remaining_{that.remaining_},
+    : cooked_{that.cooked_}, p_{that.p_}, limit_{that.limit_},
       column_{that.column_}, messages_{*that.cooked_.allSources()},
       userState_{that.userState_}, inCharLiteral_{that.inCharLiteral_},
       inFortran_{that.inFortran_}, inFixedForm_{that.inFixedForm_},
@@ -42,11 +43,11 @@ public:
       anyErrorRecovery_{that.anyErrorRecovery_}, prescanned_{that.prescanned_} {
   }
   ParseState(ParseState &&that)
-    : cooked_{that.cooked_}, p_{that.p_},
-      remaining_{that.remaining_}, column_{that.column_},
-      messages_{std::move(that.messages_)}, context_{std::move(that.context_)},
-      userState_{that.userState_}, inCharLiteral_{that.inCharLiteral_},
-      inFortran_{that.inFortran_}, inFixedForm_{that.inFixedForm_},
+    : cooked_{that.cooked_}, p_{that.p_}, limit_{that.limit_},
+      column_{that.column_}, messages_{std::move(that.messages_)},
+      context_{std::move(that.context_)}, userState_{that.userState_},
+      inCharLiteral_{that.inCharLiteral_}, inFortran_{that.inFortran_},
+      inFixedForm_{that.inFixedForm_},
       enableOldDebugLines_{that.enableOldDebugLines_}, columns_{that.columns_},
       enableBackslashEscapesInCharLiterals_{
           that.enableBackslashEscapesInCharLiterals_},
@@ -81,75 +82,65 @@ public:
   void set_userState(UserState *u) { userState_ = u; }
 
   MessageContext context() const { return context_; }
-  MessageContext set_context(MessageContext c) {
-    MessageContext was{context_};
+  ParseState &set_context(MessageContext c) {
     context_ = c;
-    return was;
+    return *this;
   }
 
   bool inCharLiteral() const { return inCharLiteral_; }
-  bool set_inCharLiteral(bool yes) {
-    bool was{inCharLiteral_};
+  ParseState &set_inCharLiteral(bool yes) {
     inCharLiteral_ = yes;
-    return was;
+    return *this;
   }
 
   bool inFortran() const { return inFortran_; }
-  bool set_inFortran(bool yes) {
-    bool was{inFortran_};
+  ParseState &set_inFortran(bool yes) {
     inFortran_ = yes;
-    return was;
+    return *this;
   }
 
   bool inFixedForm() const { return inFixedForm_; }
-  bool set_inFixedForm(bool yes) {
-    bool was{inFixedForm_};
+  ParseState &set_inFixedForm(bool yes) {
     inFixedForm_ = yes;
-    return was;
+    return *this;
   }
 
   bool enableOldDebugLines() const { return enableOldDebugLines_; }
-  bool set_enableOldDebugLines(bool yes) {
-    bool was{enableOldDebugLines_};
+  ParseState &set_enableOldDebugLines(bool yes) {
     enableOldDebugLines_ = yes;
-    return was;
+    return *this;
   }
 
   int columns() const { return columns_; }
-  int set_columns(int cols) {
-    int was{columns_};
+  ParseState &set_columns(int cols) {
     columns_ = cols;
-    return was;
+    return *this;
   }
 
   bool enableBackslashEscapesInCharLiterals() const {
     return enableBackslashEscapesInCharLiterals_;
   }
-  bool set_enableBackslashEscapesInCharLiterals(bool yes) {
-    bool was{enableBackslashEscapesInCharLiterals_};
+  ParseState &set_enableBackslashEscapesInCharLiterals(bool yes) {
     enableBackslashEscapesInCharLiterals_ = yes;
-    return was;
+    return *this;
   }
 
   bool strictConformance() const { return strictConformance_; }
-  bool set_strictConformance(bool yes) {
-    bool was{strictConformance_};
+  ParseState &set_strictConformance(bool yes) {
     strictConformance_ = yes;
-    return was;
+    return *this;
   }
 
   bool warnOnNonstandardUsage() const { return warnOnNonstandardUsage_; }
-  bool set_warnOnNonstandardUsage(bool yes) {
-    bool was{warnOnNonstandardUsage_};
+  ParseState &set_warnOnNonstandardUsage(bool yes) {
     warnOnNonstandardUsage_ = yes;
-    return was;
+    return *this;
   }
 
   bool warnOnDeprecatedUsage() const { return warnOnDeprecatedUsage_; }
-  bool set_warnOnDeprecatedUsage(bool yes) {
-    bool was{warnOnDeprecatedUsage_};
+  ParseState &set_warnOnDeprecatedUsage(bool yes) {
     warnOnDeprecatedUsage_ = yes;
-    return was;
+    return *this;
   }
 
   int skippedNewLines() const { return skippedNewLines_; }
@@ -161,7 +152,7 @@ public:
 
   const char *GetLocation() const { return p_; }
   Provenance GetProvenance(const char *at) const {
-    return cooked_.GetProvenance(at).start;
+    return cooked_.GetProvenance(at).LocalOffsetToProvenance(0);
   }
   Provenance GetProvenance() const { return GetProvenance(p_); }
 
@@ -204,18 +195,17 @@ public:
   }
   void PutMessage(const char *msg) { PutMessage(p_, msg); }
 
-  bool IsAtEnd() const { return remaining_ == 0; }
+  bool IsAtEnd() const { return p_ >= limit_; }
 
   std::optional<char> GetNextRawChar() const {
-    if (remaining_ > 0) {
+    if (p_ < limit_) {
       return {*p_};
     }
     return {};
   }
 
   void Advance() {
-    CHECK(remaining_ > 0);
-    --remaining_;
+    CHECK(p_ < limit_);
     if (*p_ == '\n') {
       column_ = 1;
       tabInCurrentLine_ = false;
@@ -233,8 +223,7 @@ public:
 private:
   // Text remaining to be parsed
   const CookedSource &cooked_;
-  const char *p_{nullptr};
-  size_t remaining_{0};
+  const char *p_{nullptr}, *limit_{nullptr};
   int column_{1};
 
   // Accumulated messages and current nested context.
index a7c6a4d..c821555 100644 (file)
@@ -26,8 +26,9 @@ Definition::Definition(const std::vector<std::string> &argNames,
     replacement_{Tokenize(argNames, repl, firstToken, tokens)} {}
 
 Definition::Definition(const std::string &predefined, AllSources *sources)
-  : isPredefined_{true}, replacement_{predefined,
-                             sources->AddCompilerInsertion(predefined).start} {}
+  : isPredefined_{true},
+    replacement_{predefined,
+        sources->AddCompilerInsertion(predefined).LocalOffsetToProvenance(0)} {}
 
 bool Definition::set_isDisabled(bool disable) {
   bool was{isDisabled_};
@@ -57,7 +58,7 @@ TokenSequence Definition::Tokenize(const std::vector<std::string> &argNames,
     if (IsIdentifierFirstCharacter(tok)) {
       auto it = args.find(tok.ToString());
       if (it != args.end()) {
-        result.Put(it->second, 0);
+        result.Put(it->second, token.GetProvenance(j));
         continue;
       }
     }
@@ -110,7 +111,7 @@ TokenSequence Definition::Apply(
           size_t argBytes{args[index][k].size()};
           for (size_t n{0}; n < argBytes; ++n) {
             char ch{arg[n]};
-            Provenance from{args[index].GetProvenance(k, n).start};
+            Provenance from{args[index].GetProvenance(k, n)};
             if (ch == '"' || ch == '\\') {
               result.PutNextTokenChar(ch, from);
             }
@@ -225,17 +226,20 @@ bool Preprocessor::MacroReplacement(const TokenSequence &input,
     if (!def.isFunctionLike()) {
       if (def.isPredefined()) {
         std::string name{def.replacement()[0].ToString()};
+        std::string repl;
         if (name == "__FILE__") {
-          std::string f{"\""s +
-              allSources_->GetPath(prescanner.GetCurrentProvenance()) + '"'};
-          result->Put(f, allSources_->AddCompilerInsertion(f).start);
-          continue;
-        }
-        if (name == "__LINE__") {
+          repl = "\""s +
+              allSources_->GetPath(prescanner.GetCurrentProvenance()) + '"';
+        } else if (name == "__LINE__") {
           std::stringstream ss;
           ss << allSources_->GetLineNumber(prescanner.GetCurrentProvenance());
-          std::string s{ss.str()};
-          result->Put(s, allSources_->AddCompilerInsertion(s).start);
+          repl = ss.str();
+        }
+        if (!repl.empty()) {
+          ProvenanceRange insert{allSources_->AddCompilerInsertion(repl)};
+          ProvenanceRange call{allSources_->AddMacroCall(
+              insert, input.GetProvenanceRange(j), repl)};
+          result->Put(repl, call.LocalOffsetToProvenance(0));
           continue;
         }
       }
@@ -517,7 +521,7 @@ bool Preprocessor::Directive(const TokenSequence &dir, Prescanner *prescanner) {
       prescanner->Complain("#include: missing name of file to include");
       return false;
     }
-    ProvenanceRange includeDirRange{dir.GetProvenance(j)};
+    ProvenanceRange includeDirRange{dir.GetProvenanceRange(j)};
     std::string include;
     if (dir[j].ToString() == "<") {
       if (dir[tokens - 1].ToString() != ">") {
@@ -642,7 +646,7 @@ static std::int64_t ExpressionValue(const TokenSequence &token,
     COMMA
   };
   static const int precedence[]{
-      15, 15, 15, 15,  // (), 0, !, ~
+      15, 15, 15, 15,  // (), 6, !, ~
       14, 14,  // unary +, -
       13, 12, 12, 12, 11, 11, 10, 10,  // **, *, /, %, +, -, <<, >>
       9, 8, 7,  // &, ^, |
index a006a4c..d3be488 100644 (file)
@@ -26,20 +26,20 @@ Prescanner::Prescanner(const Prescanner &that)
         that.enableBackslashEscapesInCharLiterals_} {}
 
 bool Prescanner::Prescan(ProvenanceRange range) {
-  startProvenance_ = range.start;
-  ProvenanceRange around{
-      cooked_->allSources()->GetContiguousRangeAround(startProvenance_)};
-  CHECK(startProvenance_ + range.bytes <= around.start + around.bytes);
+  AllSources *allSources{cooked_->allSources()};
+  ProvenanceRange around{allSources->GetContiguousRangeAround(range)};
+  startProvenance_ = range.LocalOffsetToProvenance(0);
+  size_t offset{0};
   const SourceFile *source{
-      cooked_->allSources()->GetSourceFile(startProvenance_)};
-  size_t offset{startProvenance_ - around.start};
-  lineStart_ = start_ = source->content() + offset;
-  limit_ = start_ + range.bytes;
-  BeginSourceLine(start_);
+      allSources->GetSourceFile(startProvenance_, &offset)};
+  CHECK(source != nullptr);
+  start_ = source->content() + offset;
+  limit_ = start_ + range.size();
+  lineStart_ = start_;
+  BeginSourceLine(lineStart_);
   TokenSequence tokens, preprocessed;
   while (lineStart_ < limit_) {
     if (CommentLinesAndPreprocessorDirectives() && lineStart_ >= limit_) {
-      PayNewlineDebt();
       break;
     }
     BeginSourceLineAndAdvance();
@@ -50,8 +50,9 @@ bool Prescanner::Prescan(ProvenanceRange range) {
     }
     while (NextToken(&tokens)) {
     }
+    Provenance newlineProvenance{GetCurrentProvenance()};
     if (preprocessor_->MacroReplacement(tokens, *this, &preprocessed)) {
-      preprocessed.PutNextTokenChar('\n', newlineProvenance_);
+      preprocessed.PutNextTokenChar('\n', newlineProvenance);
       preprocessed.CloseToken();
       if (IsFixedFormCommentLine(preprocessed.data()) ||
           IsFreeFormComment(preprocessed.data())) {
@@ -65,10 +66,9 @@ bool Prescanner::Prescan(ProvenanceRange range) {
       tokens.EmitWithCaseConversion(cooked_);
     }
     tokens.clear();
-    cooked_->Put('\n', newlineProvenance_);
-    PayNewlineDebt();
+    ++newlineDebt_;
+    PayNewlineDebt(newlineProvenance);
   }
-  PayNewlineDebt();
   return !anyFatalErrors_;
 }
 
@@ -573,9 +573,9 @@ bool Prescanner::FreeFormContinuation() {
   return true;
 }
 
-void Prescanner::PayNewlineDebt() {
+void Prescanner::PayNewlineDebt(Provenance p) {
   for (; newlineDebt_ > 0; --newlineDebt_) {
-    cooked_->Put('\n', newlineProvenance_);
+    cooked_->Put('\n', p);
   }
 }
 }  // namespace parser
index 76f0850..7130c42 100644 (file)
@@ -65,7 +65,7 @@ private:
   }
 
   Provenance GetProvenance(const char *sourceChar) const {
-    return startProvenance_ + sourceChar - start_;
+    return startProvenance_ + (sourceChar - start_);
   }
 
   void EmitChar(TokenSequence *tokens, char ch) {
@@ -95,13 +95,13 @@ private:
   const char *FixedFormContinuationLine();
   bool FixedFormContinuation();
   bool FreeFormContinuation();
-  void PayNewlineDebt();
+  void PayNewlineDebt(Provenance);
 
   Messages *messages_;
   CookedSource *cooked_;
   Preprocessor *preprocessor_;
 
-  Provenance startProvenance_{0};
+  Provenance startProvenance_;
   const char *start_{nullptr};  // beginning of current source file content
   const char *limit_{nullptr};  // first address after end of current source
   const char *at_{nullptr};  // next character to process; < lineStart_
@@ -119,8 +119,6 @@ private:
   bool enableOldDebugLines_{false};
   bool enableBackslashEscapesInCharLiterals_{true};
   int delimiterNesting_{0};
-  Provenance newlineProvenance_{
-      cooked_->allSources()->CompilerInsertionProvenance('\n')};
   Provenance spaceProvenance_{
       cooked_->allSources()->CompilerInsertionProvenance(' ')};
 };
index d942b26..40b1bd4 100644 (file)
@@ -1,26 +1,29 @@
 #include "provenance.h"
 #include "idioms.h"
 #include <utility>
+
 namespace Fortran {
 namespace parser {
 
-void OffsetToProvenanceMappings::clear() {
-  bytes_ = 0;
-  provenanceMap_.clear();
+void OffsetToProvenanceMappings::clear() { provenanceMap_.clear(); }
+
+size_t OffsetToProvenanceMappings::size() const {
+  if (provenanceMap_.empty()) {
+    return 0;
+  }
+  const ContiguousProvenanceMapping &last{provenanceMap_.back()};
+  return last.start + last.range.size();
 }
 
 void OffsetToProvenanceMappings::Put(ProvenanceRange range) {
   if (provenanceMap_.empty()) {
-    provenanceMap_.push_back({bytes_, range});
+    provenanceMap_.push_back({0, range});
   } else {
     ContiguousProvenanceMapping &last{provenanceMap_.back()};
-    if (range.start == last.range.start + last.range.bytes) {
-      last.range.bytes += range.bytes;
-    } else {
-      provenanceMap_.push_back({bytes_, range});
+    if (!last.range.AnnexIfPredecessor(range)) {
+      provenanceMap_.push_back({last.start + last.range.size(), range});
     }
   }
-  bytes_ += range.bytes;
 }
 
 void OffsetToProvenanceMappings::Put(const OffsetToProvenanceMappings &that) {
@@ -30,7 +33,7 @@ void OffsetToProvenanceMappings::Put(const OffsetToProvenanceMappings &that) {
 }
 
 ProvenanceRange OffsetToProvenanceMappings::Map(size_t at) const {
-  CHECK(at < bytes_);
+  CHECK(!provenanceMap_.empty());
   size_t low{0}, count{provenanceMap_.size()};
   while (count > 1) {
     size_t mid{low + (count >> 1)};
@@ -42,29 +45,31 @@ ProvenanceRange OffsetToProvenanceMappings::Map(size_t at) const {
     }
   }
   size_t offset{at - provenanceMap_[low].start};
-  return {provenanceMap_[low].start + offset,
-      provenanceMap_[low].range.bytes - offset};
+  return provenanceMap_[low].range.Suffix(offset);
 }
 
 void OffsetToProvenanceMappings::RemoveLastBytes(size_t bytes) {
   for (; bytes > 0; provenanceMap_.pop_back()) {
-    if (provenanceMap_.empty()) {
-      break;
-    }
+    CHECK(!provenanceMap_.empty());
     ContiguousProvenanceMapping &last{provenanceMap_.back()};
-    if (bytes < last.range.bytes) {
-      last.range.bytes -= bytes;
+    size_t chunk{last.range.size()};
+    if (bytes < chunk) {
+      last.range = last.range.Prefix(chunk - bytes);
       break;
     }
-    bytes -= last.range.bytes;
+    bytes -= chunk;
   }
 }
 
-AllSources::AllSources() {
-  std::string compilerInserts{" ,\"01\n"};
-  ProvenanceRange range{AddCompilerInsertion(compilerInserts)};
-  for (size_t j{0}; j < range.bytes; ++j) {
-    compilerInsertionProvenance_[compilerInserts[j]] = range.start + j;
+AllSources::AllSources() : range_{1, 1} {
+  // Start the origin_ array with a dummy that has a forced provenance,
+  // so that provenance offset 0 remains reserved as an uninitialized
+  // value.
+  origin_.emplace_back(range_, std::string{'?'});
+
+  for (char ch : " ,\"01\n"s) {
+    compilerInsertionProvenance_[ch] =
+        AddCompilerInsertion(std::string{ch}).LocalOffsetToProvenance(0);
   }
 }
 
@@ -72,7 +77,7 @@ AllSources::~AllSources() {}
 
 const char &AllSources::operator[](Provenance at) const {
   const Origin &origin{MapToOrigin(at)};
-  return origin[at - origin.start];
+  return origin[origin.covers.ProvenanceToLocalOffset(at)];
 }
 
 void AllSources::PushSearchPathDirectory(std::string directory) {
@@ -95,49 +100,54 @@ const SourceFile *AllSources::Open(std::string path, std::stringstream *error) {
 }
 
 ProvenanceRange AllSources::AddIncludedFile(
-    const SourceFile &source, ProvenanceRange from) {
-  size_t start{bytes_}, bytes{source.bytes()};
-  bytes_ += bytes;
-  origin_.emplace_back(start, source, from);
-  return {start, bytes};
+    const SourceFile &source, ProvenanceRange from, bool isModule) {
+  ProvenanceRange covers{range_.NextAfter(), source.bytes()};
+  CHECK(range_.AnnexIfPredecessor(covers));
+  CHECK(origin_.back().covers.IsPredecessor(covers));
+  origin_.emplace_back(covers, source, from, isModule);
+  return covers;
 }
 
 ProvenanceRange AllSources::AddMacroCall(
     ProvenanceRange def, ProvenanceRange use, const std::string &expansion) {
-  size_t start{bytes_}, bytes{expansion.size()};
-  bytes_ += bytes;
-  origin_.emplace_back(start, def, use, expansion);
-  return {start, bytes};
+  ProvenanceRange covers{range_.NextAfter(), expansion.size()};
+  CHECK(range_.AnnexIfPredecessor(covers));
+  CHECK(origin_.back().covers.IsPredecessor(covers));
+  origin_.emplace_back(covers, def, use, expansion);
+  return covers;
 }
 
-ProvenanceRange AllSources::AddCompilerInsertion(const std::string &text) {
-  size_t start{bytes_}, bytes{text.size()};
-  bytes_ += bytes;
-  origin_.emplace_back(start, text);
-  return {start, bytes};
+ProvenanceRange AllSources::AddCompilerInsertion(std::string text) {
+  ProvenanceRange covers{range_.NextAfter(), text.size()};
+  CHECK(range_.AnnexIfPredecessor(covers));
+  CHECK(origin_.back().covers.IsPredecessor(covers));
+  origin_.emplace_back(covers, text);
+  return covers;
 }
 
 void AllSources::Identify(
     std::ostream &o, Provenance at, const std::string &prefix) const {
+  CHECK(IsValid(at));
   static const std::string indented{prefix + "  "};
   const Origin &origin{MapToOrigin(at)};
   std::visit(
-      visitors{[&](const Inclusion &inc) {
-                 std::pair<int, int> pos{
-                     inc.source.FindOffsetLineAndColumn(at - origin.start)};
-                 o << prefix << "at line " << pos.first << ", column "
-                   << pos.second << " in the file " << inc.source.path()
-                   << '\n';
-                 if (origin.replaces.bytes > 0) {
-                   o << prefix << " that was included\n";
-                   Identify(o, origin.replaces.start, indented);
-                 }
-               },
+      visitors{
+          [&](const Inclusion &inc) {
+            size_t offset{origin.covers.ProvenanceToLocalOffset(at)};
+            std::pair<int, int> pos{inc.source.FindOffsetLineAndColumn(offset)};
+            o << prefix << "at line " << pos.first << ", column " << pos.second
+              << " in the " << (inc.isModule ? "module " : "file ")
+              << inc.source.path() << '\n';
+            if (IsValid(origin.replaces)) {
+              o << prefix << " that was " << (inc.isModule ? "used\n" : "included\n");
+              Identify(o, origin.replaces.LocalOffsetToProvenance(0), indented);
+            }
+          },
           [&](const Macro &mac) {
             o << prefix << "in the expansion of a macro that was defined\n";
-            Identify(o, mac.definition.start, indented);
+            Identify(o, mac.definition.LocalOffsetToProvenance(0), indented);
             o << prefix << "... and called\n";
-            Identify(o, origin.replaces.start, indented);
+            Identify(o, origin.replaces.LocalOffsetToProvenance(0), indented);
             o << prefix << "... and expanded to\n"
               << indented << mac.expansion << '\n';
           },
@@ -148,21 +158,35 @@ void AllSources::Identify(
       origin.u);
 }
 
-const SourceFile *AllSources::GetSourceFile(Provenance at) const {
+const SourceFile *AllSources::GetSourceFile(
+    Provenance at, size_t *offset) const {
   const Origin &origin{MapToOrigin(at)};
-  return std::visit(visitors{[](const Inclusion &inc) { return &inc.source; },
-                        [&origin, this](const Macro &mac) {
-                          return GetSourceFile(origin.replaces.start);
-                        },
-                        [](const CompilerInsertion &) {
-                          return static_cast<const SourceFile *>(nullptr);
-                        }},
+  return std::visit(
+      visitors{[&](const Inclusion &inc) {
+                 if (offset != nullptr) {
+                   *offset = origin.covers.ProvenanceToLocalOffset(at);
+                 }
+                 return &inc.source;
+               },
+          [&](const Macro &mac) {
+            return GetSourceFile(
+                origin.replaces.LocalOffsetToProvenance(0), offset);
+          },
+          [offset](const CompilerInsertion &) {
+            if (offset != nullptr) {
+              *offset = 0;
+            }
+            return static_cast<const SourceFile *>(nullptr);
+          }},
       origin.u);
 }
 
-ProvenanceRange AllSources::GetContiguousRangeAround(Provenance at) const {
-  const Origin &origin{MapToOrigin(at)};
-  return {origin.start, origin.size()};
+ProvenanceRange AllSources::GetContiguousRangeAround(
+    ProvenanceRange range) const {
+  CHECK(IsValid(range));
+  const Origin &origin{MapToOrigin(range.LocalOffsetToProvenance(0))};
+  CHECK(origin.covers.Contains(range));
+  return origin.covers;
 }
 
 std::string AllSources::GetPath(Provenance at) const {
@@ -171,32 +195,26 @@ std::string AllSources::GetPath(Provenance at) const {
 }
 
 int AllSources::GetLineNumber(Provenance at) const {
-  const SourceFile *source{GetSourceFile(at)};
-  return source ? source->FindOffsetLineAndColumn(at).first : 0;
+  size_t offset{0};
+  const SourceFile *source{GetSourceFile(at, &offset)};
+  return source ? source->FindOffsetLineAndColumn(offset).first : 0;
 }
 
 Provenance AllSources::CompilerInsertionProvenance(char ch) const {
   return compilerInsertionProvenance_.find(ch)->second;
 }
 
-AllSources::Origin::Origin(size_t s, const SourceFile &source)
-  : start{s}, u{Inclusion{source}} {}
+AllSources::Origin::Origin(ProvenanceRange r, const SourceFile &source)
+  : u{Inclusion{source}}, covers{r} {}
 AllSources::Origin::Origin(
-    size_t s, const SourceFile &included, ProvenanceRange from)
-  : start{s}, u{Inclusion{included}}, replaces{from} {}
-AllSources::Origin::Origin(size_t s, ProvenanceRange def, ProvenanceRange use,
-    const std::string &expansion)
-  : start{s}, u{Macro{def, expansion}}, replaces{use} {}
-AllSources::Origin::Origin(size_t s, const std::string &text)
-  : start{s}, u{CompilerInsertion{text}} {}
-
-size_t AllSources::Origin::size() const {
-  return std::visit(
-      visitors{[](const Inclusion &inc) { return inc.source.bytes(); },
-          [](const Macro &mac) { return mac.expansion.size(); },
-          [](const CompilerInsertion &ins) { return ins.text.size(); }},
-      u);
-}
+    ProvenanceRange r, const SourceFile &included, ProvenanceRange from,
+    bool isModule)
+  : u{Inclusion{included, isModule}}, covers{r}, replaces{from} {}
+AllSources::Origin::Origin(ProvenanceRange r, ProvenanceRange def,
+    ProvenanceRange use, const std::string &expansion)
+  : u{Macro{def, expansion}}, covers{r}, replaces{use} {}
+AllSources::Origin::Origin(ProvenanceRange r, const std::string &text)
+  : u{CompilerInsertion{text}}, covers{r} {}
 
 const char &AllSources::Origin::operator[](size_t n) const {
   return std::visit(
@@ -211,19 +229,18 @@ const char &AllSources::Origin::operator[](size_t n) const {
 }
 
 const AllSources::Origin &AllSources::MapToOrigin(Provenance at) const {
-  CHECK(at < bytes_);
+  CHECK(range_.Contains(at));
   size_t low{0}, count{origin_.size()};
   while (count > 1) {
     size_t mid{low + (count >> 1)};
-    if (origin_[mid].start > at) {
+    if (at < origin_[mid].covers.LocalOffsetToProvenance(0)) {
       count = mid - low;
     } else {
       count -= mid - low;
       low = mid;
     }
   }
-  CHECK(at >= origin_[low].start);
-  CHECK(low + 1 == origin_.size() || at < origin_[low + 1].start);
+  CHECK(origin_[low].covers.Contains(at));
   return origin_[low];
 }
 
@@ -233,7 +250,8 @@ ProvenanceRange CookedSource::GetProvenance(const char *at) const {
 
 void CookedSource::Marshal() {
   CHECK(provenanceMap_.size() == buffer_.size());
-  provenanceMap_.Put(allSources_->AddCompilerInsertion("EOF"));
+  provenanceMap_.Put(
+      allSources_->AddCompilerInsertion("(after end of source)"));
   data_.resize(buffer_.size());
   char *p{&data_[0]};
   for (char ch : buffer_) {
@@ -241,5 +259,48 @@ void CookedSource::Marshal() {
   }
   buffer_.clear();
 }
+
+void ProvenanceRange::Dump(std::ostream &o) const {
+  o << "[" << start_.offset() << ".." << (start_.offset() + bytes_ - 1) << "] ("
+    << bytes_ << " bytes)";
+}
+
+void OffsetToProvenanceMappings::Dump(std::ostream &o) const {
+  for (const ContiguousProvenanceMapping &m : provenanceMap_) {
+    size_t n{m.range.size()};
+    o << "offsets [" << m.start << ".." << (m.start + n - 1)
+      << "] -> provenances ";
+    m.range.Dump(o);
+    o << '\n';
+  }
+}
+
+void AllSources::Dump(std::ostream &o) const {
+  o << "AllSources range_ ";
+  range_.Dump(o);
+  o << '\n';
+  for (const Origin &m : origin_) {
+    o << "   ";
+    m.covers.Dump(o);
+    o << " -> ";
+    std::visit(visitors{[&](const Inclusion &inc) {
+                          if (inc.isModule) { o << "module "; }
+                          o << "file " << inc.source.path();
+                        },
+                   [&](const Macro &mac) { o << "macro " << mac.expansion; },
+                   [&](const CompilerInsertion &ins) {
+                     o << "compiler " << ins.text;
+                   }},
+        m.u);
+    o << '\n';
+  }
+}
+
+void CookedSource::Dump(std::ostream &o) const {
+  o << "CookedSource:\n";
+  allSources_->Dump(o);
+  o << "CookedSource::provenanceMap_:\n";
+  provenanceMap_.Dump(o);
+}
 }  // namespace parser
 }  // namespace Fortran
index 3e40f80..c674326 100644 (file)
@@ -2,9 +2,11 @@
 #define FORTRAN_PROVENANCE_H_
 
 #include "char-buffer.h"
+#include "idioms.h"
 #include "source.h"
 #include <map>
 #include <memory>
+#include <ostream>
 #include <sstream>
 #include <string>
 #include <utility>
@@ -20,41 +22,121 @@ namespace parser {
 // The location of this original character to which a parsable character
 // corresponds is its provenance.
 //
-// Provenances are offsets into an unmaterialized marshaling of all of the
-// entire contents of the original source files, include files,  macro
+// Provenances are offsets into an (unmaterialized) marshaling of the
+// entire contents of all the original source files, include files, macro
 // expansions, &c. for each visit to each source.  These origins of the
 // original source characters constitute a forest whose roots are
 // the original source files named on the compiler's command line.
-// We can describe provenances precisely by walking up this tree.
+// Given a Provenance, we can find the tree node that contains it in time
+// O(log(# of origins)), and describe the position precisely by walking
+// up the tree.  (It would be possible, via a time/space trade-off, to
+// cap the time by the use of an intermediate table that would be indexed
+// by the upper bits of an offset, but that does not appear to be
+// necessary.)
+
+class Provenance {
+public:
+  Provenance() {}
+  Provenance(size_t offset) : offset_{offset} { CHECK(offset > 0); }
+  Provenance(const Provenance &that) = default;
+  Provenance(Provenance &&that) = default;
+  Provenance &operator=(const Provenance &that) = default;
+  Provenance &operator=(Provenance &&that) = default;
+  Provenance operator+(ptrdiff_t n) const {
+    CHECK(n > -static_cast<ptrdiff_t>(offset_));
+    return {offset_ + static_cast<size_t>(n)};
+  }
+  Provenance operator+(size_t n) const { return {offset_ + n}; }
+  bool operator<(Provenance that) const { return offset_ < that.offset_; }
+  bool operator<=(Provenance that) const { return offset_ <= that.offset_; }
+  bool operator==(Provenance that) const { return offset_ == that.offset_; }
+  size_t offset() const { return offset_; }
 
-using Provenance = size_t;
+private:
+  size_t offset_{0};
+};
 
-struct ProvenanceRange {
+class ProvenanceRange {
+public:
   ProvenanceRange() {}
-  ProvenanceRange(Provenance s, size_t n) : start{s}, bytes{n} {}
+  ProvenanceRange(Provenance s, size_t n) : start_{s}, bytes_{n} {
+    CHECK(n > 0);
+  }
   ProvenanceRange(const ProvenanceRange &) = default;
   ProvenanceRange(ProvenanceRange &&) = default;
   ProvenanceRange &operator=(const ProvenanceRange &) = default;
   ProvenanceRange &operator=(ProvenanceRange &&) = default;
 
-  bool operator==(const ProvenanceRange &that) const {
-    return start == that.start && bytes == that.bytes;
+  bool operator==(ProvenanceRange that) const {
+    return start_ == that.start_ && bytes_ == that.bytes_;
+  }
+
+  size_t size() const { return bytes_; }
+
+  bool Contains(Provenance at) const {
+    return start_ <= at && at < start_ + bytes_;
+  }
+
+  bool Contains(ProvenanceRange that) const {
+    return Contains(that.start_) && Contains(that.start_ + (that.bytes_ - 1));
+  }
+
+  size_t ProvenanceToLocalOffset(Provenance at) const {
+    CHECK(Contains(at));
+    return at.offset() - start_.offset();
+  }
+
+  Provenance LocalOffsetToProvenance(size_t at) const {
+    CHECK(at < bytes_);
+    return start_ + at;
+  }
+
+  Provenance NextAfter() const { return start_ + bytes_; }
+
+  ProvenanceRange Suffix(size_t at) const {
+    CHECK(at < bytes_);
+    return {start_ + at, bytes_ - at};
+  }
+
+  ProvenanceRange Prefix(size_t bytes) const {
+    CHECK(bytes > 0);
+    return {start_, std::min(bytes_, bytes)};
+  }
+
+  bool IsPredecessor(ProvenanceRange next) {
+    return start_ + bytes_ == next.start_;
   }
 
-  Provenance start{0};
-  size_t bytes{0};
+  bool AnnexIfPredecessor(ProvenanceRange next) {
+    if (IsPredecessor(next)) {
+      bytes_ += next.bytes_;
+      return true;
+    }
+    return false;
+  }
+
+  void Dump(std::ostream &) const;
+
+private:
+  Provenance start_;
+  size_t bytes_{0};
 };
 
+// Maps 0-based local offsets in some contiguous range (e.g., a token
+// sequence) to their provenances.  Lookup time is on the order of
+// O(log(#of intervals with contiguous provenances)).  As mentioned
+// above, this time could be capped via a time/space trade-off.
 class OffsetToProvenanceMappings {
 public:
   OffsetToProvenanceMappings() {}
-  size_t size() const { return bytes_; }
+  size_t size() const;
   void clear();
   void shrink_to_fit() { provenanceMap_.shrink_to_fit(); }
   void Put(ProvenanceRange);
   void Put(const OffsetToProvenanceMappings &);
   ProvenanceRange Map(size_t at) const;
   void RemoveLastBytes(size_t);
+  void Dump(std::ostream &) const;
 
 private:
   struct ContiguousProvenanceMapping {
@@ -62,7 +144,6 @@ private:
     ProvenanceRange range;
   };
 
-  size_t bytes_{0};
   std::vector<ContiguousProvenanceMapping> provenanceMap_;
 };
 
@@ -71,28 +152,38 @@ public:
   AllSources();
   ~AllSources();
 
-  size_t size() const { return bytes_; }
+  size_t size() const { return range_.size(); }
   const char &operator[](Provenance) const;
 
   void PushSearchPathDirectory(std::string);
   std::string PopSearchPathDirectory();
   const SourceFile *Open(std::string path, std::stringstream *error);
 
-  ProvenanceRange AddIncludedFile(const SourceFile &, ProvenanceRange);
+  ProvenanceRange AddIncludedFile(const SourceFile &, ProvenanceRange,
+                                  bool isModule = false);
   ProvenanceRange AddMacroCall(
       ProvenanceRange def, ProvenanceRange use, const std::string &expansion);
-  ProvenanceRange AddCompilerInsertion(const std::string &);
+  ProvenanceRange AddCompilerInsertion(std::string);
 
+  bool IsValid(Provenance at) const { return range_.Contains(at); }
+  bool IsValid(ProvenanceRange range) const {
+    return range.size() > 0 && range_.Contains(range);
+  }
   void Identify(std::ostream &, Provenance, const std::string &prefix) const;
-  const SourceFile *GetSourceFile(Provenance) const;
-  ProvenanceRange GetContiguousRangeAround(Provenance) const;
+  const SourceFile *GetSourceFile(Provenance, size_t *offset = nullptr) const;
+  ProvenanceRange GetContiguousRangeAround(ProvenanceRange) const;
   std::string GetPath(Provenance) const;  // __FILE__
   int GetLineNumber(Provenance) const;  // __LINE__
   Provenance CompilerInsertionProvenance(char ch) const;
+  void Dump(std::ostream &) const;
 
 private:
   struct Inclusion {
     const SourceFile &source;
+    bool isModule;
+  };
+  struct Module {
+    const SourceFile &source;
   };
   struct Macro {
     ProvenanceRange definition;
@@ -103,24 +194,23 @@ private:
   };
 
   struct Origin {
-    Origin(size_t start, const SourceFile &);
-    Origin(size_t start, const SourceFile &, ProvenanceRange);
-    Origin(size_t start, ProvenanceRange def, ProvenanceRange use,
+    Origin(ProvenanceRange, const SourceFile &);
+    Origin(ProvenanceRange, const SourceFile &, ProvenanceRange,
+           bool isModule = false);
+    Origin(ProvenanceRange, ProvenanceRange def, ProvenanceRange use,
         const std::string &expansion);
-    Origin(size_t start, const std::string &);
+    Origin(ProvenanceRange, const std::string &);
 
-    size_t size() const;
     const char &operator[](size_t) const;
 
-    size_t start;
     std::variant<Inclusion, Macro, CompilerInsertion> u;
-    ProvenanceRange replaces;
+    ProvenanceRange covers, replaces;
   };
 
   const Origin &MapToOrigin(Provenance) const;
 
   std::vector<Origin> origin_;
-  size_t bytes_{0};
+  ProvenanceRange range_;
   std::map<char, Provenance> compilerInsertionProvenance_;
   std::vector<std::unique_ptr<SourceFile>> ownedSourceFiles_;
   std::vector<std::string> searchPath_;
@@ -148,7 +238,8 @@ public:
   void PutProvenanceMappings(const OffsetToProvenanceMappings &pm) {
     provenanceMap_.Put(pm);
   }
-  void Marshal();  // marshalls all text into one contiguous block
+  void Marshal();  // marshals all text into one contiguous block
+  void Dump(std::ostream &) const;
 
 private:
   AllSources *allSources_;
index f1ad10a..99f7cd0 100644 (file)
@@ -54,11 +54,11 @@ void TokenSequence::Put(const TokenSequence &that, size_t at, size_t tokens) {
     CharPointerWithLength tok{that[at]};
     size_t tokBytes{tok.size()};
     for (size_t j{0}; j < tokBytes; ++j) {
-      if (offset == provenance.bytes) {
+      if (offset == provenance.size()) {
         offset = 0;
         provenance = that.provenances_.Map(that.start_[at] + j);
       }
-      PutNextTokenChar(tok[j], provenance.start + offset++);
+      PutNextTokenChar(tok[j], provenance.LocalOffsetToProvenance(offset++));
     }
     CloseToken();
   }
@@ -66,7 +66,7 @@ void TokenSequence::Put(const TokenSequence &that, size_t at, size_t tokens) {
 
 void TokenSequence::Put(const char *s, size_t bytes, Provenance provenance) {
   for (size_t j{0}; j < bytes; ++j) {
-    PutNextTokenChar(s[j], provenance++);
+    PutNextTokenChar(s[j], provenance + j);
   }
   CloseToken();
 }
@@ -104,10 +104,15 @@ std::string TokenSequence::ToString() const {
   return {&char_[0], char_.size()};
 }
 
-ProvenanceRange TokenSequence::GetProvenance(
+Provenance TokenSequence::GetProvenance(size_t token, size_t offset) const {
+  ProvenanceRange range{provenances_.Map(start_[token] + offset)};
+  return range.LocalOffsetToProvenance(0);
+}
+
+ProvenanceRange TokenSequence::GetProvenanceRange(
     size_t token, size_t offset) const {
   ProvenanceRange range{provenances_.Map(start_[token] + offset)};
-  return {range.start, std::min(range.bytes, TokenBytes(token) - offset)};
+  return range.Prefix(TokenBytes(token) - offset);
 }
 }  // namespace parser
 }  // namespace Fortran
index f6e0902..324ac0f 100644 (file)
@@ -128,7 +128,8 @@ public:
   void Put(const std::stringstream &, Provenance);
   void EmitWithCaseConversion(CookedSource *) const;
   std::string ToString() const;
-  ProvenanceRange GetProvenance(size_t token, size_t offset = 0) const;
+  Provenance GetProvenance(size_t token, size_t offset = 0) const;
+  ProvenanceRange GetProvenanceRange(size_t token, size_t offset = 0) const;
 
 private:
   size_t TokenBytes(size_t token) const {
index 3d563e7..8045b70 100644 (file)
@@ -49,7 +49,7 @@ int main(int argc, char *const argv[]) {
   std::string progName{args.front()};
   args.pop_front();
 
-  bool dumpCookedChars{false};
+  bool dumpCookedChars{false}, dumpProvenance{false};
   bool fixedForm{false};
   bool backslashEscapes{true};
   bool standard{false};
@@ -79,6 +79,8 @@ int main(int argc, char *const argv[]) {
         columns = 132;
       } else if (flag == "-fdebug-dump-cooked-chars") {
         dumpCookedChars = true;
+      } else if (flag == "-fdebug-dump-provenance") {
+        dumpProvenance = true;
       } else if (flag == "-ed") {
         enableOldDebugLines = true;
       } else if (flag == "-I") {
@@ -129,14 +131,18 @@ int main(int argc, char *const argv[]) {
   columns = std::numeric_limits<int>::max();
 
   cooked.Marshal();
+  if (dumpProvenance) {
+    cooked.Dump(std::cout);
+  }
+
   Fortran::parser::ParseState state{cooked};
-  state.set_inFixedForm(fixedForm);
-  state.set_enableBackslashEscapesInCharLiterals(backslashEscapes);
-  state.set_strictConformance(standard);
-  state.set_columns(columns);
-  state.set_enableOldDebugLines(enableOldDebugLines);
   Fortran::parser::UserState ustate;
-  state.set_userState(&ustate);
+  state.set_inFixedForm(fixedForm)
+       .set_enableBackslashEscapesInCharLiterals(backslashEscapes)
+       .set_strictConformance(standard)
+       .set_columns(columns)
+       .set_enableOldDebugLines(enableOldDebugLines)
+       .set_userState(&ustate);
 
   if (dumpCookedChars) {
     while (std::optional<char> och{
@@ -146,18 +152,8 @@ int main(int argc, char *const argv[]) {
     return 0;
   }
 
-  std::optional<typename decltype(grammar)::resultType> result;
-#if 0
-  for (int j = 0; j < 1000; ++j) {
-    Fortran::parser::ParseState state1{state};
-    result = grammar.Parse(&state1);
-    if (!result) {
-      std::cerr << "demo FAIL in timing loop\n";
-      break;
-    }
-  }
-#endif
-  result = grammar.Parse(&state);
+  std::optional<typename decltype(grammar)::resultType> result{
+      grammar.Parse(&state)};
   if (result.has_value() && !state.anyErrorRecovery()) {
     std::cout << "demo PASS\n" << *result << '\n';
   } else {