1 // Copyright (c) 2018-2019, NVIDIA CORPORATION.  All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "parsing.h"
16 #include "grammar.h"
17 #include "instrumented-parser.h"
18 #include "message.h"
19 #include "openmp-grammar.h"
20 #include "preprocessor.h"
21 #include "prescan.h"
22 #include "provenance.h"
23 #include "source.h"
24 #include <sstream>
25 
26 namespace Fortran::parser {
27 
Parsing(AllSources & s)28 Parsing::Parsing(AllSources &s) : cooked_{s} {}
~Parsing()29 Parsing::~Parsing() {}
30 
Prescan(const std::string & path,Options options)31 const SourceFile *Parsing::Prescan(const std::string &path, Options options) {
32   options_ = options;
33   AllSources &allSources{cooked_.allSources()};
34   if (options.isModuleFile) {
35     for (const auto &path : options.searchDirectories) {
36       allSources.PushSearchPathDirectory(path);
37     }
38   }
39 
40   std::stringstream fileError;
41   const SourceFile *sourceFile;
42   if (path == "-") {
43     sourceFile = allSources.ReadStandardInput(&fileError);
44   } else {
45     sourceFile = allSources.Open(path, &fileError);
46   }
47   if (!fileError.str().empty()) {
48     ProvenanceRange range{allSources.AddCompilerInsertion(path)};
49     messages_.Say(range, "%s"_err_en_US, fileError.str());
50     return sourceFile;
51   }
52   if (sourceFile->bytes() == 0) {
53     ProvenanceRange range{allSources.AddCompilerInsertion(path)};
54     messages_.Say(range, "file is empty"_err_en_US);
55     return sourceFile;
56   }
57 
58   if (!options.isModuleFile) {
59     // For .mod files we always want to look in the search directories.
60     // For normal source files we don't push them until after the primary
61     // source file has been opened.  If foo.f is missing from the current
62     // working directory, we don't want to accidentally read another foo.f
63     // from another directory that's on the search path.
64     for (const auto &path : options.searchDirectories) {
65       allSources.PushSearchPathDirectory(path);
66     }
67   }
68 
69   Preprocessor preprocessor{allSources};
70   for (const auto &predef : options.predefinitions) {
71     if (predef.second.has_value()) {
72       preprocessor.Define(predef.first, *predef.second);
73     } else {
74       preprocessor.Undefine(predef.first);
75     }
76   }
77   Prescanner prescanner{messages_, cooked_, preprocessor, options.features};
78   prescanner.set_fixedForm(options.isFixedForm)
79       .set_fixedFormColumnLimit(options.fixedFormColumns)
80       .AddCompilerDirectiveSentinel("dir$");
81   if (options.features.IsEnabled(LanguageFeature::OpenMP)) {
82     prescanner.AddCompilerDirectiveSentinel("$omp");
83     prescanner.AddCompilerDirectiveSentinel("$");  // OMP conditional line
84   }
85   ProvenanceRange range{allSources.AddIncludedFile(
86       *sourceFile, ProvenanceRange{}, options.isModuleFile)};
87   prescanner.Prescan(range);
88   cooked_.Marshal();
89   if (options.needProvenanceRangeToCharBlockMappings) {
90     cooked_.CompileProvenanceRangeToOffsetMappings();
91   }
92   return sourceFile;
93 }
94 
DumpCookedChars(std::ostream & out) const95 void Parsing::DumpCookedChars(std::ostream &out) const {
96   UserState userState{cooked_, LanguageFeatureControl{}};
97   ParseState parseState{cooked_};
98   parseState.set_inFixedForm(options_.isFixedForm).set_userState(&userState);
99   while (std::optional<const char *> p{parseState.GetNextChar()}) {
100     out << **p;
101   }
102 }
103 
DumpProvenance(std::ostream & out) const104 void Parsing::DumpProvenance(std::ostream &out) const { cooked_.Dump(out); }
105 
DumpParsingLog(std::ostream & out) const106 void Parsing::DumpParsingLog(std::ostream &out) const {
107   log_.Dump(out, cooked_);
108 }
109 
Parse(std::ostream * out)110 void Parsing::Parse(std::ostream *out) {
111   UserState userState{cooked_, options_.features};
112   userState.set_debugOutput(out)
113       .set_instrumentedParse(options_.instrumentedParse)
114       .set_log(&log_);
115   ParseState parseState{cooked_};
116   parseState.set_inFixedForm(options_.isFixedForm).set_userState(&userState);
117   parseTree_ = program.Parse(parseState);
118   CHECK(
119       !parseState.anyErrorRecovery() || parseState.messages().AnyFatalError());
120   consumedWholeFile_ = parseState.IsAtEnd();
121   messages_.Annex(std::move(parseState.messages()));
122   finalRestingPlace_ = parseState.GetLocation();
123 }
124 
ClearLog()125 void Parsing::ClearLog() { log_.clear(); }
126 
ForTesting(std::string path,std::ostream & err)127 bool Parsing::ForTesting(std::string path, std::ostream &err) {
128   Prescan(path, Options{});
129   if (messages_.AnyFatalError()) {
130     messages_.Emit(err, cooked_);
131     err << "could not scan " << path << '\n';
132     return false;
133   }
134   Parse();
135   messages_.Emit(err, cooked_);
136   if (!consumedWholeFile_) {
137     EmitMessage(err, finalRestingPlace_, "parser FAIL; final position");
138     return false;
139   }
140   if (messages_.AnyFatalError() || !parseTree_.has_value()) {
141     err << "could not parse " << path << '\n';
142     return false;
143   }
144   return true;
145 }
146 }
147