mirror of
https://github.com/nim-lang/Nim.git
synced 2026-04-29 18:53:58 +00:00
nimsuggest: more precise cursor tracking
This commit is contained in:
@@ -1,52 +0,0 @@
|
||||
|
||||
|
||||
import strutils, os, osproc, streams
|
||||
|
||||
const
|
||||
DummyEof = "!EOF!"
|
||||
|
||||
proc getPosition(s: string): (int, int) =
|
||||
result = (1, 1)
|
||||
var col = 0
|
||||
for i in 0..<s.len:
|
||||
if s[i] == '\L':
|
||||
inc result[0]
|
||||
col = 0
|
||||
else:
|
||||
inc col
|
||||
result[1] = col+1
|
||||
|
||||
proc callNimsuggest() =
|
||||
let cl = parseCmdLine("nimsuggest --tester temp000.nim")
|
||||
var p = startProcess(command=cl[0], args=cl[1 .. ^1],
|
||||
options={poStdErrToStdOut, poUsePath,
|
||||
poInteractive, poDemon})
|
||||
let outp = p.outputStream
|
||||
let inp = p.inputStream
|
||||
var report = ""
|
||||
var a = newStringOfCap(120)
|
||||
let contents = readFile("tools/nimsuggest/crashtester.nim")
|
||||
try:
|
||||
# read and ignore anything nimsuggest says at startup:
|
||||
while outp.readLine(a):
|
||||
if a == DummyEof: break
|
||||
|
||||
var line = 0
|
||||
for i in 0..< contents.len:
|
||||
let slic = contents[0..i]
|
||||
writeFile("temp000.nim", slic)
|
||||
let (line, col) = getPosition(slic)
|
||||
inp.writeLine("sug temp000.nim:$#:$#" % [$line, $col])
|
||||
inp.flush()
|
||||
var answer = ""
|
||||
while outp.readLine(a):
|
||||
if a == DummyEof: break
|
||||
answer.add a
|
||||
answer.add '\L'
|
||||
echo answer
|
||||
finally:
|
||||
inp.writeLine("quit")
|
||||
inp.flush()
|
||||
close(p)
|
||||
|
||||
callNimsuggest()
|
||||
@@ -1,602 +0,0 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2017 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Nimsuggest is a tool that helps to give editors IDE like capabilities.
|
||||
|
||||
import strutils, os, parseopt, parseutils, sequtils, net, rdstdin, sexp
|
||||
# Do NOT import suggest. It will lead to wierd bugs with
|
||||
# suggestionResultHook, because suggest.nim is included by sigmatch.
|
||||
# So we import that one instead.
|
||||
import compiler / [options, commands, modules, sem,
|
||||
passes, passaux, msgs, nimconf,
|
||||
extccomp, condsyms,
|
||||
sigmatch, ast, scriptconfig,
|
||||
idents, modulegraphs, vm, prefixmatches]
|
||||
|
||||
when defined(windows):
|
||||
import winlean
|
||||
else:
|
||||
import posix
|
||||
|
||||
const DummyEof = "!EOF!"
|
||||
const Usage = """
|
||||
Nimsuggest - Tool to give every editor IDE like capabilities for Nim
|
||||
Usage:
|
||||
nimsuggest [options] projectfile.nim
|
||||
|
||||
Options:
|
||||
--port:PORT port, by default 6000
|
||||
--address:HOST binds to that address, by default ""
|
||||
--stdin read commands from stdin and write results to
|
||||
stdout instead of using sockets
|
||||
--epc use emacs epc mode
|
||||
--debug enable debug output
|
||||
--log enable verbose logging to nimsuggest.log file
|
||||
--v2 use version 2 of the protocol; more features and
|
||||
much faster
|
||||
--refresh perform automatic refreshes to keep the analysis precise
|
||||
--tester implies --v2 and --stdin and outputs a line
|
||||
'""" & DummyEof & """' for the tester
|
||||
|
||||
The server then listens to the connection and takes line-based commands.
|
||||
|
||||
In addition, all command line options of Nim that do not affect code generation
|
||||
are supported.
|
||||
"""
|
||||
type
|
||||
Mode = enum mstdin, mtcp, mepc, mcmdline
|
||||
CachedMsg = object
|
||||
info: TLineInfo
|
||||
msg: string
|
||||
sev: Severity
|
||||
CachedMsgs = seq[CachedMsg]
|
||||
|
||||
var
|
||||
gPort = 6000.Port
|
||||
gAddress = ""
|
||||
gMode: Mode
|
||||
gEmitEof: bool # whether we write '!EOF!' dummy lines
|
||||
gLogging = false
|
||||
gRefresh: bool
|
||||
|
||||
requests: Channel[string]
|
||||
results: Channel[Suggest]
|
||||
|
||||
proc writelnToChannel(line: string) =
|
||||
results.send(Suggest(section: ideMsg, doc: line))
|
||||
|
||||
proc sugResultHook(s: Suggest) =
|
||||
results.send(s)
|
||||
|
||||
proc errorHook(info: TLineInfo; msg: string; sev: Severity) =
|
||||
results.send(Suggest(section: ideChk, filePath: toFullPath(info),
|
||||
line: toLinenumber(info), column: toColumn(info), doc: msg,
|
||||
forth: $sev))
|
||||
|
||||
const
|
||||
seps = {':', ';', ' ', '\t'}
|
||||
Help = "usage: sug|con|def|use|dus|chk|mod|highlight|outline|known file.nim[;dirtyfile.nim]:line:col\n" &
|
||||
"type 'quit' to quit\n" &
|
||||
"type 'debug' to toggle debug mode on/off\n" &
|
||||
"type 'terse' to toggle terse mode on/off"
|
||||
|
||||
type
|
||||
EUnexpectedCommand = object of Exception
|
||||
|
||||
proc parseQuoted(cmd: string; outp: var string; start: int): int =
|
||||
var i = start
|
||||
i += skipWhitespace(cmd, i)
|
||||
if cmd[i] == '"':
|
||||
i += parseUntil(cmd, outp, '"', i+1)+2
|
||||
else:
|
||||
i += parseUntil(cmd, outp, seps, i)
|
||||
result = i
|
||||
|
||||
proc sexp(s: IdeCmd|TSymKind|PrefixMatch): SexpNode = sexp($s)
|
||||
|
||||
proc sexp(s: Suggest): SexpNode =
|
||||
# If you change the order here, make sure to change it over in
|
||||
# nim-mode.el too.
|
||||
let qp = if s.qualifiedPath.isNil: @[] else: s.qualifiedPath
|
||||
result = convertSexp([
|
||||
s.section,
|
||||
s.symkind,
|
||||
qp.map(newSString),
|
||||
s.filePath,
|
||||
s.forth,
|
||||
s.line,
|
||||
s.column,
|
||||
s.doc,
|
||||
s.quality
|
||||
])
|
||||
if s.section == ideSug:
|
||||
result.add convertSexp(s.prefix)
|
||||
|
||||
proc sexp(s: seq[Suggest]): SexpNode =
|
||||
result = newSList()
|
||||
for sug in s:
|
||||
result.add(sexp(sug))
|
||||
|
||||
proc listEpc(): SexpNode =
|
||||
# This function is called from Emacs to show available options.
|
||||
let
|
||||
argspecs = sexp("file line column dirtyfile".split(" ").map(newSSymbol))
|
||||
docstring = sexp("line starts at 1, column at 0, dirtyfile is optional")
|
||||
result = newSList()
|
||||
for command in ["sug", "con", "def", "use", "dus", "chk", "mod"]:
|
||||
let
|
||||
cmd = sexp(command)
|
||||
methodDesc = newSList()
|
||||
methodDesc.add(cmd)
|
||||
methodDesc.add(argspecs)
|
||||
methodDesc.add(docstring)
|
||||
result.add(methodDesc)
|
||||
|
||||
proc findNode(n: PNode): PSym =
|
||||
#echo "checking node ", n.info
|
||||
if n.kind == nkSym:
|
||||
if isTracked(n.info, n.sym.name.s.len): return n.sym
|
||||
else:
|
||||
for i in 0 ..< safeLen(n):
|
||||
let res = n.sons[i].findNode
|
||||
if res != nil: return res
|
||||
|
||||
proc symFromInfo(graph: ModuleGraph; gTrackPos: TLineInfo): PSym =
|
||||
let m = graph.getModule(gTrackPos.fileIndex)
|
||||
#echo m.isNil, " I knew it ", gTrackPos.fileIndex
|
||||
if m != nil and m.ast != nil:
|
||||
result = m.ast.findNode
|
||||
|
||||
proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int;
|
||||
graph: ModuleGraph; cache: IdentCache) =
|
||||
if gLogging:
|
||||
log("cmd: " & $cmd & ", file: " & file & ", dirtyFile: " & dirtyfile &
|
||||
"[" & $line & ":" & $col & "]")
|
||||
gIdeCmd = cmd
|
||||
if cmd == ideChk:
|
||||
msgs.structuredErrorHook = errorHook
|
||||
msgs.writelnHook = proc (s: string) = discard
|
||||
else:
|
||||
msgs.structuredErrorHook = nil
|
||||
msgs.writelnHook = proc (s: string) = discard
|
||||
if cmd == ideUse and suggestVersion != 2:
|
||||
graph.resetAllModules()
|
||||
var isKnownFile = true
|
||||
let dirtyIdx = file.fileInfoIdx(isKnownFile)
|
||||
|
||||
if dirtyfile.len != 0: msgs.setDirtyFile(dirtyIdx, dirtyfile)
|
||||
else: msgs.setDirtyFile(dirtyIdx, nil)
|
||||
|
||||
gTrackPos = newLineInfo(dirtyIdx, line, col)
|
||||
gErrorCounter = 0
|
||||
if suggestVersion < 2:
|
||||
graph.usageSym = nil
|
||||
if not isKnownFile:
|
||||
graph.compileProject(cache)
|
||||
if suggestVersion == 2 and gIdeCmd in {ideUse, ideDus} and
|
||||
dirtyfile.len == 0:
|
||||
discard "no need to recompile anything"
|
||||
else:
|
||||
let modIdx = graph.parentModule(dirtyIdx)
|
||||
graph.markDirty dirtyIdx
|
||||
graph.markClientsDirty dirtyIdx
|
||||
if gIdeCmd != ideMod:
|
||||
graph.compileProject(cache, modIdx)
|
||||
if gIdeCmd in {ideUse, ideDus}:
|
||||
let u = if suggestVersion >= 2: graph.symFromInfo(gTrackPos) else: graph.usageSym
|
||||
if u != nil:
|
||||
listUsages(u)
|
||||
else:
|
||||
localError(gTrackPos, "found no symbol at this position " & $gTrackPos)
|
||||
|
||||
proc executeEpc(cmd: IdeCmd, args: SexpNode;
|
||||
graph: ModuleGraph; cache: IdentCache) =
|
||||
let
|
||||
file = args[0].getStr
|
||||
line = args[1].getNum
|
||||
column = args[2].getNum
|
||||
var dirtyfile = ""
|
||||
if len(args) > 3:
|
||||
dirtyfile = args[3].getStr(nil)
|
||||
execute(cmd, file, dirtyfile, int(line), int(column), graph, cache)
|
||||
|
||||
proc returnEpc(socket: Socket, uid: BiggestInt, s: SexpNode|string,
|
||||
return_symbol = "return") =
|
||||
let response = $convertSexp([newSSymbol(return_symbol), uid, s])
|
||||
socket.send(toHex(len(response), 6))
|
||||
socket.send(response)
|
||||
|
||||
template checkSanity(client, sizeHex, size, messageBuffer: typed) =
|
||||
if client.recv(sizeHex, 6) != 6:
|
||||
raise newException(ValueError, "didn't get all the hexbytes")
|
||||
if parseHex(sizeHex, size) == 0:
|
||||
raise newException(ValueError, "invalid size hex: " & $sizeHex)
|
||||
if client.recv(messageBuffer, size) != size:
|
||||
raise newException(ValueError, "didn't get all the bytes")
|
||||
|
||||
proc toStdout() {.gcsafe.} =
|
||||
while true:
|
||||
let res = results.recv()
|
||||
case res.section
|
||||
of ideNone: break
|
||||
of ideMsg: echo res.doc
|
||||
of ideKnown: echo res.quality == 1
|
||||
else: echo res
|
||||
|
||||
proc toSocket(stdoutSocket: Socket) {.gcsafe.} =
|
||||
while true:
|
||||
let res = results.recv()
|
||||
case res.section
|
||||
of ideNone: break
|
||||
of ideMsg: stdoutSocket.send(res.doc & "\c\L")
|
||||
of ideKnown: stdoutSocket.send($(res.quality == 1) & "\c\L")
|
||||
else: stdoutSocket.send($res & "\c\L")
|
||||
|
||||
proc toEpc(client: Socket; uid: BiggestInt) {.gcsafe.} =
|
||||
var list = newSList()
|
||||
while true:
|
||||
let res = results.recv()
|
||||
case res.section
|
||||
of ideNone: break
|
||||
of ideMsg:
|
||||
list.add sexp(res.doc)
|
||||
of ideKnown:
|
||||
list.add sexp(res.quality == 1)
|
||||
else:
|
||||
list.add sexp(res)
|
||||
returnEpc(client, uid, list)
|
||||
|
||||
template setVerbosity(level: typed) =
|
||||
gVerbosity = level
|
||||
gNotes = NotesVerbosity[gVerbosity]
|
||||
|
||||
proc connectToNextFreePort(server: Socket, host: string): Port =
|
||||
server.bindaddr(Port(0), host)
|
||||
let (_, port) = server.getLocalAddr
|
||||
result = port
|
||||
|
||||
type
|
||||
ThreadParams = tuple[port: Port; address: string]
|
||||
|
||||
proc replStdinSingleCmd(line: string) =
|
||||
requests.send line
|
||||
toStdout()
|
||||
echo ""
|
||||
flushFile(stdout)
|
||||
|
||||
proc replStdin(x: ThreadParams) {.thread.} =
|
||||
if gEmitEof:
|
||||
echo DummyEof
|
||||
while true:
|
||||
let line = readLine(stdin)
|
||||
requests.send line
|
||||
toStdout()
|
||||
echo DummyEof
|
||||
flushFile(stdout)
|
||||
else:
|
||||
echo Help
|
||||
var line = ""
|
||||
while readLineFromStdin("> ", line):
|
||||
replStdinSingleCmd(line)
|
||||
|
||||
proc replCmdline(x: ThreadParams) {.thread.} =
|
||||
replStdinSingleCmd(x.address)
|
||||
requests.send "quit"
|
||||
|
||||
proc replTcp(x: ThreadParams) {.thread.} =
|
||||
var server = newSocket()
|
||||
server.bindAddr(x.port, x.address)
|
||||
var inp = "".TaintedString
|
||||
server.listen()
|
||||
while true:
|
||||
var stdoutSocket = newSocket()
|
||||
accept(server, stdoutSocket)
|
||||
|
||||
stdoutSocket.readLine(inp)
|
||||
requests.send inp
|
||||
toSocket(stdoutSocket)
|
||||
stdoutSocket.send("\c\L")
|
||||
stdoutSocket.close()
|
||||
|
||||
proc argsToStr(x: SexpNode): string =
|
||||
if x.kind != SList: return x.getStr
|
||||
doAssert x.kind == SList
|
||||
doAssert x.len >= 4
|
||||
let file = x[0].getStr
|
||||
let line = x[1].getNum
|
||||
let col = x[2].getNum
|
||||
let dirty = x[3].getStr
|
||||
result = x[0].getStr.escape
|
||||
if dirty.len > 0:
|
||||
result.add ';'
|
||||
result.add dirty.escape
|
||||
result.add ':'
|
||||
result.add line
|
||||
result.add ':'
|
||||
result.add col
|
||||
|
||||
proc replEpc(x: ThreadParams) {.thread.} =
|
||||
var server = newSocket()
|
||||
let port = connectToNextFreePort(server, "localhost")
|
||||
server.listen()
|
||||
echo port
|
||||
stdout.flushFile()
|
||||
|
||||
var client = newSocket()
|
||||
# Wait for connection
|
||||
accept(server, client)
|
||||
while true:
|
||||
var
|
||||
sizeHex = ""
|
||||
size = 0
|
||||
messageBuffer = ""
|
||||
checkSanity(client, sizeHex, size, messageBuffer)
|
||||
let
|
||||
message = parseSexp($messageBuffer)
|
||||
epcApi = message[0].getSymbol
|
||||
case epcApi
|
||||
of "call":
|
||||
let
|
||||
uid = message[1].getNum
|
||||
args = message[3]
|
||||
|
||||
gIdeCmd = parseIdeCmd(message[2].getSymbol)
|
||||
case gIdeCmd
|
||||
of ideSug, ideCon, ideDef, ideUse, ideDus, ideOutline, ideHighlight:
|
||||
setVerbosity(0)
|
||||
else: discard
|
||||
let cmd = $gIdeCmd & " " & args.argsToStr
|
||||
if gLogging:
|
||||
log "MSG CMD: " & cmd
|
||||
requests.send(cmd)
|
||||
toEpc(client, uid)
|
||||
of "methods":
|
||||
returnEpc(client, message[1].getNum, listEpc())
|
||||
of "epc-error":
|
||||
# an unhandled exception forces down the whole process anyway, so we
|
||||
# use 'quit' here instead of 'raise'
|
||||
quit("recieved epc error: " & $messageBuffer)
|
||||
else:
|
||||
let errMessage = case epcApi
|
||||
of "return", "return-error":
|
||||
"no return expected"
|
||||
else:
|
||||
"unexpected call: " & epcAPI
|
||||
quit errMessage
|
||||
|
||||
proc execCmd(cmd: string; graph: ModuleGraph; cache: IdentCache; cachedMsgs: CachedMsgs) =
|
||||
template sentinel() =
|
||||
# send sentinel for the input reading thread:
|
||||
results.send(Suggest(section: ideNone))
|
||||
|
||||
template toggle(sw) =
|
||||
if sw in gGlobalOptions:
|
||||
excl(gGlobalOptions, sw)
|
||||
else:
|
||||
incl(gGlobalOptions, sw)
|
||||
sentinel()
|
||||
return
|
||||
|
||||
template err() =
|
||||
echo Help
|
||||
sentinel()
|
||||
return
|
||||
|
||||
var opc = ""
|
||||
var i = parseIdent(cmd, opc, 0)
|
||||
case opc.normalize
|
||||
of "sug": gIdeCmd = ideSug
|
||||
of "con": gIdeCmd = ideCon
|
||||
of "def": gIdeCmd = ideDef
|
||||
of "use": gIdeCmd = ideUse
|
||||
of "dus": gIdeCmd = ideDus
|
||||
of "mod": gIdeCmd = ideMod
|
||||
of "chk": gIdeCmd = ideChk
|
||||
of "highlight": gIdeCmd = ideHighlight
|
||||
of "outline": gIdeCmd = ideOutline
|
||||
of "quit":
|
||||
sentinel()
|
||||
quit()
|
||||
of "debug": toggle optIdeDebug
|
||||
of "terse": toggle optIdeTerse
|
||||
of "known": gIdeCmd = ideKnown
|
||||
else: err()
|
||||
var dirtyfile = ""
|
||||
var orig = ""
|
||||
i = parseQuoted(cmd, orig, i)
|
||||
if cmd[i] == ';':
|
||||
i = parseQuoted(cmd, dirtyfile, i+1)
|
||||
i += skipWhile(cmd, seps, i)
|
||||
var line = -1
|
||||
var col = 0
|
||||
i += parseInt(cmd, line, i)
|
||||
i += skipWhile(cmd, seps, i)
|
||||
i += parseInt(cmd, col, i)
|
||||
|
||||
if gIdeCmd == ideKnown:
|
||||
results.send(Suggest(section: ideKnown, quality: ord(fileInfoKnown(orig))))
|
||||
else:
|
||||
if gIdeCmd == ideChk:
|
||||
for cm in cachedMsgs: errorHook(cm.info, cm.msg, cm.sev)
|
||||
execute(gIdeCmd, orig, dirtyfile, line, col-1, graph, cache)
|
||||
sentinel()
|
||||
|
||||
proc recompileFullProject(graph: ModuleGraph; cache: IdentCache) =
|
||||
#echo "recompiling full project"
|
||||
resetSystemArtifacts()
|
||||
vm.globalCtx = nil
|
||||
graph.resetAllModules()
|
||||
GC_fullcollect()
|
||||
compileProject(graph, cache)
|
||||
#echo GC_getStatistics()
|
||||
|
||||
proc mainThread(graph: ModuleGraph; cache: IdentCache) =
|
||||
if gLogging:
|
||||
for it in searchPaths:
|
||||
log(it)
|
||||
|
||||
proc wrHook(line: string) {.closure.} =
|
||||
if gMode == mepc:
|
||||
if gLogging: log(line)
|
||||
else:
|
||||
writelnToChannel(line)
|
||||
|
||||
msgs.writelnHook = wrHook
|
||||
suggestionResultHook = sugResultHook
|
||||
graph.doStopCompile = proc (): bool = requests.peek() > 0
|
||||
var idle = 0
|
||||
var cachedMsgs: CachedMsgs = @[]
|
||||
while true:
|
||||
let (hasData, req) = requests.tryRecv()
|
||||
if hasData:
|
||||
msgs.writelnHook = wrHook
|
||||
suggestionResultHook = sugResultHook
|
||||
execCmd(req, graph, cache, cachedMsgs)
|
||||
idle = 0
|
||||
else:
|
||||
os.sleep 250
|
||||
idle += 1
|
||||
if idle == 20 and gRefresh:
|
||||
# we use some nimsuggest activity to enable a lazy recompile:
|
||||
gIdeCmd = ideChk
|
||||
msgs.writelnHook = proc (s: string) = discard
|
||||
cachedMsgs.setLen 0
|
||||
msgs.structuredErrorHook = proc (info: TLineInfo; msg: string; sev: Severity) =
|
||||
cachedMsgs.add(CachedMsg(info: info, msg: msg, sev: sev))
|
||||
suggestionResultHook = proc (s: Suggest) = discard
|
||||
recompileFullProject(graph, cache)
|
||||
|
||||
var
|
||||
inputThread: Thread[ThreadParams]
|
||||
|
||||
proc mainCommand(graph: ModuleGraph; cache: IdentCache) =
|
||||
clearPasses()
|
||||
registerPass verbosePass
|
||||
registerPass semPass
|
||||
gCmd = cmdIdeTools
|
||||
incl gGlobalOptions, optCaasEnabled
|
||||
isServing = true
|
||||
wantMainModule()
|
||||
add(searchPaths, options.libpath)
|
||||
|
||||
# do not stop after the first error:
|
||||
msgs.gErrorMax = high(int)
|
||||
# do not print errors, but log them
|
||||
msgs.writelnHook = proc (s: string) = log(s)
|
||||
msgs.structuredErrorHook = nil
|
||||
|
||||
# compile the project before showing any input so that we already
|
||||
# can answer questions right away:
|
||||
compileProject(graph, cache)
|
||||
|
||||
open(requests)
|
||||
open(results)
|
||||
|
||||
case gMode
|
||||
of mstdin: createThread(inputThread, replStdin, (gPort, gAddress))
|
||||
of mtcp: createThread(inputThread, replTcp, (gPort, gAddress))
|
||||
of mepc: createThread(inputThread, replEpc, (gPort, gAddress))
|
||||
of mcmdline: createThread(inputThread, replCmdline,
|
||||
(gPort, "sug \"" & options.gProjectFull & "\":" & gAddress))
|
||||
mainThread(graph, cache)
|
||||
joinThread(inputThread)
|
||||
close(requests)
|
||||
close(results)
|
||||
|
||||
proc processCmdLine*(pass: TCmdLinePass, cmd: string) =
|
||||
var p = parseopt.initOptParser(cmd)
|
||||
while true:
|
||||
parseopt.next(p)
|
||||
case p.kind
|
||||
of cmdEnd: break
|
||||
of cmdLongoption, cmdShortOption:
|
||||
case p.key.normalize
|
||||
of "port":
|
||||
gPort = parseInt(p.val).Port
|
||||
gMode = mtcp
|
||||
of "address":
|
||||
gAddress = p.val
|
||||
gMode = mtcp
|
||||
of "stdin": gMode = mstdin
|
||||
of "cmdline":
|
||||
gMode = mcmdline
|
||||
suggestVersion = 2
|
||||
gAddress = p.val
|
||||
of "epc":
|
||||
gMode = mepc
|
||||
gVerbosity = 0 # Port number gotta be first.
|
||||
of "debug": incl(gGlobalOptions, optIdeDebug)
|
||||
of "v2": suggestVersion = 2
|
||||
of "tester":
|
||||
suggestVersion = 2
|
||||
gMode = mstdin
|
||||
gEmitEof = true
|
||||
gRefresh = false
|
||||
of "log": gLogging = true
|
||||
of "refresh":
|
||||
if p.val.len > 0:
|
||||
gRefresh = parseBool(p.val)
|
||||
else:
|
||||
gRefresh = true
|
||||
else: processSwitch(pass, p)
|
||||
of cmdArgument:
|
||||
options.gProjectName = unixToNativePath(p.key)
|
||||
# if processArgument(pass, p, argsCount): break
|
||||
|
||||
proc handleCmdLine(cache: IdentCache; config: ConfigRef) =
|
||||
if paramCount() == 0:
|
||||
stdout.writeline(Usage)
|
||||
else:
|
||||
processCmdLine(passCmd1, "")
|
||||
if gMode != mstdin:
|
||||
msgs.writelnHook = proc (msg: string) = discard
|
||||
if gProjectName != "":
|
||||
try:
|
||||
gProjectFull = canonicalizePath(gProjectName)
|
||||
except OSError:
|
||||
gProjectFull = gProjectName
|
||||
var p = splitFile(gProjectFull)
|
||||
gProjectPath = canonicalizePath p.dir
|
||||
gProjectName = p.name
|
||||
else:
|
||||
gProjectPath = canonicalizePath getCurrentDir()
|
||||
|
||||
# Find Nim's prefix dir.
|
||||
let binaryPath = findExe("nim")
|
||||
if binaryPath == "":
|
||||
raise newException(IOError,
|
||||
"Cannot find Nim standard library: Nim compiler not in PATH")
|
||||
gPrefixDir = binaryPath.splitPath().head.parentDir()
|
||||
#msgs.writelnHook = proc (line: string) = log(line)
|
||||
if gLogging:
|
||||
log("START " & gProjectFull)
|
||||
|
||||
loadConfigs(DefaultConfig, cache, config) # load all config files
|
||||
# now process command line arguments again, because some options in the
|
||||
# command line can overwite the config file's settings
|
||||
options.command = "nimsuggest"
|
||||
let scriptFile = gProjectFull.changeFileExt("nims")
|
||||
if fileExists(scriptFile):
|
||||
runNimScript(cache, scriptFile, freshDefines=false, config)
|
||||
# 'nim foo.nims' means to just run the NimScript file and do nothing more:
|
||||
if scriptFile == gProjectFull: return
|
||||
elif fileExists(gProjectPath / "config.nims"):
|
||||
# directory wide NimScript file
|
||||
runNimScript(cache, gProjectPath / "config.nims", freshDefines=false, config)
|
||||
|
||||
extccomp.initVars()
|
||||
processCmdLine(passCmd2, "")
|
||||
|
||||
let graph = newModuleGraph(config)
|
||||
graph.suggestMode = true
|
||||
mainCommand(graph, cache)
|
||||
|
||||
condsyms.initDefines()
|
||||
defineSymbol "nimsuggest"
|
||||
handleCmdline(newIdentCache(), newConfigRef())
|
||||
@@ -1,25 +0,0 @@
|
||||
# Special configuration file for the Nim project
|
||||
|
||||
gc:markAndSweep
|
||||
|
||||
hint[XDeclaredButNotUsed]:off
|
||||
|
||||
path:"$lib/packages/docutils"
|
||||
|
||||
define:useStdoutAsStdmsg
|
||||
define:nimsuggest
|
||||
# die when nimsuggest uses more than 4GB:
|
||||
@if cpu32:
|
||||
define:"nimMaxHeap=2000"
|
||||
@else:
|
||||
define:"nimMaxHeap=4000"
|
||||
@end
|
||||
|
||||
#cs:partial
|
||||
#define:useNodeIds
|
||||
#define:booting
|
||||
#define:noDocgen
|
||||
--path:"$nim"
|
||||
--threads:on
|
||||
--noNimblePath
|
||||
--path:"../../compiler"
|
||||
@@ -1,11 +0,0 @@
|
||||
[Package]
|
||||
name = "nimsuggest"
|
||||
version = "0.1.0"
|
||||
author = "Andreas Rumpf"
|
||||
description = "Tool for providing auto completion data for Nim source code."
|
||||
license = "MIT"
|
||||
|
||||
bin = "nimsuggest"
|
||||
|
||||
[Deps]
|
||||
Requires: "nim >= 0.11.2, compiler#head"
|
||||
@@ -1,697 +0,0 @@
|
||||
#
|
||||
#
|
||||
# Nim's Runtime Library
|
||||
# (c) Copyright 2015 Andreas Rumpf, Dominik Picheta
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
import
|
||||
hashes, strutils, lexbase, streams, unicode, macros
|
||||
|
||||
type
|
||||
SexpEventKind* = enum ## enumeration of all events that may occur when parsing
|
||||
sexpError, ## an error occurred during parsing
|
||||
sexpEof, ## end of file reached
|
||||
sexpString, ## a string literal
|
||||
sexpSymbol, ## a symbol
|
||||
sexpInt, ## an integer literal
|
||||
sexpFloat, ## a float literal
|
||||
sexpNil, ## the value ``nil``
|
||||
sexpDot, ## the dot to separate car/cdr
|
||||
sexpListStart, ## start of a list: the ``(`` token
|
||||
sexpListEnd, ## end of a list: the ``)`` token
|
||||
|
||||
TTokKind = enum # must be synchronized with SexpEventKind!
|
||||
tkError,
|
||||
tkEof,
|
||||
tkString,
|
||||
tkSymbol,
|
||||
tkInt,
|
||||
tkFloat,
|
||||
tkNil,
|
||||
tkDot,
|
||||
tkParensLe,
|
||||
tkParensRi
|
||||
tkSpace
|
||||
|
||||
SexpError* = enum ## enumeration that lists all errors that can occur
|
||||
errNone, ## no error
|
||||
errInvalidToken, ## invalid token
|
||||
errParensRiExpected, ## ``)`` expected
|
||||
errQuoteExpected, ## ``"`` expected
|
||||
errEofExpected, ## EOF expected
|
||||
|
||||
SexpParser* = object of BaseLexer ## the parser object.
|
||||
a: string
|
||||
tok: TTokKind
|
||||
kind: SexpEventKind
|
||||
err: SexpError
|
||||
|
||||
const
|
||||
errorMessages: array[SexpError, string] = [
|
||||
"no error",
|
||||
"invalid token",
|
||||
"')' expected",
|
||||
"'\"' or \"'\" expected",
|
||||
"EOF expected",
|
||||
]
|
||||
tokToStr: array[TTokKind, string] = [
|
||||
"invalid token",
|
||||
"EOF",
|
||||
"string literal",
|
||||
"symbol",
|
||||
"int literal",
|
||||
"float literal",
|
||||
"nil",
|
||||
".",
|
||||
"(", ")", "space"
|
||||
]
|
||||
|
||||
proc close*(my: var SexpParser) {.inline.} =
|
||||
## closes the parser `my` and its associated input stream.
|
||||
lexbase.close(my)
|
||||
|
||||
proc str*(my: SexpParser): string {.inline.} =
|
||||
## returns the character data for the events: ``sexpInt``, ``sexpFloat``,
|
||||
## ``sexpString``
|
||||
assert(my.kind in {sexpInt, sexpFloat, sexpString})
|
||||
result = my.a
|
||||
|
||||
proc getInt*(my: SexpParser): BiggestInt {.inline.} =
|
||||
## returns the number for the event: ``sexpInt``
|
||||
assert(my.kind == sexpInt)
|
||||
result = parseBiggestInt(my.a)
|
||||
|
||||
proc getFloat*(my: SexpParser): float {.inline.} =
|
||||
## returns the number for the event: ``sexpFloat``
|
||||
assert(my.kind == sexpFloat)
|
||||
result = parseFloat(my.a)
|
||||
|
||||
proc kind*(my: SexpParser): SexpEventKind {.inline.} =
|
||||
## returns the current event type for the SEXP parser
|
||||
result = my.kind
|
||||
|
||||
proc getColumn*(my: SexpParser): int {.inline.} =
|
||||
## get the current column the parser has arrived at.
|
||||
result = getColNumber(my, my.bufpos)
|
||||
|
||||
proc getLine*(my: SexpParser): int {.inline.} =
|
||||
## get the current line the parser has arrived at.
|
||||
result = my.lineNumber
|
||||
|
||||
proc errorMsg*(my: SexpParser): string =
|
||||
## returns a helpful error message for the event ``sexpError``
|
||||
assert(my.kind == sexpError)
|
||||
result = "($1, $2) Error: $3" % [$getLine(my), $getColumn(my), errorMessages[my.err]]
|
||||
|
||||
proc errorMsgExpected*(my: SexpParser, e: string): string =
|
||||
## returns an error message "`e` expected" in the same format as the
|
||||
## other error messages
|
||||
result = "($1, $2) Error: $3" % [$getLine(my), $getColumn(my), e & " expected"]
|
||||
|
||||
proc handleHexChar(c: char, x: var int): bool =
|
||||
result = true # Success
|
||||
case c
|
||||
of '0'..'9': x = (x shl 4) or (ord(c) - ord('0'))
|
||||
of 'a'..'f': x = (x shl 4) or (ord(c) - ord('a') + 10)
|
||||
of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10)
|
||||
else: result = false # error
|
||||
|
||||
proc parseString(my: var SexpParser): TTokKind =
|
||||
result = tkString
|
||||
var pos = my.bufpos + 1
|
||||
var buf = my.buf
|
||||
while true:
|
||||
case buf[pos]
|
||||
of '\0':
|
||||
my.err = errQuoteExpected
|
||||
result = tkError
|
||||
break
|
||||
of '"':
|
||||
inc(pos)
|
||||
break
|
||||
of '\\':
|
||||
case buf[pos+1]
|
||||
of '\\', '"', '\'', '/':
|
||||
add(my.a, buf[pos+1])
|
||||
inc(pos, 2)
|
||||
of 'b':
|
||||
add(my.a, '\b')
|
||||
inc(pos, 2)
|
||||
of 'f':
|
||||
add(my.a, '\f')
|
||||
inc(pos, 2)
|
||||
of 'n':
|
||||
add(my.a, '\L')
|
||||
inc(pos, 2)
|
||||
of 'r':
|
||||
add(my.a, '\C')
|
||||
inc(pos, 2)
|
||||
of 't':
|
||||
add(my.a, '\t')
|
||||
inc(pos, 2)
|
||||
of 'u':
|
||||
inc(pos, 2)
|
||||
var r: int
|
||||
if handleHexChar(buf[pos], r): inc(pos)
|
||||
if handleHexChar(buf[pos], r): inc(pos)
|
||||
if handleHexChar(buf[pos], r): inc(pos)
|
||||
if handleHexChar(buf[pos], r): inc(pos)
|
||||
add(my.a, toUTF8(Rune(r)))
|
||||
else:
|
||||
# don't bother with the error
|
||||
add(my.a, buf[pos])
|
||||
inc(pos)
|
||||
of '\c':
|
||||
pos = lexbase.handleCR(my, pos)
|
||||
buf = my.buf
|
||||
add(my.a, '\c')
|
||||
of '\L':
|
||||
pos = lexbase.handleLF(my, pos)
|
||||
buf = my.buf
|
||||
add(my.a, '\L')
|
||||
else:
|
||||
add(my.a, buf[pos])
|
||||
inc(pos)
|
||||
my.bufpos = pos # store back
|
||||
|
||||
proc parseNumber(my: var SexpParser) =
|
||||
var pos = my.bufpos
|
||||
var buf = my.buf
|
||||
if buf[pos] == '-':
|
||||
add(my.a, '-')
|
||||
inc(pos)
|
||||
if buf[pos] == '.':
|
||||
add(my.a, "0.")
|
||||
inc(pos)
|
||||
else:
|
||||
while buf[pos] in Digits:
|
||||
add(my.a, buf[pos])
|
||||
inc(pos)
|
||||
if buf[pos] == '.':
|
||||
add(my.a, '.')
|
||||
inc(pos)
|
||||
# digits after the dot:
|
||||
while buf[pos] in Digits:
|
||||
add(my.a, buf[pos])
|
||||
inc(pos)
|
||||
if buf[pos] in {'E', 'e'}:
|
||||
add(my.a, buf[pos])
|
||||
inc(pos)
|
||||
if buf[pos] in {'+', '-'}:
|
||||
add(my.a, buf[pos])
|
||||
inc(pos)
|
||||
while buf[pos] in Digits:
|
||||
add(my.a, buf[pos])
|
||||
inc(pos)
|
||||
my.bufpos = pos
|
||||
|
||||
proc parseSymbol(my: var SexpParser) =
|
||||
var pos = my.bufpos
|
||||
var buf = my.buf
|
||||
if buf[pos] in IdentStartChars:
|
||||
while buf[pos] in IdentChars:
|
||||
add(my.a, buf[pos])
|
||||
inc(pos)
|
||||
my.bufpos = pos
|
||||
|
||||
proc getTok(my: var SexpParser): TTokKind =
|
||||
setLen(my.a, 0)
|
||||
case my.buf[my.bufpos]
|
||||
of '-', '0'..'9': # numbers that start with a . are not parsed
|
||||
# correctly.
|
||||
parseNumber(my)
|
||||
if {'.', 'e', 'E'} in my.a:
|
||||
result = tkFloat
|
||||
else:
|
||||
result = tkInt
|
||||
of '"': #" # gotta fix nim-mode
|
||||
result = parseString(my)
|
||||
of '(':
|
||||
inc(my.bufpos)
|
||||
result = tkParensLe
|
||||
of ')':
|
||||
inc(my.bufpos)
|
||||
result = tkParensRi
|
||||
of '\0':
|
||||
result = tkEof
|
||||
of 'a'..'z', 'A'..'Z', '_':
|
||||
parseSymbol(my)
|
||||
if my.a == "nil":
|
||||
result = tkNil
|
||||
else:
|
||||
result = tkSymbol
|
||||
of ' ':
|
||||
result = tkSpace
|
||||
inc(my.bufpos)
|
||||
of '.':
|
||||
result = tkDot
|
||||
inc(my.bufpos)
|
||||
else:
|
||||
inc(my.bufpos)
|
||||
result = tkError
|
||||
my.tok = result
|
||||
|
||||
# ------------- higher level interface ---------------------------------------
|
||||
|
||||
type
|
||||
SexpNodeKind* = enum ## possible SEXP node types
|
||||
SNil,
|
||||
SInt,
|
||||
SFloat,
|
||||
SString,
|
||||
SSymbol,
|
||||
SList,
|
||||
SCons
|
||||
|
||||
SexpNode* = ref SexpNodeObj ## SEXP node
|
||||
SexpNodeObj* {.acyclic.} = object
|
||||
case kind*: SexpNodeKind
|
||||
of SString:
|
||||
str*: string
|
||||
of SSymbol:
|
||||
symbol*: string
|
||||
of SInt:
|
||||
num*: BiggestInt
|
||||
of SFloat:
|
||||
fnum*: float
|
||||
of SList:
|
||||
elems*: seq[SexpNode]
|
||||
of SCons:
|
||||
car: SexpNode
|
||||
cdr: SexpNode
|
||||
of SNil:
|
||||
discard
|
||||
|
||||
Cons = tuple[car: SexpNode, cdr: SexpNode]
|
||||
|
||||
SexpParsingError* = object of ValueError ## is raised for a SEXP error
|
||||
|
||||
proc raiseParseErr*(p: SexpParser, msg: string) {.noinline, noreturn.} =
|
||||
## raises an `ESexpParsingError` exception.
|
||||
raise newException(SexpParsingError, errorMsgExpected(p, msg))
|
||||
|
||||
proc newSString*(s: string): SexpNode {.procvar.}=
|
||||
## Creates a new `SString SexpNode`.
|
||||
new(result)
|
||||
result.kind = SString
|
||||
result.str = s
|
||||
|
||||
proc newSStringMove(s: string): SexpNode =
|
||||
new(result)
|
||||
result.kind = SString
|
||||
shallowCopy(result.str, s)
|
||||
|
||||
proc newSInt*(n: BiggestInt): SexpNode {.procvar.} =
|
||||
## Creates a new `SInt SexpNode`.
|
||||
new(result)
|
||||
result.kind = SInt
|
||||
result.num = n
|
||||
|
||||
proc newSFloat*(n: float): SexpNode {.procvar.} =
|
||||
## Creates a new `SFloat SexpNode`.
|
||||
new(result)
|
||||
result.kind = SFloat
|
||||
result.fnum = n
|
||||
|
||||
proc newSNil*(): SexpNode {.procvar.} =
|
||||
## Creates a new `SNil SexpNode`.
|
||||
new(result)
|
||||
|
||||
proc newSCons*(car, cdr: SexpNode): SexpNode {.procvar.} =
|
||||
## Creates a new `SCons SexpNode`
|
||||
new(result)
|
||||
result.kind = SCons
|
||||
result.car = car
|
||||
result.cdr = cdr
|
||||
|
||||
proc newSList*(): SexpNode {.procvar.} =
|
||||
## Creates a new `SList SexpNode`
|
||||
new(result)
|
||||
result.kind = SList
|
||||
result.elems = @[]
|
||||
|
||||
proc newSSymbol*(s: string): SexpNode {.procvar.} =
|
||||
new(result)
|
||||
result.kind = SSymbol
|
||||
result.symbol = s
|
||||
|
||||
proc newSSymbolMove(s: string): SexpNode =
|
||||
new(result)
|
||||
result.kind = SSymbol
|
||||
shallowCopy(result.symbol, s)
|
||||
|
||||
proc getStr*(n: SexpNode, default: string = ""): string =
|
||||
## Retrieves the string value of a `SString SexpNode`.
|
||||
##
|
||||
## Returns ``default`` if ``n`` is not a ``SString``.
|
||||
if n.kind != SString: return default
|
||||
else: return n.str
|
||||
|
||||
proc getNum*(n: SexpNode, default: BiggestInt = 0): BiggestInt =
|
||||
## Retrieves the int value of a `SInt SexpNode`.
|
||||
##
|
||||
## Returns ``default`` if ``n`` is not a ``SInt``.
|
||||
if n.kind != SInt: return default
|
||||
else: return n.num
|
||||
|
||||
proc getFNum*(n: SexpNode, default: float = 0.0): float =
|
||||
## Retrieves the float value of a `SFloat SexpNode`.
|
||||
##
|
||||
## Returns ``default`` if ``n`` is not a ``SFloat``.
|
||||
if n.kind != SFloat: return default
|
||||
else: return n.fnum
|
||||
|
||||
proc getSymbol*(n: SexpNode, default: string = ""): string =
|
||||
## Retrieves the int value of a `SList SexpNode`.
|
||||
##
|
||||
## Returns ``default`` if ``n`` is not a ``SList``.
|
||||
if n.kind != SSymbol: return default
|
||||
else: return n.symbol
|
||||
|
||||
proc getElems*(n: SexpNode, default: seq[SexpNode] = @[]): seq[SexpNode] =
|
||||
## Retrieves the int value of a `SList SexpNode`.
|
||||
##
|
||||
## Returns ``default`` if ``n`` is not a ``SList``.
|
||||
if n.kind == SNil: return @[]
|
||||
elif n.kind != SList: return default
|
||||
else: return n.elems
|
||||
|
||||
proc getCons*(n: SexpNode, defaults: Cons = (newSNil(), newSNil())): Cons =
|
||||
## Retrieves the cons value of a `SList SexpNode`.
|
||||
##
|
||||
## Returns ``default`` if ``n`` is not a ``SList``.
|
||||
if n.kind == SCons: return (n.car, n.cdr)
|
||||
elif n.kind == SList: return (n.elems[0], n.elems[1])
|
||||
else: return defaults
|
||||
|
||||
proc sexp*(s: string): SexpNode =
|
||||
## Generic constructor for SEXP data. Creates a new `SString SexpNode`.
|
||||
new(result)
|
||||
result.kind = SString
|
||||
result.str = s
|
||||
|
||||
proc sexp*(n: BiggestInt): SexpNode =
|
||||
## Generic constructor for SEXP data. Creates a new `SInt SexpNode`.
|
||||
new(result)
|
||||
result.kind = SInt
|
||||
result.num = n
|
||||
|
||||
proc sexp*(n: float): SexpNode =
|
||||
## Generic constructor for SEXP data. Creates a new `SFloat SexpNode`.
|
||||
new(result)
|
||||
result.kind = SFloat
|
||||
result.fnum = n
|
||||
|
||||
proc sexp*(b: bool): SexpNode =
|
||||
## Generic constructor for SEXP data. Creates a new `SSymbol
|
||||
## SexpNode` with value t or `SNil SexpNode`.
|
||||
new(result)
|
||||
if b:
|
||||
result.kind = SSymbol
|
||||
result.symbol = "t"
|
||||
else:
|
||||
result.kind = SNil
|
||||
|
||||
proc sexp*(elements: openArray[SexpNode]): SexpNode =
|
||||
## Generic constructor for SEXP data. Creates a new `SList SexpNode`
|
||||
new(result)
|
||||
result.kind = SList
|
||||
newSeq(result.elems, elements.len)
|
||||
for i, p in pairs(elements): result.elems[i] = p
|
||||
|
||||
proc sexp*(s: SexpNode): SexpNode =
|
||||
result = s
|
||||
|
||||
proc toSexp(x: NimNode): NimNode {.compiletime.} =
|
||||
case x.kind
|
||||
of nnkBracket:
|
||||
result = newNimNode(nnkBracket)
|
||||
for i in 0 .. <x.len:
|
||||
result.add(toSexp(x[i]))
|
||||
|
||||
else:
|
||||
result = x
|
||||
|
||||
result = prefix(result, "sexp")
|
||||
|
||||
macro convertSexp*(x: untyped): untyped =
|
||||
## Convert an expression to a SexpNode directly, without having to specify
|
||||
## `%` for every element.
|
||||
result = toSexp(x)
|
||||
|
||||
proc `==`* (a,b: SexpNode): bool =
|
||||
## Check two nodes for equality
|
||||
if a.isNil:
|
||||
if b.isNil: return true
|
||||
return false
|
||||
elif b.isNil or a.kind != b.kind:
|
||||
return false
|
||||
else:
|
||||
return case a.kind
|
||||
of SString:
|
||||
a.str == b.str
|
||||
of SInt:
|
||||
a.num == b.num
|
||||
of SFloat:
|
||||
a.fnum == b.fnum
|
||||
of SNil:
|
||||
true
|
||||
of SList:
|
||||
a.elems == b.elems
|
||||
of SSymbol:
|
||||
a.symbol == b.symbol
|
||||
of SCons:
|
||||
a.car == b.car and a.cdr == b.cdr
|
||||
|
||||
proc hash* (n:SexpNode): Hash =
|
||||
## Compute the hash for a SEXP node
|
||||
case n.kind
|
||||
of SList:
|
||||
result = hash(n.elems)
|
||||
of SInt:
|
||||
result = hash(n.num)
|
||||
of SFloat:
|
||||
result = hash(n.fnum)
|
||||
of SString:
|
||||
result = hash(n.str)
|
||||
of SNil:
|
||||
result = hash(0)
|
||||
of SSymbol:
|
||||
result = hash(n.symbol)
|
||||
of SCons:
|
||||
result = hash(n.car) !& hash(n.cdr)
|
||||
|
||||
proc len*(n: SexpNode): int =
|
||||
## If `n` is a `SList`, it returns the number of elements.
|
||||
## If `n` is a `JObject`, it returns the number of pairs.
|
||||
## Else it returns 0.
|
||||
case n.kind
|
||||
of SList: result = n.elems.len
|
||||
else: discard
|
||||
|
||||
proc `[]`*(node: SexpNode, index: int): SexpNode =
|
||||
## Gets the node at `index` in a List. Result is undefined if `index`
|
||||
## is out of bounds
|
||||
assert(not isNil(node))
|
||||
assert(node.kind == SList)
|
||||
return node.elems[index]
|
||||
|
||||
proc add*(father, child: SexpNode) =
|
||||
## Adds `child` to a SList node `father`.
|
||||
assert father.kind == SList
|
||||
father.elems.add(child)
|
||||
|
||||
# ------------- pretty printing ----------------------------------------------
|
||||
|
||||
proc indent(s: var string, i: int) =
|
||||
s.add(spaces(i))
|
||||
|
||||
proc newIndent(curr, indent: int, ml: bool): int =
|
||||
if ml: return curr + indent
|
||||
else: return indent
|
||||
|
||||
proc nl(s: var string, ml: bool) =
|
||||
if ml: s.add("\n")
|
||||
|
||||
proc escapeJson*(s: string): string =
|
||||
## Converts a string `s` to its JSON representation.
|
||||
result = newStringOfCap(s.len + s.len shr 3)
|
||||
result.add("\"")
|
||||
for x in runes(s):
|
||||
var r = int(x)
|
||||
if r >= 32 and r <= 127:
|
||||
var c = chr(r)
|
||||
case c
|
||||
of '"': result.add("\\\"") #" # gotta fix nim-mode
|
||||
of '\\': result.add("\\\\")
|
||||
else: result.add(c)
|
||||
else:
|
||||
result.add("\\u")
|
||||
result.add(toHex(r, 4))
|
||||
result.add("\"")
|
||||
|
||||
proc copy*(p: SexpNode): SexpNode =
|
||||
## Performs a deep copy of `a`.
|
||||
case p.kind
|
||||
of SString:
|
||||
result = newSString(p.str)
|
||||
of SInt:
|
||||
result = newSInt(p.num)
|
||||
of SFloat:
|
||||
result = newSFloat(p.fnum)
|
||||
of SNil:
|
||||
result = newSNil()
|
||||
of SSymbol:
|
||||
result = newSSymbol(p.symbol)
|
||||
of SList:
|
||||
result = newSList()
|
||||
for i in items(p.elems):
|
||||
result.elems.add(copy(i))
|
||||
of SCons:
|
||||
result = newSCons(copy(p.car), copy(p.cdr))
|
||||
|
||||
proc toPretty(result: var string, node: SexpNode, indent = 2, ml = true,
|
||||
lstArr = false, currIndent = 0) =
|
||||
case node.kind
|
||||
of SString:
|
||||
if lstArr: result.indent(currIndent)
|
||||
result.add(escapeJson(node.str))
|
||||
of SInt:
|
||||
if lstArr: result.indent(currIndent)
|
||||
result.add(node.num)
|
||||
of SFloat:
|
||||
if lstArr: result.indent(currIndent)
|
||||
result.add(node.fnum)
|
||||
of SNil:
|
||||
if lstArr: result.indent(currIndent)
|
||||
result.add("nil")
|
||||
of SSymbol:
|
||||
if lstArr: result.indent(currIndent)
|
||||
result.add(node.symbol)
|
||||
of SList:
|
||||
if lstArr: result.indent(currIndent)
|
||||
if len(node.elems) != 0:
|
||||
result.add("(")
|
||||
result.nl(ml)
|
||||
for i in 0..len(node.elems)-1:
|
||||
if i > 0:
|
||||
result.add(" ")
|
||||
result.nl(ml) # New Line
|
||||
toPretty(result, node.elems[i], indent, ml,
|
||||
true, newIndent(currIndent, indent, ml))
|
||||
result.nl(ml)
|
||||
result.indent(currIndent)
|
||||
result.add(")")
|
||||
else: result.add("nil")
|
||||
of SCons:
|
||||
if lstArr: result.indent(currIndent)
|
||||
result.add("(")
|
||||
toPretty(result, node.car, indent, ml,
|
||||
true, newIndent(currIndent, indent, ml))
|
||||
result.add(" . ")
|
||||
toPretty(result, node.cdr, indent, ml,
|
||||
true, newIndent(currIndent, indent, ml))
|
||||
result.add(")")
|
||||
|
||||
proc pretty*(node: SexpNode, indent = 2): string =
|
||||
## Converts `node` to its Sexp Representation, with indentation and
|
||||
## on multiple lines.
|
||||
result = ""
|
||||
toPretty(result, node, indent)
|
||||
|
||||
proc `$`*(node: SexpNode): string =
|
||||
## Converts `node` to its SEXP Representation on one line.
|
||||
result = ""
|
||||
toPretty(result, node, 0, false)
|
||||
|
||||
iterator items*(node: SexpNode): SexpNode =
|
||||
## Iterator for the items of `node`. `node` has to be a SList.
|
||||
assert node.kind == SList
|
||||
for i in items(node.elems):
|
||||
yield i
|
||||
|
||||
iterator mitems*(node: var SexpNode): var SexpNode =
|
||||
## Iterator for the items of `node`. `node` has to be a SList. Items can be
|
||||
## modified.
|
||||
assert node.kind == SList
|
||||
for i in mitems(node.elems):
|
||||
yield i
|
||||
|
||||
proc eat(p: var SexpParser, tok: TTokKind) =
|
||||
if p.tok == tok: discard getTok(p)
|
||||
else: raiseParseErr(p, tokToStr[tok])
|
||||
|
||||
proc parseSexp(p: var SexpParser): SexpNode =
|
||||
## Parses SEXP from a SEXP Parser `p`.
|
||||
case p.tok
|
||||
of tkString:
|
||||
# we capture 'p.a' here, so we need to give it a fresh buffer afterwards:
|
||||
result = newSStringMove(p.a)
|
||||
p.a = ""
|
||||
discard getTok(p)
|
||||
of tkInt:
|
||||
result = newSInt(parseBiggestInt(p.a))
|
||||
discard getTok(p)
|
||||
of tkFloat:
|
||||
result = newSFloat(parseFloat(p.a))
|
||||
discard getTok(p)
|
||||
of tkNil:
|
||||
result = newSNil()
|
||||
discard getTok(p)
|
||||
of tkSymbol:
|
||||
result = newSSymbolMove(p.a)
|
||||
p.a = ""
|
||||
discard getTok(p)
|
||||
of tkParensLe:
|
||||
result = newSList()
|
||||
discard getTok(p)
|
||||
while p.tok notin {tkParensRi, tkDot}:
|
||||
result.add(parseSexp(p))
|
||||
if p.tok != tkSpace: break
|
||||
discard getTok(p)
|
||||
if p.tok == tkDot:
|
||||
eat(p, tkDot)
|
||||
eat(p, tkSpace)
|
||||
result.add(parseSexp(p))
|
||||
result = newSCons(result[0], result[1])
|
||||
eat(p, tkParensRi)
|
||||
of tkSpace, tkDot, tkError, tkParensRi, tkEof:
|
||||
raiseParseErr(p, "(")
|
||||
|
||||
proc open*(my: var SexpParser, input: Stream) =
|
||||
## initializes the parser with an input stream.
|
||||
lexbase.open(my, input)
|
||||
my.kind = sexpError
|
||||
my.a = ""
|
||||
|
||||
proc parseSexp*(s: Stream): SexpNode =
|
||||
## Parses from a buffer `s` into a `SexpNode`.
|
||||
var p: SexpParser
|
||||
p.open(s)
|
||||
discard getTok(p) # read first token
|
||||
result = p.parseSexp()
|
||||
p.close()
|
||||
|
||||
proc parseSexp*(buffer: string): SexpNode =
|
||||
## Parses Sexp from `buffer`.
|
||||
result = parseSexp(newStringStream(buffer))
|
||||
|
||||
when isMainModule:
|
||||
let testSexp = parseSexp("""(1 (98 2) nil (2) foobar "foo" 9.234)""")
|
||||
assert(testSexp[0].getNum == 1)
|
||||
assert(testSexp[1][0].getNum == 98)
|
||||
assert(testSexp[2].getElems == @[])
|
||||
assert(testSexp[4].getSymbol == "foobar")
|
||||
assert(testSexp[5].getStr == "foo")
|
||||
|
||||
let alist = parseSexp("""((1 . 2) (2 . "foo"))""")
|
||||
assert(alist[0].getCons.car.getNum == 1)
|
||||
assert(alist[0].getCons.cdr.getNum == 2)
|
||||
assert(alist[1].getCons.cdr.getStr == "foo")
|
||||
|
||||
# Generator:
|
||||
var j = convertSexp([true, false, "foobar", [1, 2, "baz"]])
|
||||
assert($j == """(t nil "foobar" (1 2 "baz"))""")
|
||||
@@ -1,323 +0,0 @@
|
||||
# Tester for nimsuggest.
|
||||
# Every test file can have a #[!]# comment that is deleted from the input
|
||||
# before 'nimsuggest' is invoked to ensure this token doesn't make a
|
||||
# crucial difference for Nim's parser.
|
||||
|
||||
import os, osproc, strutils, streams, re, sexp, net
|
||||
|
||||
type
|
||||
Test = object
|
||||
cmd, dest: string
|
||||
startup: seq[string]
|
||||
script: seq[(string, string)]
|
||||
|
||||
const
|
||||
curDir = when defined(windows): "" else: ""
|
||||
DummyEof = "!EOF!"
|
||||
|
||||
template tpath(): untyped = getAppDir() / "tests"
|
||||
|
||||
proc parseTest(filename: string; epcMode=false): Test =
|
||||
const cursorMarker = "#[!]#"
|
||||
let nimsug = curDir & addFileExt("nimsuggest", ExeExt)
|
||||
let libpath = findExe("nim").splitFile().dir /../ "lib"
|
||||
result.dest = getTempDir() / extractFilename(filename)
|
||||
result.cmd = nimsug & " --tester " & result.dest
|
||||
result.script = @[]
|
||||
result.startup = @[]
|
||||
var tmp = open(result.dest, fmWrite)
|
||||
var specSection = 0
|
||||
var markers = newSeq[string]()
|
||||
var i = 1
|
||||
for x in lines(filename):
|
||||
let marker = x.find(cursorMarker)+1
|
||||
if marker > 0:
|
||||
if epcMode:
|
||||
markers.add "(\"" & filename & "\" " & $i & " " & $marker & " \"" & result.dest & "\")"
|
||||
else:
|
||||
markers.add "\"" & filename & "\";\"" & result.dest & "\":" & $i & ":" & $marker
|
||||
tmp.writeLine x.replace(cursorMarker, "")
|
||||
else:
|
||||
tmp.writeLine x
|
||||
if x.contains("""""""""):
|
||||
inc specSection
|
||||
elif specSection == 1:
|
||||
if x.startsWith("$nimsuggest"):
|
||||
result.cmd = x % ["nimsuggest", nimsug, "file", filename, "lib", libpath]
|
||||
elif x.startsWith("!"):
|
||||
if result.cmd.len == 0:
|
||||
result.startup.add x
|
||||
else:
|
||||
result.script.add((x, ""))
|
||||
elif x.startsWith(">"):
|
||||
# since 'markers' here are not complete yet, we do the $substitutions
|
||||
# afterwards
|
||||
result.script.add((x.substr(1).replaceWord("$path", tpath()), ""))
|
||||
elif x.len > 0:
|
||||
# expected output line:
|
||||
let x = x % ["file", filename, "lib", libpath]
|
||||
result.script[^1][1].add x.replace(";;", "\t") & '\L'
|
||||
# else: ignore empty lines for better readability of the specs
|
||||
inc i
|
||||
tmp.close()
|
||||
# now that we know the markers, substitute them:
|
||||
for a in mitems(result.script):
|
||||
a[0] = a[0] % markers
|
||||
|
||||
proc parseCmd(c: string): seq[string] =
|
||||
# we don't support double quotes for now so that
|
||||
# we can later support them properly with escapes and stuff.
|
||||
result = @[]
|
||||
var i = 0
|
||||
var a = ""
|
||||
while true:
|
||||
setLen(a, 0)
|
||||
# eat all delimiting whitespace
|
||||
while c[i] in {' ', '\t', '\l', '\r'}: inc(i)
|
||||
case c[i]
|
||||
of '"': raise newException(ValueError, "double quotes not yet supported: " & c)
|
||||
of '\'':
|
||||
var delim = c[i]
|
||||
inc(i) # skip ' or "
|
||||
while c[i] != '\0' and c[i] != delim:
|
||||
add a, c[i]
|
||||
inc(i)
|
||||
if c[i] != '\0': inc(i)
|
||||
of '\0': break
|
||||
else:
|
||||
while c[i] > ' ':
|
||||
add(a, c[i])
|
||||
inc(i)
|
||||
add(result, a)
|
||||
|
||||
proc edit(tmpfile: string; x: seq[string]) =
|
||||
if x.len != 3 and x.len != 4:
|
||||
quit "!edit takes two or three arguments"
|
||||
let f = if x.len >= 4: tpath() / x[3] else: tmpfile
|
||||
try:
|
||||
let content = readFile(f)
|
||||
let newcontent = content.replace(x[1], x[2])
|
||||
if content == newcontent:
|
||||
quit "wrong test case: edit had no effect"
|
||||
writeFile(f, newcontent)
|
||||
except IOError:
|
||||
quit "cannot edit file " & tmpfile
|
||||
|
||||
proc exec(x: seq[string]) =
|
||||
if x.len != 2: quit "!exec takes one argument"
|
||||
if execShellCmd(x[1]) != 0:
|
||||
quit "External program failed " & x[1]
|
||||
|
||||
proc copy(x: seq[string]) =
|
||||
if x.len != 3: quit "!copy takes two arguments"
|
||||
let rel = tpath()
|
||||
copyFile(rel / x[1], rel / x[2])
|
||||
|
||||
proc del(x: seq[string]) =
|
||||
if x.len != 2: quit "!del takes one argument"
|
||||
removeFile(tpath() / x[1])
|
||||
|
||||
proc runCmd(cmd, dest: string): bool =
|
||||
result = cmd[0] == '!'
|
||||
if not result: return
|
||||
let x = cmd.parseCmd()
|
||||
case x[0]
|
||||
of "!edit":
|
||||
edit(dest, x)
|
||||
of "!exec":
|
||||
exec(x)
|
||||
of "!copy":
|
||||
copy(x)
|
||||
of "!del":
|
||||
del(x)
|
||||
else:
|
||||
quit "unkown command: " & cmd
|
||||
|
||||
proc smartCompare(pattern, x: string): bool =
|
||||
if pattern.contains('*'):
|
||||
result = match(x, re(escapeRe(pattern).replace("\\x2A","(.*)"), {}))
|
||||
|
||||
proc sendEpcStr(socket: Socket; cmd: string) =
|
||||
let s = cmd.find(' ')
|
||||
doAssert s > 0
|
||||
var args = cmd.substr(s+1)
|
||||
if not args.startsWith("("): args = escapeJson(args)
|
||||
let c = "(call 567 " & cmd.substr(0, s) & args & ")"
|
||||
socket.send toHex(c.len, 6)
|
||||
socket.send c
|
||||
|
||||
proc recvEpc(socket: Socket): string =
|
||||
var L = newStringOfCap(6)
|
||||
if socket.recv(L, 6) != 6:
|
||||
raise newException(ValueError, "recv A failed #" & L & "#")
|
||||
let x = parseHexInt(L)
|
||||
result = newString(x)
|
||||
if socket.recv(result, x) != x:
|
||||
raise newException(ValueError, "recv B failed")
|
||||
|
||||
proc sexpToAnswer(s: SexpNode): string =
|
||||
result = ""
|
||||
doAssert s.kind == SList
|
||||
doAssert s.len >= 3
|
||||
let m = s[2]
|
||||
if m.kind != SList:
|
||||
echo s
|
||||
doAssert m.kind == SList
|
||||
for a in m:
|
||||
doAssert a.kind == SList
|
||||
#s.section,
|
||||
#s.symkind,
|
||||
#s.qualifiedPath.map(newSString),
|
||||
#s.filePath,
|
||||
#s.forth,
|
||||
#s.line,
|
||||
#s.column,
|
||||
#s.doc
|
||||
if a.len >= 9:
|
||||
let section = a[0].getStr
|
||||
let symk = a[1].getStr
|
||||
let qp = a[2]
|
||||
let file = a[3].getStr
|
||||
let typ = a[4].getStr
|
||||
let line = a[5].getNum
|
||||
let col = a[6].getNum
|
||||
let doc = a[7].getStr.escape
|
||||
result.add section
|
||||
result.add '\t'
|
||||
result.add symk
|
||||
result.add '\t'
|
||||
var i = 0
|
||||
if qp.kind == SList:
|
||||
for aa in qp:
|
||||
if i > 0: result.add '.'
|
||||
result.add aa.getStr
|
||||
inc i
|
||||
result.add '\t'
|
||||
result.add typ
|
||||
result.add '\t'
|
||||
result.add file
|
||||
result.add '\t'
|
||||
result.add line
|
||||
result.add '\t'
|
||||
result.add col
|
||||
result.add '\t'
|
||||
result.add doc
|
||||
result.add '\t'
|
||||
result.add a[8].getNum
|
||||
if a.len >= 10:
|
||||
result.add '\t'
|
||||
result.add a[9].getStr
|
||||
result.add '\L'
|
||||
|
||||
proc doReport(filename, answer, resp: string; report: var string) =
|
||||
if resp != answer and not smartCompare(resp, answer):
|
||||
report.add "\nTest failed: " & filename
|
||||
var hasDiff = false
|
||||
for i in 0..min(resp.len-1, answer.len-1):
|
||||
if resp[i] != answer[i]:
|
||||
report.add "\n Expected: " & resp.substr(i, i+200)
|
||||
report.add "\n But got: " & answer.substr(i, i+200)
|
||||
hasDiff = true
|
||||
break
|
||||
if not hasDiff:
|
||||
report.add "\n Expected: " & resp
|
||||
report.add "\n But got: " & answer
|
||||
|
||||
proc runEpcTest(filename: string): int =
|
||||
let s = parseTest(filename, true)
|
||||
for cmd in s.startup:
|
||||
if not runCmd(cmd, s.dest):
|
||||
quit "invalid command: " & cmd
|
||||
let epccmd = s.cmd.replace("--tester", "--epc --v2 --log")
|
||||
let cl = parseCmdLine(epccmd)
|
||||
var p = startProcess(command=cl[0], args=cl[1 .. ^1],
|
||||
options={poStdErrToStdOut, poUsePath,
|
||||
poInteractive, poDemon})
|
||||
let outp = p.outputStream
|
||||
let inp = p.inputStream
|
||||
var report = ""
|
||||
var socket = newSocket()
|
||||
try:
|
||||
# read the port number:
|
||||
when defined(posix):
|
||||
var a = newStringOfCap(120)
|
||||
discard outp.readLine(a)
|
||||
else:
|
||||
var i = 0
|
||||
while not osproc.hasData(p) and i < 100:
|
||||
os.sleep(50)
|
||||
inc i
|
||||
let a = outp.readAll().strip()
|
||||
let port = parseInt(a)
|
||||
socket.connect("localhost", Port(port))
|
||||
for req, resp in items(s.script):
|
||||
if not runCmd(req, s.dest):
|
||||
socket.sendEpcStr(req)
|
||||
let sx = parseSexp(socket.recvEpc())
|
||||
if not req.startsWith("mod "):
|
||||
let answer = sexpToAnswer(sx)
|
||||
doReport(filename, answer, resp, report)
|
||||
finally:
|
||||
socket.sendEpcStr "return arg"
|
||||
close(p)
|
||||
if report.len > 0:
|
||||
echo "==== EPC ========================================"
|
||||
echo report
|
||||
result = report.len
|
||||
|
||||
proc runTest(filename: string): int =
|
||||
let s = parseTest filename
|
||||
for cmd in s.startup:
|
||||
if not runCmd(cmd, s.dest):
|
||||
quit "invalid command: " & cmd
|
||||
let cl = parseCmdLine(s.cmd)
|
||||
var p = startProcess(command=cl[0], args=cl[1 .. ^1],
|
||||
options={poStdErrToStdOut, poUsePath,
|
||||
poInteractive, poDemon})
|
||||
let outp = p.outputStream
|
||||
let inp = p.inputStream
|
||||
var report = ""
|
||||
var a = newStringOfCap(120)
|
||||
try:
|
||||
# read and ignore anything nimsuggest says at startup:
|
||||
while outp.readLine(a):
|
||||
if a == DummyEof: break
|
||||
for req, resp in items(s.script):
|
||||
if not runCmd(req, s.dest):
|
||||
inp.writeLine(req)
|
||||
inp.flush()
|
||||
var answer = ""
|
||||
while outp.readLine(a):
|
||||
if a == DummyEof: break
|
||||
answer.add a
|
||||
answer.add '\L'
|
||||
doReport(filename, answer, resp, report)
|
||||
finally:
|
||||
inp.writeLine("quit")
|
||||
inp.flush()
|
||||
close(p)
|
||||
if report.len > 0:
|
||||
echo "==== STDIN ======================================"
|
||||
echo report
|
||||
result = report.len
|
||||
|
||||
proc main() =
|
||||
var failures = 0
|
||||
if os.paramCount() > 0:
|
||||
let f = os.paramStr(1)
|
||||
let x = getAppDir() / f
|
||||
let xx = expandFilename x
|
||||
failures += runTest(xx)
|
||||
failures += runEpcTest(xx)
|
||||
else:
|
||||
for x in walkFiles(getAppDir() / "tests/t*.nim"):
|
||||
echo "Test ", x
|
||||
let xx = expandFilename x
|
||||
when not defined(windows):
|
||||
# XXX Windows IO redirection seems bonkers:
|
||||
failures += runTest(xx)
|
||||
failures += runEpcTest(xx)
|
||||
if failures > 0:
|
||||
quit 1
|
||||
|
||||
main()
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
type
|
||||
Foo* = object
|
||||
x*, y*: int
|
||||
@@ -1,9 +0,0 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
type
|
||||
Foo* = object
|
||||
x*, y*: int
|
||||
z*: string
|
||||
@@ -1,27 +0,0 @@
|
||||
# test we get some suggestion at the end of the file
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
type
|
||||
|
||||
|
||||
template foo() =
|
||||
|
||||
proc main =
|
||||
|
||||
#[!]#
|
||||
discard """
|
||||
$nimsuggest --tester $file
|
||||
>chk $1
|
||||
chk;;skUnknown;;;;Hint;;???;;-1;;-1;;"tchk1 [Processing]";;0
|
||||
chk;;skUnknown;;;;Error;;$file;;12;;0;;"identifier expected, but found \'keyword template\'";;0
|
||||
chk;;skUnknown;;;;Error;;$file;;14;;0;;"complex statement requires indentation";;0
|
||||
chk;;skUnknown;;;;Error;;$file;;12;;0;;"implementation of \'foo\' expected";;0
|
||||
chk;;skUnknown;;;;Error;;$file;;17;;0;;"invalid indentation";;0
|
||||
chk;;skUnknown;;;;Hint;;$file;;12;;9;;"\'foo\' is declared but not used [XDeclaredButNotUsed]";;0
|
||||
chk;;skUnknown;;;;Hint;;$file;;14;;5;;"\'tchk1.main()\' is declared but not used [XDeclaredButNotUsed]";;0
|
||||
"""
|
||||
@@ -1,12 +0,0 @@
|
||||
# test we get some suggestion at the end of the file
|
||||
|
||||
discard """
|
||||
$nimsuggest --tester $file
|
||||
>sug $1
|
||||
sug;;skProc;;tcursor_at_end.main;;proc ();;$file;;10;;5;;"";;*
|
||||
"""
|
||||
|
||||
|
||||
proc main = discard
|
||||
|
||||
#[!]#
|
||||
@@ -1,16 +0,0 @@
|
||||
discard """
|
||||
$nimsuggest --tester $file
|
||||
>def $1
|
||||
def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, locks: 0.};;$file;;9;;5;;"Return hello";;100
|
||||
>def $1
|
||||
def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, locks: 0.};;$file;;9;;5;;"Return hello";;100
|
||||
"""
|
||||
|
||||
proc hello(): string =
|
||||
## Return hello
|
||||
"Hello"
|
||||
|
||||
hel#[!]#lo()
|
||||
|
||||
# v uncompleted id for sug (13,2)
|
||||
he
|
||||
@@ -1,14 +0,0 @@
|
||||
discard """
|
||||
$nimsuggest --tester $file
|
||||
>sug $1
|
||||
sug;;skField;;x;;int;;$file;;11;;4;;"";;100;;None
|
||||
sug;;skField;;y;;int;;$file;;11;;7;;"";;100;;None
|
||||
sug;;skProc;;tdot1.main;;proc (f: Foo);;$file;;13;;5;;"";;100;;None
|
||||
"""
|
||||
|
||||
type
|
||||
Foo = object
|
||||
x, y: int
|
||||
|
||||
proc main(f: Foo) =
|
||||
if f.#[!]#:
|
||||
@@ -1,29 +0,0 @@
|
||||
# Test basic editing. We replace the 'false' by 'true' to
|
||||
# see whether then the z field is suggested.
|
||||
|
||||
const zField = 0i32
|
||||
|
||||
type
|
||||
Foo = object
|
||||
x, y: int
|
||||
when zField == 1i32:
|
||||
z: string
|
||||
|
||||
proc main(f: Foo) =
|
||||
f.#[!]#
|
||||
|
||||
# the tester supports the spec section at the bottom of the file and
|
||||
# this way, the line numbers more often stay the same
|
||||
discard """
|
||||
$nimsuggest --tester $file
|
||||
>sug $1
|
||||
sug;;skField;;x;;int;;$file;;8;;4;;"";;100;;None
|
||||
sug;;skField;;y;;int;;$file;;8;;7;;"";;100;;None
|
||||
sug;;skProc;;tdot2.main;;proc (f: Foo);;$file;;12;;5;;"";;100;;None
|
||||
!edit 0i32 1i32
|
||||
>sug $1
|
||||
sug;;skField;;x;;int;;$file;;8;;4;;"";;100;;None
|
||||
sug;;skField;;y;;int;;$file;;8;;7;;"";;100;;None
|
||||
sug;;skField;;z;;string;;$file;;10;;6;;"";;100;;None
|
||||
sug;;skProc;;tdot2.main;;proc (f: Foo);;$file;;12;;5;;"";;100;;None
|
||||
"""
|
||||
@@ -1,27 +0,0 @@
|
||||
# Test basic module dependency recompilations.
|
||||
|
||||
import dep
|
||||
|
||||
proc main(f: Foo) =
|
||||
f.#[!]#
|
||||
|
||||
# the tester supports the spec section at the bottom of the file and
|
||||
# this way, the line numbers more often stay the same
|
||||
|
||||
discard """
|
||||
!copy dep_v1.nim dep.nim
|
||||
$nimsuggest --tester $file
|
||||
>sug $1
|
||||
sug;;skField;;x;;int;;*dep.nim;;8;;4;;"";;100;;None
|
||||
sug;;skField;;y;;int;;*dep.nim;;8;;8;;"";;100;;None
|
||||
sug;;skProc;;tdot3.main;;proc (f: Foo);;$file;;5;;5;;"";;100;;None
|
||||
|
||||
!copy dep_v2.nim dep.nim
|
||||
>mod $path/dep.nim
|
||||
>sug $1
|
||||
sug;;skField;;x;;int;;*dep.nim;;8;;4;;"";;100;;None
|
||||
sug;;skField;;y;;int;;*dep.nim;;8;;8;;"";;100;;None
|
||||
sug;;skField;;z;;string;;*dep.nim;;9;;4;;"";;100;;None
|
||||
sug;;skProc;;tdot3.main;;proc (f: Foo);;$file;;5;;5;;"";;100;;None
|
||||
!del dep.nim
|
||||
"""
|
||||
@@ -1,7 +0,0 @@
|
||||
discard """
|
||||
$nimsuggest --tester compiler/nim.nim
|
||||
>def compiler/semexprs.nim:13:50
|
||||
def;;skType;;ast.PSym;;PSym;;*ast.nim;;669;;2;;"";;100
|
||||
>def compiler/semexprs.nim:13:50
|
||||
def;;skType;;ast.PSym;;PSym;;*ast.nim;;669;;2;;"";;100
|
||||
"""
|
||||
@@ -1,14 +0,0 @@
|
||||
|
||||
var x: ptr int
|
||||
|
||||
proc foo(y: ptr int) =
|
||||
discard
|
||||
|
||||
x.#[!]#
|
||||
|
||||
discard """
|
||||
$nimsuggest --tester $file
|
||||
>sug $1
|
||||
sug;;skProc;;tno_deref.foo;;proc (y: ptr int)*;;$file;;4;;5;;"";;100;;None
|
||||
*
|
||||
"""
|
||||
@@ -1,9 +0,0 @@
|
||||
discard """
|
||||
$nimsuggest --tester lib/pure/strutils.nim
|
||||
>def lib/pure/strutils.nim:2300:6
|
||||
def;;skTemplate;;system.doAssert;;proc (cond: bool, msg: string): typed;;*/lib/system.nim;;*;;9;;"same as `assert` but is always turned on and not affected by the\x0A``--assertions`` command line switch.";;100
|
||||
"""
|
||||
|
||||
# Line 2300 in strutils.nim is doAssert and this is unlikely to change
|
||||
# soon since there are a whole lot of doAsserts there.
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
# test we only get suggestions, not error messages:
|
||||
|
||||
import tables, sets, parsecfg
|
||||
|
||||
type X = object
|
||||
|
||||
proc main =
|
||||
# bug #52
|
||||
var
|
||||
set0 = initSet[int]()
|
||||
set1 = initSet[X]()
|
||||
set2 = initSet[ref int]()
|
||||
|
||||
map0 = initTable[int, int]()
|
||||
map1 = initOrderedTable[string, int]()
|
||||
cfg = loadConfig("file")
|
||||
map0.#[!]#
|
||||
|
||||
discard """
|
||||
$nimsuggest --tester $file
|
||||
>sug $1
|
||||
sug;;skProc;;tables.getOrDefault;;proc (t: Table[getOrDefault.A, getOrDefault.B], key: A): B;;$lib/pure/collections/tables.nim;;178;;5;;"";;100;;None
|
||||
sug;;skProc;;tables.hasKey;;proc (t: Table[hasKey.A, hasKey.B], key: A): bool;;$lib/pure/collections/tables.nim;;233;;5;;"returns true iff `key` is in the table `t`.";;100;;None
|
||||
sug;;skProc;;tables.add;;proc (t: var Table[add.A, add.B], key: A, val: B);;$lib/pure/collections/tables.nim;;297;;5;;"puts a new (key, value)-pair into `t` even if ``t[key]`` already exists.";;100;;None
|
||||
sug;;skIterator;;tables.allValues;;iterator (t: Table[allValues.A, allValues.B], key: A): B{.inline.};;$lib/pure/collections/tables.nim;;225;;9;;"iterates over any value in the table `t` that belongs to the given `key`.";;100;;None
|
||||
sug;;skProc;;tables.clear;;proc (t: var Table[clear.A, clear.B]);;$lib/pure/collections/tables.nim;;121;;5;;"Resets the table so that it is empty.";;100;;None
|
||||
*
|
||||
"""
|
||||
@@ -1,213 +0,0 @@
|
||||
|
||||
import macros
|
||||
|
||||
macro class*(head, body: untyped): untyped =
|
||||
# The macro is immediate, since all its parameters are untyped.
|
||||
# This means, it doesn't resolve identifiers passed to it.
|
||||
|
||||
var typeName, baseName: NimNode
|
||||
|
||||
# flag if object should be exported
|
||||
var exported: bool
|
||||
|
||||
if head.kind == nnkInfix and head[0].ident == !"of":
|
||||
# `head` is expression `typeName of baseClass`
|
||||
# echo head.treeRepr
|
||||
# --------------------
|
||||
# Infix
|
||||
# Ident !"of"
|
||||
# Ident !"Animal"
|
||||
# Ident !"RootObj"
|
||||
typeName = head[1]
|
||||
baseName = head[2]
|
||||
|
||||
elif head.kind == nnkInfix and head[0].ident == !"*" and
|
||||
head[2].kind == nnkPrefix and head[2][0].ident == !"of":
|
||||
# `head` is expression `typeName* of baseClass`
|
||||
# echo head.treeRepr
|
||||
# --------------------
|
||||
# Infix
|
||||
# Ident !"*"
|
||||
# Ident !"Animal"
|
||||
# Prefix
|
||||
# Ident !"of"
|
||||
# Ident !"RootObj"
|
||||
typeName = head[1]
|
||||
baseName = head[2][1]
|
||||
exported = true
|
||||
|
||||
else:
|
||||
quit "Invalid node: " & head.lispRepr
|
||||
|
||||
# The following prints out the AST structure:
|
||||
#
|
||||
# import macros
|
||||
# dumptree:
|
||||
# type X = ref object of Y
|
||||
# z: int
|
||||
# --------------------
|
||||
# StmtList
|
||||
# TypeSection
|
||||
# TypeDef
|
||||
# Ident !"X"
|
||||
# Empty
|
||||
# RefTy
|
||||
# ObjectTy
|
||||
# Empty
|
||||
# OfInherit
|
||||
# Ident !"Y"
|
||||
# RecList
|
||||
# IdentDefs
|
||||
# Ident !"z"
|
||||
# Ident !"int"
|
||||
# Empty
|
||||
|
||||
# create a type section in the result
|
||||
result =
|
||||
if exported:
|
||||
# mark `typeName` with an asterisk
|
||||
quote do:
|
||||
type `typeName`* = ref object of `baseName`
|
||||
else:
|
||||
quote do:
|
||||
type `typeName` = ref object of `baseName`
|
||||
|
||||
# echo treeRepr(body)
|
||||
# --------------------
|
||||
# StmtList
|
||||
# VarSection
|
||||
# IdentDefs
|
||||
# Ident !"name"
|
||||
# Ident !"string"
|
||||
# Empty
|
||||
# IdentDefs
|
||||
# Ident !"age"
|
||||
# Ident !"int"
|
||||
# Empty
|
||||
# MethodDef
|
||||
# Ident !"vocalize"
|
||||
# Empty
|
||||
# Empty
|
||||
# FormalParams
|
||||
# Ident !"string"
|
||||
# Empty
|
||||
# Empty
|
||||
# StmtList
|
||||
# StrLit ...
|
||||
# MethodDef
|
||||
# Ident !"age_human_yrs"
|
||||
# Empty
|
||||
# Empty
|
||||
# FormalParams
|
||||
# Ident !"int"
|
||||
# Empty
|
||||
# Empty
|
||||
# StmtList
|
||||
# DotExpr
|
||||
# Ident !"this"
|
||||
# Ident !"age"
|
||||
|
||||
# var declarations will be turned into object fields
|
||||
var recList = newNimNode(nnkRecList)
|
||||
|
||||
# expected name of constructor
|
||||
let ctorName = newIdentNode("new" & $typeName)
|
||||
|
||||
# Iterate over the statements, adding `this: T`
|
||||
# to the parameters of functions, unless the
|
||||
# function is a constructor
|
||||
for node in body.children:
|
||||
case node.kind:
|
||||
|
||||
of nnkMethodDef, nnkProcDef:
|
||||
# check if it is the ctor proc
|
||||
if node.name.kind != nnkAccQuoted and node.name.basename == ctorName:
|
||||
# specify the return type of the ctor proc
|
||||
node.params[0] = typeName
|
||||
else:
|
||||
# inject `self: T` into the arguments
|
||||
node.params.insert(1, newIdentDefs(ident("self"), typeName))
|
||||
result.add(node)
|
||||
|
||||
of nnkVarSection:
|
||||
# variables get turned into fields of the type.
|
||||
for n in node.children:
|
||||
recList.add(n)
|
||||
|
||||
else:
|
||||
result.add(node)
|
||||
|
||||
# Inspect the tree structure:
|
||||
#
|
||||
# echo result.treeRepr
|
||||
# --------------------
|
||||
# StmtList
|
||||
# TypeSection
|
||||
# TypeDef
|
||||
# Ident !"Animal"
|
||||
# Empty
|
||||
# RefTy
|
||||
# ObjectTy
|
||||
# Empty
|
||||
# OfInherit
|
||||
# Ident !"RootObj"
|
||||
# Empty <= We want to replace this
|
||||
# MethodDef
|
||||
# ...
|
||||
|
||||
result[0][0][2][0][2] = recList
|
||||
|
||||
# Lets inspect the human-readable version of the output
|
||||
#echo repr(result)
|
||||
|
||||
# ---
|
||||
|
||||
class Animal of RootObj:
|
||||
var name: string
|
||||
var age: int
|
||||
method vocalize: string {.base.} = "..." # use `base` pragma to annonate base methods
|
||||
method age_human_yrs: int {.base.} = self.age # `this` is injected
|
||||
proc `$`: string = "animal:" & self.name & ":" & $self.age
|
||||
|
||||
class Dog of Animal:
|
||||
method vocalize: string = "woof"
|
||||
method age_human_yrs: int = self.age * 7
|
||||
proc `$`: string = "dog:" & self.name & ":" & $self.age
|
||||
|
||||
class Cat of Animal:
|
||||
method vocalize: string = "meow"
|
||||
proc `$`: string = "cat:" & self.name & ":" & $self.age
|
||||
|
||||
class Rabbit of Animal:
|
||||
proc newRabbit(name: string, age: int) = # the constructor doesn't need a return type
|
||||
result = Rabbit(name: name, age: age)
|
||||
method vocalize: string = "meep"
|
||||
proc `$`: string =
|
||||
self.#[!]#
|
||||
result = "rabbit:" & self.name & ":" & $self.age
|
||||
|
||||
# ---
|
||||
|
||||
var animals: seq[Animal] = @[]
|
||||
animals.add(Dog(name: "Sparky", age: 10))
|
||||
animals.add(Cat(name: "Mitten", age: 10))
|
||||
|
||||
for a in animals:
|
||||
echo a.vocalize()
|
||||
echo a.age_human_yrs()
|
||||
|
||||
let r = newRabbit("Fluffy", 3)
|
||||
echo r.vocalize()
|
||||
echo r.age_human_yrs()
|
||||
echo r
|
||||
|
||||
discard """
|
||||
$nimsuggest --tester $file
|
||||
>sug $1
|
||||
sug;;skField;;age;;int;;$file;;167;;6;;"";;100;;None
|
||||
sug;;skField;;name;;string;;$file;;166;;6;;"";;100;;None
|
||||
sug;;skMethod;;twithin_macro.age_human_yrs;;proc (self: Animal): int;;$file;;169;;9;;"";;100;;None
|
||||
sug;;skMethod;;twithin_macro.vocalize;;proc (self: Animal): string;;$file;;168;;9;;"";;100;;None
|
||||
sug;;skMethod;;twithin_macro.vocalize;;proc (self: Rabbit): string;;$file;;184;;9;;"";;100;;None
|
||||
sug;;skMacro;;twithin_macro.class;;proc (head: untyped, body: untyped): untyped{.gcsafe, locks: <unknown>.};;$file;;4;;6;;"";;50;;None*
|
||||
"""
|
||||
@@ -1,209 +0,0 @@
|
||||
|
||||
import macros
|
||||
|
||||
macro class*(head, body: untyped): untyped =
|
||||
# The macro is immediate, since all its parameters are untyped.
|
||||
# This means, it doesn't resolve identifiers passed to it.
|
||||
|
||||
var typeName, baseName: NimNode
|
||||
|
||||
# flag if object should be exported
|
||||
var exported: bool
|
||||
|
||||
if head.kind == nnkInfix and head[0].ident == !"of":
|
||||
# `head` is expression `typeName of baseClass`
|
||||
# echo head.treeRepr
|
||||
# --------------------
|
||||
# Infix
|
||||
# Ident !"of"
|
||||
# Ident !"Animal"
|
||||
# Ident !"RootObj"
|
||||
typeName = head[1]
|
||||
baseName = head[2]
|
||||
|
||||
elif head.kind == nnkInfix and head[0].ident == !"*" and
|
||||
head[2].kind == nnkPrefix and head[2][0].ident == !"of":
|
||||
# `head` is expression `typeName* of baseClass`
|
||||
# echo head.treeRepr
|
||||
# --------------------
|
||||
# Infix
|
||||
# Ident !"*"
|
||||
# Ident !"Animal"
|
||||
# Prefix
|
||||
# Ident !"of"
|
||||
# Ident !"RootObj"
|
||||
typeName = head[1]
|
||||
baseName = head[2][1]
|
||||
exported = true
|
||||
|
||||
else:
|
||||
quit "Invalid node: " & head.lispRepr
|
||||
|
||||
# The following prints out the AST structure:
|
||||
#
|
||||
# import macros
|
||||
# dumptree:
|
||||
# type X = ref object of Y
|
||||
# z: int
|
||||
# --------------------
|
||||
# StmtList
|
||||
# TypeSection
|
||||
# TypeDef
|
||||
# Ident !"X"
|
||||
# Empty
|
||||
# RefTy
|
||||
# ObjectTy
|
||||
# Empty
|
||||
# OfInherit
|
||||
# Ident !"Y"
|
||||
# RecList
|
||||
# IdentDefs
|
||||
# Ident !"z"
|
||||
# Ident !"int"
|
||||
# Empty
|
||||
|
||||
# create a type section in the result
|
||||
result =
|
||||
if exported:
|
||||
# mark `typeName` with an asterisk
|
||||
quote do:
|
||||
type `typeName`* = ref object of `baseName`
|
||||
else:
|
||||
quote do:
|
||||
type `typeName` = ref object of `baseName`
|
||||
|
||||
# echo treeRepr(body)
|
||||
# --------------------
|
||||
# StmtList
|
||||
# VarSection
|
||||
# IdentDefs
|
||||
# Ident !"name"
|
||||
# Ident !"string"
|
||||
# Empty
|
||||
# IdentDefs
|
||||
# Ident !"age"
|
||||
# Ident !"int"
|
||||
# Empty
|
||||
# MethodDef
|
||||
# Ident !"vocalize"
|
||||
# Empty
|
||||
# Empty
|
||||
# FormalParams
|
||||
# Ident !"string"
|
||||
# Empty
|
||||
# Empty
|
||||
# StmtList
|
||||
# StrLit ...
|
||||
# MethodDef
|
||||
# Ident !"age_human_yrs"
|
||||
# Empty
|
||||
# Empty
|
||||
# FormalParams
|
||||
# Ident !"int"
|
||||
# Empty
|
||||
# Empty
|
||||
# StmtList
|
||||
# DotExpr
|
||||
# Ident !"this"
|
||||
# Ident !"age"
|
||||
|
||||
# var declarations will be turned into object fields
|
||||
var recList = newNimNode(nnkRecList)
|
||||
|
||||
# expected name of constructor
|
||||
let ctorName = newIdentNode("new" & $typeName)
|
||||
|
||||
# Iterate over the statements, adding `this: T`
|
||||
# to the parameters of functions, unless the
|
||||
# function is a constructor
|
||||
for node in body.children:
|
||||
case node.kind:
|
||||
|
||||
of nnkMethodDef, nnkProcDef:
|
||||
# check if it is the ctor proc
|
||||
if node.name.kind != nnkAccQuoted and node.name.basename == ctorName:
|
||||
# specify the return type of the ctor proc
|
||||
node.params[0] = typeName
|
||||
else:
|
||||
# inject `self: T` into the arguments
|
||||
node.params.insert(1, newIdentDefs(ident("self"), typeName))
|
||||
result.add(node)
|
||||
|
||||
of nnkVarSection:
|
||||
# variables get turned into fields of the type.
|
||||
for n in node.children:
|
||||
recList.add(n)
|
||||
|
||||
else:
|
||||
result.add(node)
|
||||
|
||||
# Inspect the tree structure:
|
||||
#
|
||||
# echo result.treeRepr
|
||||
# --------------------
|
||||
# StmtList
|
||||
# TypeSection
|
||||
# TypeDef
|
||||
# Ident !"Animal"
|
||||
# Empty
|
||||
# RefTy
|
||||
# ObjectTy
|
||||
# Empty
|
||||
# OfInherit
|
||||
# Ident !"RootObj"
|
||||
# Empty <= We want to replace this
|
||||
# MethodDef
|
||||
# ...
|
||||
|
||||
result[0][0][2][0][2] = recList
|
||||
|
||||
# Lets inspect the human-readable version of the output
|
||||
#echo repr(result)
|
||||
|
||||
# ---
|
||||
|
||||
class Animal of RootObj:
|
||||
var name: string
|
||||
var age: int
|
||||
method vocalize: string {.base.} = "..." # use `base` pragma to annonate base methods
|
||||
method age_human_yrs: int {.base.} = self.age # `this` is injected
|
||||
proc `$`: string = "animal:" & self.name & ":" & $self.age
|
||||
|
||||
class Dog of Animal:
|
||||
method vocalize: string = "woof"
|
||||
method age_human_yrs: int = self.age * 7
|
||||
proc `$`: string = "dog:" & self.name & ":" & $self.age
|
||||
|
||||
class Cat of Animal:
|
||||
method vocalize: string = "meow"
|
||||
proc `$`: string = "cat:" & self.name & ":" & $self.age
|
||||
|
||||
class Rabbit of Animal:
|
||||
proc newRabbit(name: string, age: int) = # the constructor doesn't need a return type
|
||||
result = Rabbit(name: name, age: age)
|
||||
method vocalize: string = "meep"
|
||||
proc `$`: string =
|
||||
self.ag#[!]#
|
||||
result = "rabbit:" & self.name & ":" & $self.age
|
||||
|
||||
# ---
|
||||
|
||||
var animals: seq[Animal] = @[]
|
||||
animals.add(Dog(name: "Sparky", age: 10))
|
||||
animals.add(Cat(name: "Mitten", age: 10))
|
||||
|
||||
for a in animals:
|
||||
echo a.vocalize()
|
||||
echo a.age_human_yrs()
|
||||
|
||||
let r = newRabbit("Fluffy", 3)
|
||||
echo r.vocalize()
|
||||
echo r.age_human_yrs()
|
||||
echo r
|
||||
|
||||
discard """
|
||||
$nimsuggest --tester $file
|
||||
>sug $1
|
||||
sug;;skField;;age;;int;;$file;;167;;6;;"";;100;;Prefix
|
||||
sug;;skMethod;;twithin_macro_prefix.age_human_yrs;;proc (self: Animal): int;;$file;;169;;9;;"";;100;;Prefix
|
||||
"""
|
||||
Reference in New Issue
Block a user