# # # The Nim Compiler # (c) Copyright 2017 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## This module implements the module graph data structure. The module graph ## represents a complete Nim project. Single modules can either be kept in RAM ## or stored in a ROD file. The ROD file mechanism is not yet integrated here. ## ## The caching of modules is critical for 'nimsuggest' and is tricky to get ## right. If module E is being edited, we need autocompletion (and type ## checking) for E but we don't want to recompile depending ## modules right away for faster turnaround times. Instead we mark the module's ## dependencies as 'dirty'. Let D be a dependency of E. If D is dirty, we ## need to recompile it and all of its dependencies that are marked as 'dirty'. ## 'nimsuggest sug' actually is invoked for the file being edited so we know ## its content changed and there is no need to compute any checksums. ## Instead of a recursive algorithm, we use an iterative algorithm: ## ## - If a module gets recompiled, its dependencies need to be updated. ## - Its dependent module stays the same. ## import ast, intsets, tables, options type ModuleGraph* = ref object modules*: seq[PSym] ## indexed by int32 fileIdx packageSyms*: TStrTable deps*: IntSet # the dependency graph or potentially its transitive closure. suggestMode*: bool # whether we are in nimsuggest mode or not. invalidTransitiveClosure: bool inclToMod*: Table[int32, int32] # mapping of include file to the # first module that included it importStack*: seq[int32] # The current import stack. Used for detecting recursive # module dependencies. backend*: RootRef # minor hack so that a backend can extend this easily config*: ConfigRef doStopCompile*: proc(): bool {.closure.} usageSym*: PSym # for nimsuggest owners*: seq[PSym] methods*: seq[tuple[methods: TSymSeq, dispatcher: PSym]] {.this: g.} proc stopCompile*(g: ModuleGraph): bool {.inline.} = result = doStopCompile != nil and doStopCompile() proc newModuleGraph*(config: ConfigRef = nil): ModuleGraph = result = ModuleGraph() initStrTable(result.packageSyms) result.deps = initIntSet() result.modules = @[] result.importStack = @[] result.inclToMod = initTable[int32, int32]() if config.isNil: result.config = newConfigRef() else: result.config = config result.owners = @[] result.methods = @[] proc resetAllModules*(g: ModuleGraph) = initStrTable(packageSyms) deps = initIntSet() modules = @[] importStack = @[] inclToMod = initTable[int32, int32]() usageSym = nil owners = @[] methods = @[] proc getModule*(g: ModuleGraph; fileIdx: int32): PSym = if fileIdx >= 0 and fileIdx < modules.len: result = modules[fileIdx] proc dependsOn(a, b: int): int {.inline.} = (a shl 15) + b proc addDep*(g: ModuleGraph; m: PSym, dep: int32) = if suggestMode: deps.incl m.position.dependsOn(dep) # we compute the transitive closure later when quering the graph lazily. # this improve efficiency quite a lot: #invalidTransitiveClosure = true proc addIncludeDep*(g: ModuleGraph; module, includeFile: int32) = discard hasKeyOrPut(inclToMod, includeFile, module) proc parentModule*(g: ModuleGraph; fileIdx: int32): int32 = ## returns 'fileIdx' if the file belonging to this index is ## directly used as a module or else the module that first ## references this include file. if fileIdx >= 0 and fileIdx < modules.len and modules[fileIdx] != nil: result = fileIdx else: result = inclToMod.getOrDefault(fileIdx) proc transitiveClosure(g: var IntSet; n: int) = # warshall's algorithm for k in 0..