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 std.experimental.allocator; 36 import std.experimental.allocator.building_blocks.allocator_list; 37 import std.experimental.allocator.building_blocks.region; 38 import std.experimental.allocator.building_blocks.null_allocator; 39 import std.experimental.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 try foreach (f; dirEntries(root, SpanMode.shallow)) 122 { 123 if (f.name.isFile) 124 { 125 if (!f.name.extension.among(".d", ".di") || f.name.baseName.startsWith(".#")) 126 continue; 127 cacheModule(f.name); 128 } 129 else scanFrom(f.name); 130 } 131 catch(FileException) {} 132 } 133 scanFrom(path); 134 } 135 } 136 } 137 138 /// TODO: Implement 139 void clear() 140 { 141 info("ModuleCache.clear is not yet implemented."); 142 } 143 144 /** 145 * Caches the module at the given location 146 */ 147 DSymbol* cacheModule(string location) 148 { 149 import dsymbol.string_interning : internString; 150 import std.stdio : File; 151 import std.typecons : scoped; 152 153 assert (location !is null); 154 155 istring cachedLocation = internString(location); 156 157 158 if (!needsReparsing(cachedLocation)) 159 return getModuleSymbol(cachedLocation); 160 161 recursionGuard.insert(cachedLocation); 162 163 File f = File(cachedLocation); 164 immutable fileSize = cast(size_t) f.size; 165 if (fileSize == 0) 166 return null; 167 168 const(Token)[] tokens; 169 auto parseStringCache = StringCache(StringCache.defaultBucketCount); 170 { 171 ubyte[] source = cast(ubyte[]) Mallocator.instance.allocate(fileSize); 172 scope (exit) Mallocator.instance.deallocate(source); 173 f.rawRead(source); 174 LexerConfig config; 175 config.fileName = cachedLocation; 176 177 // The first three bytes are sliced off here if the file starts with a 178 // Unicode byte order mark. The lexer/parser don't handle them. 179 tokens = getTokensForParser( 180 (source.length >= 3 && source[0 .. 3] == "\xef\xbb\xbf"c) 181 ? source[3 .. $] : source, 182 config, &parseStringCache); 183 } 184 185 CacheEntry* newEntry = Mallocator.instance.make!CacheEntry(); 186 187 auto semanticAllocator = scoped!(ASTAllocator); 188 import dparse.rollback_allocator:RollbackAllocator; 189 RollbackAllocator parseAllocator; 190 Module m = parseModuleSimple(tokens[], cachedLocation, &parseAllocator); 191 192 assert (symbolAllocator); 193 auto first = scoped!FirstPass(m, cachedLocation, symbolAllocator, 194 semanticAllocator, false, &this, newEntry); 195 first.run(); 196 197 secondPass(first.rootSymbol, first.moduleScope, this); 198 199 typeid(Scope).destroy(first.moduleScope); 200 symbolsAllocated += first.symbolsAllocated; 201 202 SysTime access; 203 SysTime modification; 204 getTimes(cachedLocation.data, access, modification); 205 206 newEntry.symbol = first.rootSymbol.acSymbol; 207 newEntry.modificationTime = modification; 208 newEntry.path = cachedLocation; 209 210 CacheEntry* oldEntry = getEntryFor(cachedLocation); 211 if (oldEntry !is null) 212 { 213 // Generate update mapping from the old symbol to the new one 214 UpdatePairCollection updatePairs; 215 generateUpdatePairs(oldEntry.symbol, newEntry.symbol, updatePairs); 216 217 // Apply updates to all symbols in modules that depend on this one 218 cache[].filter!(a => a.dependencies.contains(cachedLocation)).each!( 219 upstream => upstream.symbol.updateTypes(updatePairs)); 220 221 // Remove the old symbol. 222 cache.remove(oldEntry, entry => Mallocator.instance.dispose(entry)); 223 } 224 225 cache.insert(newEntry); 226 recursionGuard.remove(cachedLocation); 227 228 resolveDeferredTypes(cachedLocation); 229 230 typeid(SemanticSymbol).destroy(first.rootSymbol); 231 232 return newEntry.symbol; 233 } 234 235 /** 236 * Resolves types for deferred symbols 237 */ 238 void resolveDeferredTypes(istring location) 239 { 240 UnrolledList!(DeferredSymbol*) temp; 241 temp.insert(deferredSymbols[]); 242 deferredSymbols.clear(); 243 foreach (deferred; temp[]) 244 { 245 if (!deferred.imports.empty && !deferred.dependsOn(location)) 246 { 247 deferredSymbols.insert(deferred); 248 continue; 249 } 250 assert(deferred.symbol.type is null); 251 if (deferred.symbol.kind == CompletionKind.importSymbol) 252 { 253 resolveImport(deferred.symbol, deferred.typeLookups, this); 254 } 255 else if (!deferred.typeLookups.empty) 256 { 257 // TODO: Is .front the right thing to do here? 258 resolveTypeFromType(deferred.symbol, deferred.typeLookups.front, null, 259 this, &deferred.imports); 260 } 261 Mallocator.instance.dispose(deferred); 262 } 263 } 264 265 /** 266 * Params: 267 * moduleName = the name of the module in "a/b/c" form 268 * Returns: 269 * The symbols defined in the given module, or null if the module is 270 * not cached yet. 271 */ 272 DSymbol* getModuleSymbol(istring location) 273 { 274 auto existing = getEntryFor(location); 275 return existing is null ? null : existing.symbol; 276 } 277 278 /** 279 * Params: 280 * moduleName = the name of the module being imported, in "a/b/c" style 281 * Returns: 282 * The absolute path to the file that contains the module, or null if 283 * not found. 284 */ 285 istring resolveImportLocation(string moduleName) 286 { 287 assert(moduleName !is null, "module name is null"); 288 if (isRooted(moduleName)) 289 return internString(moduleName); 290 string[] alternatives; 291 foreach (path; importPaths[]) 292 { 293 if (path.isFile) 294 { 295 if (path.stripExtension.endsWith(moduleName)) 296 alternatives ~= path; 297 } 298 else 299 { 300 string dotDi = buildPath(path, moduleName) ~ ".di"; 301 string dotD = dotDi[0 .. $ - 1]; 302 string withoutSuffix = dotDi[0 .. $ - 3]; 303 if (exists(dotD) && isFile(dotD)) 304 alternatives = dotD ~ alternatives; 305 else if (exists(dotDi) && isFile(dotDi)) 306 alternatives ~= dotDi; 307 else if (exists(withoutSuffix) && isDir(withoutSuffix)) 308 { 309 string packagePath = buildPath(withoutSuffix, "package.di"); 310 if (exists(packagePath) && isFile(packagePath)) 311 { 312 alternatives ~= packagePath; 313 continue; 314 } 315 if (exists(packagePath[0 .. $ - 1]) && isFile(packagePath[0 .. $ - 1])) 316 alternatives ~= packagePath[0 .. $ - 1]; 317 } 318 } 319 } 320 return alternatives.length > 0 ? internString(alternatives[0]) : istring(null); 321 } 322 323 auto getImportPaths() const 324 { 325 return importPaths[]; 326 } 327 328 auto getAllSymbols() const 329 { 330 return cache[]; 331 } 332 333 IAllocator symbolAllocator; 334 335 UnrolledList!(DeferredSymbol*) deferredSymbols; 336 337 /// Count of autocomplete symbols that have been allocated 338 uint symbolsAllocated; 339 340 private: 341 342 CacheEntry* getEntryFor(istring cachedLocation) 343 { 344 CacheEntry dummy; 345 dummy.path = cachedLocation; 346 auto r = cache.equalRange(&dummy); 347 return r.empty ? null : r.front; 348 } 349 350 /** 351 * Params: 352 * mod = the path to the module 353 * Returns: 354 * true if the module needs to be reparsed, false otherwise 355 */ 356 bool needsReparsing(istring mod) 357 { 358 if (recursionGuard.contains(mod)) 359 return false; 360 if (!exists(mod.data)) 361 return true; 362 CacheEntry e; 363 e.path = mod; 364 auto r = cache.equalRange(&e); 365 if (r.empty) 366 return true; 367 SysTime access; 368 SysTime modification; 369 getTimes(mod.data, access, modification); 370 return r.front.modificationTime != modification; 371 } 372 373 // Mapping of file paths to their cached symbols. 374 TTree!(CacheEntry*) cache; 375 376 HashSet!string recursionGuard; 377 378 // Listing of paths to check for imports 379 UnrolledList!string importPaths; 380 }