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