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