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