299 lines · cpp
1//===-- lib/Parser/parsing.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-exception6//7//===----------------------------------------------------------------------===//8 9#include "flang/Parser/parsing.h"10#include "prescan.h"11#include "type-parsers.h"12#include "flang/Parser/message.h"13#include "flang/Parser/preprocessor.h"14#include "flang/Parser/provenance.h"15#include "flang/Parser/source.h"16#include "llvm/Support/raw_ostream.h"17 18namespace Fortran::parser {19 20Parsing::Parsing(AllCookedSources &allCooked) : allCooked_{allCooked} {}21Parsing::~Parsing() {}22 23const SourceFile *Parsing::Prescan(const std::string &path, Options options) {24 options_ = options;25 AllSources &allSources{allCooked_.allSources()};26 allSources.ClearSearchPath();27 if (options.isModuleFile) {28 for (const auto &path : options.searchDirectories) {29 allSources.AppendSearchPathDirectory(path);30 }31 }32 33 std::string buf;34 llvm::raw_string_ostream fileError{buf};35 const SourceFile *sourceFile{nullptr};36 if (path == "-") {37 sourceFile = allSources.ReadStandardInput(fileError);38 } else if (options.isModuleFile) {39 // Don't mess with intrinsic module search path40 sourceFile = allSources.Open(path, fileError);41 } else {42 sourceFile =43 allSources.Open(path, fileError, "."s /*prepend to search path*/);44 }45 if (!buf.empty()) {46 ProvenanceRange range{allSources.AddCompilerInsertion(path)};47 messages_.Say(range, "%s"_err_en_US, buf);48 return sourceFile;49 }50 CHECK(sourceFile);51 52 if (!options.isModuleFile) {53 // For .mod files we always want to look in the search directories.54 // For normal source files we don't add them until after the primary55 // source file has been opened. If foo.f is missing from the current56 // working directory, we don't want to accidentally read another foo.f57 // from another directory that's on the search path.58 for (const auto &path : options.searchDirectories) {59 allSources.AppendSearchPathDirectory(path);60 }61 }62 63 if (!options.predefinitions.empty()) {64 preprocessor_.DefineStandardMacros();65 for (const auto &predef : options.predefinitions) {66 if (predef.second) {67 preprocessor_.Define(predef.first, *predef.second);68 } else {69 preprocessor_.Undefine(predef.first);70 }71 }72 }73 currentCooked_ = &allCooked_.NewCookedSource();74 Prescanner prescanner{75 messages_, *currentCooked_, preprocessor_, options.features};76 prescanner.set_fixedForm(options.isFixedForm)77 .set_fixedFormColumnLimit(options.fixedFormColumns)78 .set_preprocessingOnly(options.prescanAndReformat)79 .set_expandIncludeLines(!options.prescanAndReformat ||80 options.expandIncludeLinesInPreprocessedOutput)81 .AddCompilerDirectiveSentinel("dir$");82 bool noneOfTheAbove{!options.features.IsEnabled(LanguageFeature::OpenACC) &&83 !options.features.IsEnabled(LanguageFeature::OpenMP) &&84 !options.features.IsEnabled(LanguageFeature::CUDA)};85 if (options.features.IsEnabled(LanguageFeature::OpenACC) ||86 (options.prescanAndReformat && noneOfTheAbove)) {87 prescanner.AddCompilerDirectiveSentinel("$acc");88 prescanner.AddCompilerDirectiveSentinel("@acc");89 }90 if (options.features.IsEnabled(LanguageFeature::OpenMP) ||91 (options.prescanAndReformat && noneOfTheAbove)) {92 prescanner.AddCompilerDirectiveSentinel("$omp");93 prescanner.AddCompilerDirectiveSentinel("$"); // OMP conditional line94 }95 if (options.features.IsEnabled(LanguageFeature::CUDA) ||96 (options.prescanAndReformat && noneOfTheAbove)) {97 prescanner.AddCompilerDirectiveSentinel("$cuf");98 prescanner.AddCompilerDirectiveSentinel("@cuf");99 }100 ProvenanceRange range{allSources.AddIncludedFile(101 *sourceFile, ProvenanceRange{}, options.isModuleFile)};102 prescanner.Prescan(range);103 if (currentCooked_->BufferedBytes() == 0 && !options.isModuleFile) {104 // Input is empty. Append a newline so that any warning105 // message about nonstandard usage will have provenance.106 currentCooked_->Put('\n', range.start());107 }108 currentCooked_->Marshal(allCooked_);109 if (options.needProvenanceRangeToCharBlockMappings) {110 currentCooked_->CompileProvenanceRangeToOffsetMappings(allSources);111 }112 if (options.showColors) {113 allSources.setShowColors(/*showColors=*/true);114 }115 return sourceFile;116}117 118void Parsing::EmitPreprocessorMacros(llvm::raw_ostream &out) const {119 preprocessor_.PrintMacros(out);120}121 122void Parsing::EmitPreprocessedSource(123 llvm::raw_ostream &out, bool lineDirectives) const {124 const std::string *sourcePath{nullptr};125 int sourceLine{0};126 int column{1};127 bool inDirective{false};128 bool ompConditionalLine{false};129 bool inContinuation{false};130 bool lineWasBlankBefore{true};131 const AllSources &allSources{allCooked().allSources()};132 // All directives that flang supports are known to have a length of 4 chars,133 // except for OpenMP conditional compilation lines (!$).134 constexpr int directiveNameLength{4};135 // We need to know the current directive in order to provide correct136 // continuation for the directive137 std::string directive;138 for (const char &atChar : cooked().AsCharBlock()) {139 char ch{atChar};140 if (ch == '\n') {141 out << '\n'; // TODO: DOS CR-LF line ending if necessary142 column = 1;143 inDirective = false;144 ompConditionalLine = false;145 inContinuation = false;146 lineWasBlankBefore = true;147 ++sourceLine;148 directive.clear();149 } else {150 auto provenance{cooked().GetProvenanceRange(CharBlock{&atChar, 1})};151 152 // Preserves original case of the character153 const auto getOriginalChar{[&](char ch) {154 if (IsLetter(ch) && provenance && provenance->size() == 1) {155 if (const char *orig{allSources.GetSource(*provenance)}) {156 char upper{ToUpperCaseLetter(ch)};157 if (*orig == upper) {158 return upper;159 }160 }161 }162 return ch;163 }};164 165 bool inDirectiveSentinel{false};166 if (ch == '!' && lineWasBlankBefore) {167 // Other comment markers (C, *, D) in original fixed form source168 // input card column 1 will have been deleted or normalized to !,169 // which signifies a comment (directive) in both source forms.170 inDirective = true;171 inDirectiveSentinel = true;172 } else if (inDirective && !ompConditionalLine &&173 directive.size() < directiveNameLength) {174 if (IsLetter(ch) || ch == '$' || ch == '@') {175 directive += getOriginalChar(ch);176 inDirectiveSentinel = true;177 } else if (directive == "$"s) {178 ompConditionalLine = true;179 }180 }181 182 std::optional<SourcePosition> position{provenance183 ? allSources.GetSourcePosition(provenance->start())184 : std::nullopt};185 if (column == 1 && position) {186 if (lineDirectives) {187 if (&*position->path != sourcePath) {188 out << "#line \"" << *position->path << "\" " << position->line189 << '\n';190 } else if (position->line != sourceLine) {191 if (sourceLine < position->line &&192 sourceLine + 10 >= position->line) {193 // Emit a few newlines to catch up when they'll likely194 // require fewer bytes than a #line directive would have195 // occupied.196 while (sourceLine++ < position->line) {197 out << '\n';198 }199 } else {200 out << "#line " << position->line << '\n';201 }202 }203 }204 sourcePath = &*position->path;205 sourceLine = position->line;206 }207 if (column > 72) {208 // Wrap long lines in a portable fashion that works in both209 // of the Fortran source forms. The first free-form continuation210 // marker ("&") lands in column 73, which begins the card commentary211 // field of fixed form, and the second one is put in column 6,212 // where it signifies fixed form line continuation.213 // The standard Fortran fixed form column limit (72) is used214 // for output, even if the input was parsed with a nonstandard215 // column limit override option.216 // OpenMP and OpenACC directives' continuations should have the217 // corresponding sentinel at the next line.218 out << "&\n";219 if (inDirective) {220 if (ompConditionalLine) {221 out << "!$ &";222 } else {223 out << '!' << directive << '&';224 }225 } else {226 out << " &";227 }228 column = 7; // start of fixed form source field229 ++sourceLine;230 inContinuation = true;231 } else if (!inDirective && !ompConditionalLine && ch != ' ' &&232 (ch < '0' || ch > '9')) {233 // Put anything other than a label or directive into the234 // Fortran fixed form source field (columns [7:72]).235 for (int toCol{ch == '&' ? 6 : 7}; column < toCol; ++column) {236 out << ' ';237 }238 }239 if (ch != ' ') {240 if (ompConditionalLine) {241 // Only digits can stay in the label field242 if (!(ch >= '0' && ch <= '9')) {243 for (int toCol{ch == '&' ? 6 : 7}; column < toCol; ++column) {244 out << ' ';245 }246 }247 } else if (!inContinuation && !inDirectiveSentinel && position &&248 position->line == sourceLine && position->column < 72) {249 // Preserve original indentation250 for (; column < position->column; ++column) {251 out << ' ';252 }253 }254 }255 out << getOriginalChar(ch);256 lineWasBlankBefore = ch == ' ' && lineWasBlankBefore;257 ++column;258 }259 }260}261 262void Parsing::DumpCookedChars(llvm::raw_ostream &out) const {263 UserState userState{allCooked_, common::LanguageFeatureControl{}};264 ParseState parseState{cooked()};265 parseState.set_inFixedForm(options_.isFixedForm).set_userState(&userState);266 while (std::optional<const char *> p{parseState.GetNextChar()}) {267 out << **p;268 }269}270 271void Parsing::DumpProvenance(llvm::raw_ostream &out) const {272 allCooked_.Dump(out);273}274 275void Parsing::DumpParsingLog(llvm::raw_ostream &out) const {276 log_.Dump(out, allCooked_);277}278 279void Parsing::Parse(llvm::raw_ostream &out) {280 UserState userState{allCooked_, options_.features};281 userState.set_debugOutput(out)282 .set_instrumentedParse(options_.instrumentedParse)283 .set_log(&log_);284 ParseState parseState{cooked()};285 parseState.set_inFixedForm(options_.isFixedForm).set_userState(&userState);286 // Don't bother managing message buffers when parsing module files.287 parseState.set_deferMessages(options_.isModuleFile);288 parseTree_ = program.Parse(parseState);289 CHECK(290 !parseState.anyErrorRecovery() || parseState.messages().AnyFatalError());291 consumedWholeFile_ = parseState.IsAtEnd();292 messages_.Annex(std::move(parseState.messages()));293 finalRestingPlace_ = parseState.GetLocation();294}295 296void Parsing::ClearLog() { log_.clear(); }297 298} // namespace Fortran::parser299