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