| 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: color, Bold: 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 | |