mirror of
https://github.com/nim-lang/Nim.git
synced 2025-12-28 17:04:41 +00:00
734 lines
27 KiB
Nim
734 lines
27 KiB
Nim
#
|
|
#
|
|
# The Nim Compiler
|
|
# (c) Copyright 2013 Andreas Rumpf
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
import
|
|
std/[strutils, os, tables, terminal, macros, times],
|
|
std/private/miscdollars,
|
|
options, lineinfos, pathutils
|
|
|
|
import ropes except `%`
|
|
|
|
when defined(nimPreviewSlimSystem):
|
|
import std/[syncio, assertions]
|
|
|
|
|
|
type InstantiationInfo* = typeof(instantiationInfo())
|
|
template instLoc*(): InstantiationInfo = instantiationInfo(-2, fullPaths = true)
|
|
|
|
template toStdOrrKind(stdOrr): untyped =
|
|
if stdOrr == stdout: stdOrrStdout else: stdOrrStderr
|
|
|
|
proc toLowerAscii(a: var string) {.inline.} =
|
|
for c in mitems(a):
|
|
if isUpperAscii(c): c = char(uint8(c) xor 0b0010_0000'u8)
|
|
|
|
proc flushDot*(conf: ConfigRef) =
|
|
## safe to call multiple times
|
|
# xxx one edge case not yet handled is when `printf` is called at CT with `compiletimeFFI`.
|
|
let stdOrr = if optStdout in conf.globalOptions: stdout else: stderr
|
|
let stdOrrKind = toStdOrrKind(stdOrr)
|
|
if stdOrrKind in conf.lastMsgWasDot:
|
|
conf.lastMsgWasDot.excl stdOrrKind
|
|
write(stdOrr, "\n")
|
|
|
|
proc toCChar*(c: char; result: var string) {.inline.} =
|
|
case c
|
|
of '\0'..'\x1F', '\x7F'..'\xFF':
|
|
result.add '\\'
|
|
result.add toOctal(c)
|
|
of '\'', '\"', '\\', '?':
|
|
result.add '\\'
|
|
result.add c
|
|
else:
|
|
result.add c
|
|
|
|
proc makeCString*(s: string): Rope =
|
|
result = newStringOfCap(int(s.len.toFloat * 1.1) + 1)
|
|
result.add("\"")
|
|
for i in 0..<s.len:
|
|
# line wrapping of string litterals in cgen'd code was a bad idea, e.g. causes: bug #16265
|
|
# It also makes reading c sources or grepping harder, for zero benefit.
|
|
# const MaxLineLength = 64
|
|
# if (i + 1) mod MaxLineLength == 0:
|
|
# res.add("\"\L\"")
|
|
toCChar(s[i], result)
|
|
result.add('\"')
|
|
|
|
proc newFileInfo(fullPath: AbsoluteFile, projPath: RelativeFile): TFileInfo =
|
|
result.fullPath = fullPath
|
|
#shallow(result.fullPath)
|
|
result.projPath = projPath
|
|
#shallow(result.projPath)
|
|
result.shortName = fullPath.extractFilename
|
|
result.quotedName = result.shortName.makeCString
|
|
result.quotedFullName = fullPath.string.makeCString
|
|
result.lines = @[]
|
|
when defined(nimpretty):
|
|
if not result.fullPath.isEmpty:
|
|
try:
|
|
result.fullContent = readFile(result.fullPath.string)
|
|
except IOError:
|
|
#rawMessage(errCannotOpenFile, result.fullPath)
|
|
# XXX fixme
|
|
result.fullContent = ""
|
|
|
|
when defined(nimpretty):
|
|
proc fileSection*(conf: ConfigRef; fid: FileIndex; a, b: int): string =
|
|
substr(conf.m.fileInfos[fid.int].fullContent, a, b)
|
|
|
|
proc canonicalCase(path: var string) {.inline.} =
|
|
## the idea is to only use this for checking whether a path is already in
|
|
## the table but otherwise keep the original case
|
|
when FileSystemCaseSensitive: discard
|
|
else: toLowerAscii(path)
|
|
|
|
proc fileInfoKnown*(conf: ConfigRef; filename: AbsoluteFile): bool =
|
|
var
|
|
canon: AbsoluteFile
|
|
try:
|
|
canon = canonicalizePath(conf, filename)
|
|
except OSError:
|
|
canon = filename
|
|
canon.string.canonicalCase
|
|
result = conf.m.filenameToIndexTbl.hasKey(canon.string)
|
|
|
|
proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile; isKnownFile: var bool): FileIndex =
|
|
var
|
|
canon: AbsoluteFile
|
|
pseudoPath = false
|
|
|
|
try:
|
|
canon = canonicalizePath(conf, filename)
|
|
except OSError:
|
|
canon = filename
|
|
# The compiler uses "filenames" such as `command line` or `stdin`
|
|
# This flag indicates that we are working with such a path here
|
|
pseudoPath = true
|
|
|
|
var canon2 = canon.string
|
|
canon2.canonicalCase
|
|
|
|
if conf.m.filenameToIndexTbl.hasKey(canon2):
|
|
isKnownFile = true
|
|
result = conf.m.filenameToIndexTbl[canon2]
|
|
else:
|
|
isKnownFile = false
|
|
result = conf.m.fileInfos.len.FileIndex
|
|
conf.m.fileInfos.add(newFileInfo(canon, if pseudoPath: RelativeFile filename
|
|
else: relativeTo(canon, conf.projectPath)))
|
|
conf.m.filenameToIndexTbl[canon2] = result
|
|
|
|
proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile): FileIndex =
|
|
var dummy: bool
|
|
result = fileInfoIdx(conf, filename, dummy)
|
|
|
|
proc fileInfoIdx*(conf: ConfigRef; filename: RelativeFile; isKnownFile: var bool): FileIndex =
|
|
fileInfoIdx(conf, AbsoluteFile expandFilename(filename.string), isKnownFile)
|
|
|
|
proc fileInfoIdx*(conf: ConfigRef; filename: RelativeFile): FileIndex =
|
|
var dummy: bool
|
|
fileInfoIdx(conf, AbsoluteFile expandFilename(filename.string), dummy)
|
|
|
|
proc newLineInfo*(fileInfoIdx: FileIndex, line, col: int): TLineInfo =
|
|
result.fileIndex = fileInfoIdx
|
|
if line < int high(uint16):
|
|
result.line = uint16(line)
|
|
else:
|
|
result.line = high(uint16)
|
|
if col < int high(int16):
|
|
result.col = int16(col)
|
|
else:
|
|
result.col = -1
|
|
|
|
proc newLineInfo*(conf: ConfigRef; filename: AbsoluteFile, line, col: int): TLineInfo {.inline.} =
|
|
result = newLineInfo(fileInfoIdx(conf, filename), line, col)
|
|
|
|
const gCmdLineInfo* = newLineInfo(commandLineIdx, 1, 1)
|
|
|
|
proc concat(strings: openArray[string]): string =
|
|
var totalLen = 0
|
|
for s in strings: totalLen += s.len
|
|
result = newStringOfCap totalLen
|
|
for s in strings: result.add s
|
|
|
|
proc suggestWriteln*(conf: ConfigRef; s: string) =
|
|
if eStdOut in conf.m.errorOutputs:
|
|
if isNil(conf.writelnHook):
|
|
writeLine(stdout, s)
|
|
flushFile(stdout)
|
|
else:
|
|
conf.writelnHook(s)
|
|
|
|
proc msgQuit*(x: int8) = quit x
|
|
proc msgQuit*(x: string) = quit x
|
|
|
|
proc suggestQuit*() =
|
|
raise newException(ESuggestDone, "suggest done")
|
|
|
|
# this format is understood by many text editors: it is the same that
|
|
# Borland and Freepascal use
|
|
const
|
|
KindFormat = " [$1]"
|
|
KindColor = fgCyan
|
|
ErrorTitle = "Error: "
|
|
ErrorColor = fgRed
|
|
WarningTitle = "Warning: "
|
|
WarningColor = fgYellow
|
|
HintTitle = "Hint: "
|
|
HintColor = fgGreen
|
|
# NOTE: currently line info line numbers start with 1,
|
|
# but column numbers start with 0, however most editors expect
|
|
# first column to be 1, so we need to +1 here
|
|
ColOffset* = 1
|
|
commandLineDesc* = "command line"
|
|
|
|
proc getInfoContextLen*(conf: ConfigRef): int = return conf.m.msgContext.len
|
|
proc setInfoContextLen*(conf: ConfigRef; L: int) = setLen(conf.m.msgContext, L)
|
|
|
|
proc pushInfoContext*(conf: ConfigRef; info: TLineInfo; detail: string = "") =
|
|
conf.m.msgContext.add((info, detail))
|
|
|
|
proc popInfoContext*(conf: ConfigRef) =
|
|
setLen(conf.m.msgContext, conf.m.msgContext.len - 1)
|
|
|
|
proc getInfoContext*(conf: ConfigRef; index: int): TLineInfo =
|
|
let i = if index < 0: conf.m.msgContext.len + index else: index
|
|
if i >=% conf.m.msgContext.len: result = unknownLineInfo
|
|
else: result = conf.m.msgContext[i].info
|
|
|
|
template toFilename*(conf: ConfigRef; fileIdx: FileIndex): string =
|
|
if fileIdx.int32 < 0 or conf == nil:
|
|
(if fileIdx == commandLineIdx: commandLineDesc else: "???")
|
|
else:
|
|
conf.m.fileInfos[fileIdx.int32].shortName
|
|
|
|
proc toProjPath*(conf: ConfigRef; fileIdx: FileIndex): string =
|
|
if fileIdx.int32 < 0 or conf == nil:
|
|
(if fileIdx == commandLineIdx: commandLineDesc else: "???")
|
|
else: conf.m.fileInfos[fileIdx.int32].projPath.string
|
|
|
|
proc toFullPath*(conf: ConfigRef; fileIdx: FileIndex): string =
|
|
if fileIdx.int32 < 0 or conf == nil:
|
|
result = (if fileIdx == commandLineIdx: commandLineDesc else: "???")
|
|
else:
|
|
result = conf.m.fileInfos[fileIdx.int32].fullPath.string
|
|
|
|
proc setDirtyFile*(conf: ConfigRef; fileIdx: FileIndex; filename: AbsoluteFile) =
|
|
assert fileIdx.int32 >= 0
|
|
conf.m.fileInfos[fileIdx.int32].dirtyFile = filename
|
|
setLen conf.m.fileInfos[fileIdx.int32].lines, 0
|
|
|
|
proc setHash*(conf: ConfigRef; fileIdx: FileIndex; hash: string) =
|
|
assert fileIdx.int32 >= 0
|
|
when defined(gcArc) or defined(gcOrc):
|
|
conf.m.fileInfos[fileIdx.int32].hash = hash
|
|
else:
|
|
shallowCopy(conf.m.fileInfos[fileIdx.int32].hash, hash)
|
|
|
|
|
|
proc getHash*(conf: ConfigRef; fileIdx: FileIndex): string =
|
|
assert fileIdx.int32 >= 0
|
|
when defined(gcArc) or defined(gcOrc):
|
|
result = conf.m.fileInfos[fileIdx.int32].hash
|
|
else:
|
|
shallowCopy(result, conf.m.fileInfos[fileIdx.int32].hash)
|
|
|
|
proc toFullPathConsiderDirty*(conf: ConfigRef; fileIdx: FileIndex): AbsoluteFile =
|
|
if fileIdx.int32 < 0:
|
|
result = AbsoluteFile(if fileIdx == commandLineIdx: commandLineDesc else: "???")
|
|
elif not conf.m.fileInfos[fileIdx.int32].dirtyFile.isEmpty:
|
|
result = conf.m.fileInfos[fileIdx.int32].dirtyFile
|
|
else:
|
|
result = conf.m.fileInfos[fileIdx.int32].fullPath
|
|
|
|
template toFilename*(conf: ConfigRef; info: TLineInfo): string =
|
|
toFilename(conf, info.fileIndex)
|
|
|
|
template toProjPath*(conf: ConfigRef; info: TLineInfo): string =
|
|
toProjPath(conf, info.fileIndex)
|
|
|
|
template toFullPath*(conf: ConfigRef; info: TLineInfo): string =
|
|
toFullPath(conf, info.fileIndex)
|
|
|
|
template toFullPathConsiderDirty*(conf: ConfigRef; info: TLineInfo): string =
|
|
string toFullPathConsiderDirty(conf, info.fileIndex)
|
|
|
|
proc toFilenameOption*(conf: ConfigRef, fileIdx: FileIndex, opt: FilenameOption): string =
|
|
case opt
|
|
of foAbs: result = toFullPath(conf, fileIdx)
|
|
of foRelProject: result = toProjPath(conf, fileIdx)
|
|
of foCanonical:
|
|
let absPath = toFullPath(conf, fileIdx)
|
|
result = canonicalImportAux(conf, absPath.AbsoluteFile)
|
|
of foName: result = toProjPath(conf, fileIdx).lastPathPart
|
|
of foLegacyRelProj:
|
|
let
|
|
absPath = toFullPath(conf, fileIdx)
|
|
relPath = toProjPath(conf, fileIdx)
|
|
result = if (relPath.len > absPath.len) or (relPath.count("..") > 2):
|
|
absPath
|
|
else:
|
|
relPath
|
|
of foStacktrace:
|
|
if optExcessiveStackTrace in conf.globalOptions:
|
|
result = toFilenameOption(conf, fileIdx, foAbs)
|
|
else:
|
|
result = toFilenameOption(conf, fileIdx, foName)
|
|
|
|
proc toMsgFilename*(conf: ConfigRef; fileIdx: FileIndex): string =
|
|
toFilenameOption(conf, fileIdx, conf.filenameOption)
|
|
|
|
template toMsgFilename*(conf: ConfigRef; info: TLineInfo): string =
|
|
toMsgFilename(conf, info.fileIndex)
|
|
|
|
proc toLinenumber*(info: TLineInfo): int {.inline.} =
|
|
result = int info.line
|
|
|
|
proc toColumn*(info: TLineInfo): int {.inline.} =
|
|
result = info.col
|
|
|
|
proc toFileLineCol(info: InstantiationInfo): string {.inline.} =
|
|
result.toLocation(info.filename, info.line, info.column + ColOffset)
|
|
|
|
proc toFileLineCol*(conf: ConfigRef; info: TLineInfo): string {.inline.} =
|
|
result.toLocation(toMsgFilename(conf, info), info.line.int, info.col.int + ColOffset)
|
|
|
|
proc `$`*(conf: ConfigRef; info: TLineInfo): string = toFileLineCol(conf, info)
|
|
|
|
proc `$`*(info: TLineInfo): string {.error.} = discard
|
|
|
|
proc `??`* (conf: ConfigRef; info: TLineInfo, filename: string): bool =
|
|
# only for debugging purposes
|
|
result = filename in toFilename(conf, info)
|
|
|
|
type
|
|
MsgFlag* = enum ## flags altering msgWriteln behavior
|
|
msgStdout, ## force writing to stdout, even stderr is default
|
|
msgSkipHook ## skip message hook even if it is present
|
|
msgNoUnitSep ## the message is a complete "paragraph".
|
|
MsgFlags* = set[MsgFlag]
|
|
|
|
proc msgWriteln*(conf: ConfigRef; s: string, flags: MsgFlags = {}) =
|
|
## Writes given message string to stderr by default.
|
|
## If ``--stdout`` option is given, writes to stdout instead. If message hook
|
|
## is present, then it is used to output message rather than stderr/stdout.
|
|
## This behavior can be altered by given optional flags.
|
|
|
|
## This is used for 'nim dump' etc. where we don't have nimsuggest
|
|
## support.
|
|
#if conf.cmd == cmdIdeTools and optCDebug notin gGlobalOptions: return
|
|
let sep = if msgNoUnitSep notin flags: conf.unitSep else: ""
|
|
if not isNil(conf.writelnHook) and msgSkipHook notin flags:
|
|
conf.writelnHook(s & sep)
|
|
elif optStdout in conf.globalOptions or msgStdout in flags:
|
|
if eStdOut in conf.m.errorOutputs:
|
|
flushDot(conf)
|
|
write stdout, s
|
|
writeLine(stdout, sep)
|
|
flushFile(stdout)
|
|
else:
|
|
if eStdErr in conf.m.errorOutputs:
|
|
flushDot(conf)
|
|
write stderr, s
|
|
writeLine(stderr, sep)
|
|
# On Windows stderr is fully-buffered when piped, regardless of C std.
|
|
when defined(windows):
|
|
flushFile(stderr)
|
|
|
|
macro callIgnoringStyle(theProc: typed, first: typed,
|
|
args: varargs[typed]): untyped =
|
|
let typForegroundColor = bindSym"ForegroundColor".getType
|
|
let typBackgroundColor = bindSym"BackgroundColor".getType
|
|
let typStyle = bindSym"Style".getType
|
|
let typTerminalCmd = bindSym"TerminalCmd".getType
|
|
result = newCall(theProc)
|
|
if first.kind != nnkNilLit: result.add(first)
|
|
for arg in children(args[0][1]):
|
|
if arg.kind == nnkNilLit: continue
|
|
let typ = arg.getType
|
|
if typ.kind != nnkEnumTy or
|
|
typ != typForegroundColor and
|
|
typ != typBackgroundColor and
|
|
typ != typStyle and
|
|
typ != typTerminalCmd:
|
|
result.add(arg)
|
|
|
|
macro callStyledWriteLineStderr(args: varargs[typed]): untyped =
|
|
result = newCall(bindSym"styledWriteLine")
|
|
result.add(bindSym"stderr")
|
|
for arg in children(args[0][1]):
|
|
result.add(arg)
|
|
when false:
|
|
# not needed because styledWriteLine already ends with resetAttributes
|
|
result = newStmtList(result, newCall(bindSym"resetAttributes", bindSym"stderr"))
|
|
|
|
template callWritelnHook(args: varargs[string, `$`]) =
|
|
conf.writelnHook concat(args)
|
|
|
|
proc msgWrite(conf: ConfigRef; s: string) =
|
|
if conf.m.errorOutputs != {}:
|
|
let stdOrr =
|
|
if optStdout in conf.globalOptions:
|
|
stdout
|
|
else:
|
|
stderr
|
|
write(stdOrr, s)
|
|
flushFile(stdOrr)
|
|
conf.lastMsgWasDot.incl stdOrr.toStdOrrKind() # subsequent writes need `flushDot`
|
|
|
|
template styledMsgWriteln(args: varargs[typed]) =
|
|
if not isNil(conf.writelnHook):
|
|
callIgnoringStyle(callWritelnHook, nil, args)
|
|
elif optStdout in conf.globalOptions:
|
|
if eStdOut in conf.m.errorOutputs:
|
|
flushDot(conf)
|
|
callIgnoringStyle(writeLine, stdout, args)
|
|
flushFile(stdout)
|
|
elif eStdErr in conf.m.errorOutputs:
|
|
flushDot(conf)
|
|
if optUseColors in conf.globalOptions:
|
|
callStyledWriteLineStderr(args)
|
|
else:
|
|
callIgnoringStyle(writeLine, stderr, args)
|
|
# On Windows stderr is fully-buffered when piped, regardless of C std.
|
|
when defined(windows):
|
|
flushFile(stderr)
|
|
|
|
proc msgKindToString*(kind: TMsgKind): string = MsgKindToStr[kind]
|
|
# later versions may provide translated error messages
|
|
|
|
proc getMessageStr(msg: TMsgKind, arg: string): string = msgKindToString(msg) % [arg]
|
|
|
|
type TErrorHandling* = enum doNothing, doAbort, doRaise
|
|
|
|
proc log*(s: string) =
|
|
var f: File
|
|
if open(f, getHomeDir() / "nimsuggest.log", fmAppend):
|
|
f.writeLine(s)
|
|
close(f)
|
|
|
|
proc quit(conf: ConfigRef; msg: TMsgKind) {.gcsafe.} =
|
|
if conf.isDefined("nimDebug"): quitOrRaise(conf, $msg)
|
|
elif defined(debug) or msg == errInternal or conf.hasHint(hintStackTrace):
|
|
{.gcsafe.}:
|
|
if stackTraceAvailable() and isNil(conf.writelnHook):
|
|
writeStackTrace()
|
|
else:
|
|
styledMsgWriteln(fgRed, """
|
|
No stack traceback available
|
|
To create a stacktrace, rerun compilation with './koch temp $1 <file>', see $2 for details""" %
|
|
[conf.command, "intern.html#debugging-the-compiler".createDocLink], conf.unitSep)
|
|
quit 1
|
|
|
|
proc handleError(conf: ConfigRef; msg: TMsgKind, eh: TErrorHandling, s: string, ignoreMsg: bool) =
|
|
if msg in fatalMsgs:
|
|
if conf.cmd == cmdIdeTools: log(s)
|
|
quit(conf, msg)
|
|
if msg >= errMin and msg <= errMax or
|
|
(msg in warnMin..hintMax and msg in conf.warningAsErrors and not ignoreMsg):
|
|
inc(conf.errorCounter)
|
|
conf.exitcode = 1'i8
|
|
if conf.errorCounter >= conf.errorMax:
|
|
# only really quit when we're not in the new 'nim check --def' mode:
|
|
if conf.ideCmd == ideNone:
|
|
quit(conf, msg)
|
|
elif eh == doAbort and conf.cmd != cmdIdeTools:
|
|
quit(conf, msg)
|
|
elif eh == doRaise:
|
|
raiseRecoverableError(s)
|
|
|
|
proc `==`*(a, b: TLineInfo): bool =
|
|
result = a.line == b.line and a.fileIndex == b.fileIndex
|
|
|
|
proc exactEquals*(a, b: TLineInfo): bool =
|
|
result = a.fileIndex == b.fileIndex and a.line == b.line and a.col == b.col
|
|
|
|
proc writeContext(conf: ConfigRef; lastinfo: TLineInfo) =
|
|
const instantiationFrom = "template/generic instantiation from here"
|
|
const instantiationOfFrom = "template/generic instantiation of `$1` from here"
|
|
var info = lastinfo
|
|
for i in 0..<conf.m.msgContext.len:
|
|
let context = conf.m.msgContext[i]
|
|
if context.info != lastinfo and context.info != info:
|
|
if conf.structuredErrorHook != nil:
|
|
conf.structuredErrorHook(conf, context.info, instantiationFrom,
|
|
Severity.Hint)
|
|
else:
|
|
let message =
|
|
if context.detail == "":
|
|
instantiationFrom
|
|
else:
|
|
instantiationOfFrom.format(context.detail)
|
|
styledMsgWriteln(styleBright, conf.toFileLineCol(context.info), " ", resetStyle, message)
|
|
info = context.info
|
|
|
|
proc ignoreMsgBecauseOfIdeTools(conf: ConfigRef; msg: TMsgKind): bool =
|
|
msg >= errGenerated and conf.cmd == cmdIdeTools and optIdeDebug notin conf.globalOptions
|
|
|
|
proc addSourceLine(conf: ConfigRef; fileIdx: FileIndex, line: string) =
|
|
conf.m.fileInfos[fileIdx.int32].lines.add line
|
|
|
|
proc numLines*(conf: ConfigRef, fileIdx: FileIndex): int =
|
|
## xxx there's an off by 1 error that should be fixed; if a file ends with "foo" or "foo\n"
|
|
## it will return same number of lines (ie, a trailing empty line is discounted)
|
|
result = conf.m.fileInfos[fileIdx.int32].lines.len
|
|
if result == 0:
|
|
try:
|
|
for line in lines(toFullPathConsiderDirty(conf, fileIdx).string):
|
|
addSourceLine conf, fileIdx, line
|
|
except IOError:
|
|
discard
|
|
result = conf.m.fileInfos[fileIdx.int32].lines.len
|
|
|
|
proc sourceLine*(conf: ConfigRef; i: TLineInfo): string =
|
|
## 1-based index (matches editor line numbers); 1st line is for i.line = 1
|
|
## last valid line is `numLines` inclusive
|
|
if i.fileIndex.int32 < 0: return ""
|
|
let num = numLines(conf, i.fileIndex)
|
|
# can happen if the error points to EOF:
|
|
if i.line.int > num: return ""
|
|
|
|
result = conf.m.fileInfos[i.fileIndex.int32].lines[i.line.int-1]
|
|
|
|
proc getSurroundingSrc(conf: ConfigRef; info: TLineInfo): string =
|
|
if conf.hasHint(hintSource) and info != unknownLineInfo:
|
|
const indent = " "
|
|
result = "\n" & indent & $sourceLine(conf, info)
|
|
if info.col >= 0:
|
|
result.add "\n" & indent & spaces(info.col) & '^'
|
|
|
|
proc formatMsg*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg: string): string =
|
|
let title = case msg
|
|
of warnMin..warnMax: WarningTitle
|
|
of hintMin..hintMax: HintTitle
|
|
else: ErrorTitle
|
|
conf.toFileLineCol(info) & " " & title & getMessageStr(msg, arg)
|
|
|
|
proc liMessage*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg: string,
|
|
eh: TErrorHandling, info2: InstantiationInfo, isRaw = false) {.gcsafe, noinline.} =
|
|
var
|
|
title: string
|
|
color: ForegroundColor
|
|
ignoreMsg = false
|
|
sev: Severity
|
|
let errorOutputsOld = conf.m.errorOutputs
|
|
if msg in fatalMsgs:
|
|
# don't gag, refs bug #7080, bug #18278; this can happen with `{.fatal.}`
|
|
# or inside a `tryConstExpr`.
|
|
conf.m.errorOutputs = {eStdOut, eStdErr}
|
|
|
|
let kind = if msg in warnMin..hintMax and msg != hintUserRaw: $msg else: "" # xxx not sure why hintUserRaw is special
|
|
case msg
|
|
of errMin..errMax:
|
|
sev = Severity.Error
|
|
writeContext(conf, info)
|
|
title = ErrorTitle
|
|
color = ErrorColor
|
|
when false:
|
|
# we try to filter error messages so that not two error message
|
|
# in the same file and line are produced:
|
|
# xxx `lastError` is only used in this disabled code; but could be useful to revive
|
|
ignoreMsg = conf.m.lastError == info and info != unknownLineInfo and eh != doAbort
|
|
if info != unknownLineInfo: conf.m.lastError = info
|
|
of warnMin..warnMax:
|
|
sev = Severity.Warning
|
|
ignoreMsg = not conf.hasWarn(msg)
|
|
if not ignoreMsg and msg in conf.warningAsErrors:
|
|
title = ErrorTitle
|
|
color = ErrorColor
|
|
else:
|
|
title = WarningTitle
|
|
color = WarningColor
|
|
if not ignoreMsg: writeContext(conf, info)
|
|
inc(conf.warnCounter)
|
|
of hintMin..hintMax:
|
|
sev = Severity.Hint
|
|
ignoreMsg = not conf.hasHint(msg)
|
|
if not ignoreMsg and msg in conf.warningAsErrors:
|
|
title = ErrorTitle
|
|
else:
|
|
title = HintTitle
|
|
color = HintColor
|
|
inc(conf.hintCounter)
|
|
|
|
let s = if isRaw: arg else: getMessageStr(msg, arg)
|
|
if not ignoreMsg:
|
|
let loc = if info != unknownLineInfo: conf.toFileLineCol(info) & " " else: ""
|
|
# we could also show `conf.cmdInput` here for `projectIsCmd`
|
|
var kindmsg = if kind.len > 0: KindFormat % kind else: ""
|
|
if conf.structuredErrorHook != nil:
|
|
conf.structuredErrorHook(conf, info, s & kindmsg, sev)
|
|
if not ignoreMsgBecauseOfIdeTools(conf, msg):
|
|
if msg == hintProcessing and conf.hintProcessingDots:
|
|
msgWrite(conf, ".")
|
|
else:
|
|
styledMsgWriteln(styleBright, loc, resetStyle, color, title, resetStyle, s, KindColor, kindmsg,
|
|
resetStyle, conf.getSurroundingSrc(info), conf.unitSep)
|
|
if hintMsgOrigin in conf.mainPackageNotes:
|
|
# xxx needs a bit of refactoring to honor `conf.filenameOption`
|
|
styledMsgWriteln(styleBright, toFileLineCol(info2), resetStyle,
|
|
" compiler msg initiated here", KindColor,
|
|
KindFormat % $hintMsgOrigin,
|
|
resetStyle, conf.unitSep)
|
|
handleError(conf, msg, eh, s, ignoreMsg)
|
|
if msg in fatalMsgs:
|
|
# most likely would have died here but just in case, we restore state
|
|
conf.m.errorOutputs = errorOutputsOld
|
|
|
|
template rawMessage*(conf: ConfigRef; msg: TMsgKind, args: openArray[string]) =
|
|
let arg = msgKindToString(msg) % args
|
|
liMessage(conf, unknownLineInfo, msg, arg, eh = doAbort, instLoc(), isRaw = true)
|
|
|
|
template rawMessage*(conf: ConfigRef; msg: TMsgKind, arg: string) =
|
|
liMessage(conf, unknownLineInfo, msg, arg, eh = doAbort, instLoc())
|
|
|
|
template fatal*(conf: ConfigRef; info: TLineInfo, arg = "", msg = errFatal) =
|
|
liMessage(conf, info, msg, arg, doAbort, instLoc())
|
|
|
|
template globalAssert*(conf: ConfigRef; cond: untyped, info: TLineInfo = unknownLineInfo, arg = "") =
|
|
## avoids boilerplate
|
|
if not cond:
|
|
var arg2 = "'$1' failed" % [astToStr(cond)]
|
|
if arg.len > 0: arg2.add "; " & astToStr(arg) & ": " & arg
|
|
liMessage(conf, info, errGenerated, arg2, doRaise, instLoc())
|
|
|
|
template globalError*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
|
|
## `local` means compilation keeps going until errorMax is reached (via `doNothing`),
|
|
## `global` means it stops.
|
|
liMessage(conf, info, msg, arg, doRaise, instLoc())
|
|
|
|
template globalError*(conf: ConfigRef; info: TLineInfo, arg: string) =
|
|
liMessage(conf, info, errGenerated, arg, doRaise, instLoc())
|
|
|
|
template localError*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
|
|
liMessage(conf, info, msg, arg, doNothing, instLoc())
|
|
|
|
template localError*(conf: ConfigRef; info: TLineInfo, arg: string) =
|
|
liMessage(conf, info, errGenerated, arg, doNothing, instLoc())
|
|
|
|
template message*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
|
|
liMessage(conf, info, msg, arg, doNothing, instLoc())
|
|
|
|
proc warningDeprecated*(conf: ConfigRef, info: TLineInfo = gCmdLineInfo, msg = "") {.inline.} =
|
|
message(conf, info, warnDeprecated, msg)
|
|
|
|
proc internalErrorImpl(conf: ConfigRef; info: TLineInfo, errMsg: string, info2: InstantiationInfo) =
|
|
if conf.cmd == cmdIdeTools and conf.structuredErrorHook.isNil: return
|
|
writeContext(conf, info)
|
|
liMessage(conf, info, errInternal, errMsg, doAbort, info2)
|
|
|
|
template internalError*(conf: ConfigRef; info: TLineInfo, errMsg: string) =
|
|
internalErrorImpl(conf, info, errMsg, instLoc())
|
|
|
|
template internalError*(conf: ConfigRef; errMsg: string) =
|
|
internalErrorImpl(conf, unknownLineInfo, errMsg, instLoc())
|
|
|
|
template internalAssert*(conf: ConfigRef, e: bool) =
|
|
# xxx merge with `globalAssert`
|
|
if not e:
|
|
const info2 = instLoc()
|
|
let arg = info2.toFileLineCol
|
|
internalErrorImpl(conf, unknownLineInfo, arg, info2)
|
|
|
|
template lintReport*(conf: ConfigRef; info: TLineInfo, beau, got: string, extraMsg = "") =
|
|
let m = "'$1' should be: '$2'$3" % [got, beau, extraMsg]
|
|
let msg = if optStyleError in conf.globalOptions: errGenerated else: hintName
|
|
liMessage(conf, info, msg, m, doNothing, instLoc())
|
|
|
|
proc quotedFilename*(conf: ConfigRef; i: TLineInfo): Rope =
|
|
if i.fileIndex.int32 < 0:
|
|
result = makeCString "???"
|
|
elif optExcessiveStackTrace in conf.globalOptions:
|
|
result = conf.m.fileInfos[i.fileIndex.int32].quotedFullName
|
|
else:
|
|
result = conf.m.fileInfos[i.fileIndex.int32].quotedName
|
|
|
|
template listMsg(title, r) =
|
|
msgWriteln(conf, title, {msgNoUnitSep})
|
|
for a in r: msgWriteln(conf, " [$1] $2" % [if a in conf.notes: "x" else: " ", $a], {msgNoUnitSep})
|
|
|
|
proc listWarnings*(conf: ConfigRef) = listMsg("Warnings:", warnMin..warnMax)
|
|
proc listHints*(conf: ConfigRef) = listMsg("Hints:", hintMin..hintMax)
|
|
|
|
proc uniqueModuleName*(conf: ConfigRef; fid: FileIndex): string =
|
|
## The unique module name is guaranteed to only contain {'A'..'Z', 'a'..'z', '0'..'9', '_'}
|
|
## so that it is useful as a C identifier snippet.
|
|
let path = AbsoluteFile toFullPath(conf, fid)
|
|
let rel =
|
|
if path.string.startsWith(conf.libpath.string):
|
|
relativeTo(path, conf.libpath).string
|
|
else:
|
|
relativeTo(path, conf.projectPath).string
|
|
let trunc = if rel.endsWith(".nim"): rel.len - len(".nim") else: rel.len
|
|
result = newStringOfCap(trunc)
|
|
for i in 0..<trunc:
|
|
let c = rel[i]
|
|
case c
|
|
of 'a'..'z':
|
|
result.add c
|
|
of {os.DirSep, os.AltSep}:
|
|
result.add 'Z' # because it looks a bit like '/'
|
|
of '.':
|
|
result.add 'O' # a circle
|
|
else:
|
|
# We mangle upper letters and digits too so that there cannot
|
|
# be clashes with our special meanings of 'Z' and 'O'
|
|
result.addInt ord(c)
|
|
|
|
proc genSuccessX*(conf: ConfigRef) =
|
|
let mem =
|
|
when declared(system.getMaxMem): formatSize(getMaxMem()) & " peakmem"
|
|
else: formatSize(getTotalMem()) & " totmem"
|
|
let loc = $conf.linesCompiled
|
|
var build = ""
|
|
var flags = ""
|
|
const debugModeHints = "none (DEBUG BUILD, `-d:release` generates faster code)"
|
|
if conf.cmd in cmdBackends:
|
|
if conf.backend != backendJs:
|
|
build.add "mm: $#; " % $conf.selectedGC
|
|
if optThreads in conf.globalOptions: build.add "threads: on; "
|
|
build.add "opt: "
|
|
if optOptimizeSpeed in conf.options: build.add "speed"
|
|
elif optOptimizeSize in conf.options: build.add "size"
|
|
else: build.add debugModeHints
|
|
# pending https://github.com/timotheecour/Nim/issues/752, point to optimization.html
|
|
if isDefined(conf, "danger"): flags.add " -d:danger"
|
|
elif isDefined(conf, "release"): flags.add " -d:release"
|
|
else:
|
|
build.add "opt: "
|
|
if isDefined(conf, "danger"):
|
|
build.add "speed"
|
|
flags.add " -d:danger"
|
|
elif isDefined(conf, "release"):
|
|
build.add "speed"
|
|
flags.add " -d:release"
|
|
else: build.add debugModeHints
|
|
if flags.len > 0: build.add "; options:" & flags
|
|
let sec = formatFloat(epochTime() - conf.lastCmdTime, ffDecimal, 3)
|
|
let project = if conf.filenameOption == foAbs: $conf.projectFull else: $conf.projectName
|
|
# xxx honor conf.filenameOption more accurately
|
|
var output: string
|
|
if optCompileOnly in conf.globalOptions and conf.cmd != cmdJsonscript:
|
|
output = $conf.jsonBuildFile
|
|
elif conf.outFile.isEmpty and conf.cmd notin {cmdJsonscript} + cmdDocLike + cmdBackends:
|
|
# for some cmd we expect a valid absOutFile
|
|
output = "unknownOutput"
|
|
else:
|
|
output = $conf.absOutFile
|
|
if conf.filenameOption != foAbs: output = output.AbsoluteFile.extractFilename
|
|
# xxx honor filenameOption more accurately
|
|
rawMessage(conf, hintSuccessX, [
|
|
"build", build,
|
|
"loc", loc,
|
|
"sec", sec,
|
|
"mem", mem,
|
|
"project", project,
|
|
"output", output,
|
|
])
|