xref: /llvm-project/flang/lib/Parser/parsing.cpp (revision 7d60232b38b66138dae1b31027d73ee5b9df5c58)
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-exception
6 //
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 
18 namespace Fortran::parser {
19 
20 Parsing::Parsing(AllCookedSources &allCooked) : allCooked_{allCooked} {}
21 Parsing::~Parsing() {}
22 
23 const 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 path
40     sourceFile = allSources.Open(path, fileError);
41   } else {
42     sourceFile =
43         allSources.Open(path, fileError, "."s /*prepend to search path*/);
44   }
45   if (!fileError.str().empty()) {
46     ProvenanceRange range{allSources.AddCompilerInsertion(path)};
47     messages_.Say(range, "%s"_err_en_US, fileError.str());
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 primary
55     // source file has been opened.  If foo.f is missing from the current
56     // working directory, we don't want to accidentally read another foo.f
57     // 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       .AddCompilerDirectiveSentinel("dir$");
79   if (options.features.IsEnabled(LanguageFeature::OpenACC)) {
80     prescanner.AddCompilerDirectiveSentinel("$acc");
81   }
82   if (options.features.IsEnabled(LanguageFeature::OpenMP)) {
83     prescanner.AddCompilerDirectiveSentinel("$omp");
84     prescanner.AddCompilerDirectiveSentinel("$"); // OMP conditional line
85   }
86   if (options.features.IsEnabled(LanguageFeature::CUDA)) {
87     prescanner.AddCompilerDirectiveSentinel("$cuf");
88     prescanner.AddCompilerDirectiveSentinel("@cuf");
89     preprocessor_.Define("_CUDA", "1");
90   }
91   ProvenanceRange range{allSources.AddIncludedFile(
92       *sourceFile, ProvenanceRange{}, options.isModuleFile)};
93   prescanner.Prescan(range);
94   if (currentCooked_->BufferedBytes() == 0 && !options.isModuleFile) {
95     // Input is empty.  Append a newline so that any warning
96     // message about nonstandard usage will have provenance.
97     currentCooked_->Put('\n', range.start());
98   }
99   currentCooked_->Marshal(allCooked_);
100   if (options.needProvenanceRangeToCharBlockMappings) {
101     currentCooked_->CompileProvenanceRangeToOffsetMappings(allSources);
102   }
103   if (options.showColors) {
104     allSources.setShowColors(/*showColors=*/true);
105   }
106   return sourceFile;
107 }
108 
109 void Parsing::EmitPreprocessorMacros(llvm::raw_ostream &out) const {
110   preprocessor_.PrintMacros(out);
111 }
112 
113 void Parsing::EmitPreprocessedSource(
114     llvm::raw_ostream &out, bool lineDirectives) const {
115   const std::string *sourcePath{nullptr};
116   int sourceLine{0};
117   int column{1};
118   bool inDirective{false};
119   bool inContinuation{false};
120   bool lineWasBlankBefore{true};
121   const AllSources &allSources{allCooked().allSources()};
122   // All directives that flang support are known to have a length of 3 chars
123   constexpr int directiveNameLength{3};
124   // We need to know the current directive in order to provide correct
125   // continuation for the directive
126   std::string directive;
127   for (const char &atChar : cooked().AsCharBlock()) {
128     char ch{atChar};
129     if (ch == '\n') {
130       out << '\n'; // TODO: DOS CR-LF line ending if necessary
131       column = 1;
132       inDirective = false;
133       inContinuation = false;
134       lineWasBlankBefore = true;
135       ++sourceLine;
136       directive.clear();
137     } else {
138       auto provenance{cooked().GetProvenanceRange(CharBlock{&atChar, 1})};
139 
140       // Preserves original case of the character
141       const auto getOriginalChar{[&](char ch) {
142         if (IsLetter(ch) && provenance && provenance->size() == 1) {
143           if (const char *orig{allSources.GetSource(*provenance)}) {
144             const char upper{ToUpperCaseLetter(ch)};
145             if (*orig == upper) {
146               return upper;
147             }
148           }
149         }
150         return ch;
151       }};
152 
153       if (ch == '!' && lineWasBlankBefore) {
154         // Other comment markers (C, *, D) in original fixed form source
155         // input card column 1 will have been deleted or normalized to !,
156         // which signifies a comment (directive) in both source forms.
157         inDirective = true;
158       }
159       if (inDirective && directive.size() < directiveNameLength &&
160           IsLetter(ch)) {
161         directive += getOriginalChar(ch);
162       }
163 
164       std::optional<SourcePosition> position{provenance
165               ? allSources.GetSourcePosition(provenance->start())
166               : std::nullopt};
167       if (lineDirectives && column == 1 && position) {
168         if (&*position->path != sourcePath) {
169           out << "#line \"" << *position->path << "\" " << position->line
170               << '\n';
171         } else if (position->line != sourceLine) {
172           if (sourceLine < position->line &&
173               sourceLine + 10 >= position->line) {
174             // Emit a few newlines to catch up when they'll likely
175             // require fewer bytes than a #line directive would have
176             // occupied.
177             while (sourceLine++ < position->line) {
178               out << '\n';
179             }
180           } else {
181             out << "#line " << position->line << '\n';
182           }
183         }
184         sourcePath = &*position->path;
185         sourceLine = position->line;
186       }
187       if (column > 72) {
188         // Wrap long lines in a portable fashion that works in both
189         // of the Fortran source forms. The first free-form continuation
190         // marker ("&") lands in column 73, which begins the card commentary
191         // field of fixed form, and the second one is put in column 6,
192         // where it signifies fixed form line continuation.
193         // The standard Fortran fixed form column limit (72) is used
194         // for output, even if the input was parsed with a nonstandard
195         // column limit override option.
196         // OpenMP and OpenACC directives' continuations should have the
197         // corresponding sentinel at the next line.
198         const auto continuation{
199             inDirective ? "&\n!$" + directive + "&" : "&\n     &"s};
200         out << continuation;
201         column = 7; // start of fixed form source field
202         ++sourceLine;
203         inContinuation = true;
204       } else if (!inDirective && ch != ' ' && (ch < '0' || ch > '9')) {
205         // Put anything other than a label or directive into the
206         // Fortran fixed form source field (columns [7:72]).
207         for (; column < 7; ++column) {
208           out << ' ';
209         }
210       }
211       if (!inContinuation && position && position->column <= 72 && ch != ' ') {
212         // Preserve original indentation
213         for (; column < position->column; ++column) {
214           out << ' ';
215         }
216       }
217       out << getOriginalChar(ch);
218       lineWasBlankBefore = ch == ' ' && lineWasBlankBefore;
219       ++column;
220     }
221   }
222 }
223 
224 void Parsing::DumpCookedChars(llvm::raw_ostream &out) const {
225   UserState userState{allCooked_, common::LanguageFeatureControl{}};
226   ParseState parseState{cooked()};
227   parseState.set_inFixedForm(options_.isFixedForm).set_userState(&userState);
228   while (std::optional<const char *> p{parseState.GetNextChar()}) {
229     out << **p;
230   }
231 }
232 
233 void Parsing::DumpProvenance(llvm::raw_ostream &out) const {
234   allCooked_.Dump(out);
235 }
236 
237 void Parsing::DumpParsingLog(llvm::raw_ostream &out) const {
238   log_.Dump(out, allCooked_);
239 }
240 
241 void Parsing::Parse(llvm::raw_ostream &out) {
242   UserState userState{allCooked_, options_.features};
243   userState.set_debugOutput(out)
244       .set_instrumentedParse(options_.instrumentedParse)
245       .set_log(&log_);
246   ParseState parseState{cooked()};
247   parseState.set_inFixedForm(options_.isFixedForm).set_userState(&userState);
248   parseTree_ = program.Parse(parseState);
249   CHECK(
250       !parseState.anyErrorRecovery() || parseState.messages().AnyFatalError());
251   consumedWholeFile_ = parseState.IsAtEnd();
252   messages_.Annex(std::move(parseState.messages()));
253   finalRestingPlace_ = parseState.GetLocation();
254 }
255 
256 void Parsing::ClearLog() { log_.clear(); }
257 
258 } // namespace Fortran::parser
259