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