diff options
author | Araq <rumpf_a@web.de> | 2017-03-09 14:58:14 +0100 |
---|---|---|
committer | Araq <rumpf_a@web.de> | 2017-03-09 14:58:14 +0100 |
commit | 475579541621ca83cfcbb35df7f5d9ef4236cdfa (patch) | |
tree | 81f95f9ae846b61c7f2878a275413f5a3fc9bea3 /tools/nimsuggest | |
parent | da821a22d9e390d59f66018630fb4c39ba83eaf3 (diff) | |
download | Nim-475579541621ca83cfcbb35df7f5d9ef4236cdfa.tar.gz |
nimsuggest: more precise cursor tracking
Diffstat (limited to 'tools/nimsuggest')
-rw-r--r-- | tools/nimsuggest/crashtester.nim | 52 | ||||
-rw-r--r-- | tools/nimsuggest/nimsuggest.nim | 602 | ||||
-rw-r--r-- | tools/nimsuggest/nimsuggest.nim.cfg | 25 | ||||
-rw-r--r-- | tools/nimsuggest/nimsuggest.nimble | 11 | ||||
-rw-r--r-- | tools/nimsuggest/sexp.nim | 697 | ||||
-rw-r--r-- | tools/nimsuggest/tester.nim | 323 | ||||
-rw-r--r-- | tools/nimsuggest/tests/dep_v1.nim | 8 | ||||
-rw-r--r-- | tools/nimsuggest/tests/dep_v2.nim | 9 | ||||
-rw-r--r-- | tools/nimsuggest/tests/tchk1.nim | 27 | ||||
-rw-r--r-- | tools/nimsuggest/tests/tcursor_at_end.nim | 12 | ||||
-rw-r--r-- | tools/nimsuggest/tests/tdef1.nim | 16 | ||||
-rw-r--r-- | tools/nimsuggest/tests/tdot1.nim | 14 | ||||
-rw-r--r-- | tools/nimsuggest/tests/tdot2.nim | 29 | ||||
-rw-r--r-- | tools/nimsuggest/tests/tdot3.nim | 27 | ||||
-rw-r--r-- | tools/nimsuggest/tests/tinclude.nim | 7 | ||||
-rw-r--r-- | tools/nimsuggest/tests/tno_deref.nim | 14 | ||||
-rw-r--r-- | tools/nimsuggest/tests/tstrutils.nim | 9 | ||||
-rw-r--r-- | tools/nimsuggest/tests/tsug_regression.nim | 28 | ||||
-rw-r--r-- | tools/nimsuggest/tests/twithin_macro.nim | 213 | ||||
-rw-r--r-- | tools/nimsuggest/tests/twithin_macro_prefix.nim | 209 |
20 files changed, 0 insertions, 2332 deletions
diff --git a/tools/nimsuggest/crashtester.nim b/tools/nimsuggest/crashtester.nim deleted file mode 100644 index 4b3ba4026..000000000 --- a/tools/nimsuggest/crashtester.nim +++ /dev/null @@ -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() diff --git a/tools/nimsuggest/nimsuggest.nim b/tools/nimsuggest/nimsuggest.nim deleted file mode 100644 index 1798ac4e9..000000000 --- a/tools/nimsuggest/nimsuggest.nim +++ /dev/null @@ -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()) diff --git a/tools/nimsuggest/nimsuggest.nim.cfg b/tools/nimsuggest/nimsuggest.nim.cfg deleted file mode 100644 index 2e14a4dd3..000000000 --- a/tools/nimsuggest/nimsuggest.nim.cfg +++ /dev/null @@ -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" diff --git a/tools/nimsuggest/nimsuggest.nimble b/tools/nimsuggest/nimsuggest.nimble deleted file mode 100644 index 3651e12bd..000000000 --- a/tools/nimsuggest/nimsuggest.nimble +++ /dev/null @@ -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" diff --git a/tools/nimsuggest/sexp.nim b/tools/nimsuggest/sexp.nim deleted file mode 100644 index 61bba10bc..000000000 --- a/tools/nimsuggest/sexp.nim +++ /dev/null @@ -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"))""") diff --git a/tools/nimsuggest/tester.nim b/tools/nimsuggest/tester.nim deleted file mode 100644 index 0bee14254..000000000 --- a/tools/nimsuggest/tester.nim +++ /dev/null @@ -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() diff --git a/tools/nimsuggest/tests/dep_v1.nim b/tools/nimsuggest/tests/dep_v1.nim deleted file mode 100644 index eae230e85..000000000 --- a/tools/nimsuggest/tests/dep_v1.nim +++ /dev/null @@ -1,8 +0,0 @@ - - - - - -type - Foo* = object - x*, y*: int diff --git a/tools/nimsuggest/tests/dep_v2.nim b/tools/nimsuggest/tests/dep_v2.nim deleted file mode 100644 index ab39721c4..000000000 --- a/tools/nimsuggest/tests/dep_v2.nim +++ /dev/null @@ -1,9 +0,0 @@ - - - - - -type - Foo* = object - x*, y*: int - z*: string diff --git a/tools/nimsuggest/tests/tchk1.nim b/tools/nimsuggest/tests/tchk1.nim deleted file mode 100644 index f9f0dc8fe..000000000 --- a/tools/nimsuggest/tests/tchk1.nim +++ /dev/null @@ -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 -""" diff --git a/tools/nimsuggest/tests/tcursor_at_end.nim b/tools/nimsuggest/tests/tcursor_at_end.nim deleted file mode 100644 index b3a0d1133..000000000 --- a/tools/nimsuggest/tests/tcursor_at_end.nim +++ /dev/null @@ -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 - -#[!]# diff --git a/tools/nimsuggest/tests/tdef1.nim b/tools/nimsuggest/tests/tdef1.nim deleted file mode 100644 index 2cd040ea1..000000000 --- a/tools/nimsuggest/tests/tdef1.nim +++ /dev/null @@ -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 diff --git a/tools/nimsuggest/tests/tdot1.nim b/tools/nimsuggest/tests/tdot1.nim deleted file mode 100644 index 9ac92f8a5..000000000 --- a/tools/nimsuggest/tests/tdot1.nim +++ /dev/null @@ -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.#[!]#: diff --git a/tools/nimsuggest/tests/tdot2.nim b/tools/nimsuggest/tests/tdot2.nim deleted file mode 100644 index f02b5cf16..000000000 --- a/tools/nimsuggest/tests/tdot2.nim +++ /dev/null @@ -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 -""" diff --git a/tools/nimsuggest/tests/tdot3.nim b/tools/nimsuggest/tests/tdot3.nim deleted file mode 100644 index 15fc1cd1c..000000000 --- a/tools/nimsuggest/tests/tdot3.nim +++ /dev/null @@ -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 -""" diff --git a/tools/nimsuggest/tests/tinclude.nim b/tools/nimsuggest/tests/tinclude.nim deleted file mode 100644 index 27391c522..000000000 --- a/tools/nimsuggest/tests/tinclude.nim +++ /dev/null @@ -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 -""" diff --git a/tools/nimsuggest/tests/tno_deref.nim b/tools/nimsuggest/tests/tno_deref.nim deleted file mode 100644 index 05cffa507..000000000 --- a/tools/nimsuggest/tests/tno_deref.nim +++ /dev/null @@ -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 -* -""" diff --git a/tools/nimsuggest/tests/tstrutils.nim b/tools/nimsuggest/tests/tstrutils.nim deleted file mode 100644 index 34da8cb53..000000000 --- a/tools/nimsuggest/tests/tstrutils.nim +++ /dev/null @@ -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. - diff --git a/tools/nimsuggest/tests/tsug_regression.nim b/tools/nimsuggest/tests/tsug_regression.nim deleted file mode 100644 index 1e440db2d..000000000 --- a/tools/nimsuggest/tests/tsug_regression.nim +++ /dev/null @@ -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 -* -""" diff --git a/tools/nimsuggest/tests/twithin_macro.nim b/tools/nimsuggest/tests/twithin_macro.nim deleted file mode 100644 index e0df03542..000000000 --- a/tools/nimsuggest/tests/twithin_macro.nim +++ /dev/null @@ -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* -""" diff --git a/tools/nimsuggest/tests/twithin_macro_prefix.nim b/tools/nimsuggest/tests/twithin_macro_prefix.nim deleted file mode 100644 index 86e406c5d..000000000 --- a/tools/nimsuggest/tests/twithin_macro_prefix.nim +++ /dev/null @@ -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 -""" |