123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- #
- #
- # 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..<n:
- for i in 0..<n:
- for j in 0..<n:
- if i != j and not g.contains(i.dependsOn(j)):
- if g.contains(i.dependsOn(k)) and g.contains(k.dependsOn(j)):
- g.incl i.dependsOn(j)
- proc markDirty*(g: ModuleGraph; fileIdx: int32) =
- let m = getModule fileIdx
- if m != nil: incl m.flags, sfDirty
- proc markClientsDirty*(g: ModuleGraph; fileIdx: int32) =
- # we need to mark its dependent modules D as dirty right away because after
- # nimsuggest is done with this module, the module's dirty flag will be
- # cleared but D still needs to be remembered as 'dirty'.
- if invalidTransitiveClosure:
- invalidTransitiveClosure = false
- transitiveClosure(deps, modules.len)
- # every module that *depends* on this file is also dirty:
- for i in 0i32..<modules.len.int32:
- let m = modules[i]
- if m != nil and deps.contains(i.dependsOn(fileIdx)):
- incl m.flags, sfDirty
- proc isDirty*(g: ModuleGraph; m: PSym): bool =
- result = suggestMode and sfDirty in m.flags
|