1 //===-- lib/Parser/parsing.cpp --------------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "flang/Parser/parsing.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"
18 namespace Fortran::parser
{
20 Parsing::Parsing(AllCookedSources
&allCooked
) : allCooked_
{allCooked
} {}
21 Parsing::~Parsing() {}
23 const SourceFile
*Parsing::Prescan(const std::string
&path
, 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
);
34 llvm::raw_string_ostream fileError
{buf
};
35 const SourceFile
*sourceFile
{nullptr};
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
);
43 allSources
.Open(path
, fileError
, "."s
/*prepend to search path*/);
46 ProvenanceRange range
{allSources
.AddCompilerInsertion(path
)};
47 messages_
.Say(range
, "%s"_err_en_US
, buf
);
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
);
63 if (!options
.predefinitions
.empty()) {
64 preprocessor_
.DefineStandardMacros();
65 for (const auto &predef
: options
.predefinitions
) {
67 preprocessor_
.Define(predef
.first
, *predef
.second
);
69 preprocessor_
.Undefine(predef
.first
);
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");
85 if (options
.features
.IsEnabled(LanguageFeature::OpenMP
)) {
86 prescanner
.AddCompilerDirectiveSentinel("$omp");
87 prescanner
.AddCompilerDirectiveSentinel("$"); // OMP conditional line
89 if (options
.features
.IsEnabled(LanguageFeature::CUDA
)) {
90 prescanner
.AddCompilerDirectiveSentinel("$cuf");
91 prescanner
.AddCompilerDirectiveSentinel("@cuf");
92 preprocessor_
.Define("_CUDA", "1");
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());
102 currentCooked_
->Marshal(allCooked_
);
103 if (options
.needProvenanceRangeToCharBlockMappings
) {
104 currentCooked_
->CompileProvenanceRangeToOffsetMappings(allSources
);
106 if (options
.showColors
) {
107 allSources
.setShowColors(/*showColors=*/true);
112 void Parsing::EmitPreprocessorMacros(llvm::raw_ostream
&out
) const {
113 preprocessor_
.PrintMacros(out
);
116 void Parsing::EmitPreprocessedSource(
117 llvm::raw_ostream
&out
, bool lineDirectives
) const {
118 const std::string
*sourcePath
{nullptr};
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()) {
133 out
<< '\n'; // TODO: DOS CR-LF line ending if necessary
136 inContinuation
= false;
137 lineWasBlankBefore
= true;
141 auto provenance
{cooked().GetProvenanceRange(CharBlock
{&atChar
, 1})};
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
) {
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.
162 if (inDirective
&& directive
.size() < directiveNameLength
&&
164 directive
+= getOriginalChar(ch
);
167 std::optional
<SourcePosition
> position
{provenance
168 ? allSources
.GetSourcePosition(provenance
->start())
170 if (lineDirectives
&& column
== 1 && position
) {
171 if (&*position
->path
!= sourcePath
) {
172 out
<< "#line \"" << *position
->path
<< "\" " << position
->line
174 } else if (position
->line
!= sourceLine
) {
175 if (sourceLine
< position
->line
&&
176 sourceLine
+ 10 >= position
->line
) {
177 // Emit a few newlines to catch up when they'll likely
178 // require fewer bytes than a #line directive would have
180 while (sourceLine
++ < position
->line
) {
184 out
<< "#line " << position
->line
<< '\n';
187 sourcePath
= &*position
->path
;
188 sourceLine
= position
->line
;
191 // Wrap long lines in a portable fashion that works in both
192 // of the Fortran source forms. The first free-form continuation
193 // marker ("&") lands in column 73, which begins the card commentary
194 // field of fixed form, and the second one is put in column 6,
195 // where it signifies fixed form line continuation.
196 // The standard Fortran fixed form column limit (72) is used
197 // for output, even if the input was parsed with a nonstandard
198 // column limit override option.
199 // OpenMP and OpenACC directives' continuations should have the
200 // corresponding sentinel at the next line.
201 const auto continuation
{
202 inDirective
? "&\n!$" + directive
+ "&" : "&\n &"s
};
204 column
= 7; // start of fixed form source field
206 inContinuation
= true;
207 } else if (!inDirective
&& ch
!= ' ' && (ch
< '0' || ch
> '9')) {
208 // Put anything other than a label or directive into the
209 // Fortran fixed form source field (columns [7:72]).
210 for (; column
< 7; ++column
) {
214 if (!inContinuation
&& position
&& position
->column
<= 72 && ch
!= ' ') {
215 // Preserve original indentation
216 for (; column
< position
->column
; ++column
) {
220 out
<< getOriginalChar(ch
);
221 lineWasBlankBefore
= ch
== ' ' && lineWasBlankBefore
;
227 void Parsing::DumpCookedChars(llvm::raw_ostream
&out
) const {
228 UserState userState
{allCooked_
, common::LanguageFeatureControl
{}};
229 ParseState parseState
{cooked()};
230 parseState
.set_inFixedForm(options_
.isFixedForm
).set_userState(&userState
);
231 while (std::optional
<const char *> p
{parseState
.GetNextChar()}) {
236 void Parsing::DumpProvenance(llvm::raw_ostream
&out
) const {
237 allCooked_
.Dump(out
);
240 void Parsing::DumpParsingLog(llvm::raw_ostream
&out
) const {
241 log_
.Dump(out
, allCooked_
);
244 void Parsing::Parse(llvm::raw_ostream
&out
) {
245 UserState userState
{allCooked_
, options_
.features
};
246 userState
.set_debugOutput(out
)
247 .set_instrumentedParse(options_
.instrumentedParse
)
249 ParseState parseState
{cooked()};
250 parseState
.set_inFixedForm(options_
.isFixedForm
).set_userState(&userState
);
251 parseTree_
= program
.Parse(parseState
);
253 !parseState
.anyErrorRecovery() || parseState
.messages().AnyFatalError());
254 consumedWholeFile_
= parseState
.IsAtEnd();
255 messages_
.Annex(std::move(parseState
.messages()));
256 finalRestingPlace_
= parseState
.GetLocation();
259 void Parsing::ClearLog() { log_
.clear(); }
261 } // namespace Fortran::parser