1 /// Author: Aziz Köksal 2 /// License: GPL3 3 /// $(Maturity average) 4 module SettingsLoader; 5 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.lexer.Funcs, 14 dil.lexer.Identifier; 15 import dil.i18n.Messages, 16 dil.i18n.ResourceBundle; 17 import dil.Diagnostics, 18 dil.Compilation, 19 dil.Unicode, 20 dil.String, 21 dil.Array; 22 import util.Path; 23 import Settings, 24 common; 25 26 /// Loads settings from a D module file. 27 abstract class SettingsLoader 28 { 29 Diagnostics diag; /// Collects error messages. 30 Module mod; /// Current module. 31 CompilationContext cc; /// The context. 32 33 /// Constructs a SettingsLoader object. 34 this(CompilationContext cc, Diagnostics diag) 35 { 36 this.cc = cc; 37 this.diag = diag; 38 } 39 40 /// Creates an error report. 41 /// Params: 42 /// token = Where the error occurred. 43 /// formatMsg = Error message. 44 void error(Token* token, cstring formatMsg, ...) 45 { 46 auto location = token.getErrorLocation(mod.filePath); 47 auto msg = Format(_arguments, _argptr, formatMsg); 48 diag ~= new SemanticError(location, msg); 49 } 50 51 T getValue(T)(cstring name, bool isOptional = false) 52 { 53 auto var = mod.lookup(hashOf(name)); 54 T value; 55 if (!var && isOptional) 56 {} 57 else if (!var) 58 error(mod.loc.t, "variable ‘{}’ is not defined", name); 59 else if (!var.isVariable) 60 error(var.loc.t, "‘{}’ is not a variable declaration", name); 61 else if (auto e = var.to!(VariableSymbol).value) 62 { 63 if ((value = e.Is!(T)) is null) // Try casting to T. 64 error(e.begin, 65 "the value of ‘{}’ must be of type ‘{}’", name, T.stringof); 66 } 67 else 68 error(var.loc.t, "‘{}’ variable has no value set", name); 69 return value; 70 } 71 72 T castTo(T)(Node n) 73 { 74 if (auto result = n.Is!(T)) 75 return result; 76 auto type = T.stringof; 77 if (is(T == StringExpr)) 78 type = "char[]"; 79 else if (is(T == ArrayInitExpr)) 80 type = "[]"; 81 else if(is(T == IntExpr)) 82 type = "int"; 83 error(n.begin, "expression is not of type ‘{}’", type); 84 return null; 85 } 86 87 void load() 88 {} 89 } 90 91 /// Loads the configuration file of DIL. 92 class ConfigLoader : SettingsLoader 93 { 94 /// Name of the configuration file. 95 static cstring configFileName = "dilconf.d"; 96 cstring executablePath; /// Absolute path to DIL's executable. 97 cstring executableDir; /// Absolute path to the directory of DIL's executable. 98 cstring dataDir; /// Absolute path to DIL's data directory. 99 cstring homePath; /// Path to the home directory. 100 cstring dilconfPath; /// Path to dilconf.d to be used. 101 102 ResourceBundle resourceBundle; /// A bundle for compiler messages. 103 104 /// Constructs a ConfigLoader object. 105 this(CompilationContext cc, Diagnostics diag, cstring arg0) 106 { 107 super(cc, diag); 108 this.homePath = Environment.get("HOME"); 109 this.executablePath = GetExecutableFilePath(arg0); 110 this.executableDir = Path(this.executablePath).path(); 111 Environment.set("BINDIR", this.executableDir); 112 } 113 114 static ConfigLoader opCall(CompilationContext cc, Diagnostics diag, 115 cstring arg0) 116 { 117 return new ConfigLoader(cc, diag, arg0); 118 } 119 120 /// Expands environment variables such as ${HOME} in a string. 121 static cstring expandVariables(cstring str) 122 { 123 char[] result; 124 const s = String(str); 125 cchar* p = s.ptr, end = s.end; 126 auto pieceBegin = p; // Points past the closing brace '}' of a variable. 127 128 while (p+3 < end) 129 { 130 auto variableBegin = String(p, end).findp(String("${")); 131 if (!variableBegin) 132 break; 133 auto variableEnd = String(variableBegin + 2, end).findp('}'); 134 if (!variableEnd) 135 break; // Don't expand unterminated variables. 136 result ~= slice(pieceBegin, variableBegin); // Copy previous string. 137 // Get the environment variable and append it to the result. 138 result ~= Environment.get(slice(variableBegin + 2, variableEnd)); 139 pieceBegin = p = variableEnd + 1; // Point to character after '}'. 140 } 141 if (pieceBegin is s.ptr) 142 return str; // Return unchanged string. 143 if (pieceBegin < end) // Copy end piece. 144 result ~= slice(pieceBegin, end); 145 return result; 146 } 147 148 /// Returns a normalized path. 149 cstring normalize(cstring path) 150 { 151 return Path(path).normalize()[]; 152 } 153 154 /// Loads the configuration file. 155 override void load() 156 { 157 // Search for the configuration file. 158 dilconfPath = findConfigurationFilePath(); 159 if (dilconfPath is null) 160 { 161 diag ~= new GeneralError(new Location("",0), 162 "the configuration file ‘"~configFileName~"’ could not be found."); 163 return; 164 } 165 // Load the file as a D module. 166 mod = new Module(dilconfPath, cc); 167 mod.parse(); 168 169 if (mod.hasErrors) 170 return; 171 172 auto pass1 = new SemanticPass1(mod, cc); 173 pass1.run(); 174 175 // Initialize the dataDir member. 176 if (auto val = getValue!(StringExpr)("DATADIR")) 177 this.dataDir = val.getString(); 178 this.dataDir = normalize(expandVariables(this.dataDir)); 179 GlobalSettings.dataDir = this.dataDir; 180 Environment.set("DATADIR", this.dataDir); 181 182 if (auto val = getValue!(StringExpr)("KANDILDIR")) 183 { 184 auto kandilDir = normalize(expandVariables(val.getString())); 185 GlobalSettings.kandilDir = kandilDir; 186 Environment.set("KANDILDIR", kandilDir); 187 } 188 189 if (auto array = getValue!(ArrayInitExpr)("VERSION_IDS")) 190 foreach (value; array.values) 191 if (auto val = castTo!(StringExpr)(value)) 192 GlobalSettings.versionIds ~= expandVariables(val.getString()); 193 if (auto val = getValue!(StringExpr)("LANG_FILE")) 194 GlobalSettings.langFile = normalize(expandVariables(val.getString())); 195 if (auto array = getValue!(ArrayInitExpr)("IMPORT_PATHS")) 196 foreach (value; array.values) 197 if (auto val = castTo!(StringExpr)(value)) 198 GlobalSettings.importPaths ~= 199 normalize(expandVariables(val.getString())); 200 if (auto array = getValue!(ArrayInitExpr)("DDOC_FILES")) 201 foreach (value; array.values) 202 if (auto val = castTo!(StringExpr)(value)) 203 GlobalSettings.ddocFilePaths ~= 204 normalize(expandVariables(val.getString())); 205 if (auto val = getValue!(StringExpr)("XML_MAP")) 206 GlobalSettings.xmlMapFile = normalize(expandVariables(val.getString())); 207 if (auto val = getValue!(StringExpr)("HTML_MAP")) 208 GlobalSettings.htmlMapFile = normalize(expandVariables(val.getString())); 209 if (auto val = getValue!(StringExpr)("LEXER_ERROR")) 210 GlobalSettings.lexerErrorFormat = expandVariables(val.getString()); 211 if (auto val = getValue!(StringExpr)("PARSER_ERROR")) 212 GlobalSettings.parserErrorFormat = expandVariables(val.getString()); 213 if (auto val = getValue!(StringExpr)("SEMANTIC_ERROR")) 214 GlobalSettings.semanticErrorFormat = expandVariables(val.getString()); 215 if (auto val = getValue!(IntExpr)("TAB_WIDTH")) 216 { 217 GlobalSettings.tabWidth = cast(uint)val.number; 218 Location.TAB_WIDTH = cast(uint)val.number; 219 } 220 221 auto langFile = expandVariables(GlobalSettings.langFile); 222 resourceBundle = loadResource(langFile); 223 } 224 225 /// Loads a language file and returns a ResouceBundle object. 226 ResourceBundle loadResource(cstring langFile) 227 { 228 if (!Path(langFile).exists) 229 { 230 diag ~= new GeneralError(new Location("", 0), 231 "the language file ‘"~langFile~"’ does not exist."); 232 goto Lerror; 233 } 234 235 // 1. Load language file. 236 mod = new Module(langFile, cc); 237 mod.parse(); 238 239 if (mod.hasErrors) 240 goto Lerror; 241 242 { 243 auto pass1 = new SemanticPass1(mod, cc); 244 pass1.run(); 245 246 // 2. Extract the values of the variables. 247 cstring[] messages = new cstring[MID.max + 1]; 248 if (auto array = getValue!(ArrayInitExpr)("messages")) 249 { 250 foreach (i, value; array.values) 251 if (i >= messages.length) 252 break; // More messages given than allowed. 253 else if (value.Is!(NullExpr)) 254 {} // messages[i] = null; 255 else if (auto val = castTo!(StringExpr)(value)) 256 messages[i] = val.getString(); 257 //if (messages.length != MID.max+1) 258 //error(mod.loc.t, 259 //"messages table in {} must exactly have {} entries, but not {}.", 260 //langFile, MID.max+1, messages.length); 261 } 262 cstring langCode; 263 if (auto val = getValue!(StringExpr)("lang_code")) 264 langCode = val.getString(); 265 cstring parentLangFile; 266 if (auto val = getValue!(StringExpr)("inherit", true)) 267 parentLangFile = expandVariables(val.getString()); 268 269 // 3. Load the parent bundle if one is specified. 270 auto parentRB = parentLangFile ? loadResource(parentLangFile) : null; 271 272 // 4. Return a new bundle. 273 auto rb = new ResourceBundle(messages, parentRB); 274 rb.langCode = langCode; 275 276 return rb; 277 } 278 Lerror: 279 return new ResourceBundle(); 280 } 281 282 /// Searches for the configuration file of DIL. 283 /// Returns: the filePath or null if the file couldn't be found. 284 cstring findConfigurationFilePath() 285 { 286 auto path = Path(); 287 bool exists(cstring s) 288 { 289 return path.set(s).exists; 290 } 291 // 1. Look in environment variable DILCONF. 292 if (exists(Environment.get("DILCONF")) || 293 // 2. Look in the current working directory. 294 exists(configFileName) || 295 // 3. Look in the directory set by HOME. 296 exists(homePath~"/"~configFileName) || 297 // 4. Look in the binary's directory. 298 exists(executableDir~"/"~configFileName) || 299 // 5. Look in /etc/. 300 exists("/etc/"~configFileName)) 301 return normalize(path[]); 302 else 303 return null; 304 } 305 } 306 307 /// Loads an associative array from a D module file. 308 class TagMapLoader : SettingsLoader 309 { 310 /// Constructs a TagMapLoader object. 311 this(CompilationContext cc, Diagnostics diag) 312 { 313 super(cc, diag); 314 } 315 316 static TagMapLoader opCall(CompilationContext cc, Diagnostics diag) 317 { 318 return new TagMapLoader(cc, diag); 319 } 320 321 cstring[hash_t] load(cstring filePath) 322 { 323 mod = new Module(filePath, cc); 324 mod.parse(); 325 if (mod.hasErrors) 326 return null; 327 328 auto pass1 = new SemanticPass1(mod, cc); 329 pass1.run(); 330 331 cstring[hash_t] map; 332 if (auto array = getValue!(ArrayInitExpr)("map")) 333 foreach (i, value; array.values) 334 { 335 auto key = array.keys[i]; 336 if (auto valExp = castTo!(StringExpr)(value)) 337 if (!key) 338 error(value.begin, "expected key : value"); 339 else if (auto keyExp = castTo!(StringExpr)(key)) 340 map[hashOf(keyExp.getString())] = valExp.getString(); 341 } 342 return map; 343 } 344 } 345 346 /// Resolves the path to a file from the executable's dir path 347 /// if it is relative. 348 /// Returns: filePath if it is absolute or execPath + filePath. 349 cstring resolvePath(cstring execPath, cstring filePath) 350 { 351 scope path = Path(filePath); 352 if (path.isAbsolute()) 353 return filePath; 354 path.set(execPath).append(filePath); 355 return path[]; 356 } 357 358 extern(Windows) uint GetModuleFileNameW(void*, wchar*, uint); 359 extern(C) size_t readlink(const char* path, char* buf, size_t bufsize); 360 extern(C) int _NSGetExecutablePath(char* buf, uint* bufsize); 361 extern(C) char* realpath(char* base, char* dest); 362 363 /// Returns the fully qualified path to this executable, 364 /// or arg0 on failure or when a platform is unsupported. 365 /// Params: 366 /// arg0 = This is argv[0] from main(cstring[] argv). 367 cstring GetExecutableFilePath(cstring arg0) 368 { 369 version(Windows) 370 { 371 wchar[] buffer = new wchar[256]; 372 size_t count; 373 374 while (1) 375 { 376 count = GetModuleFileNameW(null, buffer.ptr, buffer.length); 377 if (count == 0) 378 return arg0; 379 if (buffer.length != count && buffer[count] == '\0') 380 break; 381 buffer.length = buffer.length * 2; 382 } 383 // Reduce buffer to the actual length of the string (excluding '\0'.) 384 buffer.length = count; 385 return toUTF8(buffer); 386 } // version(Windows) 387 388 else version(linux) 389 { 390 auto buffer = CharArray(256); 391 size_t count; 392 393 while (1) 394 { // This won't work on very old Linux systems. 395 count = readlink("/proc/self/exe".ptr, buffer.ptr, buffer.cap); 396 if (count == -1) 397 return arg0; 398 if (count < buffer.cap) 399 break; 400 buffer.cap = buffer.cap * 2; 401 } 402 buffer.len = count; 403 return buffer[]; 404 } // version(linux) 405 406 else version(darwin) 407 { 408 // First, get the executable path. 409 uint size; 410 char[] path; // The first time .ptr needs to be null and size to be 0. 411 while (_NSGetExecutablePath(path.ptr, &size) == -1) 412 path = new char[size]; 413 414 // Then, convert it to the »real path«, resolving symlinks, etc. 415 char[] buffer = new char[1024]; // 1024 = PATH_MAX on Mac OS X 10.5 416 if (!realpath(path.ptr, buffer.ptr)) 417 return arg0; 418 return MString(buffer.ptr, '\0')[]; 419 } // version(darwin) 420 421 else 422 { 423 pragma(msg, 424 "Warning: GetExecutableFilePath() is not implemented on this platform."); 425 return arg0; 426 } 427 }