Parser: issue better error msgs for missing closing tokens.
[dil.git] / src / SettingsLoader.d
blobbb8594bfae542b69b623a359ea20011f232a1749
1 /// Author: Aziz Köksal
2 /// License: GPL3
3 /// $(Maturity average)
4 module SettingsLoader;
6 import dil.ast.Node,
7 dil.ast.Declarations,
8 dil.ast.Expressions;
9 import dil.semantic.Module,
10 dil.semantic.Pass1,
11 dil.semantic.Symbol,
12 dil.semantic.Symbols;
13 import dil.Messages;
14 import dil.Diagnostics;
15 import dil.Compilation;
16 import Settings;
17 import common;
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)
30 this.diag = diag;
33 /// Creates an error report.
34 /// Params:
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;
50 if (!var.isVariable)
51 return error(t, "'{}' is not a variable declaration", name), T.init;
52 auto value = var.to!(Variable).value;
53 if (!value)
54 return error(t, "'{}' variable has no value set", name), T.init;
55 T val = value.Is!(T); // Try casting to T.
56 if (!val)
57 error(value.begin, "the value of '{}' must be of type {}", name, T.stringof);
58 return val;
61 T castTo(T)(Node n)
63 if (auto result = n.Is!(T))
64 return result;
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);
70 return null;
73 void load()
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)
88 super(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];
108 char[] result;
109 char* p = val.ptr, end = p + val.length;
110 char* pieceBegin = p; // Points to the piece of the string after a variable.
112 while (p+3 < end)
114 if (p[0] == '$' && p[1] == '{')
116 auto variableBegin = p;
117 while (p < end && *p != '}')
118 p++;
119 if (p == end)
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 '}'.
127 p++;
129 if (pieceBegin < end)
130 result ~= makeString(pieceBegin, end);
131 return result;
134 void load()
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.");
142 return;
144 // Load the file as a D module.
145 mod = new Module(filePath, diag);
146 mod.parse();
148 if (mod.hasErrors)
149 return;
151 auto context = new CompilationContext;
152 auto pass1 = new SemanticPass1(mod, context);
153 pass1.run();
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);
197 mod.parse();
199 if (mod.hasErrors)
200 return;
202 pass1 = new SemanticPass1(mod, context);
203 pass1.run();
205 if (auto array = getValue!(ArrayInitExpression)("messages"))
207 char[][] 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();
244 return null;
248 /// Loads an associative array from a D module file.
249 class TagMapLoader : SettingsLoader
251 this(Diagnostics diag)
253 super(diag);
256 static TagMapLoader opCall(Diagnostics diag)
258 return new TagMapLoader(diag);
261 string[string] load(string filePath)
263 mod = new Module(filePath, diag);
264 mod.parse();
265 if (mod.hasErrors)
266 return null;
268 auto context = new CompilationContext;
269 auto pass1 = new SemanticPass1(mod, context);
270 pass1.run();
272 string[string] map;
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))
278 if (!key)
279 error(value.begin, "expected key : value");
280 else if (auto keyExp = castTo!(StringExpression)(key))
281 map[keyExp.getString()] = valExp.getString();
283 return map;
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())
294 return filePath;
295 path.set(execPath).append(filePath);
296 return path.toString();
299 version(DDoc)
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];
312 uint count;
314 while (1)
316 if (buffer is null)
317 return null;
319 count = GetModuleFileName(null, buffer.ptr, buffer.length);
320 if (count == 0)
321 return null;
322 if (buffer.length != count && buffer[count] == 0)
323 break;
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;
331 return buffer;
334 else version(linux)
336 private extern(C) size_t readlink(char* path, char* buf, size_t bufsize);
338 char[] GetExecutableFilePath()
340 char[] buffer = new char[256];
341 size_t count;
343 while (1)
345 // This won't work on very old Linux systems.
346 count = readlink("/proc/self/exe".ptr, buffer.ptr, buffer.length);
347 if (count == -1)
348 return null;
349 if (count < buffer.length)
350 break;
351 buffer.length = buffer.length * 2;
353 buffer.length = count;
354 return buffer;
357 else
358 static assert(0, "GetExecutableFilePath() is not implemented on this platform.");