1 /// Author: Aziz Köksal
3 /// $(Maturity average)
9 import dil
.semantic
.Module
,
14 import dil
.Diagnostics
;
15 import dil
.Compilation
;
19 import tango
.io
.FilePath
;
20 import tango
.sys
.Environment
;
22 /// Loads settings from a D module file.
23 abstract class SettingsLoader
25 Diagnostics diag
; /// Collects error messages.
26 Module mod
; /// Current module.
28 this(Diagnostics diag
)
33 /// Creates an error report.
35 /// token = where the error occurred.
36 /// formatMsg = error message.
37 void error(Token
* token
, char[] formatMsg
, ...)
39 auto location
= token
.getErrorLocation();
40 auto msg
= Format(_arguments
, _argptr
, formatMsg
);
41 diag
~= new SemanticError(location
, msg
);
44 T
getValue(T
)(char[] name
)
46 auto var
= mod
.lookup(name
);
47 if (!var
) // Returning T.init instead of null, because dmd gives an error.
48 return error(mod
.firstToken
, "variable '{}' is not defined", name
), T
.init
;
49 auto t
= var
.node
.begin
;
51 return error(t
, "'{}' is not a variable declaration", name
), T
.init
;
52 auto value
= var
.to
!(Variable
).value
;
54 return error(t
, "'{}' variable has no value set", name
), T
.init
;
55 T val
= value
.Is
!(T
); // Try casting to T.
57 error(value
.begin
, "the value of '{}' must be of type {}", name
, T
.stringof
);
63 if (auto result
= n
.Is
!(T
))
65 char[] type
= T
.stringof
;
66 (is(T
== StringExpression
) && (type
= "char[]").ptr
) ||
67 (is(T
== ArrayInitExpression
) && (type
= "[]").ptr
) ||
68 (is(T
== IntExpression
) && (type
= "int"));
69 error(n
.begin
, "expression is not of type {}", type
);
77 /// Loads the configuration file of dil.
78 class ConfigLoader
: SettingsLoader
80 static string configFileName
= "dilconf.d"; /// Name of the configuration file.
81 string executablePath
; /// Absolute path to dil's executable.
82 string executableDir
; /// Absolute path to the directory of dil's executable.
83 string dataDir
; /// Absolute path to dil's data directory.
84 string homePath
; /// Path to the home directory.
86 this(Diagnostics diag
)
89 this.homePath
= Environment
.get("HOME");
90 this.executablePath
= GetExecutableFilePath();
91 this.executableDir
= (new FilePath(this.executablePath
)).folder();
92 Environment
.set("BINDIR", this.executableDir
);
95 static ConfigLoader
opCall(Diagnostics diag
)
97 return new ConfigLoader(diag
);
100 static string
expandVariables(string val
)
102 char[] makeString(char* begin
, char* end
)
104 assert(begin
&& end
&& begin
<= end
);
105 return begin
[0 .. end
- begin
];
109 char* p
= val
.ptr
, end
= p
+ val
.length
;
110 char* pieceBegin
= p
; // Points to the piece of the string after a variable.
114 if (p
[0] == '$' && p
[1] == '{')
116 auto variableBegin
= p
;
117 while (p
< end
&& *p
!= '}')
120 break; // Don't expand unterminated variables.
121 result
~= makeString(pieceBegin
, variableBegin
);
122 variableBegin
+= 2; // Skip ${
123 // Get the environment variable and append it to the result.
124 result
~= Environment
.get(makeString(variableBegin
, p
));
125 pieceBegin
= p
+ 1; // Point to character after '}'.
129 if (pieceBegin
< end
)
130 result
~= makeString(pieceBegin
, end
);
136 // Search for the configuration file.
137 auto filePath
= findConfigurationFilePath();
138 if (filePath
is null)
140 diag
~= new Error(new Location("",0),
141 "the configuration file "~configFileName
~" could not be found.");
144 // Load the file as a D module.
145 mod
= new Module(filePath
, diag
);
151 auto context
= new CompilationContext
;
152 auto pass1
= new SemanticPass1(mod
, context
);
155 // Initialize the dataDir member.
156 if (auto val
= getValue
!(StringExpression
)("DATADIR"))
157 this.dataDir
= val
.getString();
158 this.dataDir
= expandVariables(this.dataDir
);
159 GlobalSettings
.dataDir
= this.dataDir
;
160 Environment
.set("DATADIR", this.dataDir
);
162 if (auto array
= getValue
!(ArrayInitExpression
)("VERSION_IDS"))
163 foreach (value
; array
.values
)
164 if (auto val
= castTo
!(StringExpression
)(value
))
165 GlobalSettings
.versionIds
~= expandVariables(val
.getString());
166 if (auto val
= getValue
!(StringExpression
)("LANG_FILE"))
167 GlobalSettings
.langFile
= expandVariables(val
.getString());
168 if (auto array
= getValue
!(ArrayInitExpression
)("IMPORT_PATHS"))
169 foreach (value
; array
.values
)
170 if (auto val
= castTo
!(StringExpression
)(value
))
171 GlobalSettings
.importPaths
~= expandVariables(val
.getString());
172 if (auto array
= getValue
!(ArrayInitExpression
)("DDOC_FILES"))
173 foreach (value
; array
.values
)
174 if (auto val
= castTo
!(StringExpression
)(value
))
175 GlobalSettings
.ddocFilePaths
~= expandVariables(val
.getString());
176 if (auto val
= getValue
!(StringExpression
)("XML_MAP"))
177 GlobalSettings
.xmlMapFile
= expandVariables(val
.getString());
178 if (auto val
= getValue
!(StringExpression
)("HTML_MAP"))
179 GlobalSettings
.htmlMapFile
= expandVariables(val
.getString());
180 if (auto val
= getValue
!(StringExpression
)("LEXER_ERROR"))
181 GlobalSettings
.lexerErrorFormat
= expandVariables(val
.getString());
182 if (auto val
= getValue
!(StringExpression
)("PARSER_ERROR"))
183 GlobalSettings
.parserErrorFormat
= expandVariables(val
.getString());
184 if (auto val
= getValue
!(StringExpression
)("SEMANTIC_ERROR"))
185 GlobalSettings
.semanticErrorFormat
= expandVariables(val
.getString());
186 if (auto val
= getValue
!(IntExpression
)("TAB_WIDTH"))
188 GlobalSettings
.tabWidth
= cast(uint)val
.number
;
189 Location
.TAB_WIDTH
= cast(uint)val
.number
;
193 // Load language file.
194 // TODO: create a separate class for this?
195 filePath
= expandVariables(GlobalSettings
.langFile
);
196 mod
= new Module(filePath
);
202 pass1
= new SemanticPass1(mod
, context
);
205 if (auto array
= getValue
!(ArrayInitExpression
)("messages"))
208 foreach (value
; array
.values
)
209 if (auto val
= castTo
!(StringExpression
)(value
))
210 messages
~= val
.getString();
211 if (messages
.length
!= MID
.max
+1)
212 error(mod
.firstToken
,
213 "messages table in {} must exactly have {} entries, but not {}.",
214 filePath
, MID
.max
+1, messages
.length
);
215 GlobalSettings
.messages
= messages
;
216 dil
.Messages
.SetMessages(messages
);
218 if (auto val
= getValue
!(StringExpression
)("lang_code"))
219 GlobalSettings
.langCode
= val
.getString();
222 /// Searches for the configuration file of dil.
223 /// Returns: the filePath or null if the file couldn't be found.
224 string
findConfigurationFilePath()
226 // 1. Look in environment variable DILCONF.
227 auto filePath
= new FilePath(Environment
.get("DILCONF"));
228 if (filePath
.exists())
229 return filePath
.toString();
230 // 2. Look in the current working directory.
231 filePath
.set(this.configFileName
);
232 if (filePath
.exists())
233 return filePath
.toString();
234 // 3. Look in the directory set by HOME.
235 filePath
.set(this.homePath
);
236 filePath
.append(this.configFileName
);
237 if (filePath
.exists())
238 return filePath
.toString();
239 // 4. Look in the binary's directory.
240 filePath
.set(this.executableDir
);
241 filePath
.append(this.configFileName
);
242 if (filePath
.exists())
243 return filePath
.toString();
248 /// Loads an associative array from a D module file.
249 class TagMapLoader
: SettingsLoader
251 this(Diagnostics diag
)
256 static TagMapLoader
opCall(Diagnostics diag
)
258 return new TagMapLoader(diag
);
261 string
[string
] load(string filePath
)
263 mod
= new Module(filePath
, diag
);
268 auto context
= new CompilationContext
;
269 auto pass1
= new SemanticPass1(mod
, context
);
273 if (auto array
= getValue
!(ArrayInitExpression
)("map"))
274 foreach (i
, value
; array
.values
)
276 auto key
= array
.keys
[i
];
277 if (auto valExp
= castTo
!(StringExpression
)(value
))
279 error(value
.begin
, "expected key : value");
280 else if (auto keyExp
= castTo
!(StringExpression
)(key
))
281 map
[keyExp
.getString()] = valExp
.getString();
287 /// Resolves the path to a file from the executable's dir path
288 /// if it is relative.
289 /// Returns: filePath if it is absolute or execPath + filePath.
290 string
resolvePath(string execPath
, string filePath
)
292 scope path
= new FilePath(filePath
);
293 if (path
.isAbsolute())
295 path
.set(execPath
).append(filePath
);
296 return path
.toString();
301 /// Returns the fully qualified path to this executable.
302 char[] GetExecutableFilePath();
304 else version(Windows
)
306 private extern(Windows
) uint GetModuleFileNameA(void*, char*, uint);
308 char[] GetExecutableFilePath()
310 alias GetModuleFileNameA GetModuleFileName
;
311 char[] buffer
= new char[256];
319 count
= GetModuleFileName(null, buffer
.ptr
, buffer
.length
);
322 if (buffer
.length
!= count
&& buffer
[count
] == 0)
324 // Increase size of buffer
325 buffer
.length
= buffer
.length
* 2;
327 assert(buffer
[count
] == 0);
328 // Reduce buffer to the actual length of the string (excluding '\0'.)
329 if (count
< buffer
.length
)
330 buffer
.length
= count
;
336 private extern(C
) size_t
readlink(char* path
, char* buf
, size_t bufsize
);
338 char[] GetExecutableFilePath()
340 char[] buffer
= new char[256];
345 // This won't work on very old Linux systems.
346 count
= readlink("/proc/self/exe".ptr
, buffer
.ptr
, buffer
.length
);
349 if (count
< buffer
.length
)
351 buffer
.length
= buffer
.length
* 2;
353 buffer
.length
= count
;
358 static assert(0, "GetExecutableFilePath() is not implemented on this platform.");