xref: /llvm-project/flang/lib/Parser/provenance.cpp (revision c1a750b8bf7c7bfcd4e72537fcf76e5f23d0f06a)
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 "llvm/Support/raw_ostream.h"
12 #include <algorithm>
13 #include <set>
14 #include <utility>
15 
16 namespace Fortran::parser {
17 
18 ProvenanceRangeToOffsetMappings::ProvenanceRangeToOffsetMappings() {}
19 ProvenanceRangeToOffsetMappings::~ProvenanceRangeToOffsetMappings() {}
20 
21 void ProvenanceRangeToOffsetMappings::Put(
22     ProvenanceRange range, std::size_t offset) {
23   auto fromTo{map_.equal_range(range)};
24   for (auto iter{fromTo.first}; iter != fromTo.second; ++iter) {
25     if (range == iter->first) {
26       iter->second = std::min(offset, iter->second);
27       return;
28     }
29   }
30   if (fromTo.second != map_.end()) {
31     map_.emplace_hint(fromTo.second, range, offset);
32   } else {
33     map_.emplace(range, offset);
34   }
35 }
36 
37 std::optional<std::size_t> ProvenanceRangeToOffsetMappings::Map(
38     ProvenanceRange range) const {
39   auto fromTo{map_.equal_range(range)};
40   std::optional<std::size_t> result;
41   for (auto iter{fromTo.first}; iter != fromTo.second; ++iter) {
42     ProvenanceRange that{iter->first};
43     if (that.Contains(range)) {
44       std::size_t offset{iter->second + that.MemberOffset(range.start())};
45       if (!result || offset < *result) {
46         result = offset;
47       }
48     }
49   }
50   return result;
51 }
52 
53 bool ProvenanceRangeToOffsetMappings::WhollyPrecedes::operator()(
54     ProvenanceRange before, ProvenanceRange after) const {
55   return before.start() + before.size() <= after.start();
56 }
57 
58 void OffsetToProvenanceMappings::clear() { provenanceMap_.clear(); }
59 
60 void OffsetToProvenanceMappings::swap(OffsetToProvenanceMappings &that) {
61   provenanceMap_.swap(that.provenanceMap_);
62 }
63 
64 void OffsetToProvenanceMappings::shrink_to_fit() {
65   provenanceMap_.shrink_to_fit();
66 }
67 
68 std::size_t OffsetToProvenanceMappings::SizeInBytes() const {
69   if (provenanceMap_.empty()) {
70     return 0;
71   } else {
72     const ContiguousProvenanceMapping &last{provenanceMap_.back()};
73     return last.start + last.range.size();
74   }
75 }
76 
77 void OffsetToProvenanceMappings::Put(ProvenanceRange range) {
78   if (provenanceMap_.empty()) {
79     provenanceMap_.push_back({0, range});
80   } else {
81     ContiguousProvenanceMapping &last{provenanceMap_.back()};
82     if (!last.range.AnnexIfPredecessor(range)) {
83       provenanceMap_.push_back({last.start + last.range.size(), range});
84     }
85   }
86 }
87 
88 void OffsetToProvenanceMappings::Put(const OffsetToProvenanceMappings &that) {
89   for (const auto &map : that.provenanceMap_) {
90     Put(map.range);
91   }
92 }
93 
94 ProvenanceRange OffsetToProvenanceMappings::Map(std::size_t at) const {
95   if (provenanceMap_.empty()) {
96     CHECK(at == 0);
97     return {};
98   }
99   std::size_t low{0}, count{provenanceMap_.size()};
100   while (count > 1) {
101     std::size_t mid{low + (count >> 1)};
102     if (provenanceMap_[mid].start > at) {
103       count = mid - low;
104     } else {
105       count -= mid - low;
106       low = mid;
107     }
108   }
109   std::size_t offset{at - provenanceMap_[low].start};
110   return provenanceMap_[low].range.Suffix(offset);
111 }
112 
113 void OffsetToProvenanceMappings::RemoveLastBytes(std::size_t bytes) {
114   for (; bytes > 0; provenanceMap_.pop_back()) {
115     CHECK(!provenanceMap_.empty());
116     ContiguousProvenanceMapping &last{provenanceMap_.back()};
117     std::size_t chunk{last.range.size()};
118     if (bytes < chunk) {
119       last.range = last.range.Prefix(chunk - bytes);
120       break;
121     }
122     bytes -= chunk;
123   }
124 }
125 
126 ProvenanceRangeToOffsetMappings OffsetToProvenanceMappings::Invert(
127     const AllSources &allSources) const {
128   ProvenanceRangeToOffsetMappings result;
129   for (const auto &contig : provenanceMap_) {
130     ProvenanceRange range{contig.range};
131     while (!range.empty()) {
132       ProvenanceRange source{allSources.IntersectionWithSourceFiles(range)};
133       if (source.empty()) {
134         break;
135       }
136       result.Put(
137           source, contig.start + contig.range.MemberOffset(source.start()));
138       Provenance after{source.NextAfter()};
139       if (range.Contains(after)) {
140         range = range.Suffix(range.MemberOffset(after));
141       } else {
142         break;
143       }
144     }
145   }
146   return result;
147 }
148 
149 AllSources::AllSources() : range_{1, 1} {
150   // Start the origin_ array with a dummy entry that has a forced provenance,
151   // so that provenance offset 0 remains reserved as an uninitialized
152   // value.
153   origin_.emplace_back(range_, std::string{'?'});
154 }
155 
156 AllSources::~AllSources() {}
157 
158 const char &AllSources::operator[](Provenance at) const {
159   const Origin &origin{MapToOrigin(at)};
160   return origin[origin.covers.MemberOffset(at)];
161 }
162 
163 void AllSources::ClearSearchPath() { searchPath_.clear(); }
164 
165 void AllSources::AppendSearchPathDirectory(std::string directory) {
166   // gfortran and ifort append to current path, PGI prepends
167   searchPath_.push_back(directory);
168 }
169 
170 const SourceFile *AllSources::OpenPath(
171     std::string path, llvm::raw_ostream &error) {
172   std::unique_ptr<SourceFile> source{std::make_unique<SourceFile>(encoding_)};
173   if (source->Open(path, error)) {
174     return ownedSourceFiles_.emplace_back(std::move(source)).get();
175   } else {
176     return nullptr;
177   }
178 }
179 
180 const SourceFile *AllSources::Open(std::string path, llvm::raw_ostream &error,
181     std::optional<std::string> &&prependPath) {
182   std::unique_ptr<SourceFile> source{std::make_unique<SourceFile>(encoding_)};
183   if (prependPath) {
184     // Set to "." for the initial source file; set to the directory name
185     // of the including file for #include "quoted-file" directives &
186     // INCLUDE statements.
187     searchPath_.emplace_front(std::move(*prependPath));
188   }
189   std::optional<std::string> found{LocateSourceFile(path, searchPath_)};
190   if (prependPath) {
191     searchPath_.pop_front();
192   }
193   if (found) {
194     return OpenPath(*found, error);
195   } else {
196     error << "Source file '" << path << "' was not found";
197     return nullptr;
198   }
199 }
200 
201 const SourceFile *AllSources::ReadStandardInput(llvm::raw_ostream &error) {
202   std::unique_ptr<SourceFile> source{std::make_unique<SourceFile>(encoding_)};
203   if (source->ReadStandardInput(error)) {
204     return ownedSourceFiles_.emplace_back(std::move(source)).get();
205   }
206   return nullptr;
207 }
208 
209 ProvenanceRange AllSources::AddIncludedFile(
210     const SourceFile &source, ProvenanceRange from, bool isModule) {
211   ProvenanceRange covers{range_.NextAfter(), source.bytes()};
212   CHECK(range_.AnnexIfPredecessor(covers));
213   CHECK(origin_.back().covers.ImmediatelyPrecedes(covers));
214   origin_.emplace_back(covers, source, from, isModule);
215   return covers;
216 }
217 
218 ProvenanceRange AllSources::AddMacroCall(
219     ProvenanceRange def, ProvenanceRange use, const std::string &expansion) {
220   ProvenanceRange covers{range_.NextAfter(), expansion.size()};
221   CHECK(range_.AnnexIfPredecessor(covers));
222   CHECK(origin_.back().covers.ImmediatelyPrecedes(covers));
223   origin_.emplace_back(covers, def, use, expansion);
224   return covers;
225 }
226 
227 ProvenanceRange AllSources::AddCompilerInsertion(std::string text) {
228   ProvenanceRange covers{range_.NextAfter(), text.size()};
229   CHECK(range_.AnnexIfPredecessor(covers));
230   CHECK(origin_.back().covers.ImmediatelyPrecedes(covers));
231   origin_.emplace_back(covers, text);
232   return covers;
233 }
234 
235 static void EmitPrefix(llvm::raw_ostream &o, llvm::raw_ostream::Colors color,
236     const std::string &prefix, bool showColors) {
237   if (prefix.empty()) {
238     return;
239   }
240   if (showColors) {
241     o.changeColor(color, true);
242   }
243   o << prefix;
244   if (showColors) {
245     o.resetColor();
246   }
247 }
248 
249 std::optional<ProvenanceRange> AllSources::GetInclusionInfo(
250     const std::optional<ProvenanceRange> &range) const {
251   if (!range || !IsValid(range->start()))
252     return std::nullopt;
253   const Origin &origin{MapToOrigin(range->start())};
254 
255   return common::visit(
256       common::visitors{
257           [&](const Inclusion &inc) -> std::optional<ProvenanceRange> {
258             if (IsValid(origin.replaces) &&
259                 range_.Contains(origin.replaces.start()))
260               return origin.replaces;
261             return std::nullopt;
262           },
263           [&](const auto &) -> std::optional<ProvenanceRange> {
264             return std::nullopt;
265           },
266       },
267       origin.u);
268 }
269 
270 void AllSources::EmitMessage(llvm::raw_ostream &o,
271     const std::optional<ProvenanceRange> &range, const std::string &message,
272     const std::string &prefix, llvm::raw_ostream::Colors color,
273     bool echoSourceLine) const {
274   if (!range) {
275     EmitPrefix(o, color, prefix, this->getShowColors());
276     o << message << '\n';
277     return;
278   }
279   CHECK(IsValid(*range));
280   const Origin &origin{MapToOrigin(range->start())};
281   common::visit(
282       common::visitors{
283           [&](const Inclusion &inc) {
284             std::size_t offset{origin.covers.MemberOffset(range->start())};
285             SourcePosition pos{inc.source.GetSourcePosition(offset)};
286             o << pos.path << ':' << pos.line << ':' << pos.column << ": ";
287             EmitPrefix(o, color, prefix, this->getShowColors());
288             o << message << '\n';
289             if (echoSourceLine) {
290               const char *text{inc.source.content().data() +
291                   inc.source.GetLineStartOffset(pos.trueLineNumber)};
292               o << "  ";
293               for (const char *p{text}; *p != '\n'; ++p) {
294                 o << *p;
295               }
296               o << "\n  ";
297               for (int j{1}; j < pos.column; ++j) {
298                 char ch{text[j - 1]};
299                 o << (ch == '\t' ? '\t' : ' ');
300               }
301               o << '^';
302               if (range->size() > 1) {
303                 auto last{range->start() + range->size() - 1};
304                 if (&MapToOrigin(last) == &origin) {
305                   auto endOffset{origin.covers.MemberOffset(last)};
306                   auto endPos{inc.source.GetSourcePosition(endOffset)};
307                   if (pos.line == endPos.line) {
308                     for (int j{pos.column}; j < endPos.column; ++j) {
309                       o << '^';
310                     }
311                   }
312                 }
313               }
314               o << '\n';
315             }
316             if (IsValid(origin.replaces)) {
317               EmitMessage(o, origin.replaces,
318                   inc.isModule ? "used here"s : "included here"s, prefix, color,
319                   echoSourceLine);
320             }
321           },
322           [&](const Macro &mac) {
323             EmitMessage(
324                 o, origin.replaces, message, prefix, color, echoSourceLine);
325             EmitMessage(o, mac.definition, "in a macro defined here", ""s,
326                 color, echoSourceLine);
327             if (echoSourceLine) {
328               o << "that expanded to:\n  " << mac.expansion << "\n  ";
329               for (std::size_t j{0};
330                    origin.covers.OffsetMember(j) < range->start(); ++j) {
331                 o << (mac.expansion[j] == '\t' ? '\t' : ' ');
332               }
333               o << "^\n";
334             }
335           },
336           [&](const CompilerInsertion &) {
337             EmitPrefix(o, color, prefix, this->getShowColors());
338             o << message << '\n';
339           },
340       },
341       origin.u);
342 }
343 
344 const SourceFile *AllSources::GetSourceFile(
345     Provenance at, std::size_t *offset, bool topLevel) const {
346   const Origin &origin{MapToOrigin(at)};
347   return common::visit(common::visitors{
348                            [&](const Inclusion &inc) {
349                              if (topLevel && !origin.replaces.empty()) {
350                                return GetSourceFile(
351                                    origin.replaces.start(), offset, topLevel);
352                              } else {
353                                if (offset) {
354                                  *offset = origin.covers.MemberOffset(at);
355                                }
356                                return &inc.source;
357                              }
358                            },
359                            [&](const Macro &) {
360                              return GetSourceFile(
361                                  origin.replaces.start(), offset);
362                            },
363                            [offset](const CompilerInsertion &) {
364                              if (offset) {
365                                *offset = 0;
366                              }
367                              return static_cast<const SourceFile *>(nullptr);
368                            },
369                        },
370       origin.u);
371 }
372 
373 const char *AllSources::GetSource(ProvenanceRange range) const {
374   Provenance start{range.start()};
375   const Origin &origin{MapToOrigin(start)};
376   return origin.covers.Contains(range)
377       ? &origin[origin.covers.MemberOffset(start)]
378       : nullptr;
379 }
380 
381 std::optional<SourcePosition> AllSources::GetSourcePosition(
382     Provenance prov) const {
383   const Origin &origin{MapToOrigin(prov)};
384   return common::visit(
385       common::visitors{
386           [&](const Inclusion &inc) -> std::optional<SourcePosition> {
387             std::size_t offset{origin.covers.MemberOffset(prov)};
388             return inc.source.GetSourcePosition(offset);
389           },
390           [&](const Macro &) {
391             return GetSourcePosition(origin.replaces.start());
392           },
393           [](const CompilerInsertion &) -> std::optional<SourcePosition> {
394             return std::nullopt;
395           },
396       },
397       origin.u);
398 }
399 
400 std::optional<ProvenanceRange> AllSources::GetFirstFileProvenance() const {
401   for (const auto &origin : origin_) {
402     if (std::holds_alternative<Inclusion>(origin.u)) {
403       return origin.covers;
404     }
405   }
406   return std::nullopt;
407 }
408 
409 std::string AllSources::GetPath(Provenance at, bool topLevel) const {
410   std::size_t offset{0};
411   const SourceFile *source{GetSourceFile(at, &offset, topLevel)};
412   return source ? *source->GetSourcePosition(offset).path : ""s;
413 }
414 
415 int AllSources::GetLineNumber(Provenance at) const {
416   std::size_t offset{0};
417   const SourceFile *source{GetSourceFile(at, &offset)};
418   return source ? source->GetSourcePosition(offset).line : 0;
419 }
420 
421 Provenance AllSources::CompilerInsertionProvenance(char ch) {
422   auto iter{compilerInsertionProvenance_.find(ch)};
423   if (iter != compilerInsertionProvenance_.end()) {
424     return iter->second;
425   }
426   ProvenanceRange newCharRange{AddCompilerInsertion(std::string{ch})};
427   Provenance newCharProvenance{newCharRange.start()};
428   compilerInsertionProvenance_.insert(std::make_pair(ch, newCharProvenance));
429   return newCharProvenance;
430 }
431 
432 ProvenanceRange AllSources::IntersectionWithSourceFiles(
433     ProvenanceRange range) const {
434   if (range.empty()) {
435     return {};
436   } else {
437     const Origin &origin{MapToOrigin(range.start())};
438     if (std::holds_alternative<Inclusion>(origin.u)) {
439       return range.Intersection(origin.covers);
440     } else {
441       auto skip{
442           origin.covers.size() - origin.covers.MemberOffset(range.start())};
443       return IntersectionWithSourceFiles(range.Suffix(skip));
444     }
445   }
446 }
447 
448 AllSources::Origin::Origin(ProvenanceRange r, const SourceFile &source)
449     : u{Inclusion{source}}, covers{r} {}
450 AllSources::Origin::Origin(ProvenanceRange r, const SourceFile &included,
451     ProvenanceRange from, bool isModule)
452     : u{Inclusion{included, isModule}}, covers{r}, replaces{from} {}
453 AllSources::Origin::Origin(ProvenanceRange r, ProvenanceRange def,
454     ProvenanceRange use, const std::string &expansion)
455     : u{Macro{def, expansion}}, covers{r}, replaces{use} {}
456 AllSources::Origin::Origin(ProvenanceRange r, const std::string &text)
457     : u{CompilerInsertion{text}}, covers{r} {}
458 
459 const char &AllSources::Origin::operator[](std::size_t n) const {
460   return common::visit(
461       common::visitors{
462           [n](const Inclusion &inc) -> const char & {
463             return inc.source.content()[n];
464           },
465           [n](const Macro &mac) -> const char & { return mac.expansion[n]; },
466           [n](const CompilerInsertion &ins) -> const char & {
467             return ins.text[n];
468           },
469       },
470       u);
471 }
472 
473 const AllSources::Origin &AllSources::MapToOrigin(Provenance at) const {
474   CHECK(range_.Contains(at));
475   std::size_t low{0}, count{origin_.size()};
476   while (count > 1) {
477     std::size_t mid{low + (count >> 1)};
478     if (at < origin_[mid].covers.start()) {
479       count = mid - low;
480     } else {
481       count -= mid - low;
482       low = mid;
483     }
484   }
485   CHECK(origin_[low].covers.Contains(at));
486   return origin_[low];
487 }
488 
489 Provenance AllSources::GetReplacedProvenance(Provenance provenance) const {
490   const Origin &origin{MapToOrigin(provenance)};
491   if (std::holds_alternative<Macro>(origin.u)) {
492     return origin.replaces.start();
493   }
494   return provenance;
495 }
496 
497 std::optional<ProvenanceRange> CookedSource::GetProvenanceRange(
498     CharBlock cookedRange) const {
499   if (!AsCharBlock().Contains(cookedRange)) {
500     return std::nullopt;
501   }
502   ProvenanceRange first{provenanceMap_.Map(cookedRange.begin() - &data_[0])};
503   if (cookedRange.size() <= first.size()) { // always true when empty
504     return first.Prefix(cookedRange.size());
505   }
506   ProvenanceRange last{provenanceMap_.Map(cookedRange.end() - 1 - &data_[0])};
507   if (first.start() <= last.start()) {
508     return {ProvenanceRange{first.start(), last.start() - first.start() + 1}};
509   } else {
510     // cookedRange may start (resp. end) in a macro expansion while it does not
511     // end (resp. start) in this macro expansion. Attempt to build a range
512     // over the replaced source.
513     Provenance firstStart{allSources_.GetReplacedProvenance(first.start())};
514     Provenance lastStart{allSources_.GetReplacedProvenance(last.start())};
515     if (firstStart <= lastStart) {
516       return {ProvenanceRange{firstStart, lastStart - firstStart + 1}};
517     } else {
518       return std::nullopt;
519     }
520   }
521 }
522 
523 std::optional<CharBlock> CookedSource::GetCharBlock(
524     ProvenanceRange range) const {
525   CHECK(!invertedMap_.empty() &&
526       "CompileProvenanceRangeToOffsetMappings not called");
527   if (auto to{invertedMap_.Map(range)}) {
528     return CharBlock{data_.c_str() + *to, range.size()};
529   } else {
530     return std::nullopt;
531   }
532 }
533 
534 std::size_t CookedSource::BufferedBytes() const { return buffer_.bytes(); }
535 
536 void CookedSource::Marshal(AllCookedSources &allCookedSources) {
537   CHECK(provenanceMap_.SizeInBytes() == buffer_.bytes());
538   provenanceMap_.Put(allCookedSources.allSources().AddCompilerInsertion(
539       "(after end of source)"));
540   data_ = buffer_.Marshal();
541   buffer_.clear();
542   for (std::size_t ffStart : possibleFixedFormContinuations_) {
543     if (ffStart > 0 && ffStart + 1 < data_.size() &&
544         data_[ffStart - 1] == '\n' && data_[ffStart] == ' ') {
545       // This fixed form include line is the first source line in an
546       // #include file (or after an empty one).  Connect it with the previous
547       // source line by deleting its terminal newline.
548       data_[ffStart - 1] = ' ';
549     }
550   }
551   possibleFixedFormContinuations_.clear();
552   allCookedSources.Register(*this);
553 }
554 
555 void CookedSource::CompileProvenanceRangeToOffsetMappings(
556     AllSources &allSources) {
557   if (invertedMap_.empty()) {
558     invertedMap_ = provenanceMap_.Invert(allSources);
559   }
560 }
561 
562 static void DumpRange(llvm::raw_ostream &o, const ProvenanceRange &r) {
563   o << "[" << r.start().offset() << ".." << r.Last().offset() << "] ("
564     << r.size() << " bytes)";
565 }
566 
567 llvm::raw_ostream &ProvenanceRangeToOffsetMappings::Dump(
568     llvm::raw_ostream &o) const {
569   for (const auto &m : map_) {
570     o << "provenances ";
571     DumpRange(o, m.first);
572     o << " -> offsets [" << m.second << ".." << (m.second + m.first.size() - 1)
573       << "]\n";
574   }
575   return o;
576 }
577 
578 llvm::raw_ostream &OffsetToProvenanceMappings::Dump(
579     llvm::raw_ostream &o) const {
580   for (const ContiguousProvenanceMapping &m : provenanceMap_) {
581     std::size_t n{m.range.size()};
582     o << "offsets [" << m.start << ".." << (m.start + n - 1)
583       << "] -> provenances ";
584     DumpRange(o, m.range);
585     o << '\n';
586   }
587   return o;
588 }
589 
590 llvm::raw_ostream &AllSources::Dump(llvm::raw_ostream &o) const {
591   o << "AllSources range_ ";
592   DumpRange(o, range_);
593   o << '\n';
594   std::set<const SourceFile *> sources;
595   for (const Origin &m : origin_) {
596     o << "   ";
597     DumpRange(o, m.covers);
598     o << " -> ";
599     common::visit(common::visitors{
600                       [&](const Inclusion &inc) {
601                         if (inc.isModule) {
602                           o << "module ";
603                         }
604                         o << "file " << inc.source.path();
605                         sources.emplace(&inc.source);
606                       },
607                       [&](const Macro &mac) { o << "macro " << mac.expansion; },
608                       [&](const CompilerInsertion &ins) {
609                         o << "compiler '" << ins.text << '\'';
610                         if (ins.text.length() == 1) {
611                           int ch = ins.text[0];
612                           o << "(0x";
613                           o.write_hex(ch & 0xff) << ")";
614                         }
615                       },
616                   },
617         m.u);
618     if (IsValid(m.replaces)) {
619       o << " replaces ";
620       DumpRange(o, m.replaces);
621     }
622     o << '\n';
623   }
624   for (const SourceFile *sf : sources) {
625     sf->Dump(o);
626   }
627   return o;
628 }
629 
630 llvm::raw_ostream &CookedSource::Dump(llvm::raw_ostream &o) const {
631   o << "CookedSource::provenanceMap_:\n";
632   provenanceMap_.Dump(o);
633   o << "CookedSource::invertedMap_:\n";
634   invertedMap_.Dump(o);
635   return o;
636 }
637 
638 AllCookedSources::AllCookedSources(AllSources &s) : allSources_{s} {}
639 AllCookedSources::~AllCookedSources() {}
640 
641 CookedSource &AllCookedSources::NewCookedSource() {
642   return cooked_.emplace_back(allSources_);
643 }
644 
645 const CookedSource *AllCookedSources::Find(CharBlock x) const {
646   auto pair{index_.equal_range(x)};
647   for (auto iter{pair.first}; iter != pair.second; ++iter) {
648     if (iter->second.AsCharBlock().Contains(x)) {
649       return &iter->second;
650     }
651   }
652   return nullptr;
653 }
654 
655 std::optional<ProvenanceRange> AllCookedSources::GetProvenanceRange(
656     CharBlock cb) const {
657   if (const CookedSource * c{Find(cb)}) {
658     return c->GetProvenanceRange(cb);
659   } else {
660     return std::nullopt;
661   }
662 }
663 
664 std::optional<CharBlock> AllCookedSources::GetCharBlockFromLineAndColumns(
665     int line, int startColumn, int endColumn) const {
666   // 2nd column is exclusive, meaning it is target column + 1.
667   CHECK(line > 0 && startColumn > 0 && endColumn > 0);
668   CHECK(startColumn < endColumn);
669   auto provenanceStart{allSources_.GetFirstFileProvenance().value().start()};
670   if (auto sourceFile{allSources_.GetSourceFile(provenanceStart)}) {
671     CHECK(line <= static_cast<int>(sourceFile->lines()));
672     return GetCharBlock(ProvenanceRange(sourceFile->GetLineStartOffset(line) +
673             provenanceStart.offset() + startColumn - 1,
674         endColumn - startColumn));
675   }
676   return std::nullopt;
677 }
678 
679 std::optional<std::pair<SourcePosition, SourcePosition>>
680 AllCookedSources::GetSourcePositionRange(CharBlock cookedRange) const {
681   if (auto range{GetProvenanceRange(cookedRange)}) {
682     if (auto firstOffset{allSources_.GetSourcePosition(range->start())}) {
683       if (auto secondOffset{
684               allSources_.GetSourcePosition(range->start() + range->size())}) {
685         return std::pair{*firstOffset, *secondOffset};
686       }
687     }
688   }
689   return std::nullopt;
690 }
691 
692 std::optional<CharBlock> AllCookedSources::GetCharBlock(
693     ProvenanceRange range) const {
694   for (const auto &c : cooked_) {
695     if (auto result{c.GetCharBlock(range)}) {
696       return result;
697     }
698   }
699   return std::nullopt;
700 }
701 
702 void AllCookedSources::Dump(llvm::raw_ostream &o) const {
703   o << "AllSources:\n";
704   allSources_.Dump(o);
705   for (const auto &c : cooked_) {
706     c.Dump(o);
707   }
708 }
709 
710 bool AllCookedSources::Precedes(CharBlock x, CharBlock y) const {
711   if (const CookedSource * xSource{Find(x)}) {
712     if (xSource->AsCharBlock().Contains(y)) {
713       return x.begin() < y.begin();
714     } else if (const CookedSource * ySource{Find(y)}) {
715       return xSource->number() < ySource->number();
716     } else {
717       return true; // by fiat, all cooked source < anything outside
718     }
719   } else if (Find(y)) {
720     return false;
721   } else {
722     // Both names are compiler-created (SaveTempName).
723     return x < y;
724   }
725 }
726 
727 void AllCookedSources::Register(CookedSource &cooked) {
728   index_.emplace(cooked.AsCharBlock(), cooked);
729   cooked.set_number(static_cast<int>(index_.size()));
730 }
731 
732 } // namespace Fortran::parser
733