Skip to content
50 changes: 37 additions & 13 deletions src/nimony/deps.nim
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,8 @@ proc defineNiflerCmd(b: var Builder; nifler: string) =
b.addStrLit "--portablePaths"
b.addStrLit "--deps"
b.addStrLit "parse"
b.addKeyw "input"
b.addKeyw "output"

proc defineHexerCmds(b: var Builder; hexer: string; bits: int) =
b.withTree "cmd":
Expand Down Expand Up @@ -540,7 +542,7 @@ proc generatePluginSemInstructions(c: DepContext; v: Node; b: var Builder) =
b.withTree "output":
b.addStrLit c.config.indexFile(v.files[0], v.plugin)

proc generateFrontendBuildFile(c: DepContext; commandLineArgs: string): string =
proc generateFrontendBuildFile(c: DepContext; commandLineArgs: string; cmd: Command): string =
result = c.config.nifcachePath / c.rootNode.files[0].modname & ".build.nif"
var b = nifbuilder.open(result)
defer: b.close()
Expand Down Expand Up @@ -568,6 +570,22 @@ proc generateFrontendBuildFile(c: DepContext; commandLineArgs: string): string =
b.withTree "output":
b.addIntLit 1 # index file output

if cmd == DoCheck:
b.withTree "cmd":
b.addSymbolDef "idetools"
b.addStrLit c.nimsem
if c.config.baseDir.len > 0:
b.addStrLit "--base:" & quoteShell(c.config.baseDir)
if commandLineArgs.len > 0:
for arg in commandLineArgs.split(' '):
if arg.len > 0:
b.addStrLit arg
b.addStrLit "idetools"
b.addKeyw "args"
b.withTree "input":
b.addIntLit 0
b.addIntLit -1 # all inputs

for plugin in c.foundPlugins:
b.withTree "cmd":
b.addSymbolDef plugin
Expand Down Expand Up @@ -606,6 +624,14 @@ proc generateFrontendBuildFile(c: DepContext; commandLineArgs: string): string =
b.withTree "output":
b.addStrLit f

if cmd == DoCheck and c.config.toTrack.mode != TrackNone:
b.withTree "do":
b.addIdent "idetools"
for v in c.nodes:
let s = c.config.semmedFile(v.files[0], v.plugin)
b.withTree "input":
b.addStrLit s

proc generateCachedConfigFile(c: DepContext; passC, passL: string) =
let path = c.config.cachedConfigFile()
let configStr = c.config.getOptionsAsOneString() & " " & c.rootNode.files[0].nimFile &
Expand Down Expand Up @@ -729,7 +755,6 @@ proc buildGraphForEval*(config: NifConfig; mainNifFile: string; dependencyNifFil
let depName = depNifFile.splitModulePath.name
allNifFiles.add(depName)
let depHexedFile = config.nifcachePath / depName & ".s.nif"
let depCFile = config.nifcachePath / depName & ".x.nif"

# Process dependency .nif file with hexer first
b.withTree "do":
Expand All @@ -743,8 +768,6 @@ proc buildGraphForEval*(config: NifConfig; mainNifFile: string; dependencyNifFil
let mainName = mainNifFile.splitModulePath.name
allNifFiles.add(mainName)
let mainHexedFile = config.nifcachePath / mainName & ".x.nif"
let mainCFile = config.nifcachePath / mainName & ".c"
let mainObjFile = config.nifcachePath / mainName & ".o"

# Process main .nif file with hexer first
b.withTree "do":
Expand Down Expand Up @@ -818,7 +841,7 @@ proc buildGraph*(config: sink NifConfig; project: string; forceRebuild, silentMa

var c = initDepContext(config, project, nifler, false, forceRebuild, moduleFlags, cmd)
generateCachedConfigFile c, passC, passL
let buildFilename = generateFrontendBuildFile(c, commandLineArgs)
let buildFilename = generateFrontendBuildFile(c, commandLineArgs, cmd)
#echo "run with: nifmake run ", buildFilename
when defined(windows):
putEnv("CC", "gcc")
Expand All @@ -829,11 +852,12 @@ proc buildGraph*(config: sink NifConfig; project: string; forceRebuild, silentMa
" -j run "
exec nifmakeCommand & quoteShell(buildFilename)

# Parse `.s.deps.nif`.
# It is generated by nimsem and doesn't contains modules imported under `when false:`.
# https://github.com/nim-lang/nimony/issues/985
c = initDepContext(config, project, nifler, true, forceRebuild, moduleFlags, cmd)
let buildFinalFilename = generateFinalBuildFile(c, commandLineArgsNifc, passC, passL)
exec nifmakeCommand & quoteShell(buildFinalFilename)
if cmd == DoRun:
exec c.config.exeFile(c.rootNode.files[0]) & executableArgs
if cmd != DoCheck:
# Parse `.s.deps.nif`.
# It is generated by nimsem and doesn't contains modules imported under `when false:`.
# https://github.com/nim-lang/nimony/issues/985
c = initDepContext(config, project, nifler, true, forceRebuild, moduleFlags, cmd)
let buildFinalFilename = generateFinalBuildFile(c, commandLineArgsNifc, passC, passL)
exec nifmakeCommand & quoteShell(buildFinalFilename)
if cmd == DoRun:
exec c.config.exeFile(c.rootNode.files[0]) & executableArgs
150 changes: 150 additions & 0 deletions src/nimony/idetools.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Nimony
# (c) Copyright 2025 Andreas Rumpf
#
# See the file "license.txt", included in this
# distribution, for details about the copyright.

import std / [sets, syncio]
include ".." / lib / nifprelude
import semos, symparser, nifindexes, tooldirs, nifconfig, nimony_model

proc lineInfoMatch*(info, toTrack: PackedLineInfo; tokenLen: int): bool =
let i = unpack(pool.man, info)
let t = unpack(pool.man, toTrack)
if i.file.isValid and t.file.isValid:
if i.file != t.file: return false
if i.line != t.line: return false
# Check if target column falls within the token's range
if t.col < i.col: return false
if t.col >= i.col + tokenLen: return false
return true

proc foundSymbol(tok: PackedToken; mode: TrackMode) =
# format that is compatible with nimsuggest's in the hope it helps:
let info = unpack(pool.man, tok.info)
if info.file.isValid:
if (tok.kind == Symbol and mode == TrackUsages) or (tok.kind == SymbolDef and mode == TrackDef):
var r = (if tok.kind == Symbol: "use\t" else: "def\t")
r.add "\t" # unknown symbol kind
r.add pool.syms[tok.symId]
r.add "\t"
# unknown signature:
r.add "\t"
# filename:
r.add "\t"
r.add pool.files[info.file]
r.add "\t"
r.addInt info.line
r.add "\t"
r.addInt info.col
stdout.writeLine(r)

proc findLocal(file: string; sym: SymId; toTrack: PackedLineInfo; mode: TrackMode) =
var buf = parseFromFile(file)
var n = beginRead(buf)
var scopes: seq[(Cursor, int)] = @[(n, 0)]

var name = pool.syms[sym]
extractBasename name

let tokenLen = name.len
var foundScope = false
var nested = 0
while true:
case n.stmtKind
of ScopeS:
inc nested
scopes.add (n, nested)
of ProcS, FuncS, MethodS, IteratorS, TemplateS, MacroS, ConverterS:
inc nested
scopes.add (n, nested)
else:
case n.kind
of Symbol, SymbolDef:
if n.symId == sym and lineInfoMatch(n.info, toTrack, tokenLen):
foundScope = true
break
of ParLe: inc nested
of ParRi:
dec nested
if nested == scopes[^1][1]:
discard scopes.pop()

if nested == 0: break
else:
discard
inc n

n = scopes[^1][0]
inc n
nested = 1 # in owning structure
while true:
case n.kind
of ParLe: inc nested
of ParRi:
dec nested
if nested == 0: break
of Symbol, SymbolDef:
if n.symId == sym:
foundSymbol(n.load, mode)
else:
discard
inc n

proc usages*(files: openArray[string]; config: NifConfig) =
# This is comparable to a linking step: We iterate over all `.idetools.nif` files to see
# what symbol is meant by the `file,line,col` tracking information.
let requestedInfo = lineinfos.pack(pool.man, pool.files.getOrIncl(config.toTrack.filename),
config.toTrack.line, config.toTrack.col)
# first pass: search for the symbol at `file,line,col`:
var isLocalSym = false
var symId = SymId 0
var symFile = ""
for file in files:
var s = nifstreams.open(file)
try:
discard processDirectives(s.r)
while true:
let tok = next(s)
case tok.kind
of Symbol, SymbolDef:
# performance critical! May run over every symbol in the project!
var name = addr pool.syms[tok.symId]
var tokenLen = 0
var dots = 0
for i in 0 ..< name[].len:
if name[][i] == '.':
inc dots
if dots == 0: inc tokenLen
if lineInfoMatch(tok.info, requestedInfo, tokenLen):
isLocalSym = dots < 2
symId = tok.symId
symFile = file
break
of EofToken: break
of UnknownToken, DotToken, Ident, StringLit, CharLit, IntLit, UIntLit, FloatLit, ParLe, ParRi:
discard "proceed"
finally:
close(s)

if symId == SymId 0:
quit "symbol not found"
elif isLocalSym:
findLocal(symFile, symId, requestedInfo, config.toTrack.mode)
else:
for file in files:
var s = nifstreams.open(file)
try:
discard processDirectives(s.r)
while true:
let tok = next(s)
case tok.kind
of Symbol:
if tok.symId == symId: foundSymbol(tok, config.toTrack.mode)
of SymbolDef:
if tok.symId == symId: foundSymbol(tok, config.toTrack.mode)
of EofToken: break
of UnknownToken, DotToken, Ident, StringLit, CharLit, IntLit, UIntLit, FloatLit, ParLe, ParRi:
discard "proceed"
finally:
close(s)
8 changes: 8 additions & 0 deletions src/nimony/nifconfig.nim
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@

## Read the configuration from the `.cfg.nif` file.

import std / [os, sets, strutils]

Check warning on line 9 in src/nimony/nifconfig.nim

View workflow job for this annotation

GitHub Actions / macos-arm64-nim-devel (master)

imported and not used: 'os' [UnusedImport]

Check warning on line 9 in src/nimony/nifconfig.nim

View workflow job for this annotation

GitHub Actions / windows-amd64-nim-devel (master)

imported and not used: 'os' [UnusedImport]

Check warning on line 9 in src/nimony/nifconfig.nim

View workflow job for this annotation

GitHub Actions / linux-amd64-nim-devel (master)

imported and not used: 'os' [UnusedImport]

import platform

include nifprelude

type
TrackMode* = enum
TrackNone, TrackUsages, TrackDef
TrackPosition* = object
mode*: TrackMode
line*, col*: int32
filename*: string

NifConfig* = object
defines*: HashSet[string]
paths*, nimblePaths*: seq[string]
Expand All @@ -22,6 +29,7 @@
compat*: bool
targetCPU*: TSystemCPU
targetOS*: TSystemOS
toTrack*: TrackPosition
cc*: string
linker*: string
ccKey*: string
Expand Down
26 changes: 23 additions & 3 deletions src/nimony/nimony.nim
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ Usage:
nimony [options] [command]
Command:
c project.nim compile the full project
m file.nim [project.nim] compile a single Nim module to hexer
check project.nim check the full project for errors; can be
combined with `--usages`, `--def` for
editor integration
m file.nim [project.nim] compile a single Nim module to Hexer

Options:
-d, --define:SYMBOL define a symbol for conditional compilation
Expand All @@ -47,6 +50,8 @@ Options:
--silentMake suppresses make output
--nimcache:PATH set the path used for generated files
--boundchecks:on|off turn bound checks on or off
--usages:file,line,col list usages of the symbol at the given position
--def:file,line,col list definition of the symbol at the given position
--cc:C_COMPILER set the C compiler; can be a path to the compiler's
executable or a name
--linker:LINKER set the linker
Expand All @@ -70,14 +75,16 @@ proc processSingleModule(nimFile: string; config: sink NifConfig; moduleFlags: s

type
Command = enum
None, SingleModule, FullProject
None, SingleModule, FullProject, CheckProject

proc dispatchBasicCommand(key: string): Command =
case key.normalize:
of "m":
SingleModule
of "c":
FullProject
of "check":
CheckProject
else:
quit "command expected"

Expand Down Expand Up @@ -212,6 +219,14 @@ proc handleCmdLine(c: var CmdOptions; cmdLineArgs: seq[string]; mode: CmdMode) =
c.config.ccKey = extractCCKey(val)
of "linker":
c.config.linker = val
of "usages":
# set for deps.nim:
c.config.toTrack.mode = TrackUsages
forwardArg = true
of "def":
# set for deps.nim:
c.config.toTrack.mode = TrackDef
forwardArg = true
else: writeHelp()
if forwardArg:
c.commandLineArgs.add " --" & key
Expand All @@ -229,7 +244,7 @@ proc compileProgram(c: var CmdOptions) =
c.config.linker = c.config.cc
if c.args.len == 0:
quit "too few command line arguments"
elif c.args.len > 2 - int(c.cmd == FullProject):
elif c.args.len > 2 - int(c.cmd in {FullProject, CheckProject}):
quit "too many command line arguments"

if c.checkModes != DefaultSettings:
Expand All @@ -251,6 +266,11 @@ proc compileProgram(c: var CmdOptions) =
buildGraph c.config, c.args[0], c.forceRebuild, c.silentMake,
c.commandLineArgs, c.commandLineArgsNifc, c.moduleFlags, (if c.doRun: DoRun else: DoCompile),
c.passC, c.passL, c.executableArgs
of CheckProject:
createDir(c.config.nifcachePath)
# check full project modules
buildGraph c.config, c.args[0], c.forceRebuild, c.silentMake,
c.commandLineArgs, c.commandLineArgsNifc, c.moduleFlags, DoCheck, c.passC, c.passL, c.executableArgs

when isMainModule:
var c = createCmdOptions(determineBaseDir())
Expand Down
Loading
Loading