Index: flang/include/flang/Parser/parsing.h =================================================================== --- flang/include/flang/Parser/parsing.h +++ flang/include/flang/Parser/parsing.h @@ -37,6 +37,7 @@ bool isModuleFile{false}; bool needProvenanceRangeToCharBlockMappings{false}; Fortran::parser::Encoding encoding{Fortran::parser::Encoding::UTF_8}; + bool prescanOnly{false}; // -E }; class Parsing { @@ -47,12 +48,15 @@ bool consumedWholeFile() const { return consumedWholeFile_; } const char *finalRestingPlace() const { return finalRestingPlace_; } AllCookedSources &allCooked() { return allCooked_; } + const AllCookedSources &allCooked() const { return allCooked_; } Messages &messages() { return messages_; } std::optional &parseTree() { return parseTree_; } const CookedSource &cooked() const { return DEREF(currentCooked_); } const SourceFile *Prescan(const std::string &path, Options); + void EmitPreprocessedSource( + llvm::raw_ostream &, bool lineDirectives = true) const; void DumpCookedChars(llvm::raw_ostream &) const; void DumpProvenance(llvm::raw_ostream &) const; void DumpParsingLog(llvm::raw_ostream &) const; Index: flang/lib/Parser/parsing.cpp =================================================================== --- flang/lib/Parser/parsing.cpp +++ flang/lib/Parser/parsing.cpp @@ -95,6 +95,91 @@ return sourceFile; } +void Parsing::EmitPreprocessedSource( + llvm::raw_ostream &out, bool lineDirectives) const { + bool fixedForm{options_.isFixedForm}; + const SourceFile *sourceFile{nullptr}; + int sourceLine{0}; + int column{1}; + static int constexpr columnLimit{72}; + bool inDirective{false}; + bool inContinuation{false}; + const AllSources &allSources{allCooked().allSources()}; + if (fixedForm) { + out << "!dir$ fixed\n"; + } + for (const char &atChar : cooked().AsCharBlock()) { + char ch{atChar}; + if (ch == '\n') { + out << '\n'; // TODO: DOS CR-LF line ending if necessary + column = 1; + inDirective = false; + inContinuation = false; + ++sourceLine; + } else { + if (ch == '!') { + inDirective = true; + if (std::memcmp(&atChar, "!dir$ fixed\n", 12) == 0) { + fixedForm = true; + } else if (std::memcmp(&atChar, "!dir$ free\n", 11) == 0) { + fixedForm = false; + } + } + auto provenance{cooked().GetProvenanceRange(CharBlock{&atChar, 1})}; + std::optional position{provenance + ? allSources.GetSourcePosition(provenance->start()) + : std::nullopt}; + if (lineDirectives && column == 1 && position) { + if (&position->file != sourceFile) { + out << "#line \"" << position->file.path() << "\" " << position->line + << '\n'; + } else if (position->line != sourceLine) { + if (sourceLine < position->line && + sourceLine + 10 >= position->line) { + while (sourceLine++ < position->line) { + out << '\n'; + } + } else { + out << "#line " << position->line << '\n'; + } + } + sourceFile = &position->file; + sourceLine = position->line; + } + if (column > columnLimit) { + // Wrap long lines in a portable fashion + out << "&\n &"; + column = 7; + ++sourceLine; + inContinuation = true; + } else if (fixedForm && !inDirective && column < 7 && ch != ' ' && + (ch < '0' || ch > '9')) { + for (; column < 7; ++column) { + out << ' '; + } + } + if (!inContinuation && position && position->column <= columnLimit && + ch != ' ') { + // Preserve original indentation + for (; column < position->column; ++column) { + out << ' '; + } + } + if (ch >= 'a' && ch <= 'z' && provenance && provenance->size() == 1) { + // Preserve original case + if (const char *orig{allSources.GetSource(*provenance)}) { + auto upper{static_cast(ch + 'A' - 'a')}; + if (*orig == upper) { + ch = upper; + } + } + } + out << ch; + ++column; + } + } +} + void Parsing::DumpCookedChars(llvm::raw_ostream &out) const { UserState userState{allCooked_, common::LanguageFeatureControl{}}; ParseState parseState{cooked()}; Index: flang/lib/Parser/provenance.cpp =================================================================== --- flang/lib/Parser/provenance.cpp +++ flang/lib/Parser/provenance.cpp @@ -593,7 +593,7 @@ return result; } } - return nullptr; + return std::nullopt; } void AllCookedSources::Dump(llvm::raw_ostream &o) const { Index: flang/tools/f18/f18.cpp =================================================================== --- flang/tools/f18/f18.cpp +++ flang/tools/f18/f18.cpp @@ -93,6 +93,7 @@ bool warningsAreErrors{false}; // -Werror bool byteswapio{false}; // -byteswapio Fortran::parser::Encoding encoding{Fortran::parser::Encoding::UTF_8}; + bool lineDirectives{true}; // -P disables bool syntaxOnly{false}; bool dumpProvenance{false}; bool dumpCookedChars{false}; @@ -221,6 +222,11 @@ parsing.DumpProvenance(llvm::outs()); return {}; } + if (options.prescanOnly) { + parsing.messages().Emit(llvm::errs(), allCookedSources); + parsing.EmitPreprocessedSource(llvm::outs(), driver.lineDirectives); + return {}; + } if (driver.dumpCookedChars) { parsing.messages().Emit(llvm::errs(), allCookedSources); parsing.DumpCookedChars(llvm::outs()); @@ -536,6 +542,10 @@ } else if (arg == "-ed") { options.features.Enable(Fortran::common::LanguageFeature::OldDebugLines); } else if (arg == "-E") { + options.prescanOnly = true; + } else if (arg == "-P") { + driver.lineDirectives = false; + } else if (arg == "-fdebug-dump-cooked-chars") { driver.dumpCookedChars = true; } else if (arg == "-fbackslash" || arg == "-fno-backslash") { options.features.Enable(