170 lines
6.1 KiB
Nim
170 lines
6.1 KiB
Nim
#
|
|
#
|
|
# 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:
|
|
return true
|
|
|
|
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)
|