1 /** 2 * This file is part of DCD, a development tool for the D programming language. 3 * Copyright (C) 2014 Brian Schott 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 */ 18 19 module dsymbol.modulecache; 20 21 import containers.dynamicarray; 22 import containers.hashset; 23 import containers.ttree; 24 import containers.unrolledlist; 25 import dsymbol.conversion; 26 import dsymbol.conversion.first; 27 import dsymbol.conversion.second; 28 import dsymbol.cache_entry; 29 import dsymbol.scope_; 30 import dsymbol.semantic; 31 import dsymbol.symbol; 32 import dsymbol.string_interning; 33 import dsymbol.deferred; 34 import std.algorithm; 35 import stdx.allocator; 36 import stdx.allocator.building_blocks.allocator_list; 37 import stdx.allocator.building_blocks.region; 38 import stdx.allocator.building_blocks.null_allocator; 39 import stdx.allocator.mallocator; 40 import std.conv; 41 import dparse.ast; 42 import std.datetime; 43 import dparse.lexer; 44 import dparse.parser; 45 import std.experimental.logger; 46 import std.file; 47 import std.experimental.lexer; 48 import std.path; 49 50 alias ASTAllocator = CAllocatorImpl!(AllocatorList!( 51 n => Region!Mallocator(1024 * 128), Mallocator)); 52 53 /** 54 * Returns: true if a file exists at the given path. 55 */ 56 bool existanceCheck(A)(A path) 57 { 58 if (path.exists()) 59 return true; 60 warning("Cannot cache modules in ", path, " because it does not exist"); 61 return false; 62 } 63 64 /** 65 * Caches pre-parsed module information. 66 */ 67 struct ModuleCache 68 { 69 /// No copying. 70 @disable this(this); 71 72 @disable this(); 73 74 this(IAllocator symbolAllocator) 75 { 76 this.symbolAllocator = symbolAllocator; 77 } 78 79 ~this() 80 { 81 foreach (entry; ModuleCache.cache[]) 82 Mallocator.instance.dispose(entry); 83 foreach (symbol; deferredSymbols[]) 84 Mallocator.instance.dispose(symbol); 85 86 // TODO: This call to deallocateAll is a workaround for issues of 87 // CAllocatorImpl and GCAllocator not interacting well. 88 symbolAllocator.deallocateAll(); 89 } 90 91 /** 92 * Adds the given path to the list of directories checked for imports. 93 * Performs duplicate checking, so multiple instances of the same path will 94 * not be present. 95 */ 96 void addImportPaths(const string[] paths) 97 { 98 import dsymbol.string_interning : internString; 99 import std.path : baseName; 100 import std.array : array; 101 102 auto newPaths = paths 103 .map!(a => absolutePath(expandTilde(a))) 104 .filter!(a => existanceCheck(a) && !importPaths[].canFind(a)) 105 .map!(internString) 106 .array; 107 importPaths.insert(newPaths); 108 109 foreach (path; newPaths[]) 110 { 111 if (path.isFile) 112 { 113 if (path.baseName.startsWith(".#")) 114 continue; 115 cacheModule(path); 116 } 117 else 118 { 119 void scanFrom(const string root) 120 { 121 if (exists(buildPath(root, ".no-dcd"))) 122 return; 123 124 try foreach (f; dirEntries(root, SpanMode.shallow)) 125 { 126 if (f.name.isFile) 127 { 128 if (!f.name.extension.among(".d", ".di") || f.name.baseName.startsWith(".#")) 129 continue; 130 cacheModule(f.name); 131 } 132 else scanFrom(f.name); 133 } 134 catch(FileException) {} 135 } 136 scanFrom(path); 137 } 138 } 139 } 140 141 /// TODO: Implement 142 void clear() 143 { 144 info("ModuleCache.clear is not yet implemented."); 145 } 146 147 /** 148 * Caches the module at the given location 149 */ 150 DSymbol* cacheModule(string location) 151 { 152 import dsymbol.string_interning : internString; 153 import std.stdio : File; 154 import std.typecons : scoped; 155 156 assert (location !is null); 157 158 istring cachedLocation = internString(location); 159 160 161 if (!needsReparsing(cachedLocation)) 162 return getModuleSymbol(cachedLocation); 163 164 recursionGuard.insert(cachedLocation); 165 166 File f = File(cachedLocation); 167 immutable fileSize = cast(size_t) f.size; 168 if (fileSize == 0) 169 return null; 170 171 const(Token)[] tokens; 172 auto parseStringCache = StringCache(StringCache.defaultBucketCount); 173 { 174 ubyte[] source = cast(ubyte[]) Mallocator.instance.allocate(fileSize); 175 scope (exit) Mallocator.instance.deallocate(source); 176 f.rawRead(source); 177 LexerConfig config; 178 config.fileName = cachedLocation; 179 180 // The first three bytes are sliced off here if the file starts with a 181 // Unicode byte order mark. The lexer/parser don't handle them. 182 tokens = getTokensForParser( 183 (source.length >= 3 && source[0 .. 3] == "\xef\xbb\xbf"c) 184 ? source[3 .. $] : source, 185 config, &parseStringCache); 186 } 187 188 CacheEntry* newEntry = Mallocator.instance.make!CacheEntry(); 189 190 auto semanticAllocator = scoped!(ASTAllocator); 191 import dparse.rollback_allocator:RollbackAllocator; 192 RollbackAllocator parseAllocator; 193 Module m = parseModuleSimple(tokens[], cachedLocation, &parseAllocator); 194 195 assert (symbolAllocator); 196 auto first = scoped!FirstPass(m, cachedLocation, symbolAllocator, 197 semanticAllocator, false, &this, newEntry); 198 first.run(); 199 200 secondPass(first.rootSymbol, first.moduleScope, this); 201 202 typeid(Scope).destroy(first.moduleScope); 203 symbolsAllocated += first.symbolsAllocated; 204 205 SysTime access; 206 SysTime modification; 207 getTimes(cachedLocation.data, access, modification); 208 209 newEntry.symbol = first.rootSymbol.acSymbol; 210 newEntry.modificationTime = modification; 211 newEntry.path = cachedLocation; 212 213 CacheEntry* oldEntry = getEntryFor(cachedLocation); 214 if (oldEntry !is null) 215 { 216 // Generate update mapping from the old symbol to the new one 217 UpdatePairCollection updatePairs; 218 generateUpdatePairs(oldEntry.symbol, newEntry.symbol, updatePairs); 219 220 // Apply updates to all symbols in modules that depend on this one 221 cache[].filter!(a => a.dependencies.contains(cachedLocation)).each!( 222 upstream => upstream.symbol.updateTypes(updatePairs)); 223 224 // Remove the old symbol. 225 cache.remove(oldEntry, entry => Mallocator.instance.dispose(entry)); 226 } 227 228 cache.insert(newEntry); 229 recursionGuard.remove(cachedLocation); 230 231 resolveDeferredTypes(cachedLocation); 232 233 typeid(SemanticSymbol).destroy(first.rootSymbol); 234 235 return newEntry.symbol; 236 } 237 238 /** 239 * Resolves types for deferred symbols 240 */ 241 void resolveDeferredTypes(istring location) 242 { 243 UnrolledList!(DeferredSymbol*) temp; 244 temp.insert(deferredSymbols[]); 245 deferredSymbols.clear(); 246 foreach (deferred; temp[]) 247 { 248 if (!deferred.imports.empty && !deferred.dependsOn(location)) 249 { 250 deferredSymbols.insert(deferred); 251 continue; 252 } 253 assert(deferred.symbol.type is null); 254 if (deferred.symbol.kind == CompletionKind.importSymbol) 255 { 256 resolveImport(deferred.symbol, deferred.typeLookups, this); 257 } 258 else if (!deferred.typeLookups.empty) 259 { 260 // TODO: Is .front the right thing to do here? 261 resolveTypeFromType(deferred.symbol, deferred.typeLookups.front, null, 262 this, &deferred.imports); 263 } 264 Mallocator.instance.dispose(deferred); 265 } 266 } 267 268 /** 269 * Params: 270 * moduleName = the name of the module in "a/b/c" form 271 * Returns: 272 * The symbols defined in the given module, or null if the module is 273 * not cached yet. 274 */ 275 DSymbol* getModuleSymbol(istring location) 276 { 277 auto existing = getEntryFor(location); 278 return existing is null ? null : existing.symbol; 279 } 280 281 /** 282 * Params: 283 * moduleName = the name of the module being imported, in "a/b/c" style 284 * Returns: 285 * The absolute path to the file that contains the module, or null if 286 * not found. 287 */ 288 istring resolveImportLocation(string moduleName) 289 { 290 assert(moduleName !is null, "module name is null"); 291 if (isRooted(moduleName)) 292 return internString(moduleName); 293 string[] alternatives; 294 foreach (path; importPaths[]) 295 { 296 if (path.isFile) 297 { 298 if (path.stripExtension.endsWith(moduleName)) 299 alternatives ~= path; 300 } 301 else 302 { 303 string dotDi = buildPath(path, moduleName) ~ ".di"; 304 string dotD = dotDi[0 .. $ - 1]; 305 string withoutSuffix = dotDi[0 .. $ - 3]; 306 if (exists(dotD) && isFile(dotD)) 307 alternatives = dotD ~ alternatives; 308 else if (exists(dotDi) && isFile(dotDi)) 309 alternatives ~= dotDi; 310 else if (exists(withoutSuffix) && isDir(withoutSuffix)) 311 { 312 string packagePath = buildPath(withoutSuffix, "package.di"); 313 if (exists(packagePath) && isFile(packagePath)) 314 { 315 alternatives ~= packagePath; 316 continue; 317 } 318 if (exists(packagePath[0 .. $ - 1]) && isFile(packagePath[0 .. $ - 1])) 319 alternatives ~= packagePath[0 .. $ - 1]; 320 } 321 } 322 } 323 return alternatives.length > 0 ? internString(alternatives[0]) : istring(null); 324 } 325 326 auto getImportPaths() const 327 { 328 return importPaths[]; 329 } 330 331 auto getAllSymbols() const 332 { 333 return cache[]; 334 } 335 336 IAllocator symbolAllocator; 337 338 UnrolledList!(DeferredSymbol*) deferredSymbols; 339 340 /// Count of autocomplete symbols that have been allocated 341 uint symbolsAllocated; 342 343 private: 344 345 CacheEntry* getEntryFor(istring cachedLocation) 346 { 347 CacheEntry dummy; 348 dummy.path = cachedLocation; 349 auto r = cache.equalRange(&dummy); 350 return r.empty ? null : r.front; 351 } 352 353 /** 354 * Params: 355 * mod = the path to the module 356 * Returns: 357 * true if the module needs to be reparsed, false otherwise 358 */ 359 bool needsReparsing(istring mod) 360 { 361 if (recursionGuard.contains(mod)) 362 return false; 363 if (!exists(mod.data)) 364 return true; 365 CacheEntry e; 366 e.path = mod; 367 auto r = cache.equalRange(&e); 368 if (r.empty) 369 return true; 370 SysTime access; 371 SysTime modification; 372 getTimes(mod.data, access, modification); 373 return r.front.modificationTime != modification; 374 } 375 376 // Mapping of file paths to their cached symbols. 377 TTree!(CacheEntry*) cache; 378 379 HashSet!string recursionGuard; 380 381 // Listing of paths to check for imports 382 UnrolledList!string importPaths; 383 }