diff --git a/src/dmd/frontend.d b/src/dmd/frontend.d index f31019844261..2bfc3d0d9fa0 100644 --- a/src/dmd/frontend.d +++ b/src/dmd/frontend.d @@ -14,36 +14,31 @@ */ module dmd.frontend; -/** -Initializes the DMD compiler +import dmd.dmodule : Module; +import std.range.primitives : isInputRange, ElementType; +import std.traits : isNarrowString; + +version (Windows) private enum sep = ";", exe = ".exe"; +version (Posix) private enum sep = ":", exe = ""; + +/* +Initializes the global variables of the DMD compiler. +This needs to be done $(I before) calling any function. */ void initDMD() { + import dmd.builtin : builtin_init; import dmd.dmodule : Module; + import dmd.expression : Expression; import dmd.globals : global; import dmd.id : Id; + import dmd.mars : addDefaultVersionIdentifiers; import dmd.mtype : Type; - import dmd.target : Target; - import dmd.expression : Expression; import dmd.objc : Objc; - import dmd.builtin : builtin_init; + import dmd.target : Target; - global._init; - - version(linux) - global.params.isLinux = 1; - else version(OSX) - global.params.isOSX = 1; - else version(FreeBSD) - global.params.isFreeBSD = 1; - else version(Windows) - global.params.isWindows = 1; - else version(Solaris) - global.params.isSolaris = 1; - else version(OpenBSD) - global.params.isOpenBSD = 1; - else - static assert(0, "OS not supported yet."); + global._init(); + addDefaultVersionIdentifiers(); Type._init(); Id.initialize(); @@ -54,3 +49,242 @@ void initDMD() builtin_init(); } +/** +Add import path to the `global.path`. +Params: + path = import to add +*/ +void addImport(string path) +{ + import dmd.globals : global; + import dmd.arraytypes : Strings; + import std.string : toStringz; + + if (global.path is null) + global.path = new Strings(); + + global.path.push(path.toStringz); +} + +/** +Searches for a `dmd.conf`. + +Params: + dmdFilePath = path to the current DMD executable + +Returns: full path to the found `dmd.conf`, `null` otherwise. +*/ +string findDMDConfig(string dmdFilePath) +{ + import dmd.dinifile : findConfFile; + import std.string : fromStringz, toStringz; + + auto f = findConfFile(dmdFilePath.toStringz, "dmd.conf"); + if (f is null) + return null; + + return f.fromStringz.idup; +} + +/** +Searches for a `ldc2.conf`. + +Params: + ldcFilePath = path to the current LDC executable + +Returns: full path to the found `ldc2.conf`, `null` otherwise. +*/ +string findLDCConfig(string ldcFilePath) +{ + import std.file : getcwd; + import std.path : buildPath, dirName; + import std.algorithm.iteration : filter; + import std.file : exists; + + auto execDir = ldcFilePath.dirName; + + immutable ldcConfig = "ldc2.conf"; + // https://wiki.dlang.org/Using_LDC + auto ldcConfigs = [ + getcwd.buildPath(ldcConfig), + execDir.buildPath(ldcConfig), + execDir.dirName.buildPath("etc", ldcConfig), + "~/.ldc".buildPath(ldcConfig), + execDir.buildPath("etc", ldcConfig), + execDir.buildPath("etc", "ldc", ldcConfig), + "/etc".buildPath(ldcConfig), + "/etc/ldc".buildPath(ldcConfig), + ].filter!exists; + if (ldcConfigs.empty) + return null; + + return ldcConfigs.front; +} + +/** +Detect the currently active compiler. +Returns: full path to the executable of the found compiler, `null` otherwise. +*/ +string determineDefaultCompiler() +{ + import std.algorithm.iteration : filter, joiner, map, splitter; + import std.file : exists; + import std.path : buildPath; + import std.process : environment; + import std.range : front, empty, transposed; + // adapted from DUB: https://github.com/dlang/dub/blob/350a0315c38fab9d3d0c4c9d30ff6bb90efb54d6/source/dub/dub.d#L1183 + + auto compilers = ["dmd", "gdc", "gdmd", "ldc2", "ldmd2"]; + + // Search the user's PATH for the compiler binary + if ("DMD" in environment) + compilers = environment.get("DMD") ~ compilers; + auto paths = environment.get("PATH", "").splitter(sep); + auto res = compilers.map!(c => paths.map!(p => p.buildPath(c~exe))).joiner.filter!exists; + return !res.empty ? res.front : null; +} + +/** +Parses a `dmd.conf` or `ldc2.conf` config file and returns defined import paths. + +Params: + iniFile = iniFile to parse imports from + execDir = directory of the compiler binary + +Returns: forward range of import paths found in `iniFile` +*/ +auto parseImportPathsFromConfig(string iniFile, string execDir) +{ + import std.algorithm, std.range, std.regex; + import std.stdio : File; + import std.path : buildNormalizedPath; + + alias expandConfigVariables = a => a.drop(2) // -I + // "set" common config variables + .replace("%@P%", execDir) + .replace("%%ldcbinarypath%%", execDir); + + // search for all -I imports in this file + alias searchForImports = l => l.matchAll(`-I[^ "]+`.regex).joiner.map!expandConfigVariables; + + return File(iniFile, "r") + .byLineCopy + .map!searchForImports + .joiner + // remove duplicated imports paths + .array + .sort + .uniq + .map!buildNormalizedPath; +} + +/** +Finds a `dmd.conf` and parses it for import paths. +This depends on the `$DMD` environment variable. +If `$DMD` is set to `ldmd`, it will try to detect and parse a `ldc2.conf` instead. + +Returns: + A forward range of normalized import paths. + +See_Also: $(LREF determineDefaultCompiler), $(LREF parseImportPathsFromConfig) +*/ +auto findImportPaths() +{ + import std.algorithm.searching : endsWith; + import std.file : exists; + import std.path : dirName; + + string execFilePath = determineDefaultCompiler(); + assert(execFilePath !is null, "No D compiler found. `Use parseImportsFromConfig` manually."); + + immutable execDir = execFilePath.dirName; + + string iniFile; + if (execFilePath.endsWith("ldc"~exe, "ldc2"~exe, "ldmd"~exe, "ldmd2"~exe)) + iniFile = findLDCConfig(execFilePath); + else + iniFile = findDMDConfig(execFilePath); + + assert(iniFile !is null && iniFile.exists, "No valid config found."); + return iniFile.parseImportPathsFromConfig(execDir); +} + +/** +Parse a module from a string. + +Params: + fileName = file to parse + code = text to use instead of opening the file + +Returns: the parsed module object +*/ +Module parseModule(string fileName, string code = null) +{ + import dmd.astcodegen : ASTCodegen; + import dmd.globals : Loc; + import dmd.parse : Parser; + import dmd.statement : Identifier; + import dmd.tokens : TOK; + import std.string : toStringz; + + auto parse(Module m, string code) + { + scope p = new Parser!ASTCodegen(m, code, false); + p.nextToken; // skip the initial token + auto members = p.parseModule; + assert(!p.errors, "Parsing error occurred."); + assert(p.token.value == TOK.endOfFile, "Didn't reach the end token. Did an error occur?"); + return members; + } + + Identifier id = Identifier.idPool(fileName); + auto m = new Module(fileName.toStringz, id, 0, 0); + if (code !is null) + m.members = parse(m, code); + else + { + m.read(Loc()); + m.parse(); + } + + return m; +} + +/** +Run full semantic analysis on a module. +*/ +void fullSemantic(Module m) +{ + import dmd.dsymbolsem : dsymbolSemantic; + import dmd.semantic2 : semantic2; + import dmd.semantic3 : semantic3; + + m.importedFrom = m; + m.importAll(null); + m.dsymbolSemantic(null); + m.semantic2(null); + m.semantic3(null); +} + +/** +Pretty print a module. + +Returns: + Pretty printed module as string. +*/ +string prettyPrint(Module m) +{ + import dmd.root.outbuffer: OutBuffer; + import dmd.hdrgen : HdrGenState, PrettyPrintVisitor; + + OutBuffer buf = { doindent: 1 }; + HdrGenState hgs = { fullDump: 1 }; + scope PrettyPrintVisitor ppv = new PrettyPrintVisitor(&buf, &hgs); + m.accept(ppv); + + import std.string : replace, fromStringz; + import std.exception : assumeUnique; + + auto generated = buf.extractData.fromStringz.replace("\t", " "); + return generated.assumeUnique; +} diff --git a/src/dmd/mars.d b/src/dmd/mars.d index b3448db073f0..39b26c09ea81 100644 --- a/src/dmd/mars.d +++ b/src/dmd/mars.d @@ -1221,7 +1221,7 @@ private void setDefaultLibrary() * variable and config file) in order to add final flags (such as `X86_64` or * the `CRuntime` used). */ -private void addDefaultVersionIdentifiers() +void addDefaultVersionIdentifiers() { VersionCondition.addPredefinedGlobalIdent("DigitalMars"); static if (TARGET.Windows) diff --git a/test/dub_package/frontend.d b/test/dub_package/frontend.d index a7f493eea4e2..c21bb9bdde9c 100755 --- a/test/dub_package/frontend.d +++ b/test/dub_package/frontend.d @@ -4,159 +4,37 @@ dependency "dmd" path="../.." +/ import std.stdio; -// add import paths -void addImports(T)(T path) -{ - import dmd.globals : global; - import dmd.arraytypes : Strings; - - stderr.writefln("addImport: %s", path); - - Strings* res = new Strings(); - foreach (p; path) - { - import std.string : toStringz; - Strings* a = new Strings(); - a.push(p.toStringz); - res.append(a); - } - global.path = res; -} - -// finds a dmd.conf and parses it for import paths -auto findImportPaths() -{ - import std.file : exists, getcwd; - import std.string : fromStringz, toStringz; - import std.path : buildPath, buildNormalizedPath, dirName; - import std.process : env = environment, execute; - import dmd.dinifile : findConfFile; - import dmd.errors : fatal; - import std.algorithm, std.range, std.regex; - - auto dmdEnv = env.get("DMD", "dmd"); - auto whichDMD = execute(["which", dmdEnv]); - if (whichDMD.status != 0) - { - stderr.writeln("Can't find DMD."); - fatal; - } - - immutable dmdFilePath = whichDMD.output; - string iniFile; - - if (dmdEnv.canFind("ldmd")) - { - immutable ldcConfig = "ldc2.conf"; - immutable binDir = dmdFilePath.dirName; - // https://wiki.dlang.org/Using_LDC - auto ldcConfigs = [ - getcwd.buildPath(ldcConfig), - binDir.buildPath(ldcConfig), - binDir.dirName.buildPath("etc", ldcConfig), - "~/.ldc".buildPath(ldcConfig), - binDir.buildPath("etc", ldcConfig), - binDir.buildPath("etc", "ldc", ldcConfig), - "/etc".buildPath(ldcConfig), - "/etc/ldc".buildPath(ldcConfig), - ].filter!exists; - assert(!ldcConfigs.empty, "No ldc2.conf found"); - iniFile = ldcConfigs.front; - } - else - { - auto f = findConfFile(dmdFilePath.toStringz, "dmd.conf"); - iniFile = f.fromStringz.idup; - assert(iniFile.exists, "No dmd.conf found."); - } - - return File(iniFile, "r") - .byLineCopy - .map!(l => l.matchAll(`-I[^ "]+`.regex) - .joiner - .map!(a => a.drop(2) - .replace("%@P%", dmdFilePath.dirName) - .replace("%%ldcbinarypath%%", dmdFilePath.dirName))) - .joiner - .array - .sort - .uniq - .map!buildNormalizedPath; -} - // test frontend void main() { - import dmd.astcodegen : ASTCodegen; - import dmd.dmodule : Module; - import dmd.globals : global, Loc; - import dmd.frontend : initDMD; - import dmd.parse : Parser; - import dmd.statement : Identifier; - import dmd.id : Id; + import dmd.frontend; + import std.algorithm : each; initDMD; - findImportPaths.addImports; - - auto parse(Module m, string code) - { - scope p = new Parser!ASTCodegen(m, code, false); - p.nextToken; // skip the initial token - auto members = p.parseModule; - assert(!p.errors, "Parsing error occurred."); - return members; - } + findImportPaths.each!addImport; - Identifier id = Identifier.idPool("test"); - auto m = new Module("test.d", id, 0, 0); - m.members = parse(m, q{ + auto m = parseModule("test.d", q{ void foo() { foreach (i; 0..10) {} } }); - void semantic() - { - import dmd.dsymbolsem : dsymbolSemantic; - import dmd.semantic2; - import dmd.semantic3; - - m.importedFrom = m; - m.importAll(null); - m.dsymbolSemantic(null); - m.semantic2(null); - m.semantic3(null); - } - semantic(); - - auto prettyPrint() - { - import dmd.root.outbuffer: OutBuffer; - import dmd.hdrgen : HdrGenState, PrettyPrintVisitor; - - OutBuffer buf = { doindent: 1 }; - HdrGenState hgs = { fullDump: 1 }; - scope PrettyPrintVisitor ppv = new PrettyPrintVisitor(&buf, &hgs); - m.accept(ppv); - return buf; - } - auto buf = prettyPrint(); + m.fullSemantic; + auto generated = m.prettyPrint; auto expected =q{import object; void foo() { { - int __key3 = 0; - int __limit4 = 10; - for (; __key3 < __limit4; __key3 += 1) + int __key2 = 0; + int __limit3 = 10; + for (; __key2 < __limit3; __key2 += 1) { - int i = __key3; + int i = __key2; } } } }; - import std.string : replace, fromStringz; - auto generated = buf.extractData.fromStringz.replace("\t", " "); assert(expected == generated, generated); } diff --git a/test/dub_package/frontend_file.d b/test/dub_package/frontend_file.d new file mode 100755 index 000000000000..76cba133f39e --- /dev/null +++ b/test/dub_package/frontend_file.d @@ -0,0 +1,60 @@ +#!/usr/bin/env dub +/+dub.sdl: +dependency "dmd" path="../.." ++/ +import std.stdio; + +// test frontend +void main() +{ + import dmd.frontend; + import std.algorithm : canFind, each; + import std.file : remove, tempDir, fwrite = write; + import std.path : buildPath; + + initDMD; + findImportPaths.each!addImport; + + auto fileName = tempDir.buildPath("d_frontend_test.d"); + scope(exit) fileName.remove; + auto sourceCode = q{ + module foo; + double average(int[] array) + { + immutable initialLength = array.length; + double accumulator = 0.0; + while (array.length) + { + // this could be also done with .front + // with import std.array : front; + accumulator += array[0]; + array = array[1 .. $]; + } + return accumulator / initialLength; + } + }; + fileName.fwrite(sourceCode); + + auto m = fileName.parseModule; + + m.fullSemantic; + auto generated = m.prettyPrint; + + auto expected =q{module foo; +import object; +double average(int[] array) +{ + immutable immutable(uint) initialLength = array.length; + double accumulator = 0.00000; + for (; array.length;) + { + { + accumulator += cast(double)array[0]; + array = array[1..__dollar]; + } + } + return accumulator / cast(double)initialLength; +} +}; + assert(generated.canFind(expected)); +}