xref: /llvm-project/flang/lib/Parser/provenance.cpp (revision 64ab3302d5a130c00b66a6957b2e7f0c9b9c537d)
1 //===-- lib/Parser/provenance.cpp -----------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "flang/Parser/provenance.h"
10 #include "flang/Common/idioms.h"
11 #include <algorithm>
12 #include <utility>
13 
14 namespace Fortran::parser {
15 
16 ProvenanceRangeToOffsetMappings::ProvenanceRangeToOffsetMappings() {}
17 ProvenanceRangeToOffsetMappings::~ProvenanceRangeToOffsetMappings() {}
18 
19 void ProvenanceRangeToOffsetMappings::Put(
20     ProvenanceRange range, std::size_t offset) {
21   auto fromTo{map_.equal_range(range)};
22   for (auto iter{fromTo.first}; iter != fromTo.second; ++iter) {
23     if (range == iter->first) {
24       iter->second = std::min(offset, iter->second);
25       return;
26     }
27   }
28   if (fromTo.second != map_.end()) {
29     map_.emplace_hint(fromTo.second, range, offset);
30   } else {
31     map_.emplace(range, offset);
32   }
33 }
34 
35 std::optional<std::size_t> ProvenanceRangeToOffsetMappings::Map(
36     ProvenanceRange range) const {
37   auto fromTo{map_.equal_range(range)};
38   std::optional<std::size_t> result;
39   for (auto iter{fromTo.first}; iter != fromTo.second; ++iter) {
40     ProvenanceRange that{iter->first};
41     if (that.Contains(range)) {
42       std::size_t offset{iter->second + that.MemberOffset(range.start())};
43       if (!result || offset < *result) {
44         result = offset;
45       }
46     }
47   }
48   return result;
49 }
50 
51 bool ProvenanceRangeToOffsetMappings::WhollyPrecedes::operator()(
52     ProvenanceRange before, ProvenanceRange after) const {
53   return before.start() + before.size() <= after.start();
54 }
55 
56 void OffsetToProvenanceMappings::clear() { provenanceMap_.clear(); }
57 
58 void OffsetToProvenanceMappings::swap(OffsetToProvenanceMappings &that) {
59   provenanceMap_.swap(that.provenanceMap_);
60 }
61 
62 void OffsetToProvenanceMappings::shrink_to_fit() {
63   provenanceMap_.shrink_to_fit();
64 }
65 
66 std::size_t OffsetToProvenanceMappings::SizeInBytes() const {
67   if (provenanceMap_.empty()) {
68     return 0;
69   } else {
70     const ContiguousProvenanceMapping &last{provenanceMap_.back()};
71     return last.start + last.range.size();
72   }
73 }
74 
75 void OffsetToProvenanceMappings::Put(ProvenanceRange range) {
76   if (provenanceMap_.empty()) {
77     provenanceMap_.push_back({0, range});
78   } else {
79     ContiguousProvenanceMapping &last{provenanceMap_.back()};
80     if (!last.range.AnnexIfPredecessor(range)) {
81       provenanceMap_.push_back({last.start + last.range.size(), range});
82     }
83   }
84 }
85 
86 void OffsetToProvenanceMappings::Put(const OffsetToProvenanceMappings &that) {
87   for (const auto &map : that.provenanceMap_) {
88     Put(map.range);
89   }
90 }
91 
92 ProvenanceRange OffsetToProvenanceMappings::Map(std::size_t at) const {
93   //  CHECK(!provenanceMap_.empty());
94   std::size_t low{0}, count{provenanceMap_.size()};
95   while (count > 1) {
96     std::size_t mid{low + (count >> 1)};
97     if (provenanceMap_[mid].start > at) {
98       count = mid - low;
99     } else {
100       count -= mid - low;
101       low = mid;
102     }
103   }
104   std::size_t offset{at - provenanceMap_[low].start};
105   return provenanceMap_[low].range.Suffix(offset);
106 }
107 
108 void OffsetToProvenanceMappings::RemoveLastBytes(std::size_t bytes) {
109   for (; bytes > 0; provenanceMap_.pop_back()) {
110     CHECK(!provenanceMap_.empty());
111     ContiguousProvenanceMapping &last{provenanceMap_.back()};
112     std::size_t chunk{last.range.size()};
113     if (bytes < chunk) {
114       last.range = last.range.Prefix(chunk - bytes);
115       break;
116     }
117     bytes -= chunk;
118   }
119 }
120 
121 ProvenanceRangeToOffsetMappings OffsetToProvenanceMappings::Invert(
122     const AllSources &allSources) const {
123   ProvenanceRangeToOffsetMappings result;
124   for (const auto &contig : provenanceMap_) {
125     ProvenanceRange range{contig.range};
126     while (!range.empty()) {
127       ProvenanceRange source{allSources.IntersectionWithSourceFiles(range)};
128       if (source.empty()) {
129         break;
130       }
131       result.Put(
132           source, contig.start + contig.range.MemberOffset(source.start()));
133       Provenance after{source.NextAfter()};
134       if (range.Contains(after)) {
135         range = range.Suffix(range.MemberOffset(after));
136       } else {
137         break;
138       }
139     }
140   }
141   return result;
142 }
143 
144 AllSources::AllSources() : range_{1, 1} {
145   // Start the origin_ array with a dummy entry that has a forced provenance,
146   // so that provenance offset 0 remains reserved as an uninitialized
147   // value.
148   origin_.emplace_back(range_, std::string{'?'});
149 }
150 
151 AllSources::~AllSources() {}
152 
153 const char &AllSources::operator[](Provenance at) const {
154   const Origin &origin{MapToOrigin(at)};
155   return origin[origin.covers.MemberOffset(at)];
156 }
157 
158 void AllSources::PushSearchPathDirectory(std::string directory) {
159   // gfortran and ifort append to current path, PGI prepends
160   searchPath_.push_back(directory);
161 }
162 
163 std::string AllSources::PopSearchPathDirectory() {
164   std::string directory{searchPath_.back()};
165   searchPath_.pop_back();
166   return directory;
167 }
168 
169 const SourceFile *AllSources::Open(std::string path, std::stringstream *error) {
170   std::unique_ptr<SourceFile> source{std::make_unique<SourceFile>(encoding_)};
171   if (source->Open(LocateSourceFile(path, searchPath_), error)) {
172     return ownedSourceFiles_.emplace_back(std::move(source)).get();
173   } else {
174     return nullptr;
175   }
176 }
177 
178 const SourceFile *AllSources::ReadStandardInput(std::stringstream *error) {
179   std::unique_ptr<SourceFile> source{std::make_unique<SourceFile>(encoding_)};
180   if (source->ReadStandardInput(error)) {
181     return ownedSourceFiles_.emplace_back(std::move(source)).get();
182   }
183   return nullptr;
184 }
185 
186 ProvenanceRange AllSources::AddIncludedFile(
187     const SourceFile &source, ProvenanceRange from, bool isModule) {
188   ProvenanceRange covers{range_.NextAfter(), source.bytes()};
189   CHECK(range_.AnnexIfPredecessor(covers));
190   CHECK(origin_.back().covers.ImmediatelyPrecedes(covers));
191   origin_.emplace_back(covers, source, from, isModule);
192   return covers;
193 }
194 
195 ProvenanceRange AllSources::AddMacroCall(
196     ProvenanceRange def, ProvenanceRange use, const std::string &expansion) {
197   ProvenanceRange covers{range_.NextAfter(), expansion.size()};
198   CHECK(range_.AnnexIfPredecessor(covers));
199   CHECK(origin_.back().covers.ImmediatelyPrecedes(covers));
200   origin_.emplace_back(covers, def, use, expansion);
201   return covers;
202 }
203 
204 ProvenanceRange AllSources::AddCompilerInsertion(std::string text) {
205   ProvenanceRange covers{range_.NextAfter(), text.size()};
206   CHECK(range_.AnnexIfPredecessor(covers));
207   CHECK(origin_.back().covers.ImmediatelyPrecedes(covers));
208   origin_.emplace_back(covers, text);
209   return covers;
210 }
211 
212 void AllSources::EmitMessage(std::ostream &o,
213     const std::optional<ProvenanceRange> &range, const std::string &message,
214     bool echoSourceLine) const {
215   if (!range) {
216     o << message << '\n';
217     return;
218   }
219   CHECK(IsValid(*range));
220   const Origin &origin{MapToOrigin(range->start())};
221   std::visit(
222       common::visitors{
223           [&](const Inclusion &inc) {
224             o << inc.source.path();
225             std::size_t offset{origin.covers.MemberOffset(range->start())};
226             SourcePosition pos{inc.source.FindOffsetLineAndColumn(offset)};
227             o << ':' << pos.line << ':' << pos.column;
228             o << ": " << message << '\n';
229             if (echoSourceLine) {
230               const char *text{inc.source.content() +
231                   inc.source.GetLineStartOffset(pos.line)};
232               o << "  ";
233               for (const char *p{text}; *p != '\n'; ++p) {
234                 o << *p;
235               }
236               o << "\n  ";
237               for (int j{1}; j < pos.column; ++j) {
238                 char ch{text[j - 1]};
239                 o << (ch == '\t' ? '\t' : ' ');
240               }
241               o << '^';
242               if (range->size() > 1) {
243                 auto last{range->start() + range->size() - 1};
244                 if (&MapToOrigin(last) == &origin) {
245                   auto endOffset{origin.covers.MemberOffset(last)};
246                   auto endPos{inc.source.FindOffsetLineAndColumn(endOffset)};
247                   if (pos.line == endPos.line) {
248                     for (int j{pos.column}; j < endPos.column; ++j) {
249                       o << '^';
250                     }
251                   }
252                 }
253               }
254               o << '\n';
255             }
256             if (IsValid(origin.replaces)) {
257               EmitMessage(o, origin.replaces,
258                   inc.isModule ? "used here"s : "included here"s,
259                   echoSourceLine);
260             }
261           },
262           [&](const Macro &mac) {
263             EmitMessage(o, origin.replaces, message, echoSourceLine);
264             EmitMessage(
265                 o, mac.definition, "in a macro defined here", echoSourceLine);
266             if (echoSourceLine) {
267               o << "that expanded to:\n  " << mac.expansion << "\n  ";
268               for (std::size_t j{0};
269                    origin.covers.OffsetMember(j) < range->start(); ++j) {
270                 o << (mac.expansion[j] == '\t' ? '\t' : ' ');
271               }
272               o << "^\n";
273             }
274           },
275           [&](const CompilerInsertion &) { o << message << '\n'; },
276       },
277       origin.u);
278 }
279 
280 const SourceFile *AllSources::GetSourceFile(
281     Provenance at, std::size_t *offset) const {
282   const Origin &origin{MapToOrigin(at)};
283   return std::visit(
284       common::visitors{
285           [&](const Inclusion &inc) {
286             if (offset) {
287               *offset = origin.covers.MemberOffset(at);
288             }
289             return &inc.source;
290           },
291           [&](const Macro &) {
292             return GetSourceFile(origin.replaces.start(), offset);
293           },
294           [offset](const CompilerInsertion &) {
295             if (offset) {
296               *offset = 0;
297             }
298             return static_cast<const SourceFile *>(nullptr);
299           },
300       },
301       origin.u);
302 }
303 
304 std::optional<SourcePosition> AllSources::GetSourcePosition(
305     Provenance prov) const {
306   const Origin &origin{MapToOrigin(prov)};
307   if (const auto *inc{std::get_if<Inclusion>(&origin.u)}) {
308     std::size_t offset{origin.covers.MemberOffset(prov)};
309     return inc->source.FindOffsetLineAndColumn(offset);
310   } else {
311     return std::nullopt;
312   }
313 }
314 
315 std::optional<ProvenanceRange> AllSources::GetFirstFileProvenance() const {
316   for (const auto &origin : origin_) {
317     if (std::holds_alternative<Inclusion>(origin.u)) {
318       return origin.covers;
319     }
320   }
321   return std::nullopt;
322 }
323 
324 std::string AllSources::GetPath(Provenance at) const {
325   const SourceFile *source{GetSourceFile(at)};
326   return source ? source->path() : ""s;
327 }
328 
329 int AllSources::GetLineNumber(Provenance at) const {
330   std::size_t offset{0};
331   const SourceFile *source{GetSourceFile(at, &offset)};
332   return source ? source->FindOffsetLineAndColumn(offset).line : 0;
333 }
334 
335 Provenance AllSources::CompilerInsertionProvenance(char ch) {
336   auto iter{compilerInsertionProvenance_.find(ch)};
337   if (iter != compilerInsertionProvenance_.end()) {
338     return iter->second;
339   }
340   ProvenanceRange newCharRange{AddCompilerInsertion(std::string{ch})};
341   Provenance newCharProvenance{newCharRange.start()};
342   compilerInsertionProvenance_.insert(std::make_pair(ch, newCharProvenance));
343   return newCharProvenance;
344 }
345 
346 ProvenanceRange AllSources::IntersectionWithSourceFiles(
347     ProvenanceRange range) const {
348   if (range.empty()) {
349     return {};
350   } else {
351     const Origin &origin{MapToOrigin(range.start())};
352     if (std::holds_alternative<Inclusion>(origin.u)) {
353       return range.Intersection(origin.covers);
354     } else {
355       auto skip{
356           origin.covers.size() - origin.covers.MemberOffset(range.start())};
357       return IntersectionWithSourceFiles(range.Suffix(skip));
358     }
359   }
360 }
361 
362 AllSources::Origin::Origin(ProvenanceRange r, const SourceFile &source)
363   : u{Inclusion{source}}, covers{r} {}
364 AllSources::Origin::Origin(ProvenanceRange r, const SourceFile &included,
365     ProvenanceRange from, bool isModule)
366   : u{Inclusion{included, isModule}}, covers{r}, replaces{from} {}
367 AllSources::Origin::Origin(ProvenanceRange r, ProvenanceRange def,
368     ProvenanceRange use, const std::string &expansion)
369   : u{Macro{def, expansion}}, covers{r}, replaces{use} {}
370 AllSources::Origin::Origin(ProvenanceRange r, const std::string &text)
371   : u{CompilerInsertion{text}}, covers{r} {}
372 
373 const char &AllSources::Origin::operator[](std::size_t n) const {
374   return std::visit(
375       common::visitors{
376           [n](const Inclusion &inc) -> const char & {
377             return inc.source.content()[n];
378           },
379           [n](const Macro &mac) -> const char & { return mac.expansion[n]; },
380           [n](const CompilerInsertion &ins) -> const char & {
381             return ins.text[n];
382           },
383       },
384       u);
385 }
386 
387 const AllSources::Origin &AllSources::MapToOrigin(Provenance at) const {
388   CHECK(range_.Contains(at));
389   std::size_t low{0}, count{origin_.size()};
390   while (count > 1) {
391     std::size_t mid{low + (count >> 1)};
392     if (at < origin_[mid].covers.start()) {
393       count = mid - low;
394     } else {
395       count -= mid - low;
396       low = mid;
397     }
398   }
399   CHECK(origin_[low].covers.Contains(at));
400   return origin_[low];
401 }
402 
403 CookedSource::CookedSource(AllSources &s) : allSources_{s} {}
404 CookedSource::~CookedSource() {}
405 
406 std::optional<ProvenanceRange> CookedSource::GetProvenanceRange(
407     CharBlock cookedRange) const {
408   if (!IsValid(cookedRange)) {
409     return std::nullopt;
410   }
411   ProvenanceRange first{provenanceMap_.Map(cookedRange.begin() - &data_[0])};
412   if (cookedRange.size() <= first.size()) {
413     return first.Prefix(cookedRange.size());
414   }
415   ProvenanceRange last{provenanceMap_.Map(cookedRange.end() - &data_[0])};
416   return {ProvenanceRange{first.start(), last.start() - first.start()}};
417 }
418 
419 std::optional<CharBlock> CookedSource::GetCharBlockFromLineAndColumns(
420     int line, int startColumn, int endColumn) const {
421   // 2nd column is exclusive, meaning it is target column + 1.
422   CHECK(line > 0 && startColumn > 0 && endColumn > 0);
423   CHECK(startColumn < endColumn);
424   auto provenanceStart{allSources_.GetFirstFileProvenance().value().start()};
425   if (auto sourceFile{allSources_.GetSourceFile(provenanceStart)}) {
426     CHECK(line <= static_cast<int>(sourceFile->lines()));
427     return GetCharBlock(ProvenanceRange(sourceFile->GetLineStartOffset(line) +
428             provenanceStart.offset() + startColumn - 1,
429         endColumn - startColumn));
430   }
431   return std::nullopt;
432 }
433 
434 std::optional<std::pair<SourcePosition, SourcePosition>>
435 CookedSource::GetSourcePositionRange(CharBlock cookedRange) const {
436   if (auto range{GetProvenanceRange(cookedRange)}) {
437     if (auto firstOffset{allSources_.GetSourcePosition(range->start())}) {
438       if (auto secondOffset{
439               allSources_.GetSourcePosition(range->start() + range->size())}) {
440         return std::pair{*firstOffset, *secondOffset};
441       }
442     }
443   }
444   return std::nullopt;
445 }
446 
447 std::optional<CharBlock> CookedSource::GetCharBlock(
448     ProvenanceRange range) const {
449   CHECK(!invertedMap_.empty() &&
450       "CompileProvenanceRangeToOffsetMappings not called");
451   if (auto to{invertedMap_.Map(range)}) {
452     return CharBlock{data_.c_str() + *to, range.size()};
453   } else {
454     return std::nullopt;
455   }
456 }
457 
458 std::size_t CookedSource::BufferedBytes() const { return buffer_.bytes(); }
459 
460 void CookedSource::Marshal() {
461   CHECK(provenanceMap_.SizeInBytes() == buffer_.bytes());
462   provenanceMap_.Put(allSources_.AddCompilerInsertion("(after end of source)"));
463   data_ = buffer_.Marshal();
464   buffer_.clear();
465 }
466 
467 void CookedSource::CompileProvenanceRangeToOffsetMappings() {
468   if (invertedMap_.empty()) {
469     invertedMap_ = provenanceMap_.Invert(allSources_);
470   }
471 }
472 
473 static void DumpRange(std::ostream &o, const ProvenanceRange &r) {
474   o << "[" << r.start().offset() << ".." << r.Last().offset() << "] ("
475     << r.size() << " bytes)";
476 }
477 
478 std::ostream &ProvenanceRangeToOffsetMappings::Dump(std::ostream &o) const {
479   for (const auto &m : map_) {
480     o << "provenances ";
481     DumpRange(o, m.first);
482     o << " -> offsets [" << m.second << ".." << (m.second + m.first.size() - 1)
483       << "]\n";
484   }
485   return o;
486 }
487 
488 std::ostream &OffsetToProvenanceMappings::Dump(std::ostream &o) const {
489   for (const ContiguousProvenanceMapping &m : provenanceMap_) {
490     std::size_t n{m.range.size()};
491     o << "offsets [" << m.start << ".." << (m.start + n - 1)
492       << "] -> provenances ";
493     DumpRange(o, m.range);
494     o << '\n';
495   }
496   return o;
497 }
498 
499 std::ostream &AllSources::Dump(std::ostream &o) const {
500   o << "AllSources range_ ";
501   DumpRange(o, range_);
502   o << '\n';
503   for (const Origin &m : origin_) {
504     o << "   ";
505     DumpRange(o, m.covers);
506     o << " -> ";
507     std::visit(
508         common::visitors{
509             [&](const Inclusion &inc) {
510               if (inc.isModule) {
511                 o << "module ";
512               }
513               o << "file " << inc.source.path();
514             },
515             [&](const Macro &mac) { o << "macro " << mac.expansion; },
516             [&](const CompilerInsertion &ins) {
517               o << "compiler '" << ins.text << '\'';
518               if (ins.text.length() == 1) {
519                 int ch = ins.text[0];
520                 o << " (0x" << std::hex << (ch & 0xff) << std::dec << ")";
521               }
522             },
523         },
524         m.u);
525     if (IsValid(m.replaces)) {
526       o << " replaces ";
527       DumpRange(o, m.replaces);
528     }
529     o << '\n';
530   }
531   return o;
532 }
533 
534 std::ostream &CookedSource::Dump(std::ostream &o) const {
535   o << "CookedSource:\n";
536   allSources_.Dump(o);
537   o << "CookedSource::provenanceMap_:\n";
538   provenanceMap_.Dump(o);
539   o << "CookedSource::invertedMap_:\n";
540   invertedMap_.Dump(o);
541   return o;
542 }
543 }
544