Skip to content

Commit 2af57e8

Browse files
committed
Move frontend utility functions from the DUB packag to dmd.frontend
1 parent 07bbd0a commit 2af57e8

4 files changed

Lines changed: 326 additions & 155 deletions

File tree

src/dmd/frontend.d

Lines changed: 255 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,36 +14,31 @@
1414
*/
1515
module dmd.frontend;
1616

17-
/**
18-
Initializes the DMD compiler
17+
import dmd.dmodule : Module;
18+
import std.range.primitives : isInputRange, ElementType;
19+
import std.traits : isNarrowString;
20+
21+
version (Windows) private enum sep = ";", exe = ".exe";
22+
version (Posix) private enum sep = ":", exe = "";
23+
24+
/*
25+
Initializes the global variables of the DMD compiler.
26+
This needs to be done $(I before) calling any function.
1927
*/
2028
void initDMD()
2129
{
30+
import dmd.builtin : builtin_init;
2231
import dmd.dmodule : Module;
32+
import dmd.expression : Expression;
2333
import dmd.globals : global;
2434
import dmd.id : Id;
35+
import dmd.mars : addDefaultVersionIdentifiers;
2536
import dmd.mtype : Type;
26-
import dmd.target : Target;
27-
import dmd.expression : Expression;
2837
import dmd.objc : Objc;
29-
import dmd.builtin : builtin_init;
38+
import dmd.target : Target;
3039

31-
global._init;
32-
33-
version(linux)
34-
global.params.isLinux = 1;
35-
else version(OSX)
36-
global.params.isOSX = 1;
37-
else version(FreeBSD)
38-
global.params.isFreeBSD = 1;
39-
else version(Windows)
40-
global.params.isWindows = 1;
41-
else version(Solaris)
42-
global.params.isSolaris = 1;
43-
else version(OpenBSD)
44-
global.params.isOpenBSD = 1;
45-
else
46-
static assert(0, "OS not supported yet.");
40+
global._init();
41+
addDefaultVersionIdentifiers();
4742

4843
Type._init();
4944
Id.initialize();
@@ -54,3 +49,242 @@ void initDMD()
5449
builtin_init();
5550
}
5651

52+
/**
53+
Add import path to the `global.path`.
54+
Params:
55+
path = import to add
56+
*/
57+
void addImport(string path)
58+
{
59+
import dmd.globals : global;
60+
import dmd.arraytypes : Strings;
61+
import std.string : toStringz;
62+
63+
if (global.path is null)
64+
global.path = new Strings();
65+
66+
global.path.push(path.toStringz);
67+
}
68+
69+
/**
70+
Searches for a `dmd.conf`.
71+
72+
Params:
73+
dmdFilePath = path to the current DMD executable
74+
75+
Returns: full path to the found `dmd.conf`, `null` otherwise.
76+
*/
77+
string findDMDConfig(string dmdFilePath)
78+
{
79+
import dmd.dinifile : findConfFile;
80+
import std.string : fromStringz, toStringz;
81+
82+
auto f = findConfFile(dmdFilePath.toStringz, "dmd.conf");
83+
if (f is null)
84+
return null;
85+
86+
return f.fromStringz.idup;
87+
}
88+
89+
/**
90+
Searches for a `ldc2.conf`.
91+
92+
Params:
93+
ldcFilePath = path to the current LDC executable
94+
95+
Returns: full path to the found `ldc2.conf`, `null` otherwise.
96+
*/
97+
string findLDCConfig(string ldcFilePath)
98+
{
99+
import std.file : getcwd;
100+
import std.path : buildPath, dirName;
101+
import std.algorithm.iteration : filter;
102+
import std.file : exists;
103+
104+
auto execDir = ldcFilePath.dirName;
105+
106+
immutable ldcConfig = "ldc2.conf";
107+
// https://wiki.dlang.org/Using_LDC
108+
auto ldcConfigs = [
109+
getcwd.buildPath(ldcConfig),
110+
execDir.buildPath(ldcConfig),
111+
execDir.dirName.buildPath("etc", ldcConfig),
112+
"~/.ldc".buildPath(ldcConfig),
113+
execDir.buildPath("etc", ldcConfig),
114+
execDir.buildPath("etc", "ldc", ldcConfig),
115+
"/etc".buildPath(ldcConfig),
116+
"/etc/ldc".buildPath(ldcConfig),
117+
].filter!exists;
118+
if (ldcConfigs.empty)
119+
return null;
120+
121+
return ldcConfigs.front;
122+
}
123+
124+
/**
125+
Detect the currently active compiler.
126+
Returns: full path to the executable of the found compiler, `null` otherwise.
127+
*/
128+
string determineDefaultCompiler()
129+
{
130+
import std.algorithm.iteration : filter, joiner, map, splitter;
131+
import std.file : exists;
132+
import std.path : buildPath;
133+
import std.process : environment;
134+
import std.range : front, empty, transposed;
135+
// adapted from DUB: https://github.com/dlang/dub/blob/350a0315c38fab9d3d0c4c9d30ff6bb90efb54d6/source/dub/dub.d#L1183
136+
137+
auto compilers = ["dmd", "gdc", "gdmd", "ldc2", "ldmd2"];
138+
139+
// Search the user's PATH for the compiler binary
140+
if ("DMD" in environment)
141+
compilers = environment.get("DMD") ~ compilers;
142+
auto paths = environment.get("PATH", "").splitter(sep);
143+
auto res = compilers.map!(c => paths.map!(p => p.buildPath(c~exe))).joiner.filter!exists;
144+
return !res.empty ? res.front : null;
145+
}
146+
147+
/**
148+
Parses a `dmd.conf` or `ldc2.conf` config file and returns defined import paths.
149+
150+
Params:
151+
iniFile = iniFile to parse imports from
152+
execDir = directory of the compiler binary
153+
154+
Returns: forward range of import paths found in `iniFile`
155+
*/
156+
auto parseImportPathsFromConfig(string iniFile, string execDir)
157+
{
158+
import std.algorithm, std.range, std.regex;
159+
import std.stdio : File;
160+
import std.path : buildNormalizedPath;
161+
162+
alias expandConfigVariables = a => a.drop(2) // -I
163+
// "set" common config variables
164+
.replace("%@P%", execDir)
165+
.replace("%%ldcbinarypath%%", execDir);
166+
167+
// search for all -I imports in this file
168+
alias searchForImports = l => l.matchAll(`-I[^ "]+`.regex).joiner.map!expandConfigVariables;
169+
170+
return File(iniFile, "r")
171+
.byLineCopy
172+
.map!searchForImports
173+
.joiner
174+
// remove duplicated imports paths
175+
.array
176+
.sort
177+
.uniq
178+
.map!buildNormalizedPath;
179+
}
180+
181+
/**
182+
Finds a `dmd.conf` and parses it for import paths.
183+
This depends on the `$DMD` environment variable.
184+
If `$DMD` is set to `ldmd`, it will try to detect and parse a `ldc2.conf` instead.
185+
186+
Returns:
187+
A forward range of normalized import paths.
188+
189+
See_Also: $(LREF determineDefaultCompiler), $(LREF parseImportPathsFromConfig)
190+
*/
191+
auto findImportPaths()
192+
{
193+
import std.algorithm.searching : endsWith;
194+
import std.file : exists;
195+
import std.path : dirName;
196+
197+
string execFilePath = determineDefaultCompiler();
198+
assert(execFilePath !is null, "No D compiler found. `Use parseImportsFromConfig` manually.");
199+
200+
immutable execDir = execFilePath.dirName;
201+
202+
string iniFile;
203+
if (execFilePath.endsWith("ldc"~exe, "ldc2"~exe, "ldmd"~exe, "ldmd2"~exe))
204+
iniFile = findLDCConfig(execFilePath);
205+
else
206+
iniFile = findDMDConfig(execFilePath);
207+
208+
assert(iniFile !is null && iniFile.exists, "No valid config found.");
209+
return iniFile.parseImportPathsFromConfig(execDir);
210+
}
211+
212+
/**
213+
Parse a module from a string.
214+
215+
Params:
216+
fileName = file to parse
217+
code = text to use instead of opening the file
218+
219+
Returns: the parsed module object
220+
*/
221+
Module parseModule(string fileName, string code = null)
222+
{
223+
import dmd.astcodegen : ASTCodegen;
224+
import dmd.globals : Loc;
225+
import dmd.parse : Parser;
226+
import dmd.statement : Identifier;
227+
import dmd.tokens : TOKeof;
228+
import std.string : toStringz;
229+
230+
auto parse(Module m, string code)
231+
{
232+
scope p = new Parser!ASTCodegen(m, code, false);
233+
p.nextToken; // skip the initial token
234+
auto members = p.parseModule;
235+
assert(!p.errors, "Parsing error occurred.");
236+
assert(p.token.value == TOKeof, "Didn't reach the end token. Did an error occur?");
237+
return members;
238+
}
239+
240+
Identifier id = Identifier.idPool(fileName);
241+
auto m = new Module(fileName.toStringz, id, 0, 0);
242+
if (code !is null)
243+
m.members = parse(m, code);
244+
else
245+
{
246+
m.read(Loc());
247+
m.parse();
248+
}
249+
250+
return m;
251+
}
252+
253+
/**
254+
Run full semantic analysis on a module.
255+
*/
256+
void fullSemantic(Module m)
257+
{
258+
import dmd.dsymbolsem : dsymbolSemantic;
259+
import dmd.semantic2 : semantic2;
260+
import dmd.semantic3 : semantic3;
261+
262+
m.importedFrom = m;
263+
m.importAll(null);
264+
m.dsymbolSemantic(null);
265+
m.semantic2(null);
266+
m.semantic3(null);
267+
}
268+
269+
/**
270+
Pretty print a module.
271+
272+
Returns:
273+
Pretty printed module as string.
274+
*/
275+
string prettyPrint(Module m)
276+
{
277+
import dmd.root.outbuffer: OutBuffer;
278+
import dmd.hdrgen : HdrGenState, PrettyPrintVisitor;
279+
280+
OutBuffer buf = { doindent: 1 };
281+
HdrGenState hgs = { fullDump: 1 };
282+
scope PrettyPrintVisitor ppv = new PrettyPrintVisitor(&buf, &hgs);
283+
m.accept(ppv);
284+
285+
import std.string : replace, fromStringz;
286+
import std.exception : assumeUnique;
287+
288+
auto generated = buf.extractData.fromStringz.replace("\t", " ");
289+
return generated.assumeUnique;
290+
}

src/dmd/mars.d

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1206,7 +1206,7 @@ private void setDefaultLibrary()
12061206
* variable and config file) in order to add final flags (such as `X86_64` or
12071207
* the `CRuntime` used).
12081208
*/
1209-
private void addDefaultVersionIdentifiers()
1209+
void addDefaultVersionIdentifiers()
12101210
{
12111211
VersionCondition.addPredefinedGlobalIdent("DigitalMars");
12121212
static if (TARGET_WINDOS)

0 commit comments

Comments
 (0)