1 /** 2 * Contains high-level interfaces for interacting with DMD as a library. 3 * 4 * Copyright: Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved 5 * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) 6 * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 7 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/id.d, _id.d) 8 * Documentation: https://dlang.org/phobos/dmd_frontend.html 9 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/frontend.d 10 */ 11 module dmd.frontend; 12 13 import dmd.astcodegen : ASTCodegen; 14 import dmd.dmodule : Module; 15 import dmd.globals : CHECKENABLE, DiagnosticReporting; 16 import dmd.errors; 17 import dmd.location; 18 19 import std.range.primitives : isInputRange, ElementType; 20 import std.traits : isNarrowString; 21 import std.typecons : Tuple; 22 import core.stdc.stdarg; 23 24 version (Windows) private enum sep = ";", exe = ".exe"; 25 version (Posix) private enum sep = ":", exe = ""; 26 27 /// Contains aggregated diagnostics information. 28 immutable struct Diagnostics 29 { 30 /// Number of errors diagnosed 31 uint errors; 32 33 /// Number of warnings diagnosed 34 uint warnings; 35 36 /// Returns: `true` if errors have been diagnosed 37 bool hasErrors() 38 { 39 return errors > 0; 40 } 41 42 /// Returns: `true` if warnings have been diagnosed 43 bool hasWarnings() 44 { 45 return warnings > 0; 46 } 47 } 48 49 /// Indicates the checking state of various contracts. 50 enum ContractChecking : CHECKENABLE 51 { 52 /// Initial value 53 default_ = CHECKENABLE._default, 54 55 /// Never do checking 56 disabled = CHECKENABLE.off, 57 58 /// Always do checking 59 enabled = CHECKENABLE.on, 60 61 /// Only do checking in `@safe` functions 62 enabledInSafe = CHECKENABLE.safeonly 63 } 64 65 static assert( 66 __traits(allMembers, ContractChecking).length == 67 __traits(allMembers, CHECKENABLE).length 68 ); 69 70 /// Indicates which contracts should be checked or not. 71 struct ContractChecks 72 { 73 /// Precondition checks (in contract). 74 ContractChecking precondition = ContractChecking.enabled; 75 76 /// Invariant checks. 77 ContractChecking invariant_ = ContractChecking.enabled; 78 79 /// Postcondition checks (out contract). 80 ContractChecking postcondition = ContractChecking.enabled; 81 82 /// Array bound checks. 83 ContractChecking arrayBounds = ContractChecking.enabled; 84 85 /// Assert checks. 86 ContractChecking assert_ = ContractChecking.enabled; 87 88 /// Switch error checks. 89 ContractChecking switchError = ContractChecking.enabled; 90 } 91 92 /* 93 Initializes the global variables of the DMD compiler. 94 This needs to be done $(I before) calling any function. 95 96 Params: 97 diagnosticHandler = a delegate to configure what to do with diagnostics (other than printing to console or stderr). 98 fatalErrorHandler = a delegate to configure what to do with fatal errors (default is to call exit(EXIT_FAILURE)). 99 contractChecks = indicates which contracts should be enabled or not 100 versionIdentifiers = a list of version identifiers that should be enabled 101 */ 102 void initDMD( 103 DiagnosticHandler diagnosticHandler = null, 104 FatalErrorHandler fatalErrorHandler = null, 105 const string[] versionIdentifiers = [], 106 ContractChecks contractChecks = ContractChecks() 107 ) 108 { 109 import std.algorithm : each; 110 111 import dmd.root.ctfloat : CTFloat; 112 113 version (CRuntime_Microsoft) 114 import dmd.root.longdouble : initFPU; 115 116 import dmd.cond : VersionCondition; 117 import dmd.dmodule : Module; 118 import dmd.escape : EscapeState; 119 import dmd.expression : Expression; 120 import dmd.globals : CHECKENABLE, global; 121 import dmd.id : Id; 122 import dmd.identifier : Identifier; 123 import dmd.mtype : Type; 124 import dmd.objc : Objc; 125 import dmd.target : target, defaultTargetOS, addDefaultVersionIdentifiers; 126 127 .diagnosticHandler = diagnosticHandler; 128 .fatalErrorHandler = fatalErrorHandler; 129 130 global._init(); 131 132 with (global.params) 133 { 134 useIn = contractChecks.precondition; 135 useInvariants = contractChecks.invariant_; 136 useOut = contractChecks.postcondition; 137 useArrayBounds = contractChecks.arrayBounds; 138 useAssert = contractChecks.assert_; 139 useSwitchError = contractChecks.switchError; 140 } 141 142 versionIdentifiers.each!(VersionCondition.addGlobalIdent); 143 144 target.os = defaultTargetOS(); 145 target._init(global.params); 146 Type._init(); 147 Id.initialize(); 148 Module._init(); 149 Expression._init(); 150 Objc._init(); 151 EscapeState.reset(); 152 153 addDefaultVersionIdentifiers(global.params, target); 154 155 version (CRuntime_Microsoft) 156 initFPU(); 157 158 CTFloat.initialize(); 159 } 160 161 /** 162 Deinitializes the global variables of the DMD compiler. 163 164 This can be used to restore the state set by `initDMD` to its original state. 165 Useful if there's a need for multiple sessions of the DMD compiler in the same 166 application. 167 */ 168 void deinitializeDMD() 169 { 170 import dmd.dmodule : Module; 171 import dmd.dsymbol : Dsymbol; 172 import dmd.escape : EscapeState; 173 import dmd.expression : Expression; 174 import dmd.globals : global; 175 import dmd.id : Id; 176 import dmd.mtype : Type; 177 import dmd.objc : Objc; 178 import dmd.target : target; 179 180 diagnosticHandler = null; 181 fatalErrorHandler = null; 182 183 global.deinitialize(); 184 185 Type.deinitialize(); 186 Id.deinitialize(); 187 Module.deinitialize(); 188 target.deinitialize(); 189 Expression.deinitialize(); 190 Objc.deinitialize(); 191 Dsymbol.deinitialize(); 192 EscapeState.reset(); 193 } 194 195 /** 196 Add import path to the `global.path`. 197 Params: 198 path = import to add 199 */ 200 void addImport(const(char)[] path) 201 { 202 import dmd.globals : global; 203 import dmd.arraytypes : Strings; 204 import std.string : toStringz; 205 206 if (global.path is null) 207 global.path = new Strings(); 208 209 global.path.push(path.toStringz); 210 } 211 212 /** 213 Add string import path to `global.filePath`. 214 Params: 215 path = string import to add 216 */ 217 void addStringImport(const(char)[] path) 218 { 219 import std.string : toStringz; 220 221 import dmd.globals : global; 222 import dmd.arraytypes : Strings; 223 224 if (global.filePath is null) 225 global.filePath = new Strings(); 226 227 global.filePath.push(path.toStringz); 228 } 229 230 /** 231 Searches for a `dmd.conf`. 232 233 Params: 234 dmdFilePath = path to the current DMD executable 235 236 Returns: full path to the found `dmd.conf`, `null` otherwise. 237 */ 238 string findDMDConfig(const(char)[] dmdFilePath) 239 { 240 import dmd.dinifile : findConfFile; 241 242 version (Windows) 243 enum configFile = "sc.ini"; 244 else 245 enum configFile = "dmd.conf"; 246 247 return findConfFile(dmdFilePath, configFile).idup; 248 } 249 250 /** 251 Searches for a `ldc2.conf`. 252 253 Params: 254 ldcFilePath = path to the current LDC executable 255 256 Returns: full path to the found `ldc2.conf`, `null` otherwise. 257 */ 258 string findLDCConfig(const(char)[] ldcFilePath) 259 { 260 import std.file : getcwd; 261 import std.path : buildPath, dirName; 262 import std.algorithm.iteration : filter; 263 import std.file : exists; 264 265 auto execDir = ldcFilePath.dirName; 266 267 immutable ldcConfig = "ldc2.conf"; 268 // https://wiki.dlang.org/Using_LDC 269 auto ldcConfigs = [ 270 getcwd.buildPath(ldcConfig), 271 execDir.buildPath(ldcConfig), 272 execDir.dirName.buildPath("etc", ldcConfig), 273 "~/.ldc".buildPath(ldcConfig), 274 execDir.buildPath("etc", ldcConfig), 275 execDir.buildPath("etc", "ldc", ldcConfig), 276 "/etc".buildPath(ldcConfig), 277 "/etc/ldc".buildPath(ldcConfig), 278 ].filter!exists; 279 if (ldcConfigs.empty) 280 return null; 281 282 return ldcConfigs.front; 283 } 284 285 /** 286 Detect the currently active compiler. 287 Returns: full path to the executable of the found compiler, `null` otherwise. 288 */ 289 string determineDefaultCompiler() 290 { 291 import std.algorithm.iteration : filter, joiner, map, splitter; 292 import std.file : exists; 293 import std.path : buildPath; 294 import std.process : environment; 295 import std.range : front, empty, transposed; 296 // adapted from DUB: https://github.com/dlang/dub/blob/350a0315c38fab9d3d0c4c9d30ff6bb90efb54d6/source/dub/dub.d#L1183 297 298 auto compilers = ["dmd", "gdc", "gdmd", "ldc2", "ldmd2"]; 299 300 // Search the user's PATH for the compiler binary 301 if ("DMD" in environment) 302 compilers = environment.get("DMD") ~ compilers; 303 auto paths = environment.get("PATH", "").splitter(sep); 304 auto res = compilers.map!(c => paths.map!(p => p.buildPath(c~exe))).joiner.filter!exists; 305 return !res.empty ? res.front : null; 306 } 307 308 /** 309 Parses a `dmd.conf` or `ldc2.conf` config file and returns defined import paths. 310 311 Params: 312 iniFile = iniFile to parse imports from 313 execDir = directory of the compiler binary 314 315 Returns: forward range of import paths found in `iniFile` 316 */ 317 auto parseImportPathsFromConfig(const(char)[] iniFile, const(char)[] execDir) 318 { 319 import std.algorithm, std.range, std.regex; 320 import std.stdio : File; 321 import std.path : buildNormalizedPath; 322 323 alias expandConfigVariables = a => a.drop(2) // -I 324 // "set" common config variables 325 .replace("%@P%", execDir) 326 .replace("%%ldcbinarypath%%", execDir); 327 328 // search for all -I imports in this file 329 alias searchForImports = l => l.matchAll(`-I[^ "]+`.regex).joiner.map!expandConfigVariables; 330 331 return File(iniFile, "r") 332 .byLineCopy 333 .map!searchForImports 334 .joiner 335 // remove duplicated imports paths 336 .array 337 .sort 338 .uniq 339 .map!buildNormalizedPath; 340 } 341 342 /** 343 Finds a `dmd.conf` and parses it for import paths. 344 This depends on the `$DMD` environment variable. 345 If `$DMD` is set to `ldmd`, it will try to detect and parse a `ldc2.conf` instead. 346 347 Returns: 348 A forward range of normalized import paths. 349 350 See_Also: $(LREF determineDefaultCompiler), $(LREF parseImportPathsFromConfig) 351 */ 352 auto findImportPaths() 353 { 354 import std.algorithm.searching : endsWith; 355 import std.file : exists; 356 import std.path : dirName; 357 358 string execFilePath = determineDefaultCompiler(); 359 assert(execFilePath !is null, "No D compiler found. `Use parseImportsFromConfig` manually."); 360 361 immutable execDir = execFilePath.dirName; 362 363 string iniFile; 364 if (execFilePath.endsWith("ldc"~exe, "ldc2"~exe, "ldmd"~exe, "ldmd2"~exe)) 365 iniFile = findLDCConfig(execFilePath); 366 else 367 iniFile = findDMDConfig(execFilePath); 368 369 assert(iniFile !is null && iniFile.exists, "No valid config found."); 370 return iniFile.parseImportPathsFromConfig(execDir); 371 } 372 373 /** 374 Parse a module from a string. 375 376 Params: 377 fileName = file to parse 378 code = text to use instead of opening the file 379 380 Returns: the parsed module object 381 */ 382 Tuple!(Module, "module_", Diagnostics, "diagnostics") parseModule(AST = ASTCodegen)( 383 const(char)[] fileName, 384 const(char)[] code = null) 385 { 386 import dmd.root.file : File, Buffer; 387 388 import dmd.globals : global; 389 import dmd.location; 390 import dmd.parse : Parser; 391 import dmd.identifier : Identifier; 392 import dmd.tokens : TOK; 393 394 import std.path : baseName, stripExtension; 395 import std.string : toStringz; 396 import std.typecons : tuple; 397 398 auto id = Identifier.idPool(fileName.baseName.stripExtension); 399 auto m = new Module(fileName, id, 1, 0); 400 401 if (code is null) 402 m.read(Loc.initial); 403 else 404 { 405 import dmd.root.filename : FileName; 406 407 auto fb = cast(ubyte[]) code.dup ~ '\0'; 408 global.fileManager.add(FileName(fileName), fb); 409 m.src = fb; 410 } 411 412 m.importedFrom = m; 413 m = m.parseModule!AST(); 414 415 Diagnostics diagnostics = { 416 errors: global.errors, 417 warnings: global.warnings 418 }; 419 420 return typeof(return)(m, diagnostics); 421 } 422 423 /** 424 Run full semantic analysis on a module. 425 */ 426 void fullSemantic(Module m) 427 { 428 import dmd.dsymbolsem : dsymbolSemantic; 429 import dmd.semantic2 : semantic2; 430 import dmd.semantic3 : semantic3; 431 432 m.importedFrom = m; 433 m.importAll(null); 434 435 m.dsymbolSemantic(null); 436 Module.runDeferredSemantic(); 437 438 m.semantic2(null); 439 Module.runDeferredSemantic2(); 440 441 m.semantic3(null); 442 Module.runDeferredSemantic3(); 443 } 444 445 /** 446 Pretty print a module. 447 448 Returns: 449 Pretty printed module as string. 450 */ 451 string prettyPrint(Module m) 452 { 453 import dmd.common.outbuffer: OutBuffer; 454 import dmd.hdrgen : HdrGenState, moduleToBuffer2; 455 456 auto buf = OutBuffer(); 457 buf.doindent = 1; 458 HdrGenState hgs = { fullDump: 1 }; 459 moduleToBuffer2(m, buf, &hgs); 460 461 import std.string : replace, fromStringz; 462 import std.exception : assumeUnique; 463 464 auto generated = buf.extractSlice.replace("\t", " "); 465 return generated.assumeUnique; 466 } 467 468 /// Interface for diagnostic reporting. 469 abstract class DiagnosticReporter 470 { 471 import dmd.console : Color; 472 473 nothrow: 474 DiagnosticHandler prevHandler; 475 476 this() 477 { 478 prevHandler = diagnosticHandler; 479 diagnosticHandler = &diagHandler; 480 } 481 482 ~this() 483 { 484 // assumed to be used scoped 485 diagnosticHandler = prevHandler; 486 } 487 488 bool diagHandler(const ref Loc loc, Color headerColor, const(char)* header, 489 const(char)* format, va_list ap, const(char)* p1, const(char)* p2) 490 { 491 import core.stdc.string; 492 493 // recover type from header and color 494 if (strncmp (header, "Error:", 6) == 0) 495 return error(loc, format, ap, p1, p2); 496 if (strncmp (header, "Warning:", 8) == 0) 497 return warning(loc, format, ap, p1, p2); 498 if (strncmp (header, "Deprecation:", 12) == 0) 499 return deprecation(loc, format, ap, p1, p2); 500 501 if (cast(Classification)headerColor == Classification.warning) 502 return warningSupplemental(loc, format, ap, p1, p2); 503 if (cast(Classification)headerColor == Classification.deprecation) 504 return deprecationSupplemental(loc, format, ap, p1, p2); 505 506 return errorSupplemental(loc, format, ap, p1, p2); 507 } 508 509 /// Returns: the number of errors that occurred during lexing or parsing. 510 abstract int errorCount(); 511 512 /// Returns: the number of warnings that occurred during lexing or parsing. 513 abstract int warningCount(); 514 515 /// Returns: the number of deprecations that occurred during lexing or parsing. 516 abstract int deprecationCount(); 517 518 /** 519 Reports an error message. 520 521 Params: 522 loc = Location of error 523 format = format string for error 524 args = printf-style variadic arguments 525 p1 = additional message prefix 526 p2 = additional message prefix 527 528 Returns: false if the message should also be printed to stderr, true otherwise 529 */ 530 abstract bool error(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2); 531 532 /** 533 Reports additional details about an error message. 534 535 Params: 536 loc = Location of error 537 format = format string for supplemental message 538 args = printf-style variadic arguments 539 p1 = additional message prefix 540 p2 = additional message prefix 541 542 Returns: false if the message should also be printed to stderr, true otherwise 543 */ 544 abstract bool errorSupplemental(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2); 545 546 /** 547 Reports a warning message. 548 549 Params: 550 loc = Location of warning 551 format = format string for warning 552 args = printf-style variadic arguments 553 p1 = additional message prefix 554 p2 = additional message prefix 555 556 Returns: false if the message should also be printed to stderr, true otherwise 557 */ 558 abstract bool warning(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2); 559 560 /** 561 Reports additional details about a warning message. 562 563 Params: 564 loc = Location of warning 565 format = format string for supplemental message 566 args = printf-style variadic arguments 567 p1 = additional message prefix 568 p2 = additional message prefix 569 570 Returns: false if the message should also be printed to stderr, true otherwise 571 */ 572 abstract bool warningSupplemental(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2); 573 574 /** 575 Reports a deprecation message. 576 577 Params: 578 loc = Location of the deprecation 579 format = format string for the deprecation 580 args = printf-style variadic arguments 581 p1 = additional message prefix 582 p2 = additional message prefix 583 584 Returns: false if the message should also be printed to stderr, true otherwise 585 */ 586 abstract bool deprecation(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2); 587 588 /** 589 Reports additional details about a deprecation message. 590 591 Params: 592 loc = Location of deprecation 593 format = format string for supplemental message 594 args = printf-style variadic arguments 595 p1 = additional message prefix 596 p2 = additional message prefix 597 598 Returns: false if the message should also be printed to stderr, true otherwise 599 */ 600 abstract bool deprecationSupplemental(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2); 601 } 602 603 /** 604 Diagnostic reporter which prints the diagnostic messages to stderr. 605 606 This is usually the default diagnostic reporter. 607 */ 608 final class StderrDiagnosticReporter : DiagnosticReporter 609 { 610 private const DiagnosticReporting useDeprecated; 611 612 private int errorCount_; 613 private int warningCount_; 614 private int deprecationCount_; 615 616 nothrow: 617 618 /** 619 Initializes this object. 620 621 Params: 622 useDeprecated = indicates how deprecation diagnostics should be 623 handled 624 */ 625 this(DiagnosticReporting useDeprecated) 626 { 627 this.useDeprecated = useDeprecated; 628 } 629 630 override int errorCount() 631 { 632 return errorCount_; 633 } 634 635 override int warningCount() 636 { 637 return warningCount_; 638 } 639 640 override int deprecationCount() 641 { 642 return deprecationCount_; 643 } 644 645 override bool error(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2) 646 { 647 errorCount_++; 648 return false; 649 } 650 651 override bool errorSupplemental(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2) 652 { 653 return false; 654 } 655 656 override bool warning(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2) 657 { 658 warningCount_++; 659 return false; 660 } 661 662 override bool warningSupplemental(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2) 663 { 664 return false; 665 } 666 667 override bool deprecation(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2) 668 { 669 if (useDeprecated == DiagnosticReporting.error) 670 errorCount_++; 671 else 672 deprecationCount_++; 673 return false; 674 } 675 676 override bool deprecationSupplemental(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2) 677 { 678 return false; 679 } 680 }