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