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