1 //===-- tools/f18/f18-parse-demo.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 // F18 parsing demonstration. 10 // f18-parse-demo [ -E | -fdump-parse-tree | -funparse-only ] 11 // foo.{f,F,f77,F77,f90,F90,&c.} 12 // 13 // By default, runs the supplied source files through the F18 preprocessing and 14 // parsing phases, reconstitutes a Fortran program from the parse tree, and 15 // passes that Fortran program to a Fortran compiler identified by the $F18_FC 16 // environment variable (defaulting to gfortran). The Fortran preprocessor is 17 // always run, whatever the case of the source file extension. Unrecognized 18 // options are passed through to the underlying Fortran compiler. 19 // 20 // This program is actually a stripped-down variant of f18.cpp, a temporary 21 // scaffolding compiler driver that can test some semantic passes of the 22 // F18 compiler under development. 23 24 #include "flang/Common/Fortran-features.h" 25 #include "flang/Common/default-kinds.h" 26 #include "flang/Parser/characters.h" 27 #include "flang/Parser/dump-parse-tree.h" 28 #include "flang/Parser/message.h" 29 #include "flang/Parser/parse-tree-visitor.h" 30 #include "flang/Parser/parse-tree.h" 31 #include "flang/Parser/parsing.h" 32 #include "flang/Parser/provenance.h" 33 #include "flang/Parser/unparse.h" 34 #include "llvm/Support/Errno.h" 35 #include "llvm/Support/FileSystem.h" 36 #include "llvm/Support/Program.h" 37 #include "llvm/Support/raw_ostream.h" 38 #include <cstdio> 39 #include <cstring> 40 #include <fstream> 41 #include <list> 42 #include <memory> 43 #include <optional> 44 #include <stdlib.h> 45 #include <string> 46 #include <time.h> 47 #include <vector> 48 49 static std::list<std::string> argList(int argc, char *const argv[]) { 50 std::list<std::string> result; 51 for (int j = 0; j < argc; ++j) { 52 result.emplace_back(argv[j]); 53 } 54 return result; 55 } 56 57 std::vector<std::string> filesToDelete; 58 59 void CleanUpAtExit() { 60 for (const auto &path : filesToDelete) { 61 if (!path.empty()) { 62 unlink(path.data()); 63 } 64 } 65 } 66 67 #if _POSIX_C_SOURCE >= 199309L && _POSIX_TIMERS > 0 && _POSIX_CPUTIME && \ 68 defined CLOCK_PROCESS_CPUTIME_ID 69 static constexpr bool canTime{true}; 70 double CPUseconds() { 71 struct timespec tspec; 72 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tspec); 73 return tspec.tv_nsec * 1.0e-9 + tspec.tv_sec; 74 } 75 #else 76 static constexpr bool canTime{false}; 77 double CPUseconds() { return 0; } 78 #endif 79 80 struct DriverOptions { 81 DriverOptions() {} 82 bool verbose{false}; // -v 83 bool compileOnly{false}; // -c 84 std::string outputPath; // -o path 85 std::vector<std::string> searchDirectories{"."s}; // -I dir 86 bool forcedForm{false}; // -Mfixed or -Mfree appeared 87 bool warnOnNonstandardUsage{false}; // -Mstandard 88 bool warningsAreErrors{false}; // -Werror 89 Fortran::parser::Encoding encoding{Fortran::parser::Encoding::LATIN_1}; 90 bool parseOnly{false}; 91 bool dumpProvenance{false}; 92 bool dumpCookedChars{false}; 93 bool dumpUnparse{false}; 94 bool dumpParseTree{false}; 95 bool timeParse{false}; 96 std::vector<std::string> fcArgs; 97 const char *prefix{nullptr}; 98 }; 99 100 void Exec(std::vector<llvm::StringRef> &argv, bool verbose = false) { 101 if (verbose) { 102 for (size_t j{0}; j < argv.size(); ++j) { 103 llvm::errs() << (j > 0 ? " " : "") << argv[j]; 104 } 105 llvm::errs() << '\n'; 106 } 107 std::string ErrMsg; 108 llvm::ErrorOr<std::string> Program = llvm::sys::findProgramByName(argv[0]); 109 if (!Program) 110 ErrMsg = Program.getError().message(); 111 if (!Program || 112 llvm::sys::ExecuteAndWait( 113 Program.get(), argv, llvm::None, {}, 0, 0, &ErrMsg)) { 114 llvm::errs() << "execvp(" << argv[0] << ") failed: " << ErrMsg << '\n'; 115 exit(EXIT_FAILURE); 116 } 117 } 118 119 void RunOtherCompiler(DriverOptions &driver, char *source, char *relo) { 120 std::vector<llvm::StringRef> argv; 121 for (size_t j{0}; j < driver.fcArgs.size(); ++j) { 122 argv.push_back(driver.fcArgs[j]); 123 } 124 char dashC[3] = "-c", dashO[3] = "-o"; 125 argv.push_back(dashC); 126 argv.push_back(dashO); 127 argv.push_back(relo); 128 argv.push_back(source); 129 Exec(argv, driver.verbose); 130 } 131 132 std::string RelocatableName(const DriverOptions &driver, std::string path) { 133 if (driver.compileOnly && !driver.outputPath.empty()) { 134 return driver.outputPath; 135 } 136 std::string base{path}; 137 auto slash{base.rfind("/")}; 138 if (slash != std::string::npos) { 139 base = base.substr(slash + 1); 140 } 141 std::string relo{base}; 142 auto dot{base.rfind(".")}; 143 if (dot != std::string::npos) { 144 relo = base.substr(0, dot); 145 } 146 relo += ".o"; 147 return relo; 148 } 149 150 int exitStatus{EXIT_SUCCESS}; 151 152 std::string CompileFortran( 153 std::string path, Fortran::parser::Options options, DriverOptions &driver) { 154 if (!driver.forcedForm) { 155 auto dot{path.rfind(".")}; 156 if (dot != std::string::npos) { 157 std::string suffix{path.substr(dot + 1)}; 158 options.isFixedForm = suffix == "f" || suffix == "F" || suffix == "ff"; 159 } 160 } 161 options.searchDirectories = driver.searchDirectories; 162 Fortran::parser::AllSources allSources; 163 Fortran::parser::Parsing parsing{allSources}; 164 165 auto start{CPUseconds()}; 166 parsing.Prescan(path, options); 167 if (!parsing.messages().empty() && 168 (driver.warningsAreErrors || parsing.messages().AnyFatalError())) { 169 llvm::errs() << driver.prefix << "could not scan " << path << '\n'; 170 parsing.messages().Emit(llvm::errs(), parsing.cooked()); 171 exitStatus = EXIT_FAILURE; 172 return {}; 173 } 174 if (driver.dumpProvenance) { 175 parsing.DumpProvenance(llvm::outs()); 176 return {}; 177 } 178 if (driver.dumpCookedChars) { 179 parsing.DumpCookedChars(llvm::outs()); 180 return {}; 181 } 182 parsing.Parse(llvm::outs()); 183 auto stop{CPUseconds()}; 184 if (driver.timeParse) { 185 if (canTime) { 186 llvm::outs() << "parse time for " << path << ": " << (stop - start) 187 << " CPU seconds\n"; 188 } else { 189 llvm::outs() << "no timing information due to lack of clock_gettime()\n"; 190 } 191 } 192 193 parsing.ClearLog(); 194 parsing.messages().Emit(llvm::errs(), parsing.cooked()); 195 if (!parsing.consumedWholeFile()) { 196 parsing.EmitMessage(llvm::errs(), parsing.finalRestingPlace(), 197 "parser FAIL (final position)"); 198 exitStatus = EXIT_FAILURE; 199 return {}; 200 } 201 if ((!parsing.messages().empty() && 202 (driver.warningsAreErrors || parsing.messages().AnyFatalError())) || 203 !parsing.parseTree()) { 204 llvm::errs() << driver.prefix << "could not parse " << path << '\n'; 205 exitStatus = EXIT_FAILURE; 206 return {}; 207 } 208 auto &parseTree{*parsing.parseTree()}; 209 if (driver.dumpParseTree) { 210 Fortran::parser::DumpTree(llvm::outs(), parseTree); 211 return {}; 212 } 213 if (driver.dumpUnparse) { 214 Unparse(llvm::outs(), parseTree, driver.encoding, true /*capitalize*/, 215 options.features.IsEnabled( 216 Fortran::common::LanguageFeature::BackslashEscapes)); 217 return {}; 218 } 219 if (driver.parseOnly) { 220 return {}; 221 } 222 223 std::string relo{RelocatableName(driver, path)}; 224 225 llvm::SmallString<32> tmpSourcePath; 226 { 227 int fd; 228 std::error_code EC = 229 llvm::sys::fs::createUniqueFile("f18-%%%%.f90", fd, tmpSourcePath); 230 if (EC) { 231 llvm::errs() << EC.message() << "\n"; 232 std::exit(EXIT_FAILURE); 233 } 234 llvm::raw_fd_ostream tmpSource(fd, /*shouldClose*/ true); 235 Unparse(tmpSource, parseTree, driver.encoding, true /*capitalize*/, 236 options.features.IsEnabled( 237 Fortran::common::LanguageFeature::BackslashEscapes)); 238 } 239 240 RunOtherCompiler(driver, tmpSourcePath.data(), relo.data()); 241 filesToDelete.emplace_back(tmpSourcePath); 242 if (!driver.compileOnly && driver.outputPath.empty()) { 243 filesToDelete.push_back(relo); 244 } 245 return relo; 246 } 247 248 std::string CompileOtherLanguage(std::string path, DriverOptions &driver) { 249 std::string relo{RelocatableName(driver, path)}; 250 RunOtherCompiler(driver, path.data(), relo.data()); 251 if (!driver.compileOnly && driver.outputPath.empty()) { 252 filesToDelete.push_back(relo); 253 } 254 return relo; 255 } 256 257 void Link(std::vector<std::string> &relocatables, DriverOptions &driver) { 258 std::vector<llvm::StringRef> argv; 259 for (size_t j{0}; j < driver.fcArgs.size(); ++j) { 260 argv.push_back(driver.fcArgs[j].data()); 261 } 262 for (auto &relo : relocatables) { 263 argv.push_back(relo.data()); 264 } 265 if (!driver.outputPath.empty()) { 266 char dashO[3] = "-o"; 267 argv.push_back(dashO); 268 argv.push_back(driver.outputPath.data()); 269 } 270 Exec(argv, driver.verbose); 271 } 272 273 int main(int argc, char *const argv[]) { 274 275 atexit(CleanUpAtExit); 276 277 DriverOptions driver; 278 const char *fc{getenv("F18_FC")}; 279 driver.fcArgs.push_back(fc ? fc : "gfortran"); 280 281 std::list<std::string> args{argList(argc, argv)}; 282 std::string prefix{args.front()}; 283 args.pop_front(); 284 prefix += ": "; 285 driver.prefix = prefix.data(); 286 287 Fortran::parser::Options options; 288 options.predefinitions.emplace_back("__F18", "1"); 289 options.predefinitions.emplace_back("__F18_MAJOR__", "1"); 290 options.predefinitions.emplace_back("__F18_MINOR__", "1"); 291 options.predefinitions.emplace_back("__F18_PATCHLEVEL__", "1"); 292 293 options.features.Enable( 294 Fortran::common::LanguageFeature::BackslashEscapes, true); 295 296 Fortran::common::IntrinsicTypeDefaultKinds defaultKinds; 297 298 std::vector<std::string> fortranSources, otherSources, relocatables; 299 bool anyFiles{false}; 300 301 while (!args.empty()) { 302 std::string arg{std::move(args.front())}; 303 args.pop_front(); 304 if (arg.empty()) { 305 } else if (arg.at(0) != '-') { 306 anyFiles = true; 307 auto dot{arg.rfind(".")}; 308 if (dot == std::string::npos) { 309 driver.fcArgs.push_back(arg); 310 } else { 311 std::string suffix{arg.substr(dot + 1)}; 312 if (suffix == "f" || suffix == "F" || suffix == "ff" || 313 suffix == "f90" || suffix == "F90" || suffix == "ff90" || 314 suffix == "f95" || suffix == "F95" || suffix == "ff95" || 315 suffix == "cuf" || suffix == "CUF" || suffix == "f18" || 316 suffix == "F18" || suffix == "ff18") { 317 fortranSources.push_back(arg); 318 } else if (suffix == "o" || suffix == "a") { 319 relocatables.push_back(arg); 320 } else { 321 otherSources.push_back(arg); 322 } 323 } 324 } else if (arg == "-") { 325 fortranSources.push_back("-"); 326 } else if (arg == "--") { 327 while (!args.empty()) { 328 fortranSources.emplace_back(std::move(args.front())); 329 args.pop_front(); 330 } 331 break; 332 } else if (arg == "-Mfixed") { 333 driver.forcedForm = true; 334 options.isFixedForm = true; 335 } else if (arg == "-Mfree") { 336 driver.forcedForm = true; 337 options.isFixedForm = false; 338 } else if (arg == "-Mextend") { 339 options.fixedFormColumns = 132; 340 } else if (arg == "-Mbackslash") { 341 options.features.Enable( 342 Fortran::common::LanguageFeature::BackslashEscapes, false); 343 } else if (arg == "-Mnobackslash") { 344 options.features.Enable( 345 Fortran::common::LanguageFeature::BackslashEscapes); 346 } else if (arg == "-Mstandard") { 347 driver.warnOnNonstandardUsage = true; 348 } else if (arg == "-fopenmp") { 349 options.features.Enable(Fortran::common::LanguageFeature::OpenMP); 350 options.predefinitions.emplace_back("_OPENMP", "201511"); 351 } else if (arg == "-Werror") { 352 driver.warningsAreErrors = true; 353 } else if (arg == "-ed") { 354 options.features.Enable(Fortran::common::LanguageFeature::OldDebugLines); 355 } else if (arg == "-E" || arg == "-fpreprocess-only") { 356 driver.dumpCookedChars = true; 357 } else if (arg == "-fbackslash") { 358 options.features.Enable( 359 Fortran::common::LanguageFeature::BackslashEscapes); 360 } else if (arg == "-fno-backslash") { 361 options.features.Enable( 362 Fortran::common::LanguageFeature::BackslashEscapes, false); 363 } else if (arg == "-fdump-provenance") { 364 driver.dumpProvenance = true; 365 } else if (arg == "-fdump-parse-tree") { 366 driver.dumpParseTree = true; 367 } else if (arg == "-funparse") { 368 driver.dumpUnparse = true; 369 } else if (arg == "-ftime-parse") { 370 driver.timeParse = true; 371 } else if (arg == "-fparse-only") { 372 driver.parseOnly = true; 373 } else if (arg == "-c") { 374 driver.compileOnly = true; 375 } else if (arg == "-o") { 376 driver.outputPath = args.front(); 377 args.pop_front(); 378 } else if (arg.substr(0, 2) == "-D") { 379 auto eq{arg.find('=')}; 380 if (eq == std::string::npos) { 381 options.predefinitions.emplace_back(arg.substr(2), "1"); 382 } else { 383 options.predefinitions.emplace_back( 384 arg.substr(2, eq - 2), arg.substr(eq + 1)); 385 } 386 } else if (arg.substr(0, 2) == "-U") { 387 options.predefinitions.emplace_back( 388 arg.substr(2), std::optional<std::string>{}); 389 } else if (arg == "-r8" || arg == "-fdefault-real-8") { 390 defaultKinds.set_defaultRealKind(8); 391 } else if (arg == "-i8" || arg == "-fdefault-integer-8") { 392 defaultKinds.set_defaultIntegerKind(8); 393 } else if (arg == "-help" || arg == "--help" || arg == "-?") { 394 llvm::errs() 395 << "f18-parse-demo options:\n" 396 << " -Mfixed | -Mfree force the source form\n" 397 << " -Mextend 132-column fixed form\n" 398 << " -f[no-]backslash enable[disable] \\escapes in literals\n" 399 << " -M[no]backslash disable[enable] \\escapes in literals\n" 400 << " -Mstandard enable conformance warnings\n" 401 << " -r8 | -fdefault-real-8 | -i8 | -fdefault-integer-8 " 402 "change default kinds of intrinsic types\n" 403 << " -Werror treat warnings as errors\n" 404 << " -ed enable fixed form D lines\n" 405 << " -E prescan & preprocess only\n" 406 << " -ftime-parse measure parsing time\n" 407 << " -fparse-only parse only, no output except messages\n" 408 << " -funparse parse & reformat only, no code " 409 "generation\n" 410 << " -fdump-provenance dump the provenance table (no code)\n" 411 << " -fdump-parse-tree dump the parse tree (no code)\n" 412 << " -v -c -o -I -D -U have their usual meanings\n" 413 << " -help print this again\n" 414 << "Other options are passed through to the $F18_FC compiler.\n"; 415 return exitStatus; 416 } else if (arg == "-V") { 417 llvm::errs() << "\nf18-parse-demo\n"; 418 return exitStatus; 419 } else { 420 driver.fcArgs.push_back(arg); 421 if (arg == "-v") { 422 driver.verbose = true; 423 } else if (arg == "-I") { 424 driver.fcArgs.push_back(args.front()); 425 driver.searchDirectories.push_back(args.front()); 426 args.pop_front(); 427 } else if (arg.substr(0, 2) == "-I") { 428 driver.searchDirectories.push_back(arg.substr(2)); 429 } 430 } 431 } 432 433 if (driver.warnOnNonstandardUsage) { 434 options.features.WarnOnAllNonstandard(); 435 } 436 if (!options.features.IsEnabled( 437 Fortran::common::LanguageFeature::BackslashEscapes)) { 438 driver.fcArgs.push_back("-fno-backslash"); // PGI "-Mbackslash" 439 } 440 441 if (!anyFiles) { 442 driver.dumpUnparse = true; 443 CompileFortran("-", options, driver); 444 return exitStatus; 445 } 446 for (const auto &path : fortranSources) { 447 std::string relo{CompileFortran(path, options, driver)}; 448 if (!driver.compileOnly && !relo.empty()) { 449 relocatables.push_back(relo); 450 } 451 } 452 for (const auto &path : otherSources) { 453 std::string relo{CompileOtherLanguage(path, driver)}; 454 if (!driver.compileOnly && !relo.empty()) { 455 relocatables.push_back(relo); 456 } 457 } 458 if (!relocatables.empty()) { 459 Link(relocatables, driver); 460 } 461 return exitStatus; 462 } 463