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