123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591 |
- #
- #
- # The Nim Compiler
- # (c) Copyright 2013 Andreas Rumpf
- #
- # See the file "copying.txt", included in this
- # distribution, for details about the copyright.
- #
- import
- options, strutils, os, tables, ropes, platform, terminal, macros,
- lineinfos, pathutils
- proc toCChar*(c: char; result: var string) =
- 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 =
- const MaxLineLength = 64
- result = nil
- var res = newStringOfCap(int(s.len.toFloat * 1.1) + 1)
- add(res, "\"")
- for i in 0 ..< len(s):
- if (i + 1) mod MaxLineLength == 0:
- add(res, "\"\L\"")
- toCChar(s[i], res)
- add(res, '\"')
- add(result, rope(res))
- 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 fileInfoKnown*(conf: ConfigRef; filename: AbsoluteFile): bool =
- var
- canon: AbsoluteFile
- try:
- canon = canonicalizePath(conf, filename)
- except OSError:
- canon = filename
- 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)
- shallow(canon.string)
- 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
- if conf.m.filenameToIndexTbl.hasKey(canon.string):
- result = conf.m.filenameToIndexTbl[canon.string]
- 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[canon.string] = result
- proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile): FileIndex =
- var dummy: bool
- result = fileInfoIdx(conf, filename, 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)
- 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
- PosFormat = "$1($2, $3) "
- 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
- 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, len(conf.m.msgContext) - 1)
- proc getInfoContext*(conf: ConfigRef; index: int): TLineInfo =
- let L = conf.m.msgContext.len
- let i = if index < 0: L + index else: index
- if i >=% L: result = unknownLineInfo()
- else: result = conf.m.msgContext[i].info
- template toFilename*(conf: ConfigRef; fileIdx: FileIndex): string =
- if fileIdx.int32 < 0 or conf == nil:
- "???"
- else:
- conf.m.fileInfos[fileIdx.int32].shortName
- proc toProjPath*(conf: ConfigRef; fileIdx: FileIndex): string =
- if fileIdx.int32 < 0 or conf == nil: "???"
- else: conf.m.fileInfos[fileIdx.int32].projPath.string
- proc toFullPath*(conf: ConfigRef; fileIdx: FileIndex): string =
- if fileIdx.int32 < 0 or conf == nil: result = "???"
- 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
- shallowCopy(conf.m.fileInfos[fileIdx.int32].hash, hash)
- proc getHash*(conf: ConfigRef; fileIdx: FileIndex): string =
- assert fileIdx.int32 >= 0
- shallowCopy(result, conf.m.fileInfos[fileIdx.int32].hash)
- proc toFullPathConsiderDirty*(conf: ConfigRef; fileIdx: FileIndex): AbsoluteFile =
- if fileIdx.int32 < 0:
- result = AbsoluteFile"???"
- 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 toMsgFilename*(conf: ConfigRef; info: FileIndex): string =
- let
- absPath = toFullPath(conf, info)
- relPath = toProjPath(conf, info)
- result = if (optListFullPaths in conf.globalOptions) or
- (relPath.len > absPath.len) or
- (relPath.count("..") > 2):
- absPath
- else:
- relPath
- 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*(conf: ConfigRef; info: TLineInfo): string {.inline.} =
- # consider calling `helpers.lineInfoToString` instead
- result = toMsgFilename(conf, info) & "(" & $info.line & ", " &
- $(info.col + 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
- 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
- if not isNil(conf.writelnHook) and msgSkipHook notin flags:
- conf.writelnHook(s)
- elif optStdout in conf.globalOptions or msgStdout in flags:
- if eStdOut in conf.m.errorOutputs:
- writeLine(stdout, s)
- flushFile(stdout)
- else:
- if eStdErr in conf.m.errorOutputs:
- writeLine(stderr, s)
- # 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)
- template callWritelnHook(args: varargs[string, `$`]) =
- conf.writelnHook concat(args)
- 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:
- callIgnoringStyle(writeLine, stdout, args)
- flushFile(stdout)
- else:
- if eStdErr in conf.m.errorOutputs:
- 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 coordToStr(coord: int): string =
- if coord == -1: result = "???"
- else: result = $coord
- proc msgKindToString*(kind: TMsgKind): string =
- # later versions may provide translated error messages
- result = MsgKindToStr[kind]
- proc getMessageStr(msg: TMsgKind, arg: string): string =
- result = msgKindToString(msg) % [arg]
- type
- TErrorHandling = enum doNothing, doAbort, doRaise
- proc log*(s: string) {.procvar.} =
- var f: File
- if open(f, getHomeDir() / "nimsuggest.log", fmAppend):
- f.writeLine(s)
- close(f)
- proc quit(conf: ConfigRef; msg: TMsgKind) {.gcsafe.} =
- if defined(debug) or msg == errInternal or hintStackTrace in conf.notes:
- {.gcsafe.}:
- if stackTraceAvailable() and isNil(conf.writelnHook):
- writeStackTrace()
- else:
- styledMsgWriteln(fgRed, "No stack traceback available\n" &
- "To create a stacktrace, rerun compilation with ./koch temp " &
- conf.command & " <file>")
- quit 1
- proc handleError(conf: ConfigRef; msg: TMsgKind, eh: TErrorHandling, s: string) =
- if msg >= fatalMin and msg <= fatalMax:
- if conf.cmd == cmdIdeTools: log(s)
- quit(conf, msg)
- if msg >= errMin and msg <= errMax:
- inc(conf.errorCounter)
- conf.exitcode = 1'i8
- if conf.errorCounter >= conf.errorMax:
- 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 ..< len(conf.m.msgContext):
- 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.Error)
- else:
- let message = if context.detail == "":
- instantiationFrom
- else:
- instantiationOfFrom.format(context.detail)
- styledMsgWriteln(styleBright,
- PosFormat % [toMsgFilename(conf, context.info),
- coordToStr(context.info.line.int),
- coordToStr(context.info.col+ColOffset)],
- resetStyle,
- message)
- info = context.info
- proc ignoreMsgBecauseOfIdeTools(conf: ConfigRef; msg: TMsgKind): bool =
- msg >= errGenerated and conf.cmd == cmdIdeTools and optIdeDebug notin conf.globalOptions
- proc rawMessage*(conf: ConfigRef; msg: TMsgKind, args: openArray[string]) =
- var
- title: string
- color: ForegroundColor
- kind: string
- sev: Severity
- case msg
- of errMin..errMax:
- sev = Severity.Error
- writeContext(conf, unknownLineInfo())
- title = ErrorTitle
- color = ErrorColor
- of warnMin..warnMax:
- sev = Severity.Warning
- if optWarns notin conf.options: return
- if msg notin conf.notes: return
- writeContext(conf, unknownLineInfo())
- title = WarningTitle
- color = WarningColor
- kind = WarningsToStr[ord(msg) - ord(warnMin)]
- inc(conf.warnCounter)
- of hintMin..hintMax:
- sev = Severity.Hint
- if optHints notin conf.options: return
- if msg notin conf.notes: return
- title = HintTitle
- color = HintColor
- if msg != hintUserRaw: kind = HintsToStr[ord(msg) - ord(hintMin)]
- inc(conf.hintCounter)
- let s = msgKindToString(msg) % args
- if conf.structuredErrorHook != nil:
- conf.structuredErrorHook(conf, unknownLineInfo(),
- s & (if kind.len > 0: KindFormat % kind else: ""), sev)
- if not ignoreMsgBecauseOfIdeTools(conf, msg):
- if kind.len > 0:
- styledMsgWriteln(color, title, resetStyle, s,
- KindColor, `%`(KindFormat, kind))
- else:
- styledMsgWriteln(color, title, resetStyle, s)
- handleError(conf, msg, doAbort, s)
- proc rawMessage*(conf: ConfigRef; msg: TMsgKind, arg: string) =
- rawMessage(conf, msg, [arg])
- proc resetAttributes*(conf: ConfigRef) =
- if {optUseColors, optStdout} * conf.globalOptions == {optUseColors}:
- terminal.resetAttributes(stderr)
- proc addSourceLine(conf: ConfigRef; fileIdx: FileIndex, line: string) =
- conf.m.fileInfos[fileIdx.int32].lines.add line
- proc sourceLine*(conf: ConfigRef; i: TLineInfo): string =
- if i.fileIndex.int32 < 0: return ""
- if conf.m.fileInfos[i.fileIndex.int32].lines.len == 0:
- try:
- for line in lines(toFullPathConsiderDirty(conf, i)):
- addSourceLine conf, i.fileIndex, line.string
- except IOError:
- discard
- assert i.fileIndex.int32 < conf.m.fileInfos.len
- # can happen if the error points to EOF:
- if i.line.int > conf.m.fileInfos[i.fileIndex.int32].lines.len: return ""
- result = conf.m.fileInfos[i.fileIndex.int32].lines[i.line.int-1]
- proc writeSurroundingSrc(conf: ConfigRef; info: TLineInfo) =
- const indent = " "
- msgWriteln(conf, indent & $sourceLine(conf, info))
- if info.col >= 0:
- msgWriteln(conf, 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
- result = PosFormat % [toMsgFilename(conf, info), coordToStr(info.line.int),
- coordToStr(info.col+ColOffset)] &
- title &
- getMessageStr(msg, arg)
- proc liMessage(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg: string,
- eh: TErrorHandling) =
- var
- title: string
- color: ForegroundColor
- kind: string
- ignoreMsg = false
- sev: Severity
- case msg
- of errMin..errMax:
- sev = Severity.Error
- writeContext(conf, info)
- title = ErrorTitle
- color = ErrorColor
- # we try to filter error messages so that not two error message
- # in the same file and line are produced:
- #ignoreMsg = lastError == info and eh != doAbort
- conf.m.lastError = info
- of warnMin..warnMax:
- sev = Severity.Warning
- ignoreMsg = optWarns notin conf.options or msg notin conf.notes
- if not ignoreMsg: writeContext(conf, info)
- title = WarningTitle
- color = WarningColor
- kind = WarningsToStr[ord(msg) - ord(warnMin)]
- inc(conf.warnCounter)
- of hintMin..hintMax:
- sev = Severity.Hint
- ignoreMsg = optHints notin conf.options or msg notin conf.notes
- title = HintTitle
- color = HintColor
- if msg != hintUserRaw: kind = HintsToStr[ord(msg) - ord(hintMin)]
- inc(conf.hintCounter)
- let x = PosFormat % [toMsgFilename(conf, info), coordToStr(info.line.int),
- coordToStr(info.col+ColOffset)]
- let s = getMessageStr(msg, arg)
- if not ignoreMsg:
- if conf.structuredErrorHook != nil:
- conf.structuredErrorHook(conf, info, s & (if kind.len > 0: KindFormat % kind else: ""), sev)
- if not ignoreMsgBecauseOfIdeTools(conf, msg):
- if kind.len > 0:
- styledMsgWriteln(styleBright, x, resetStyle, color, title, resetStyle, s,
- KindColor, `%`(KindFormat, kind))
- else:
- styledMsgWriteln(styleBright, x, resetStyle, color, title, resetStyle, s)
- if hintSource in conf.notes:
- conf.writeSurroundingSrc(info)
- handleError(conf, msg, eh, s)
- proc fatal*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
- # this fixes bug #7080 so that it is at least obvious 'fatal'
- # was executed.
- conf.m.errorOutputs = {eStdOut, eStdErr}
- liMessage(conf, info, msg, arg, doAbort)
- proc globalError*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
- liMessage(conf, info, msg, arg, doRaise)
- proc globalError*(conf: ConfigRef; info: TLineInfo, arg: string) =
- liMessage(conf, info, errGenerated, arg, doRaise)
- proc localError*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
- liMessage(conf, info, msg, arg, doNothing)
- proc localError*(conf: ConfigRef; info: TLineInfo, arg: string) =
- liMessage(conf, info, errGenerated, arg, doNothing)
- proc localError*(conf: ConfigRef; info: TLineInfo, format: string, params: openarray[string]) =
- localError(conf, info, format % params)
- proc message*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
- liMessage(conf, info, msg, arg, doNothing)
- proc internalError*(conf: ConfigRef; info: TLineInfo, errMsg: string) =
- if conf.cmd == cmdIdeTools and conf.structuredErrorHook.isNil: return
- writeContext(conf, info)
- liMessage(conf, info, errInternal, errMsg, doAbort)
- proc internalError*(conf: ConfigRef; errMsg: string) =
- if conf.cmd == cmdIdeTools and conf.structuredErrorHook.isNil: return
- writeContext(conf, unknownLineInfo())
- rawMessage(conf, errInternal, errMsg)
- template assertNotNil*(conf: ConfigRef; e): untyped =
- if e == nil: internalError(conf, $instantiationInfo())
- e
- template internalAssert*(conf: ConfigRef, e: bool) =
- if not e: internalError(conf, $instantiationInfo())
- 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
- proc listWarnings*(conf: ConfigRef) =
- msgWriteln(conf, "Warnings:")
- for warn in warnMin..warnMax:
- msgWriteln(conf, " [$1] $2" % [
- if warn in conf.notes: "x" else: " ",
- lineinfos.WarningsToStr[ord(warn) - ord(warnMin)]
- ])
- proc listHints*(conf: ConfigRef) =
- msgWriteln(conf, "Hints:")
- for hint in hintMin..hintMax:
- msgWriteln(conf, " [$1] $2" % [
- if hint in conf.notes: "x" else: " ",
- lineinfos.HintsToStr[ord(hint) - ord(hintMin)]
- ])
|