1 /// Author: Aziz Köksal 2 /// License: GPL3 3 /// $(Maturity high) 4 module dil.ModuleManager; 5 6 import dil.semantic.Module, 7 dil.semantic.Package, 8 dil.semantic.Symbol; 9 import dil.lexer.Token; 10 import dil.i18n.Messages; 11 import dil.Compilation, 12 dil.Diagnostics, 13 dil.String; 14 import util.Path; 15 import common; 16 17 import std.algorithm : sort; 18 import std.range : assumeSorted; 19 20 /// Manages loaded modules in various tables. 21 class ModuleManager 22 { 23 /// The root package. Contains all other modules and packages. 24 Package rootPackage; 25 /// Maps full package names to packages. E.g.: dil.ast 26 Package[hash_t] packageTable; 27 /// Maps FQN paths to modules. E.g.: dil/ast/Node 28 Module[hash_t] moduleFQNPathTable; 29 /// Maps absolute file paths to modules. E.g.: /home/user/dil/src/main.d 30 Module[hash_t] absFilePathTable; 31 /// Loaded modules in sequential order. 32 Module[] loadedModules; 33 /// Loaded modules which are ordered according to the number of 34 /// import statements in each module (ascending order.) 35 Module[] orderedModules; 36 /// Provides tables and compiler variables. 37 CompilationContext cc; 38 39 /// Constructs a ModuleManager object. 40 this(CompilationContext cc) 41 { 42 this.rootPackage = new Package(null, cc.tables.idents); 43 packageTable[0] = this.rootPackage; 44 assert(hashOf("") == 0); 45 this.cc = cc; 46 } 47 48 /// Looks up a module by its file path. E.g.: "src/dil/ModuleManager.d" 49 /// Relative paths are made absolute. 50 Module moduleByPath(cstring moduleFilePath) 51 { 52 auto absFilePath = absolutePath(moduleFilePath); 53 if (auto existingModule = hashOf(absFilePath) in absFilePathTable) 54 return *existingModule; 55 return null; 56 } 57 58 /// Looks up a module by its f.q.n. path. E.g.: "dil/ModuleManager" 59 Module moduleByFQN(cstring moduleFQNPath) 60 { 61 if (auto existingModule = hashOf(moduleFQNPath) in moduleFQNPathTable) 62 return *existingModule; 63 return null; 64 } 65 66 /// Loads and parses a module given a file path. 67 /// Returns: A new Module instance or an existing one from the table. 68 Module loadModuleFile(cstring moduleFilePath) 69 { 70 if (auto existingModule = moduleByPath(moduleFilePath)) 71 return existingModule; 72 73 if (Path(moduleFilePath).isFolder) 74 { 75 auto msg = cc.diag.formatMsg(MID.ModulePathIsFolder, moduleFilePath); 76 cc.diag ~= new LexerError(new Location(moduleFilePath, 0), msg); 77 return null; 78 } 79 80 // Create a new module. 81 auto newModule = new Module(moduleFilePath, cc); 82 newModule.parse(); 83 84 addModule(newModule); 85 86 return newModule; 87 } 88 89 /// Loads a module given an FQN path. Searches import paths. 90 Module loadModule(cstring moduleFQNPath) 91 { 92 // Look up in table if the module is already loaded. 93 if (auto existingModule = moduleByFQN(moduleFQNPath)) 94 return existingModule; 95 96 // Locate the module in the file system. 97 auto moduleFilePath = findModuleFile(moduleFQNPath); 98 if (!moduleFilePath.length) 99 return null; // No module found. 100 101 // Load the module file. 102 auto modul = loadModuleFile(moduleFilePath); 103 if (!modul) 104 return null; 105 106 auto packageFQN = getPackageFQN(moduleFQNPath); 107 if (getPackageFQN(modul.getFQNPath()) != packageFQN) 108 // Error: the requested module is not in the correct package. 109 error(modul, MID.ModuleNotInPackage, packageFQN); 110 111 return modul; 112 } 113 114 /// Inserts the given module into the tables. 115 void addModule(Module newModule) 116 { 117 auto absFilePath = absolutePath(newModule.filePath()); 118 119 auto moduleFQNPath = newModule.getFQNPath(); 120 auto fqnPathHash = hashOf(moduleFQNPath); 121 122 if (auto existingModule = fqnPathHash in moduleFQNPathTable) 123 // Error: two module files have the same f.q. module name. 124 return error(newModule, 125 MID.ConflictingModuleFiles, newModule.filePath()); 126 127 // Insert into the tables. 128 moduleFQNPathTable[fqnPathHash] = newModule; 129 absFilePathTable[hashOf(absFilePath)] = newModule; 130 loadedModules ~= newModule; 131 newModule.ID = loadedModules.length; 132 insertOrdered(newModule); 133 134 auto nrOfPckgs = packageTable.length; // Remember for error checking. 135 // Add the module to its package. 136 auto pckg = getPackage(newModule.packageName); 137 pckg.add(newModule); 138 139 if (auto p = hashOf(newModule.getFQN()) in packageTable) 140 // Error: module and package share the same name. 141 // Happens when: "src/dil/module.d", "src/dil.d" 142 // There's a package dil and a module dil. 143 return error(newModule, 144 MID.ConflictingModuleAndPackage, newModule.getFQN()); 145 146 if (nrOfPckgs != packageTable.length) // Were new packages added? 147 { // Check whether any new package is in conflict with an existing module. 148 uint i; // Used to get the exact package in a module declaration. 149 auto p = newModule.parent.to!(Package); 150 for (; p.parent; p = p.parentPackage()) // Go up until root package. 151 { 152 i++; 153 auto pckgFQN = p.getFQN(); // E.g.: dil.ast 154 auto pckgFQNPath = pckgFQN.replace('.', dirSep); // E.g.: dil/ast 155 if (hashOf(pckgFQNPath) in moduleFQNPathTable) 156 // Error: package and module share the same name. 157 return error(newModule.moduleDecl.packages[$-i], newModule, 158 MID.ConflictingPackageAndModule, pckgFQN); 159 } 160 } 161 } 162 163 /// Compares the number of imports of two modules. 164 /// Returns: true if a imports less than b. 165 static bool compareImports(Module a, Module b) 166 { 167 return a.imports.length < b.imports.length; 168 } 169 170 /// Insert a module into the ordered list. 171 void insertOrdered(Module newModule) 172 { 173 auto sorted = orderedModules.assumeSorted!compareImports(); 174 auto i = sorted.lowerBound(newModule).length; 175 if (i == orderedModules.length) 176 orderedModules ~= newModule; 177 else 178 orderedModules = orderedModules[0..i] ~ newModule ~ orderedModules[i..$]; 179 } 180 181 /// Returns the package given a f.q. package name. 182 /// Returns the root package for an empty string. 183 Package getPackage(cstring pckgFQN) 184 { 185 auto fqnHash = hashOf(pckgFQN); 186 if (auto existingPackage = fqnHash in packageTable) 187 return *existingPackage; 188 189 cstring prevFQN, lastPckgName; 190 // E.g.: pckgFQN = 'dil.ast', prevFQN = 'dil', lastPckgName = 'ast' 191 splitPackageFQN(pckgFQN, prevFQN, lastPckgName); 192 // Recursively build package hierarchy. 193 auto parentPckg = getPackage(prevFQN); // E.g.: 'dil' 194 195 // Create a new package. 196 auto pckg = new Package(lastPckgName, cc.tables.idents); // E.g.: 'ast' 197 parentPckg.add(pckg); // 'dil'.add('ast') 198 199 // Insert the package into the table. 200 packageTable[fqnHash] = pckg; 201 202 return pckg; 203 } 204 205 /// Splits e.g. 'dil.ast.xyz' into 'dil.ast' and 'xyz'. 206 /// Params: 207 /// pckgFQN = The full package name to be split. 208 /// prevFQN = Set to 'dil.ast' in the example. 209 /// lastName = The last package name; set to 'xyz' in the example. 210 void splitPackageFQN(cstring pckgFQN, 211 out cstring prevFQN, out cstring lastName) 212 { 213 auto s = String(pckgFQN).rpartition('.'); 214 prevFQN = s[0][]; 215 lastName = s[1][]; 216 } 217 218 /// Returns e.g. 'dil.ast' for 'dil/ast/Node'. 219 static char[] getPackageFQN(cstring moduleFQNPath) 220 { 221 return String(moduleFQNPath).sub(dirSep, '.').rpartition('.')[0][]; 222 } 223 224 /// Searches for a module in the file system looking in importPaths. 225 /// Returns: The file path to the module, or null if it wasn't found. 226 static cstring findModuleFile(cstring moduleFQNPath, cstring[] importPaths) 227 { 228 auto filePath = Path(); 229 foreach (importPath; importPaths) 230 { // E.g.: "path/to/src" ~ "/" ~ "dil/ast/Node" ~ ".d" 231 (filePath.set(importPath) /= moduleFQNPath) ~= ".d"; 232 // or: "src/dil/ast/Node.di" 233 if (filePath.exists() || (filePath~="i").exists()) 234 return filePath[]; 235 } 236 return null; 237 } 238 239 /// ditto 240 cstring findModuleFile(cstring moduleFQNPath) 241 { 242 return findModuleFile(moduleFQNPath, cc.importPaths); 243 } 244 245 /// A predicate for sorting symbols in ascending order. 246 /// Compares symbol names ignoring case. 247 static bool compareSymbolNames(Symbol a, Symbol b) 248 { 249 return String(a.name.str).icmp(b.name.str) < 0; 250 } 251 252 /// Sorts the the subpackages and submodules of pckg. 253 void sortPackageTree(Package pckg) 254 { 255 pckg.packages.sort!(compareSymbolNames)(); 256 pckg.modules.sort!(compareSymbolNames)(); 257 foreach (subpckg; pckg.packages) 258 sortPackageTree(subpckg); 259 } 260 261 /// Calls sortPackageTree() with this.rootPackage. 262 void sortPackageTree() 263 { 264 sortPackageTree(rootPackage); 265 } 266 267 /// Returns a normalized, absolute path. 268 static cstring absolutePath(cstring path) 269 { 270 return Path(path).absolute().normalize()[]; 271 } 272 273 /// Reports an error. 274 void error(Module modul, MID mid, ...) 275 { 276 auto msg = cc.diag.formatMsg(mid, _arguments, _argptr); 277 auto loc = modul.loc.t.getErrorLocation(modul.filePath); 278 cc.diag ~= new SemanticError(loc, msg); 279 } 280 281 /// Reports an error. 282 void error(Token* locTok, Module modul, MID mid, ...) 283 { 284 auto location = locTok.getErrorLocation(modul.filePath()); 285 auto msg = cc.diag.formatMsg(mid, _arguments, _argptr); 286 cc.diag ~= new SemanticError(location, msg); 287 } 288 289 /// Reports the error that the module was not found. 290 /// Params: 291 /// modulePath = File path to the module. 292 /// loc = Optional source location (from an import statement.) 293 void errorModuleNotFound(cstring modulePath, Location loc = null) 294 { 295 if(!loc) loc = new Location(modulePath, 0); 296 auto msg = cc.diag.formatMsg(MID.CouldntLoadModule, modulePath); 297 cc.diag ~= new LexerError(loc, msg); 298 } 299 }