| //===-- lib/Parser/provenance.cpp -----------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "flang/Parser/provenance.h" | |
| #include "flang/Common/idioms.h" | |
| #include "llvm/Support/raw_ostream.h" | |
| #include <algorithm> | |
| #include <set> | |
| #include <utility> | |
| namespace Fortran::parser { | |
| ProvenanceRangeToOffsetMappings::ProvenanceRangeToOffsetMappings() {} | |
| ProvenanceRangeToOffsetMappings::~ProvenanceRangeToOffsetMappings() {} | |
| void ProvenanceRangeToOffsetMappings::Put( | |
| ProvenanceRange range, std::size_t offset) { | |
| auto fromTo{map_.equal_range(range)}; | |
| for (auto iter{fromTo.first}; iter != fromTo.second; ++iter) { | |
| if (range == iter->first) { | |
| iter->second = std::min(offset, iter->second); | |
| return; | |
| } | |
| } | |
| if (fromTo.second != map_.end()) { | |
| map_.emplace_hint(fromTo.second, range, offset); | |
| } else { | |
| map_.emplace(range, offset); | |
| } | |
| } | |
| std::optional<std::size_t> ProvenanceRangeToOffsetMappings::Map( | |
| ProvenanceRange range) const { | |
| auto fromTo{map_.equal_range(range)}; | |
| std::optional<std::size_t> result; | |
| for (auto iter{fromTo.first}; iter != fromTo.second; ++iter) { | |
| ProvenanceRange that{iter->first}; | |
| if (that.Contains(range)) { | |
| std::size_t offset{iter->second + that.MemberOffset(range.start())}; | |
| if (!result || offset < *result) { | |
| result = offset; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| bool ProvenanceRangeToOffsetMappings::WhollyPrecedes::operator()( | |
| ProvenanceRange before, ProvenanceRange after) const { | |
| return before.start() + before.size() <= after.start(); | |
| } | |
| void OffsetToProvenanceMappings::clear() { provenanceMap_.clear(); } | |
| void OffsetToProvenanceMappings::swap(OffsetToProvenanceMappings &that) { | |
| provenanceMap_.swap(that.provenanceMap_); | |
| } | |
| void OffsetToProvenanceMappings::shrink_to_fit() { | |
| provenanceMap_.shrink_to_fit(); | |
| } | |
| std::size_t OffsetToProvenanceMappings::SizeInBytes() const { | |
| if (provenanceMap_.empty()) { | |
| return 0; | |
| } else { | |
| const ContiguousProvenanceMapping &last{provenanceMap_.back()}; | |
| return last.start + last.range.size(); | |
| } | |
| } | |
| void OffsetToProvenanceMappings::Put(ProvenanceRange range) { | |
| if (provenanceMap_.empty()) { | |
| provenanceMap_.push_back({0, range}); | |
| } else { | |
| ContiguousProvenanceMapping &last{provenanceMap_.back()}; | |
| if (!last.range.AnnexIfPredecessor(range)) { | |
| provenanceMap_.push_back({last.start + last.range.size(), range}); | |
| } | |
| } | |
| } | |
| void OffsetToProvenanceMappings::Put(const OffsetToProvenanceMappings &that) { | |
| for (const auto &map : that.provenanceMap_) { | |
| Put(map.range); | |
| } | |
| } | |
| ProvenanceRange OffsetToProvenanceMappings::Map(std::size_t at) const { | |
| if (provenanceMap_.empty()) { | |
| CHECK(at == 0); | |
| return {}; | |
| } | |
| std::size_t low{0}, count{provenanceMap_.size()}; | |
| while (count > 1) { | |
| std::size_t mid{low + (count >> 1)}; | |
| if (provenanceMap_[mid].start > at) { | |
| count = mid - low; | |
| } else { | |
| count -= mid - low; | |
| low = mid; | |
| } | |
| } | |
| std::size_t offset{at - provenanceMap_[low].start}; | |
| return provenanceMap_[low].range.Suffix(offset); | |
| } | |
| void OffsetToProvenanceMappings::RemoveLastBytes(std::size_t bytes) { | |
| for (; bytes > 0; provenanceMap_.pop_back()) { | |
| CHECK(!provenanceMap_.empty()); | |
| ContiguousProvenanceMapping &last{provenanceMap_.back()}; | |
| std::size_t chunk{last.range.size()}; | |
| if (bytes < chunk) { | |
| last.range = last.range.Prefix(chunk - bytes); | |
| break; | |
| } | |
| bytes -= chunk; | |
| } | |
| } | |
| ProvenanceRangeToOffsetMappings OffsetToProvenanceMappings::Invert( | |
| const AllSources &allSources) const { | |
| ProvenanceRangeToOffsetMappings result; | |
| for (const auto &contig : provenanceMap_) { | |
| ProvenanceRange range{contig.range}; | |
| while (!range.empty()) { | |
| ProvenanceRange source{allSources.IntersectionWithSourceFiles(range)}; | |
| if (source.empty()) { | |
| break; | |
| } | |
| result.Put( | |
| source, contig.start + contig.range.MemberOffset(source.start())); | |
| Provenance after{source.NextAfter()}; | |
| if (range.Contains(after)) { | |
| range = range.Suffix(range.MemberOffset(after)); | |
| } else { | |
| break; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| AllSources::AllSources() : range_{1, 1} { | |
| // Start the origin_ array with a dummy entry that has a forced provenance, | |
| // so that provenance offset 0 remains reserved as an uninitialized | |
| // value. | |
| origin_.emplace_back(range_, std::string{'?'}); | |
| } | |
| AllSources::~AllSources() {} | |
| const char &AllSources::operator[](Provenance at) const { | |
| const Origin &origin{MapToOrigin(at)}; | |
| return origin[origin.covers.MemberOffset(at)]; | |
| } | |
| void AllSources::ClearSearchPath() { searchPath_.clear(); } | |
| void AllSources::AppendSearchPathDirectory(std::string directory) { | |
| // gfortran and ifort append to current path, PGI prepends | |
| searchPath_.push_back(directory); | |
| } | |
| const SourceFile *AllSources::OpenPath( | |
| std::string path, llvm::raw_ostream &error) { | |
| std::unique_ptr<SourceFile> source{std::make_unique<SourceFile>(encoding_)}; | |
| if (source->Open(path, error)) { | |
| return ownedSourceFiles_.emplace_back(std::move(source)).get(); | |
| } else { | |
| return nullptr; | |
| } | |
| } | |
| const SourceFile *AllSources::Open(std::string path, llvm::raw_ostream &error, | |
| std::optional<std::string> &&prependPath) { | |
| std::unique_ptr<SourceFile> source{std::make_unique<SourceFile>(encoding_)}; | |
| if (prependPath) { | |
| // Set to "." for the initial source file; set to the directory name | |
| // of the including file for #include "quoted-file" directives & | |
| // INCLUDE statements. | |
| searchPath_.emplace_front(std::move(*prependPath)); | |
| } | |
| std::optional<std::string> found{LocateSourceFile(path, searchPath_)}; | |
| if (prependPath) { | |
| searchPath_.pop_front(); | |
| } | |
| if (found) { | |
| return OpenPath(*found, error); | |
| } else { | |
| error << "Source file '" << path << "' was not found"; | |
| return nullptr; | |
| } | |
| } | |
| const SourceFile *AllSources::ReadStandardInput(llvm::raw_ostream &error) { | |
| std::unique_ptr<SourceFile> source{std::make_unique<SourceFile>(encoding_)}; | |
| if (source->ReadStandardInput(error)) { | |
| return ownedSourceFiles_.emplace_back(std::move(source)).get(); | |
| } | |
| return nullptr; | |
| } | |
| std::vector<std::string> AllSources::GetIncludedFilePaths() const { | |
| std::vector<std::string> paths; | |
| std::set<std::string> seen; | |
| for (const auto &source : ownedSourceFiles_) { | |
| const std::string &path{source->path()}; | |
| if (path.empty() || path == "-") { | |
| continue; | |
| } | |
| if (seen.insert(path).second) { | |
| paths.push_back(path); | |
| } | |
| } | |
| return paths; | |
| } | |
| ProvenanceRange AllSources::AddIncludedFile( | |
| const SourceFile &source, ProvenanceRange from, bool isModule) { | |
| ProvenanceRange covers{range_.NextAfter(), source.bytes()}; | |
| CHECK(range_.AnnexIfPredecessor(covers)); | |
| CHECK(origin_.back().covers.ImmediatelyPrecedes(covers)); | |
| origin_.emplace_back(covers, source, from, isModule); | |
| return covers; | |
| } | |
| ProvenanceRange AllSources::AddMacroCall( | |
| ProvenanceRange def, ProvenanceRange use, const std::string &expansion) { | |
| ProvenanceRange covers{range_.NextAfter(), expansion.size()}; | |
| CHECK(range_.AnnexIfPredecessor(covers)); | |
| CHECK(origin_.back().covers.ImmediatelyPrecedes(covers)); | |
| origin_.emplace_back(covers, def, use, expansion); | |
| return covers; | |
| } | |
| ProvenanceRange AllSources::AddCompilerInsertion(std::string text) { | |
| ProvenanceRange covers{range_.NextAfter(), text.size()}; | |
| CHECK(range_.AnnexIfPredecessor(covers)); | |
| CHECK(origin_.back().covers.ImmediatelyPrecedes(covers)); | |
| origin_.emplace_back(covers, text); | |
| return covers; | |
| } | |
| static void EmitPrefix(llvm::raw_ostream &o, llvm::raw_ostream::Colors color, | |
| const std::string &prefix, bool showColors) { | |
| if (prefix.empty()) { | |
| return; | |
| } | |
| if (showColors) { | |
| o.changeColor(color, true); | |
| } | |
| o << prefix; | |
| if (showColors) { | |
| o.resetColor(); | |
| } | |
| } | |
| std::optional<ProvenanceRange> AllSources::GetInclusionInfo( | |
| const std::optional<ProvenanceRange> &range) const { | |
| if (!range || !IsValid(range->start())) | |
| return std::nullopt; | |
| const Origin &origin{MapToOrigin(range->start())}; | |
| return common::visit( | |
| common::visitors{ | |
| [&](const Inclusion &inc) -> std::optional<ProvenanceRange> { | |
| if (IsValid(origin.replaces) && | |
| range_.Contains(origin.replaces.start())) | |
| return origin.replaces; | |
| return std::nullopt; | |
| }, | |
| [&](const auto &) -> std::optional<ProvenanceRange> { | |
| return std::nullopt; | |
| }, | |
| }, | |
| origin.u); | |
| } | |
| void AllSources::EmitMessage(llvm::raw_ostream &o, | |
| const std::optional<ProvenanceRange> &range, const std::string &message, | |
| const std::string &prefix, llvm::raw_ostream::Colors color, | |
| bool echoSourceLine) const { | |
| if (!range) { | |
| EmitPrefix(o, color, prefix, this->getShowColors()); | |
| o << message << '\n'; | |
| return; | |
| } | |
| CHECK(IsValid(*range)); | |
| const Origin &origin{MapToOrigin(range->start())}; | |
| common::visit( | |
| common::visitors{ | |
| [&](const Inclusion &inc) { | |
| std::size_t offset{origin.covers.MemberOffset(range->start())}; | |
| SourcePosition pos{inc.source.GetSourcePosition(offset)}; | |
| o << pos.path << ':' << pos.line << ':' << pos.column << ": "; | |
| EmitPrefix(o, color, prefix, this->getShowColors()); | |
| o << message << '\n'; | |
| if (echoSourceLine) { | |
| const char *text{inc.source.content().data() + | |
| inc.source.GetLineStartOffset(pos.trueLineNumber)}; | |
| o << " "; | |
| for (const char *p{text}; *p != '\n'; ++p) { | |
| o << *p; | |
| } | |
| o << "\n "; | |
| for (int j{1}; j < pos.column; ++j) { | |
| char ch{text[j - 1]}; | |
| o << (ch == '\t' ? '\t' : ' '); | |
| } | |
| o << '^'; | |
| if (range->size() > 1) { | |
| auto last{range->start() + range->size() - 1}; | |
| if (&MapToOrigin(last) == &origin) { | |
| auto endOffset{origin.covers.MemberOffset(last)}; | |
| auto endPos{inc.source.GetSourcePosition(endOffset)}; | |
| if (pos.line == endPos.line) { | |
| for (int j{pos.column}; j < endPos.column; ++j) { | |
| o << '^'; | |
| } | |
| } | |
| } | |
| } | |
| o << '\n'; | |
| } | |
| if (IsValid(origin.replaces)) { | |
| EmitMessage(o, origin.replaces, | |
| inc.isModule ? "used here"s : "included here"s, prefix, color, | |
| echoSourceLine); | |
| } | |
| }, | |
| [&](const Macro &mac) { | |
| EmitMessage( | |
| o, origin.replaces, message, prefix, color, echoSourceLine); | |
| EmitMessage(o, mac.definition, "in a macro defined here", ""s, | |
| color, echoSourceLine); | |
| if (echoSourceLine) { | |
| o << "that expanded to:\n " << mac.expansion << "\n "; | |
| for (std::size_t j{0}; | |
| origin.covers.OffsetMember(j) < range->start(); ++j) { | |
| o << (mac.expansion[j] == '\t' ? '\t' : ' '); | |
| } | |
| o << "^\n"; | |
| } | |
| }, | |
| [&](const CompilerInsertion &) { | |
| EmitPrefix(o, color, prefix, this->getShowColors()); | |
| o << message << '\n'; | |
| }, | |
| }, | |
| origin.u); | |
| } | |
| const SourceFile *AllSources::GetSourceFile( | |
| Provenance at, std::size_t *offset, bool topLevel) const { | |
| const Origin &origin{MapToOrigin(at)}; | |
| return common::visit(common::visitors{ | |
| [&](const Inclusion &inc) { | |
| if (topLevel && !origin.replaces.empty()) { | |
| return GetSourceFile( | |
| origin.replaces.start(), offset, topLevel); | |
| } else { | |
| if (offset) { | |
| *offset = origin.covers.MemberOffset(at); | |
| } | |
| return &inc.source; | |
| } | |
| }, | |
| [&](const Macro &) { | |
| return GetSourceFile( | |
| origin.replaces.start(), offset); | |
| }, | |
| [offset](const CompilerInsertion &) { | |
| if (offset) { | |
| *offset = 0; | |
| } | |
| return static_cast<const SourceFile *>(nullptr); | |
| }, | |
| }, | |
| origin.u); | |
| } | |
| const char *AllSources::GetSource(ProvenanceRange range) const { | |
| Provenance start{range.start()}; | |
| const Origin &origin{MapToOrigin(start)}; | |
| return origin.covers.Contains(range) | |
| ? &origin[origin.covers.MemberOffset(start)] | |
| : nullptr; | |
| } | |
| std::optional<SourcePosition> AllSources::GetSourcePosition( | |
| Provenance prov) const { | |
| const Origin &origin{MapToOrigin(prov)}; | |
| return common::visit( | |
| common::visitors{ | |
| [&](const Inclusion &inc) -> std::optional<SourcePosition> { | |
| std::size_t offset{origin.covers.MemberOffset(prov)}; | |
| return inc.source.GetSourcePosition(offset); | |
| }, | |
| [&](const Macro &) { | |
| return GetSourcePosition(origin.replaces.start()); | |
| }, | |
| [](const CompilerInsertion &) -> std::optional<SourcePosition> { | |
| return std::nullopt; | |
| }, | |
| }, | |
| origin.u); | |
| } | |
| std::optional<ProvenanceRange> AllSources::GetFirstFileProvenance() const { | |
| for (const auto &origin : origin_) { | |
| if (std::holds_alternative<Inclusion>(origin.u)) { | |
| return origin.covers; | |
| } | |
| } | |
| return std::nullopt; | |
| } | |
| std::string AllSources::GetPath(Provenance at, bool topLevel) const { | |
| std::size_t offset{0}; | |
| const SourceFile *source{GetSourceFile(at, &offset, topLevel)}; | |
| return source ? *source->GetSourcePosition(offset).path : ""s; | |
| } | |
| int AllSources::GetLineNumber(Provenance at) const { | |
| std::size_t offset{0}; | |
| const SourceFile *source{GetSourceFile(at, &offset)}; | |
| return source ? source->GetSourcePosition(offset).line : 0; | |
| } | |
| Provenance AllSources::CompilerInsertionProvenance(char ch) { | |
| auto iter{compilerInsertionProvenance_.find(ch)}; | |
| if (iter != compilerInsertionProvenance_.end()) { | |
| return iter->second; | |
| } | |
| ProvenanceRange newCharRange{AddCompilerInsertion(std::string{ch})}; | |
| Provenance newCharProvenance{newCharRange.start()}; | |
| compilerInsertionProvenance_.insert(std::make_pair(ch, newCharProvenance)); | |
| return newCharProvenance; | |
| } | |
| ProvenanceRange AllSources::IntersectionWithSourceFiles( | |
| ProvenanceRange range) const { | |
| if (range.empty()) { | |
| return {}; | |
| } else { | |
| const Origin &origin{MapToOrigin(range.start())}; | |
| if (std::holds_alternative<Inclusion>(origin.u)) { | |
| return range.Intersection(origin.covers); | |
| } else { | |
| auto skip{ | |
| origin.covers.size() - origin.covers.MemberOffset(range.start())}; | |
| return IntersectionWithSourceFiles(range.Suffix(skip)); | |
| } | |
| } | |
| } | |
| AllSources::Origin::Origin(ProvenanceRange r, const SourceFile &source) | |
| : u{Inclusion{source}}, covers{r} {} | |
| AllSources::Origin::Origin(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[](std::size_t n) const { | |
| return common::visit( | |
| common::visitors{ | |
| [n](const Inclusion &inc) -> const char & { | |
| return inc.source.content()[n]; | |
| }, | |
| [n](const Macro &mac) -> const char & { return mac.expansion[n]; }, | |
| [n](const CompilerInsertion &ins) -> const char & { | |
| return ins.text[n]; | |
| }, | |
| }, | |
| u); | |
| } | |
| const AllSources::Origin &AllSources::MapToOrigin(Provenance at) const { | |
| CHECK(range_.Contains(at)); | |
| std::size_t low{0}, count{origin_.size()}; | |
| while (count > 1) { | |
| std::size_t mid{low + (count >> 1)}; | |
| if (at < origin_[mid].covers.start()) { | |
| count = mid - low; | |
| } else { | |
| count -= mid - low; | |
| low = mid; | |
| } | |
| } | |
| CHECK(origin_[low].covers.Contains(at)); | |
| return origin_[low]; | |
| } | |
| Provenance AllSources::GetReplacedProvenance(Provenance provenance) const { | |
| const Origin &origin{MapToOrigin(provenance)}; | |
| if (std::holds_alternative<Macro>(origin.u)) { | |
| return origin.replaces.start(); | |
| } | |
| return provenance; | |
| } | |
| std::optional<ProvenanceRange> CookedSource::GetProvenanceRange( | |
| CharBlock cookedRange) const { | |
| if (!AsCharBlock().Contains(cookedRange)) { | |
| return std::nullopt; | |
| } | |
| ProvenanceRange first{provenanceMap_.Map(cookedRange.begin() - &data_[0])}; | |
| if (cookedRange.size() <= first.size()) { // always true when empty | |
| return first.Prefix(cookedRange.size()); | |
| } | |
| ProvenanceRange last{provenanceMap_.Map(cookedRange.end() - 1 - &data_[0])}; | |
| if (first.start() <= last.start()) { | |
| return {ProvenanceRange{first.start(), last.start() - first.start() + 1}}; | |
| } else { | |
| // cookedRange may start (resp. end) in a macro expansion while it does not | |
| // end (resp. start) in this macro expansion. Attempt to build a range | |
| // over the replaced source. | |
| Provenance firstStart{allSources_.GetReplacedProvenance(first.start())}; | |
| Provenance lastStart{allSources_.GetReplacedProvenance(last.start())}; | |
| if (firstStart <= lastStart) { | |
| return {ProvenanceRange{firstStart, lastStart - firstStart + 1}}; | |
| } else { | |
| return std::nullopt; | |
| } | |
| } | |
| } | |
| std::optional<CharBlock> CookedSource::GetCharBlock( | |
| ProvenanceRange range) const { | |
| CHECK(!invertedMap_.empty() && | |
| "CompileProvenanceRangeToOffsetMappings not called"); | |
| if (auto to{invertedMap_.Map(range)}) { | |
| return CharBlock{data_.c_str() + *to, range.size()}; | |
| } else { | |
| return std::nullopt; | |
| } | |
| } | |
| std::size_t CookedSource::BufferedBytes() const { return buffer_.bytes(); } | |
| void CookedSource::Marshal(AllCookedSources &allCookedSources) { | |
| CHECK(provenanceMap_.SizeInBytes() == buffer_.bytes()); | |
| provenanceMap_.Put(allCookedSources.allSources().AddCompilerInsertion( | |
| "(after end of source)")); | |
| data_ = buffer_.Marshal(); | |
| buffer_.clear(); | |
| for (std::size_t ffStart : possibleFixedFormContinuations_) { | |
| if (ffStart > 0 && ffStart + 1 < data_.size() && | |
| data_[ffStart - 1] == '\n' && data_[ffStart] == ' ') { | |
| // This fixed form include line is the first source line in an | |
| // #include file (or after an empty one). Connect it with the previous | |
| // source line by deleting its terminal newline. | |
| data_[ffStart - 1] = ' '; | |
| } | |
| } | |
| possibleFixedFormContinuations_.clear(); | |
| allCookedSources.Register(*this); | |
| } | |
| void CookedSource::CompileProvenanceRangeToOffsetMappings( | |
| AllSources &allSources) { | |
| if (invertedMap_.empty()) { | |
| invertedMap_ = provenanceMap_.Invert(allSources); | |
| } | |
| } | |
| static void DumpRange(llvm::raw_ostream &o, const ProvenanceRange &r) { | |
| o << "[" << r.start().offset() << ".." << r.Last().offset() << "] (" | |
| << r.size() << " bytes)"; | |
| } | |
| llvm::raw_ostream &ProvenanceRangeToOffsetMappings::Dump( | |
| llvm::raw_ostream &o) const { | |
| for (const auto &m : map_) { | |
| o << "provenances "; | |
| DumpRange(o, m.first); | |
| o << " -> offsets [" << m.second << ".." << (m.second + m.first.size() - 1) | |
| << "]\n"; | |
| } | |
| return o; | |
| } | |
| llvm::raw_ostream &OffsetToProvenanceMappings::Dump( | |
| llvm::raw_ostream &o) const { | |
| for (const ContiguousProvenanceMapping &m : provenanceMap_) { | |
| std::size_t n{m.range.size()}; | |
| o << "offsets [" << m.start << ".." << (m.start + n - 1) | |
| << "] -> provenances "; | |
| DumpRange(o, m.range); | |
| o << '\n'; | |
| } | |
| return o; | |
| } | |
| llvm::raw_ostream &AllSources::Dump(llvm::raw_ostream &o) const { | |
| o << "AllSources range_ "; | |
| DumpRange(o, range_); | |
| o << '\n'; | |
| std::set<const SourceFile *> sources; | |
| for (const Origin &m : origin_) { | |
| o << " "; | |
| DumpRange(o, m.covers); | |
| o << " -> "; | |
| common::visit(common::visitors{ | |
| [&](const Inclusion &inc) { | |
| if (inc.isModule) { | |
| o << "module "; | |
| } | |
| o << "file " << inc.source.path(); | |
| sources.emplace(&inc.source); | |
| }, | |
| [&](const Macro &mac) { o << "macro " << mac.expansion; }, | |
| [&](const CompilerInsertion &ins) { | |
| o << "compiler '" << ins.text << '\''; | |
| if (ins.text.length() == 1) { | |
| int ch = ins.text[0]; | |
| o << "(0x"; | |
| o.write_hex(ch & 0xff) << ")"; | |
| } | |
| }, | |
| }, | |
| m.u); | |
| if (IsValid(m.replaces)) { | |
| o << " replaces "; | |
| DumpRange(o, m.replaces); | |
| } | |
| o << '\n'; | |
| } | |
| for (const SourceFile *sf : sources) { | |
| sf->Dump(o); | |
| } | |
| return o; | |
| } | |
| llvm::raw_ostream &CookedSource::Dump(llvm::raw_ostream &o) const { | |
| o << "CookedSource::provenanceMap_:\n"; | |
| provenanceMap_.Dump(o); | |
| o << "CookedSource::invertedMap_:\n"; | |
| invertedMap_.Dump(o); | |
| return o; | |
| } | |
| AllCookedSources::AllCookedSources(AllSources &s) : allSources_{s} {} | |
| AllCookedSources::~AllCookedSources() {} | |
| CookedSource &AllCookedSources::NewCookedSource() { | |
| return cooked_.emplace_back(allSources_); | |
| } | |
| const CookedSource *AllCookedSources::Find(CharBlock x) const { | |
| auto pair{index_.equal_range(x)}; | |
| for (auto iter{pair.first}; iter != pair.second; ++iter) { | |
| if (iter->second.AsCharBlock().Contains(x)) { | |
| return &iter->second; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| std::optional<ProvenanceRange> AllCookedSources::GetProvenanceRange( | |
| CharBlock cb) const { | |
| if (const CookedSource * c{Find(cb)}) { | |
| return c->GetProvenanceRange(cb); | |
| } else { | |
| return std::nullopt; | |
| } | |
| } | |
| std::optional<CharBlock> AllCookedSources::GetCharBlockFromLineAndColumns( | |
| int line, int startColumn, int endColumn) const { | |
| // 2nd column is exclusive, meaning it is target column + 1. | |
| CHECK(line > 0 && startColumn > 0 && endColumn > 0); | |
| CHECK(startColumn < endColumn); | |
| auto provenanceStart{allSources_.GetFirstFileProvenance().value().start()}; | |
| if (auto sourceFile{allSources_.GetSourceFile(provenanceStart)}) { | |
| CHECK(line <= static_cast<int>(sourceFile->lines())); | |
| return GetCharBlock(ProvenanceRange(sourceFile->GetLineStartOffset(line) + | |
| provenanceStart.offset() + startColumn - 1, | |
| endColumn - startColumn)); | |
| } | |
| return std::nullopt; | |
| } | |
| std::optional<std::pair<SourcePosition, SourcePosition>> | |
| AllCookedSources::GetSourcePositionRange(CharBlock cookedRange) const { | |
| if (auto range{GetProvenanceRange(cookedRange)}) { | |
| if (auto firstOffset{allSources_.GetSourcePosition(range->start())}) { | |
| if (auto secondOffset{ | |
| allSources_.GetSourcePosition(range->start() + range->size())}) { | |
| return std::pair{*firstOffset, *secondOffset}; | |
| } | |
| } | |
| } | |
| return std::nullopt; | |
| } | |
| std::optional<CharBlock> AllCookedSources::GetCharBlock( | |
| ProvenanceRange range) const { | |
| for (const auto &c : cooked_) { | |
| if (auto result{c.GetCharBlock(range)}) { | |
| return result; | |
| } | |
| } | |
| return std::nullopt; | |
| } | |
| void AllCookedSources::Dump(llvm::raw_ostream &o) const { | |
| o << "AllSources:\n"; | |
| allSources_.Dump(o); | |
| for (const auto &c : cooked_) { | |
| c.Dump(o); | |
| } | |
| } | |
| bool AllCookedSources::Precedes(CharBlock x, CharBlock y) const { | |
| if (const CookedSource * xSource{Find(x)}) { | |
| if (xSource->AsCharBlock().Contains(y)) { | |
| return x.begin() < y.begin(); | |
| } else if (const CookedSource * ySource{Find(y)}) { | |
| return xSource->number() < ySource->number(); | |
| } else { | |
| return true; // by fiat, all cooked source < anything outside | |
| } | |
| } else if (Find(y)) { | |
| return false; | |
| } else { | |
| // Both names are compiler-created (SaveTempName). | |
| return x < y; | |
| } | |
| } | |
| void AllCookedSources::Register(CookedSource &cooked) { | |
| index_.emplace(cooked.AsCharBlock(), cooked); | |
| cooked.set_number(static_cast<int>(index_.size())); | |
| } | |
| } // namespace Fortran::parser |