123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- #
- #
- # The Nim Compiler
- # (c) Copyright 2021 Andreas Rumpf
- #
- # See the file "copying.txt", included in this
- # distribution, for details about the copyright.
- #
- ## Supports the "nim check --ic:on --defusages:FILE,LINE,COL"
- ## IDE-like features. It uses the set of .rod files to accomplish
- ## its task. The set must cover a complete Nim project.
- import sets
- from os import nil
- from std/private/miscdollars import toLocation
- when defined(nimPreviewSlimSystem):
- import std/assertions
- import ".." / [ast, modulegraphs, msgs, options]
- import packed_ast, bitabs, ic
- type
- NavContext = object
- g: ModuleGraph
- thisModule: int32
- trackPos: PackedLineInfo
- alreadyEmitted: HashSet[string]
- outputSep: char # for easier testing, use short filenames and spaces instead of tabs.
- proc isTracked(current, trackPos: PackedLineInfo, tokenLen: int): bool =
- if current.file == trackPos.file and current.line == trackPos.line:
- let col = trackPos.col
- if col >= current.col and col < current.col+tokenLen:
- result = true
- else:
- result = false
- else:
- result = false
- proc searchLocalSym(c: var NavContext; s: PackedSym; info: PackedLineInfo): bool =
- result = s.name != LitId(0) and
- isTracked(info, c.trackPos, c.g.packed[c.thisModule].fromDisk.strings[s.name].len)
- proc searchForeignSym(c: var NavContext; s: ItemId; info: PackedLineInfo): bool =
- let name = c.g.packed[s.module].fromDisk.syms[s.item].name
- result = name != LitId(0) and
- isTracked(info, c.trackPos, c.g.packed[s.module].fromDisk.strings[name].len)
- const
- EmptyItemId = ItemId(module: -1'i32, item: -1'i32)
- proc search(c: var NavContext; tree: PackedTree): ItemId =
- # We use the linear representation here directly:
- for i in 0..high(tree.nodes):
- case tree.nodes[i].kind
- of nkSym:
- let item = tree.nodes[i].operand
- if searchLocalSym(c, c.g.packed[c.thisModule].fromDisk.syms[item], tree.nodes[i].info):
- return ItemId(module: c.thisModule, item: item)
- of nkModuleRef:
- if tree.nodes[i].info.line == c.trackPos.line and tree.nodes[i].info.file == c.trackPos.file:
- let (n1, n2) = sons2(tree, NodePos i)
- assert n1.kind == nkInt32Lit
- assert n2.kind == nkInt32Lit
- let pId = PackedItemId(module: n1.litId, item: tree.nodes[n2.int].operand)
- let itemId = translateId(pId, c.g.packed, c.thisModule, c.g.config)
- if searchForeignSym(c, itemId, tree.nodes[i].info):
- return itemId
- else: discard
- return EmptyItemId
- proc isDecl(tree: PackedTree; n: NodePos): bool =
- # XXX This is not correct yet.
- const declarativeNodes = procDefs + {nkMacroDef, nkTemplateDef,
- nkLetSection, nkVarSection, nkUsingStmt, nkConstSection, nkTypeSection,
- nkIdentDefs, nkEnumTy, nkVarTuple}
- result = n.int >= 0 and tree[n.int].kind in declarativeNodes
- proc usage(c: var NavContext; info: PackedLineInfo; isDecl: bool) =
- var m = ""
- var file = c.g.packed[c.thisModule].fromDisk.strings[info.file]
- if c.outputSep == ' ':
- file = os.extractFilename file
- toLocation(m, file, info.line.int, info.col.int + ColOffset)
- if not c.alreadyEmitted.containsOrIncl(m):
- msgWriteln c.g.config, (if isDecl: "def" else: "usage") & c.outputSep & m
- proc list(c: var NavContext; tree: PackedTree; sym: ItemId) =
- for i in 0..high(tree.nodes):
- case tree.nodes[i].kind
- of nkSym:
- let item = tree.nodes[i].operand
- if sym.item == item and sym.module == c.thisModule:
- usage(c, tree.nodes[i].info, isDecl(tree, parent(NodePos i)))
- of nkModuleRef:
- let (n1, n2) = sons2(tree, NodePos i)
- assert n1.kind == nkInt32Lit
- assert n2.kind == nkInt32Lit
- let pId = PackedItemId(module: n1.litId, item: tree.nodes[n2.int].operand)
- let itemId = translateId(pId, c.g.packed, c.thisModule, c.g.config)
- if itemId.item == sym.item and sym.module == itemId.module:
- usage(c, tree.nodes[i].info, isDecl(tree, parent(NodePos i)))
- else: discard
- proc searchForIncludeFile(g: ModuleGraph; fullPath: string): int =
- for i in 0..high(g.packed):
- for k in 1..high(g.packed[i].fromDisk.includes):
- # we start from 1 because the first "include" file is
- # the module's filename.
- if os.cmpPaths(g.packed[i].fromDisk.strings[g.packed[i].fromDisk.includes[k][0]], fullPath) == 0:
- return i
- return -1
- proc nav(g: ModuleGraph) =
- # translate the track position to a packed position:
- let unpacked = g.config.m.trackPos
- var mid = unpacked.fileIndex.int
- let fullPath = toFullPath(g.config, unpacked.fileIndex)
- if g.packed[mid].status == undefined:
- # check if 'mid' is an include file of some other module:
- mid = searchForIncludeFile(g, fullPath)
- if mid < 0:
- localError(g.config, unpacked, "unknown file name: " & fullPath)
- return
- let fileId = g.packed[mid].fromDisk.strings.getKeyId(fullPath)
- if fileId == LitId(0):
- internalError(g.config, unpacked, "cannot find a valid file ID")
- return
- var c = NavContext(
- g: g,
- thisModule: int32 mid,
- trackPos: PackedLineInfo(line: unpacked.line, col: unpacked.col, file: fileId),
- outputSep: if isDefined(g.config, "nimIcNavigatorTests"): ' ' else: '\t'
- )
- var symId = search(c, g.packed[mid].fromDisk.topLevel)
- if symId == EmptyItemId:
- symId = search(c, g.packed[mid].fromDisk.bodies)
- if symId == EmptyItemId:
- localError(g.config, unpacked, "no symbol at this position")
- return
- for i in 0..high(g.packed):
- # case statement here to enforce exhaustive checks.
- case g.packed[i].status
- of undefined:
- discard "nothing to do"
- of loading:
- assert false, "cannot check integrity: Module still loading"
- of stored, storing, outdated, loaded:
- c.thisModule = int32 i
- list(c, g.packed[i].fromDisk.topLevel, symId)
- list(c, g.packed[i].fromDisk.bodies, symId)
- proc navDefinition*(g: ModuleGraph) = nav(g)
- proc navUsages*(g: ModuleGraph) = nav(g)
- proc navDefusages*(g: ModuleGraph) = nav(g)
- proc writeRodFiles*(g: ModuleGraph) =
- for i in 0..high(g.packed):
- case g.packed[i].status
- of undefined, loading, stored, loaded:
- discard "nothing to do"
- of storing, outdated:
- closeRodFile(g, g.packed[i].module)
|