diff options
Diffstat (limited to 'compiler')
-rw-r--r-- | compiler/nimsuggest/nimsuggest.nim | 208 | ||||
-rw-r--r-- | compiler/options.nim | 19 | ||||
-rw-r--r-- | compiler/suggest.nim | 119 |
3 files changed, 264 insertions, 82 deletions
diff --git a/compiler/nimsuggest/nimsuggest.nim b/compiler/nimsuggest/nimsuggest.nim index 8285d81d9..7ba3379c3 100644 --- a/compiler/nimsuggest/nimsuggest.nim +++ b/compiler/nimsuggest/nimsuggest.nim @@ -9,9 +9,17 @@ ## Nimsuggest is a tool that helps to give editors IDE like capabilities. -import strutils, os, parseopt, parseUtils +import strutils, os, parseopt, parseutils, sequtils, net +# 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 options, commands, modules, sem, passes, passaux, msgs, nimconf, - extccomp, condsyms, lists, net, rdstdin + extccomp, condsyms, lists, net, rdstdin, sexp, sigmatch, ast + +when defined(windows): + import winlean +else: + import posix const Usage = """ Nimsuggest - Tool to give every editor IDE like capabilities for Nim @@ -23,17 +31,20 @@ Options: --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 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 var gPort = 6000.Port gAddress = "" - gUseStdin: bool + gMode: Mode const seps = {':', ';', ' ', '\t'} @@ -42,6 +53,9 @@ const "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) @@ -51,7 +65,98 @@ proc parseQuoted(cmd: string; outp: var string; start: int): int = i += parseUntil(cmd, outp, seps, i) result = i -proc action(cmd: string) = +# make sure it's in the same order as the proc below +let order: SexpNode = + sexp(@["section", "symkind", "qualifiedPath", "filePath", "forth", "line", "column", "doc"].map(newSSymbol)) + +proc sexp(s: IdeCmd): SexpNode = sexp($s) + +proc sexp(s: TSymKind): SexpNode = sexp($s) + +proc sexp(s: Suggest): SexpNode = + result = convertSexp([ + s.section, + s.symkind, + s.qualifiedPath.map(newSString), + s.filePath, + s.forth, + s.line, + s.column, + s.doc + ]) + +proc sexp(s: seq[Suggest]): SexpNode = + result = newSList() + for sug in s: + result.add(sexp(sug)) + +proc listEPC(): SexpNode = + 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"]: + let + cmd = sexp(command) + methodDesc = newSList() + methodDesc.add(cmd) + methodDesc.add(argspecs) + methodDesc.add(docstring) + result.add(methodDesc) + +proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int) = + gIdeCmd = cmd + if cmd == ideUse: + modules.resetAllModules() + var isKnownFile = true + let dirtyIdx = file.fileInfoIdx(isKnownFile) + + if dirtyfile.len != 0: msgs.setDirtyFile(dirtyIdx, dirtyfile) + else: msgs.setDirtyFile(dirtyIdx, nil) + + resetModule dirtyIdx + if dirtyIdx != gProjectMainIdx: + resetModule gProjectMainIdx + + gTrackPos = newLineInfo(dirtyIdx, line, col) + gErrorCounter = 0 + if not isKnownFile: + compileProject(dirtyIdx) + else: + compileProject() + +proc executeEPC(cmd: IdeCmd, args: SexpNode) = + 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)) + +proc returnEPC(socket: var Socket, uid: BiggestInt, s: SexpNode, return_symbol = "return") = + let response = $convertSexp([newSSymbol(return_symbol), uid, s]) + socket.send(toHex(len(response), 6)) + socket.send(response) + +proc nextFreePort(server: Socket, host: string, start = 30000): int = + result = start + while true: + try: + server.bindaddr(Port(result), host) + return + except OsError: + when defined(windows): + let checkFor = WSAEADDRINUSE.OSErrorCode + else: + let checkFor = EADDRINUSE.OSErrorCode + if osLastError() != checkFor: + raise getCurrentException() + else: + result += 1 + +proc parseCmdLine(cmd: string) = template toggle(sw) = if sw in gGlobalOptions: excl(gGlobalOptions, sw) @@ -69,9 +174,7 @@ proc action(cmd: string) = of "sug": gIdeCmd = ideSug of "con": gIdeCmd = ideCon of "def": gIdeCmd = ideDef - of "use": - modules.resetAllModules() - gIdeCmd = ideUse + of "use": gIdeCmd = ideUse of "quit": quit() of "debug": toggle optIdeDebug of "terse": toggle optIdeTerse @@ -88,35 +191,20 @@ proc action(cmd: string) = i += skipWhile(cmd, seps, i) i += parseInt(cmd, col, i) - var isKnownFile = true - if orig.len == 0: err() - let dirtyIdx = orig.fileInfoIdx(isKnownFile) - - if dirtyfile.len != 0: msgs.setDirtyFile(dirtyIdx, dirtyfile) - else: msgs.setDirtyFile(dirtyIdx, nil) - - resetModule dirtyIdx - if dirtyIdx != gProjectMainIdx: - resetModule gProjectMainIdx - gTrackPos = newLineInfo(dirtyIdx, line, col-1) - #echo dirtyfile, gDirtyBufferIdx, " project ", gProjectMainIdx - gErrorCounter = 0 - if not isKnownFile: - compileProject(dirtyIdx) - else: - compileProject() + execute(gIdeCmd, orig, dirtyfile, line, col-1) proc serve() = # do not stop after the first error: msgs.gErrorMax = high(int) - if gUseStdin: + case gMode: + of mstdin: echo Help var line = "" while readLineFromStdin("> ", line): - action line + parseCmdLine line echo "" flushFile(stdout) - else: + of mtcp: var server = newSocket() server.bindAddr(gPort, gAddress) var inp = "".TaintedString @@ -130,10 +218,55 @@ proc serve() = accept(server, stdoutSocket) stdoutSocket.readLine(inp) - action inp.string + parseCmdLine inp.string stdoutSocket.send("\c\L") stdoutSocket.close() + of mepc: + var server = newSocket() + let port = nextFreePort(server, "localhost") + var inp = "".TaintedString + server.listen() + echo(port) + var client = newSocket() + # Wait for connection + accept(server, client) + while true: + var sizeHex = "" + if client.recv(sizeHex, 6) != 6: + raise newException(ValueError, "didn't get all the hexbytes") + var size = 0 + if parseHex(sizeHex, size) == 0: + raise newException(ValueError, "invalid size hex: " & $sizeHex) + var messageBuffer = "" + if client.recv(messageBuffer, size) != size: + raise newException(ValueError, "didn't get all the bytes") + let + message = parseSexp($messageBuffer) + messageType = message[0].getSymbol + case messageType: + of "call": + var results: seq[Suggest] = @[] + suggestionResultHook = proc (s: Suggest) = + results.add(s) + + let + uid = message[1].getNum + cmd = parseIdeCmd(message[2].getSymbol) + args = message[3] + executeEPC(cmd, args) + returnEPC(client, uid, sexp(results)) + of "return": + raise newException(EUnexpectedCommand, "no return expected") + of "return-error": + raise newException(EUnexpectedCommand, "no return expected") + of "epc-error": + stderr.writeln("recieved epc error: " & $messageBuffer) + raise newException(IOError, "epc error") + of "methods": + returnEPC(client, message[1].getNum, listEPC()) + else: + raise newException(EUnexpectedCommand, "unexpected call: " & messageType) proc mainCommand = registerPass verbosePass @@ -151,15 +284,22 @@ proc mainCommand = proc processCmdLine*(pass: TCmdLinePass, cmd: string) = var p = parseopt.initOptParser(cmd) - while true: + while true: parseopt.next(p) case p.kind - of cmdEnd: break - of cmdLongoption, cmdShortOption: + of cmdEnd: break + of cmdLongoption, cmdShortOption: case p.key.normalize - of "port": gPort = parseInt(p.val).Port - of "address": gAddress = p.val - of "stdin": gUseStdin = true + of "port": + gPort = parseInt(p.val).Port + gMode = mtcp + of "address": + gAddress = p.val + gMode = mtcp + of "stdin": gMode = mstdin + of "epc": + gMode = mepc + gVerbosity = 0 # Port number gotta be first. else: processSwitch(pass, p) of cmdArgument: options.gProjectName = unixToNativePath(p.key) diff --git a/compiler/options.nim b/compiler/options.nim index 998ab7781..d07342fce 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -83,11 +83,11 @@ type # please make sure we have under 32 options TGCMode* = enum # the selected GC gcNone, gcBoehm, gcMarkAndSweep, gcRefc, gcV2, gcGenerational - TIdeCmd* = enum + IdeCmd* = enum ideNone, ideSug, ideCon, ideDef, ideUse var - gIdeCmd*: TIdeCmd + gIdeCmd*: IdeCmd const ChecksOptions* = {optObjCheck, optFieldCheck, optRangeCheck, optNilCheck, @@ -395,3 +395,18 @@ template cnimdbg*: expr = p.module.module.fileIdx == gProjectMainIdx template pnimdbg*: expr = p.lex.fileIdx == gProjectMainIdx template lnimdbg*: expr = L.fileIdx == gProjectMainIdx +proc parseIdeCmd*(s: string): IdeCmd = + case s: + of "sug": ideSug + of "con": ideCon + of "def": ideDef + of "use": ideUse + else: ideNone + +proc `$`*(c: IdeCmd): string = + case c: + of ideSug: "sug" + of ideCon: "con" + of ideDef: "def" + of ideUse: "use" + of ideNone: "none" diff --git a/compiler/suggest.nim b/compiler/suggest.nim index 6b168670c..f1628db94 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -11,61 +11,88 @@ # included from sigmatch.nim -import algorithm, sequtils +import algorithm, sequtils, strutils, ast, msgs, options, renderer, + types, docgen, lexer, semdata, astalgo, idents, sigmatch, lookups const sep = '\t' - sectionSuggest = "sug" - sectionDef = "def" - sectionContext = "con" - sectionUsage = "use" + +type + Suggest* = object + section*: IdeCmd + qualifiedPath*: seq[string] + filePath*: string + line*: int # Starts at 1 + column*: int # Starts at 0 + doc*: string # Not escaped (yet) + symkind*: TSymKind + forth*: string # XXX TODO object on symkind + +var + suggestionResultHook*: proc (result: Suggest) {.closure.} #template sectionSuggest(): expr = "##begin\n" & getStackTrace() & "##end\n" template origModuleName(m: PSym): string = m.name.s -proc symToStr(s: PSym, isLocal: bool, section: string, li: TLineInfo): string = - result = section - result.add(sep) +proc symToSuggest(s: PSym, isLocal: bool, section: string, li: TLineInfo): Suggest = + result.section = parseIdeCmd(section) if optIdeTerse in gGlobalOptions: if s.kind in routineKinds: - result.add renderTree(s.ast, {renderNoBody, renderNoComments, - renderDocComments, renderNoPragmas}) + result.symkind = parseEnum[TSymKind](renderTree(s.ast, {renderNoBody, renderNoComments, + renderDocComments, renderNoPragmas})) else: - result.add s.name.s - result.add(sep) - result.add(toFullPath(li)) - result.add(sep) - result.add($toLinenumber(li)) - result.add(sep) - result.add($toColumn(li)) + result.symkind = parseEnum[TSymKind](s.name.s) + result.filePath = toFullPath(li) + result.line = toLinenumber(li) + result.column = toColumn(li) else: - result.add($s.kind) - result.add(sep) + result.symkind = s.kind + result.qualifiedPath = @[] if not isLocal and s.kind != skModule: let ow = s.owner if ow.kind != skModule and ow.owner != nil: let ow2 = ow.owner - result.add(ow2.origModuleName) - result.add('.') - result.add(ow.origModuleName) - result.add('.') - result.add(s.name.s) - result.add(sep) + result.qualifiedPath.add(ow2.origModuleName) + result.qualifiedPath.add(ow.origModuleName) + result.qualifiedPath.add(s.name.s) + if s.typ != nil: - result.add(typeToString(s.typ)) - result.add(sep) - result.add(toFullPath(li)) - result.add(sep) - result.add($toLinenumber(li)) - result.add(sep) - result.add($toColumn(li)) - result.add(sep) + result.forth = typeToString(s.typ) + else: + result.forth = "" + result.filePath = toFullPath(li) + result.line = toLinenumber(li) + result.column = toColumn(li) when not defined(noDocgen): - result.add(s.extractDocComment.escape) + result.doc = s.extractDocComment -proc symToStr(s: PSym, isLocal: bool, section: string): string = - result = symToStr(s, isLocal, section, s.info) +proc `$`(suggest: Suggest): string = + result = $suggest.section + result.add(sep) + result.add($suggest.symkind) + result.add(sep) + result.add(suggest.qualifiedPath.join(".")) + result.add(sep) + result.add(suggest.forth) + result.add(sep) + result.add(suggest.filePath) + result.add(sep) + result.add($suggest.line) + result.add(sep) + result.add($suggest.column) + result.add(sep) + when not defined(noDocgen): + result.add(suggest.doc.escape) + +proc symToSuggest(s: PSym, isLocal: bool, section: string): Suggest = + result = symToSuggest(s, isLocal, section, s.info) + +proc suggestResult(s: Suggest) = + if not isNil(suggestionResultHook): + suggestionResultHook(s) + else: + suggestWriteln($(s)) proc filterSym(s: PSym): bool {.inline.} = result = s.kind != skModule @@ -84,7 +111,7 @@ proc fieldVisible*(c: PContext, f: PSym): bool {.inline.} = proc suggestField(c: PContext, s: PSym, outputs: var int) = if filterSym(s) and fieldVisible(c, s): - suggestWriteln(symToStr(s, isLocal=true, sectionSuggest)) + suggestResult(symToSuggest(s, isLocal=true, $ideSug)) inc outputs template wholeSymTab(cond, section: expr) {.immediate.} = @@ -97,7 +124,7 @@ template wholeSymTab(cond, section: expr) {.immediate.} = for item in entries: let it {.inject.} = item if cond: - suggestWriteln(symToStr(it, isLocal = isLocal, section)) + suggestResult(symToSuggest(it, isLocal = isLocal, section)) inc outputs proc suggestSymList(c: PContext, list: PNode, outputs: var int) = @@ -140,7 +167,7 @@ proc argsFit(c: PContext, candidate: PSym, n, nOrig: PNode): bool = proc suggestCall(c: PContext, n, nOrig: PNode, outputs: var int) = wholeSymTab(filterSym(it) and nameFits(c, it, n) and argsFit(c, it, n, nOrig), - sectionContext) + $ideCon) proc typeFits(c: PContext, s: PSym, firstArg: PType): bool {.inline.} = if s.typ != nil and sonsLen(s.typ) > 1 and s.typ.sons[1] != nil: @@ -157,7 +184,7 @@ proc typeFits(c: PContext, s: PSym, firstArg: PType): bool {.inline.} = proc suggestOperations(c: PContext, n: PNode, typ: PType, outputs: var int) = assert typ != nil - wholeSymTab(filterSymNoOpr(it) and typeFits(c, it, typ), sectionSuggest) + wholeSymTab(filterSymNoOpr(it) and typeFits(c, it, typ), $ideSug) proc suggestEverything(c: PContext, n: PNode, outputs: var int) = # do not produce too many symbols: @@ -166,7 +193,7 @@ proc suggestEverything(c: PContext, n: PNode, outputs: var int) = if scope == c.topLevelScope: isLocal = false for it in items(scope.symbols): if filterSym(it): - suggestWriteln(symToStr(it, isLocal = isLocal, sectionSuggest)) + suggestResult(symToSuggest(it, isLocal = isLocal, $ideSug)) inc outputs if scope == c.topLevelScope: break @@ -181,12 +208,12 @@ proc suggestFieldAccess(c: PContext, n: PNode, outputs: var int) = # all symbols accessible, because we are in the current module: for it in items(c.topLevelScope.symbols): if filterSym(it): - suggestWriteln(symToStr(it, isLocal=false, sectionSuggest)) + suggestResult(symToSuggest(it, isLocal=false, $ideSug)) inc outputs else: for it in items(n.sym.tab): if filterSym(it): - suggestWriteln(symToStr(it, isLocal=false, sectionSuggest)) + suggestResult(symToSuggest(it, isLocal=false, $ideSug)) inc outputs else: # fallback: @@ -263,16 +290,16 @@ var proc findUsages(info: TLineInfo; s: PSym) = if usageSym == nil and isTracked(info, s.name.s.len): usageSym = s - suggestWriteln(symToStr(s, isLocal=false, sectionUsage)) + suggestResult(symToSuggest(s, isLocal=false, $ideUse)) elif s == usageSym: if lastLineInfo != info: - suggestWriteln(symToStr(s, isLocal=false, sectionUsage, info)) + suggestResult(symToSuggest(s, isLocal=false, $ideUse, info)) lastLineInfo = info proc findDefinition(info: TLineInfo; s: PSym) = if s.isNil: return if isTracked(info, s.name.s.len): - suggestWriteln(symToStr(s, isLocal=false, sectionDef)) + suggestResult(symToSuggest(s, isLocal=false, $ideDef)) suggestQuit() proc ensureIdx[T](x: var T, y: int) = |