diff options
Diffstat (limited to 'lib')
60 files changed, 2596 insertions, 2163 deletions
diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 334a4b8c7..e290cce32 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -627,7 +627,7 @@ proc `$`*(node: PNimrodNode): string {.compileTime.} = case node.kind of nnkIdent: result = $node.ident - of nnkStrLit: + of nnkStrLit..nnkTripleStrLit: result = node.strVal else: badNodeKind node.kind, "$" diff --git a/lib/core/typeinfo.nim b/lib/core/typeinfo.nim index edb4d1188..8df1b3dfb 100644 --- a/lib/core/typeinfo.nim +++ b/lib/core/typeinfo.nim @@ -102,7 +102,7 @@ proc newAny(value: pointer, rawType: PNimType): TAny = result.value = value result.rawType = rawType -when defined(system.TVarSlot): +when declared(system.TVarSlot): proc toAny*(x: TVarSlot): TAny {.inline.} = ## constructs a ``TAny`` object from a variable slot ``x``. ## This captures `x`'s address, so `x` can be modified with its @@ -420,6 +420,59 @@ proc setBiggestInt*(x: TAny, y: biggestInt) = of tyUInt32: cast[ptr uint32](x.value)[] = uint32(y) else: assert false +proc getUInt*(x: TAny): uint = + ## retrieve the uint value out of `x`, `x` needs to represent an uint. + assert skipRange(x.rawtype).kind == tyUInt + result = cast[ptr uint](x.value)[] + +proc getUInt8*(x: TAny): uint8 = + ## retrieve the uint8 value out of `x`, `x` needs to represent an + ## uint8. + assert skipRange(x.rawtype).kind == tyUInt8 + result = cast[ptr uint8](x.value)[] + +proc getUInt16*(x: TAny): uint16 = + ## retrieve the uint16 value out of `x`, `x` needs to represent an + ## uint16. + assert skipRange(x.rawtype).kind == tyUInt16 + result = cast[ptr uint16](x.value)[] + +proc getUInt32*(x: TAny): uint32 = + ## retrieve the uint32 value out of `x`, `x` needs to represent an + ## uint32. + assert skipRange(x.rawtype).kind == tyUInt32 + result = cast[ptr uint32](x.value)[] + +proc getUInt64*(x: TAny): uint64 = + ## retrieve the uint64 value out of `x`, `x` needs to represent an + ## uint64. + assert skipRange(x.rawtype).kind == tyUInt64 + result = cast[ptr uint64](x.value)[] + +proc getBiggestUint*(x: TAny): uint64 = + ## retrieve the unsigned integer value out of `x`. `x` needs to + ## represent an unsigned integer. + var t = skipRange(x.rawtype) + case t.kind + of tyUInt: result = uint64(cast[ptr uint](x.value)[]) + of tyUInt8: result = uint64(cast[ptr uint8](x.value)[]) + of tyUInt16: result = uint64(cast[ptr uint16](x.value)[]) + of tyUInt32: result = uint64(cast[ptr uint32](x.value)[]) + of tyUInt64: result = uint64(cast[ptr uint64](x.value)[]) + else: assert false + +proc setBiggestUint*(x: TAny; y: uint64) = + ## sets the unsigned integer value of `c`. `c` needs to represent an + ## unsigned integer. + var t = skipRange(x.rawtype) + case t.kind: + of tyUInt: cast[ptr uint](x.value)[] = uint(y) + of tyUInt8: cast[ptr uint8](x.value)[] = uint8(y) + of tyUInt16: cast[ptr uint16](x.value)[] = uint16(y) + of tyUInt32: cast[ptr uint32](x.value)[] = uint32(y) + of tyUInt64: cast[ptr uint64](x.value)[] = uint64(y) + else: assert false + proc getChar*(x: TAny): char = ## retrieve the char value out of `x`. `x` needs to represent a char. var t = skipRange(x.rawtype) diff --git a/lib/impure/re.nim b/lib/impure/re.nim index f6511dab4..ac07b2d6b 100644 --- a/lib/impure/re.nim +++ b/lib/impure/re.nim @@ -243,7 +243,7 @@ template `=~` *(s: string, pattern: TRegex): expr = ## echo("syntax error") ## bind maxSubPatterns - when not definedInScope(matches): + when not declaredInScope(matches): var matches {.inject.}: array[0..MaxSubpatterns-1, string] match(s, pattern, matches) diff --git a/lib/packages/docutils/highlite.nim b/lib/packages/docutils/highlite.nim index 80fbf3a51..ff371f4e1 100644 --- a/lib/packages/docutils/highlite.nim +++ b/lib/packages/docutils/highlite.nim @@ -50,7 +50,7 @@ const "break", "case", "cast", "const", "continue", "converter", "discard", "distinct", "div", "do", "elif", "else", "end", "enum", "except", "export", "finally", "for", "from", "generic", "if", "import", "in", "include", - "interface", "is", "isnot", "iterator", "lambda", "let", "macro", "method", + "interface", "is", "isnot", "iterator", "let", "macro", "method", "mixin", "mod", "nil", "not", "notin", "object", "of", "or", "out", "proc", "ptr", "raise", "ref", "return", "shl", "shr", "static", "template", "try", "tuple", "type", "using", "var", "when", "while", "with", diff --git a/lib/packages/docutils/rstgen.nim b/lib/packages/docutils/rstgen.nim index fdbca4ca8..e9bae69b5 100644 --- a/lib/packages/docutils/rstgen.nim +++ b/lib/packages/docutils/rstgen.nim @@ -775,7 +775,7 @@ proc renderCodeBlock(d: PDoc, n: PRstNode, result: var string) = dispA(d.target, result, "<pre>", "\\begin{rstpre}\n", []) if lang == langNone: d.msgHandler(d.filename, 1, 0, mwUnsupportedLanguage, langstr) - result.add(m.text) + for letter in m.text: escChar(d.target, result, letter) else: var g: TGeneralTokenizer initGeneralTokenizer(g, m.text) diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index 8e66336c2..e1bcd9dfc 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -90,7 +90,7 @@ type d_ino*: Tino ## File serial number. d_name*: array [0..255, char] ## Name of entry. - Tflock* {.importc: "flock", final, pure, + Tflock* {.importc: "struct flock", final, pure, header: "<fcntl.h>".} = object ## flock type l_type*: cshort ## Type of lock; F_RDLCK, F_WRLCK, F_UNLCK. l_whence*: cshort ## Flag for starting offset. @@ -1579,6 +1579,16 @@ var MSG_OOB* {.importc, header: "<sys/socket.h>".}: cint ## Out-of-band data. + +when defined(linux): + var + MAP_POPULATE* {.importc, header: "<sys/mman.h>".}: cint + ## Populate (prefault) page tables for a mapping. +else: + var + MAP_POPULATE*: cint = 0 + + when defined(macosx): var MSG_HAVEMORE* {.importc, header: "<sys/socket.h>".}: cint diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index d410f8ce1..0ea8ef43b 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -9,7 +9,7 @@ include "system/inclrtl" -import os, oids, tables, strutils, macros +import os, oids, tables, strutils, macros, times import rawsockets, net @@ -27,12 +27,12 @@ export TPort, TSocketFlags ## **Note:** This module is still largely experimental. -# TODO: Discarded void PFutures need to be checked for exception. # TODO: ``except`` statement (without `try`) does not work. # TODO: Multiple exception names in a ``except`` don't work. # TODO: The effect system (raises: []) has trouble with my try transformation. # TODO: Can't await in a 'except' body # TODO: getCurrentException(Msg) don't work +# TODO: Check if yielded future is nil and throw a more meaningful exception # -- Futures @@ -41,27 +41,41 @@ type cb: proc () {.closure,gcsafe.} finished: bool error*: ref EBase - stackTrace: string ## For debugging purposes only. + errorStackTrace*: string + when not defined(release): + stackTrace: string ## For debugging purposes only. + id: int + fromProc: string PFuture*[T] = ref object of PFutureBase value: T -proc newFuture*[T](): PFuture[T] = +var currentID* = 0 +proc newFuture*[T](fromProc: string = "unspecified"): PFuture[T] = ## Creates a new future. + ## + ## Specifying ``fromProc``, which is a string specifying the name of the proc + ## that this future belongs to, is a good habit as it helps with debugging. new(result) result.finished = false - result.stackTrace = getStackTrace() + when not defined(release): + result.stackTrace = getStackTrace() + result.id = currentID + result.fromProc = fromProc + currentID.inc() proc checkFinished[T](future: PFuture[T]) = - if future.finished: - echo("<----->") - echo(future.stackTrace) - echo("-----") - when T is string: - echo("Contents: ", future.value.repr) - echo("<----->") - echo("Future already finished, cannot finish twice.") - assert false + when not defined(release): + if future.finished: + echo("<-----> ", future.id, " ", future.fromProc) + echo(future.stackTrace) + echo("-----") + when T is string: + echo("Contents: ", future.value.repr) + echo("<----->") + echo("Future already finished, cannot finish twice.") + echo getStackTrace() + assert false proc complete*[T](future: PFuture[T], val: T) = ## Completes ``future`` with value ``val``. @@ -88,6 +102,8 @@ proc fail*[T](future: PFuture[T], error: ref EBase) = checkFinished(future) future.finished = true future.error = error + future.errorStackTrace = + if getStackTrace(error) == "": getStackTrace() else: getStackTrace(error) if future.cb != nil: future.cb() else: @@ -115,13 +131,24 @@ proc `callback=`*[T](future: PFuture[T], ## If future has already completed then ``cb`` will be called immediately. future.callback = proc () = cb(future) +proc echoOriginalStackTrace[T](future: PFuture[T]) = + # TODO: Come up with something better. + when not defined(release): + echo("Original stack trace in ", future.fromProc, ":") + if not future.errorStackTrace.isNil() and future.errorStackTrace != "": + echo(future.errorStackTrace) + else: + echo("Empty or nil stack trace.") + proc read*[T](future: PFuture[T]): T = ## Retrieves the value of ``future``. Future must be finished otherwise ## this function will fail with a ``EInvalidValue`` exception. ## ## If the result of the future is an error then that error will be raised. if future.finished: - if future.error != nil: raise future.error + if future.error != nil: + echoOriginalStackTrace(future) + raise future.error when T isnot void: return future.value else: @@ -150,7 +177,44 @@ proc asyncCheck*[T](future: PFuture[T]) = ## This should be used instead of ``discard`` to discard void futures. future.callback = proc () = - if future.failed: raise future.error + if future.failed: + echoOriginalStackTrace(future) + raise future.error + +proc `and`*[T, Y](fut1: PFuture[T], fut2: PFuture[Y]): PFuture[void] = + ## Returns a future which will complete once both ``fut1`` and ``fut2`` + ## complete. + var retFuture = newFuture[void]("asyncdispatch.`and`") + fut1.callback = + proc () = + if fut2.finished: retFuture.complete() + fut2.callback = + proc () = + if fut1.finished: retFuture.complete() + return retFuture + +proc `or`*[T, Y](fut1: PFuture[T], fut2: PFuture[Y]): PFuture[void] = + ## Returns a future which will complete once either ``fut1`` or ``fut2`` + ## complete. + var retFuture = newFuture[void]("asyncdispatch.`or`") + proc cb() = + if not retFuture.finished: retFuture.complete() + fut1.callback = cb + fut2.callback = cb + return retFuture + +type + PDispatcherBase = ref object of PObject + timers: seq[tuple[finishAt: float, fut: PFuture[void]]] + +proc processTimers(p: PDispatcherBase) = + var oldTimers = p.timers + p.timers = @[] + for t in oldTimers: + if epochTime() >= t.finishAt: + t.fut.complete() + else: + p.timers.add(t) when defined(windows) or defined(nimdoc): import winlean, sets, hashes @@ -162,7 +226,7 @@ when defined(windows) or defined(nimdoc): cb: proc (sock: TAsyncFD, bytesTransferred: DWORD, errcode: TOSErrorCode) {.closure,gcsafe.} - PDispatcher* = ref object + PDispatcher* = ref object of PDispatcherBase ioPort: THandle handles: TSet[TAsyncFD] @@ -181,6 +245,7 @@ when defined(windows) or defined(nimdoc): new result result.ioPort = CreateIOCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1) result.handles = initSet[TAsyncFD]() + result.timers = @[] var gDisp{.threadvar.}: PDispatcher ## Global dispatcher proc getGlobalDispatcher*(): PDispatcher = @@ -207,8 +272,9 @@ when defined(windows) or defined(nimdoc): proc poll*(timeout = 500) = ## Waits for completion events and processes them. let p = getGlobalDispatcher() - if p.handles.len == 0: - raise newException(EInvalidValue, "No handles registered in dispatcher.") + if p.handles.len == 0 and p.timers.len == 0: + raise newException(EInvalidValue, + "No handles or timers registered in dispatcher.") let llTimeout = if timeout == -1: winlean.INFINITE @@ -242,6 +308,9 @@ when defined(windows) or defined(nimdoc): discard else: osError(errCode) + # Timer processing. + processTimers(p) + var connectExPtr: pointer = nil var acceptExPtr: pointer = nil var getAcceptExSockAddrsPtr: pointer = nil @@ -314,7 +383,7 @@ when defined(windows) or defined(nimdoc): ## Returns a ``PFuture`` which will complete when the connection succeeds ## or an error occurs. verifyPresence(socket) - var retFuture = newFuture[void]() + var retFuture = newFuture[void]("connect") # Apparently ``ConnectEx`` expects the socket to be initially bound: var saddr: Tsockaddr_in saddr.sin_family = int16(toInt(af)) @@ -384,7 +453,7 @@ when defined(windows) or defined(nimdoc): # '\0' in the message currently signifies a socket disconnect. Who # knows what will happen when someone sends that to our socket. verifyPresence(socket) - var retFuture = newFuture[string]() + var retFuture = newFuture[string]("recv") var dataBuf: TWSABuf dataBuf.buf = cast[cstring](alloc0(size)) dataBuf.len = size @@ -405,7 +474,10 @@ when defined(windows) or defined(nimdoc): copyMem(addr data[0], addr dataBuf.buf[0], bytesCount) retFuture.complete($data) else: - retFuture.fail(newException(EOS, osErrorMsg(errcode))) + if flags.isDisconnectionError(errcode): + retFuture.complete("") + else: + retFuture.fail(newException(EOS, osErrorMsg(errcode))) if dataBuf.buf != nil: dealloc dataBuf.buf dataBuf.buf = nil @@ -459,7 +531,7 @@ when defined(windows) or defined(nimdoc): ## Sends ``data`` to ``socket``. The returned future will complete once all ## data has been sent. verifyPresence(socket) - var retFuture = newFuture[void]() + var retFuture = newFuture[void]("send") var dataBuf: TWSABuf dataBuf.buf = data # since this is not used in a callback, this is fine @@ -474,7 +546,10 @@ when defined(windows) or defined(nimdoc): if errcode == TOSErrorCode(-1): retFuture.complete() else: - retFuture.fail(newException(EOS, osErrorMsg(errcode))) + if flags.isDisconnectionError(errcode): + retFuture.complete() + else: + retFuture.fail(newException(EOS, osErrorMsg(errcode))) ) let ret = WSASend(socket.TSocketHandle, addr dataBuf, 1, addr bytesReceived, @@ -494,15 +569,21 @@ when defined(windows) or defined(nimdoc): # free ``ol``. return retFuture - proc acceptAddr*(socket: TAsyncFD): + proc acceptAddr*(socket: TAsyncFD, flags = {TSocketFlags.SafeDisconn}): PFuture[tuple[address: string, client: TAsyncFD]] = ## Accepts a new connection. Returns a future containing the client socket ## corresponding to that connection and the remote address of the client. ## The future will complete when the connection is successfully accepted. ## - ## The resulting client socket is automatically registered to dispatcher. + ## The resulting client socket is automatically registered to the + ## dispatcher. + ## + ## The ``accept`` call may result in an error if the connecting socket + ## disconnects during the duration of the ``accept``. If the ``SafeDisconn`` + ## flag is specified then this error will not be raised and instead + ## accept will be called again. verifyPresence(socket) - var retFuture = newFuture[tuple[address: string, client: TAsyncFD]]() + var retFuture = newFuture[tuple[address: string, client: TAsyncFD]]("acceptAddr") var clientSock = newRawSocket() if clientSock == osInvalidSocket: osError(osLastError()) @@ -534,6 +615,18 @@ when defined(windows) or defined(nimdoc): client: clientSock.TAsyncFD) ) + template failAccept(errcode): stmt = + if flags.isDisconnectionError(errcode): + var newAcceptFut = acceptAddr(socket, flags) + newAcceptFut.callback = + proc () = + if newAcceptFut.failed: + retFuture.fail(newAcceptFut.readError) + else: + retFuture.complete(newAcceptFut.read) + else: + retFuture.fail(newException(EOS, osErrorMsg(errcode))) + var ol = PCustomOverlapped() GC_ref(ol) ol.data = TCompletionData(sock: socket, cb: @@ -542,7 +635,7 @@ when defined(windows) or defined(nimdoc): if errcode == TOSErrorCode(-1): completeAccept() else: - retFuture.fail(newException(EOS, osErrorMsg(errcode))) + failAccept(errcode) ) # http://msdn.microsoft.com/en-us/library/windows/desktop/ms737524%28v=vs.85%29.aspx @@ -555,7 +648,7 @@ when defined(windows) or defined(nimdoc): if not ret: let err = osLastError() if err.int32 != ERROR_IO_PENDING: - retFuture.fail(newException(EOS, osErrorMsg(err))) + failAccept(err) GC_unref(ol) else: completeAccept() @@ -606,7 +699,7 @@ else: readCBs: seq[TCallback] writeCBs: seq[TCallback] - PDispatcher* = ref object + PDispatcher* = ref object of PDispatcherBase selector: PSelector proc `==`*(x, y: TAsyncFD): bool {.borrow.} @@ -614,6 +707,7 @@ else: proc newDispatcher*(): PDispatcher = new result result.selector = newSelector() + result.timers = @[] var gDisp{.threadvar.}: PDispatcher ## Global dispatcher proc getGlobalDispatcher*(): PDispatcher = @@ -693,10 +787,12 @@ else: else: # FD no longer a part of the selector. Likely been closed # (e.g. socket disconnected). + + processTimers(p) proc connect*(socket: TAsyncFD, address: string, port: TPort, af = AF_INET): PFuture[void] = - var retFuture = newFuture[void]() + var retFuture = newFuture[void]("connect") proc cb(sock: TAsyncFD): bool = # We have connected. @@ -731,7 +827,7 @@ else: proc recv*(socket: TAsyncFD, size: int, flags = {TSocketFlags.SafeDisconn}): PFuture[string] = - var retFuture = newFuture[string]() + var retFuture = newFuture[string]("recv") var readBuffer = newString(size) @@ -762,7 +858,7 @@ else: proc send*(socket: TAsyncFD, data: string, flags = {TSocketFlags.SafeDisconn}): PFuture[void] = - var retFuture = newFuture[void]() + var retFuture = newFuture[void]("send") var written = 0 @@ -792,9 +888,10 @@ else: addWrite(socket, cb) return retFuture - proc acceptAddr*(socket: TAsyncFD): + proc acceptAddr*(socket: TAsyncFD, flags = {TSocketFlags.SafeDisconn}): PFuture[tuple[address: string, client: TAsyncFD]] = - var retFuture = newFuture[tuple[address: string, client: TAsyncFD]]() + var retFuture = newFuture[tuple[address: string, + client: TAsyncFD]]("acceptAddr") proc cb(sock: TAsyncFD): bool = result = true var sockAddress: Tsockaddr_in @@ -807,19 +904,31 @@ else: if lastError.int32 == EINTR: return false else: - retFuture.fail(newException(EOS, osErrorMsg(lastError))) + if flags.isDisconnectionError(lastError): + return false + else: + retFuture.fail(newException(EOS, osErrorMsg(lastError))) else: register(client.TAsyncFD) retFuture.complete(($inet_ntoa(sockAddress.sin_addr), client.TAsyncFD)) addRead(socket, cb) return retFuture -proc accept*(socket: TAsyncFD): PFuture[TAsyncFD] = +proc sleepAsync*(ms: int): PFuture[void] = + ## Suspends the execution of the current async procedure for the next + ## ``ms`` miliseconds. + var retFuture = newFuture[void]("sleepAsync") + let p = getGlobalDispatcher() + p.timers.add((epochTime() + (ms / 1000), retFuture)) + return retFuture + +proc accept*(socket: TAsyncFD, + flags = {TSocketFlags.SafeDisconn}): PFuture[TAsyncFD] = ## Accepts a new connection. Returns a future containing the client socket ## corresponding to that connection. ## The future will complete when the connection is successfully accepted. - var retFut = newFuture[TAsyncFD]() - var fut = acceptAddr(socket) + var retFut = newFuture[TAsyncFD]("accept") + var fut = acceptAddr(socket, flags) fut.callback = proc (future: PFuture[tuple[address: string, client: TAsyncFD]]) = assert future.finished @@ -845,11 +954,16 @@ template createCb*(retFutureSym, iteratorNameSym, else: next.callback = cb except: - retFutureSym.fail(getCurrentException()) + if retFutureSym.finished: + # Take a look at tasyncexceptions for the bug which this fixes. + # That test explains it better than I can here. + raise + else: + retFutureSym.fail(getCurrentException()) cb() #{.pop.} proc generateExceptionCheck(futSym, - exceptBranch, rootReceiver: PNimrodNode): PNimrodNode {.compileTime.} = + exceptBranch, rootReceiver, fromNode: PNimrodNode): PNimrodNode {.compileTime.} = if exceptBranch == nil: result = rootReceiver else: @@ -869,20 +983,21 @@ proc generateExceptionCheck(futSym, ) ) ) - let elseNode = newNimNode(nnkElse) - elseNode.add newNimNode(nnkStmtList) + let elseNode = newNimNode(nnkElse, fromNode) + elseNode.add newNimNode(nnkStmtList, fromNode) elseNode[0].add rootReceiver result.add elseNode template createVar(result: var PNimrodNode, futSymName: string, asyncProc: PNimrodNode, - valueReceiver, rootReceiver: expr) = - result = newNimNode(nnkStmtList) + valueReceiver, rootReceiver: expr, + fromNode: PNimrodNode) = + result = newNimNode(nnkStmtList, fromNode) var futSym = genSym(nskVar, "future") result.add newVarStmt(futSym, asyncProc) # -> var future<x> = y - result.add newNimNode(nnkYieldStmt).add(futSym) # -> yield future<x> + result.add newNimNode(nnkYieldStmt, fromNode).add(futSym) # -> yield future<x> valueReceiver = newDotExpr(futSym, newIdentNode("read")) # -> future<x>.read - result.add generateExceptionCheck(futSym, exceptBranch, rootReceiver) + result.add generateExceptionCheck(futSym, exceptBranch, rootReceiver, fromNode) proc processBody(node, retFutureSym: PNimrodNode, subTypeIsVoid: bool, @@ -891,7 +1006,7 @@ proc processBody(node, retFutureSym: PNimrodNode, result = node case node.kind of nnkReturnStmt: - result = newNimNode(nnkStmtList) + result = newNimNode(nnkStmtList, node) if node[0].kind == nnkEmpty: if not subtypeIsVoid: result.add newCall(newIdentNode("complete"), retFutureSym, @@ -902,36 +1017,36 @@ proc processBody(node, retFutureSym: PNimrodNode, result.add newCall(newIdentNode("complete"), retFutureSym, node[0].processBody(retFutureSym, subtypeIsVoid, exceptBranch)) - result.add newNimNode(nnkReturnStmt).add(newNilLit()) + result.add newNimNode(nnkReturnStmt, node).add(newNilLit()) return # Don't process the children of this return stmt - of nnkCommand: + of nnkCommand, nnkCall: if node[0].kind == nnkIdent and node[0].ident == !"await": case node[1].kind - of nnkIdent: + of nnkIdent, nnkInfix: # await x - result = newNimNode(nnkYieldStmt).add(node[1]) # -> yield x - of nnkCall: + result = newNimNode(nnkYieldStmt, node).add(node[1]) # -> yield x + of nnkCall, nnkCommand: # await foo(p, x) var futureValue: PNimrodNode result.createVar("future" & $node[1][0].toStrLit, node[1], futureValue, - futureValue) + futureValue, node) else: error("Invalid node kind in 'await', got: " & $node[1].kind) - elif node[1].kind == nnkCommand and node[1][0].kind == nnkIdent and - node[1][0].ident == !"await": + elif node.len > 1 and node[1].kind == nnkCommand and + node[1][0].kind == nnkIdent and node[1][0].ident == !"await": # foo await x var newCommand = node result.createVar("future" & $node[0].toStrLit, node[1][1], newCommand[1], - newCommand) + newCommand, node) of nnkVarSection, nnkLetSection: case node[0][2].kind of nnkCommand: - if node[0][2][0].ident == !"await": + if node[0][2][0].kind == nnkIdent and node[0][2][0].ident == !"await": # var x = await y var newVarSection = node # TODO: Should this use copyNimNode? result.createVar("future" & $node[0][0].ident, node[0][2][1], - newVarSection[0][2], newVarSection) + newVarSection[0][2], newVarSection, node) else: discard of nnkAsgn: case node[1].kind @@ -939,7 +1054,7 @@ proc processBody(node, retFutureSym: PNimrodNode, if node[1][0].ident == !"await": # x = await y var newAsgn = node - result.createVar("future" & $node[0].toStrLit, node[1][1], newAsgn[1], newAsgn) + result.createVar("future" & $node[0].toStrLit, node[1][1], newAsgn[1], newAsgn, node) else: discard of nnkDiscardStmt: # discard await x @@ -947,10 +1062,10 @@ proc processBody(node, retFutureSym: PNimrodNode, node[0][0].ident == !"await": var newDiscard = node result.createVar("futureDiscard_" & $toStrLit(node[0][1]), node[0][1], - newDiscard[0], newDiscard) + newDiscard[0], newDiscard, node) of nnkTryStmt: # try: await x; except: ... - result = newNimNode(nnkStmtList) + result = newNimNode(nnkStmtList, node) proc processForTry(n: PNimrodNode, i: var int, res: PNimrodNode): bool {.compileTime.} = result = false @@ -1009,7 +1124,7 @@ macro async*(prc: stmt): stmt {.immediate.} = (returnType.kind == nnkBracketExpr and returnType[1].kind == nnkIdent and returnType[1].ident == !"void") - var outerProcBody = newNimNode(nnkStmtList) + var outerProcBody = newNimNode(nnkStmtList, prc[6]) # -> var retFuture = newFuture[T]() var retFutureSym = genSym(nskVar, "retFuture") @@ -1019,9 +1134,10 @@ macro async*(prc: stmt): stmt {.immediate.} = outerProcBody.add( newVarStmt(retFutureSym, newCall( - newNimNode(nnkBracketExpr).add( + newNimNode(nnkBracketExpr, prc[6]).add( newIdentNode(!"newFuture"), # TODO: Strange bug here? Remove the `!`. - subRetType)))) # Get type from return type of this proc + subRetType), + newLit(prc[0].getName)))) # Get type from return type of this proc # -> iterator nameIter(): PFutureBase {.closure.} = # -> var result: T @@ -1030,7 +1146,7 @@ macro async*(prc: stmt): stmt {.immediate.} = var iteratorNameSym = genSym(nskIterator, $prc[0].getName & "Iter") var procBody = prc[6].processBody(retFutureSym, subtypeIsVoid, nil) if not subtypeIsVoid: - procBody.insert(0, newNimNode(nnkVarSection).add( + procBody.insert(0, newNimNode(nnkVarSection, prc[6]).add( newIdentDefs(newIdentNode("result"), returnType[1]))) # -> var result: T procBody.add( newCall(newIdentNode("complete"), @@ -1041,7 +1157,7 @@ macro async*(prc: stmt): stmt {.immediate.} = var closureIterator = newProc(iteratorNameSym, [newIdentNode("PFutureBase")], procBody, nnkIteratorDef) - closureIterator[4] = newNimNode(nnkPragma).add(newIdentNode("closure")) + closureIterator[4] = newNimNode(nnkPragma, prc[6]).add(newIdentNode("closure")) outerProcBody.add(closureIterator) # -> createCb(retFuture) @@ -1051,7 +1167,7 @@ macro async*(prc: stmt): stmt {.immediate.} = outerProcBody.add procCb # -> return retFuture - outerProcBody.add newNimNode(nnkReturnStmt).add(retFutureSym) + outerProcBody.add newNimNode(nnkReturnStmt, prc[6][prc[6].len-1]).add(retFutureSym) result = prc @@ -1068,8 +1184,8 @@ macro async*(prc: stmt): stmt {.immediate.} = result[6] = outerProcBody #echo(treeRepr(result)) - #if prc[0].getName == "routeReq": - #echo(toStrLit(result)) + #if prc[0].getName == "getFile": + # echo(toStrLit(result)) proc recvLine*(socket: TAsyncFD): PFuture[string] {.async.} = ## Reads a line of data from ``socket``. Returned future will complete once @@ -1110,3 +1226,11 @@ proc runForever*() = ## Begins a never ending global dispatcher poll loop. while true: poll() + +proc waitFor*[T](fut: PFuture[T]) = + ## **Blocks** the current thread until the specified future completes. + while not fut.finished: + poll() + + if fut.failed: + raise fut.error diff --git a/lib/pure/asyncftpclient.nim b/lib/pure/asyncftpclient.nim new file mode 100644 index 000000000..f1b1d1400 --- /dev/null +++ b/lib/pure/asyncftpclient.nim @@ -0,0 +1,295 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2014 Dominik Picheta +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +import asyncdispatch, asyncnet, strutils, parseutils, os, times + +from ftpclient import TFtpBase, EInvalidReply, TFtpEvent +from net import bufferSize + +type + TAsyncFtpClient* = TFtpBase[PAsyncSocket] + PAsyncFtpClient* = ref TAsyncFtpClient + + ProgressChangedProc* = + proc (total, progress: BiggestInt, speed: float): + PFuture[void] {.closure, gcsafe.} + +proc expectReply(ftp: PAsyncFtpClient): PFuture[TaintedString] = + result = ftp.csock.recvLine() + +proc send*(ftp: PAsyncFtpClient, m: string): PFuture[TaintedString] {.async.} = + ## Send a message to the server, and wait for a primary reply. + ## ``\c\L`` is added for you. + await ftp.csock.send(m & "\c\L") + return await ftp.expectReply() + +proc assertReply(received: TaintedString, expected: varargs[string]) = + for i in items(expected): + if received.string.startsWith(i): return + raise newException(EInvalidReply, + "Expected reply '$1' got: $2" % + [expected.join("' or '"), received.string]) + +proc pasv(ftp: PAsyncFtpClient) {.async.} = + ## Negotiate a data connection. + ftp.dsock = newAsyncSocket() + + var pasvMsg = (await ftp.send("PASV")).string.strip.TaintedString + assertReply(pasvMsg, "227") + var betweenParens = captureBetween(pasvMsg.string, '(', ')') + var nums = betweenParens.split(',') + var ip = nums[0.. -3] + var port = nums[-2.. -1] + var properPort = port[0].parseInt()*256+port[1].parseInt() + await ftp.dsock.connect(ip.join("."), TPort(properPort.toU16)) + ftp.dsockConnected = True + +proc normalizePathSep(path: string): string = + return replace(path, '\\', '/') + +proc connect*(ftp: PAsyncFtpClient) {.async.} = + ## Connect to the FTP server specified by ``ftp``. + await ftp.csock.connect(ftp.address, ftp.port) + + var reply = await ftp.expectReply() + if reply.startsWith("120"): + # 120 Service ready in nnn minutes. + # We wait until we receive 220. + reply = await ftp.expectReply() + assertReply(reply, "220") + + if ftp.user != "": + assertReply(await(ftp.send("USER " & ftp.user)), "230", "331") + + if ftp.pass != "": + assertReply(await(ftp.send("PASS " & ftp.pass)), "230") + +proc pwd*(ftp: PAsyncFtpClient): PFuture[TaintedString] {.async.} = + ## Returns the current working directory. + let wd = await ftp.send("PWD") + assertReply wd, "257" + return wd.string.captureBetween('"').TaintedString # " + +proc cd*(ftp: PAsyncFtpClient, dir: string) {.async.} = + ## Changes the current directory on the remote FTP server to ``dir``. + assertReply(await(ftp.send("CWD " & dir.normalizePathSep)), "250") + +proc cdup*(ftp: PAsyncFtpClient) {.async.} = + ## Changes the current directory to the parent of the current directory. + assertReply(await(ftp.send("CDUP")), "200") + +proc getLines(ftp: PAsyncFtpClient): PFuture[string] {.async.} = + ## Downloads text data in ASCII mode + result = "" + assert ftp.dsockConnected + while ftp.dsockConnected: + let r = await ftp.dsock.recvLine() + if r.string == "": + ftp.dsockConnected = false + else: + result.add(r.string & "\n") + + assertReply(await(ftp.expectReply()), "226") + +proc listDirs*(ftp: PAsyncFtpClient, dir = ""): PFuture[seq[string]] {.async.} = + ## Returns a list of filenames in the given directory. If ``dir`` is "", + ## the current directory is used. If ``async`` is true, this + ## function will return immediately and it will be your job to + ## use asyncio's ``poll`` to progress this operation. + await ftp.pasv() + + assertReply(await(ftp.send("NLST " & dir.normalizePathSep)), ["125", "150"]) + + result = splitLines(await ftp.getLines()) + +proc existsFile*(ftp: PAsyncFtpClient, file: string): PFuture[bool] {.async.} = + ## Determines whether ``file`` exists. + var files = await ftp.listDirs() + for f in items(files): + if f.normalizePathSep == file.normalizePathSep: return true + +proc createDir*(ftp: PAsyncFtpClient, dir: string, recursive = false){.async.} = + ## Creates a directory ``dir``. If ``recursive`` is true, the topmost + ## subdirectory of ``dir`` will be created first, following the secondmost... + ## etc. this allows you to give a full path as the ``dir`` without worrying + ## about subdirectories not existing. + if not recursive: + assertReply(await(ftp.send("MKD " & dir.normalizePathSep)), "257") + else: + var reply = TaintedString"" + var previousDirs = "" + for p in split(dir, {os.dirSep, os.altSep}): + if p != "": + previousDirs.add(p) + reply = await ftp.send("MKD " & previousDirs) + previousDirs.add('/') + assertReply reply, "257" + +proc chmod*(ftp: PAsyncFtpClient, path: string, + permissions: set[TFilePermission]) {.async.} = + ## Changes permission of ``path`` to ``permissions``. + var userOctal = 0 + var groupOctal = 0 + var otherOctal = 0 + for i in items(permissions): + case i + of fpUserExec: userOctal.inc(1) + of fpUserWrite: userOctal.inc(2) + of fpUserRead: userOctal.inc(4) + of fpGroupExec: groupOctal.inc(1) + of fpGroupWrite: groupOctal.inc(2) + of fpGroupRead: groupOctal.inc(4) + of fpOthersExec: otherOctal.inc(1) + of fpOthersWrite: otherOctal.inc(2) + of fpOthersRead: otherOctal.inc(4) + + var perm = $userOctal & $groupOctal & $otherOctal + assertReply(await(ftp.send("SITE CHMOD " & perm & + " " & path.normalizePathSep)), "200") + +proc list*(ftp: PAsyncFtpClient, dir = ""): PFuture[string] {.async.} = + ## Lists all files in ``dir``. If ``dir`` is ``""``, uses the current + ## working directory. + await ftp.pasv() + + let reply = await ftp.send("LIST" & " " & dir.normalizePathSep) + assertReply(reply, ["125", "150"]) + + result = await ftp.getLines() + +proc retrText*(ftp: PAsyncFtpClient, file: string): PFuture[string] {.async.} = + ## Retrieves ``file``. File must be ASCII text. + await ftp.pasv() + let reply = await ftp.send("RETR " & file.normalizePathSep) + assertReply(reply, ["125", "150"]) + + result = await ftp.getLines() + +proc getFile(ftp: PAsyncFtpClient, file: TFile, total: BiggestInt, + onProgressChanged: ProgressChangedProc) {.async.} = + assert ftp.dsockConnected + var progress = 0 + var progressInSecond = 0 + var countdownFut = sleepAsync(1000) + var dataFut = ftp.dsock.recv(bufferSize) + while ftp.dsockConnected: + await dataFut or countdownFut + if countdownFut.finished: + asyncCheck onProgressChanged(total, progress, + progressInSecond.float) + progressInSecond = 0 + countdownFut = sleepAsync(1000) + + if dataFut.finished: + let data = dataFut.read + if data != "": + progress.inc(data.len) + progressInSecond.inc(data.len) + file.write(data) + dataFut = ftp.dsock.recv(bufferSize) + else: + ftp.dsockConnected = False + + assertReply(await(ftp.expectReply()), "226") + +proc defaultOnProgressChanged*(total, progress: BiggestInt, + speed: float): PFuture[void] {.nimcall,gcsafe.} = + ## Default FTP ``onProgressChanged`` handler. Does nothing. + result = newFuture[void]() + #echo(total, " ", progress, " ", speed) + result.complete() + +proc retrFile*(ftp: PAsyncFtpClient, file, dest: string, + onProgressChanged = defaultOnProgressChanged) {.async.} = + ## Downloads ``file`` and saves it to ``dest``. + ## The ``EvRetr`` event is passed to the specified ``handleEvent`` function + ## when the download is finished. The event's ``filename`` field will be equal + ## to ``file``. + var destFile = open(dest, mode = fmWrite) + await ftp.pasv() + var reply = await ftp.send("RETR " & file.normalizePathSep) + assertReply reply, ["125", "150"] + if {'(', ')'} notin reply.string: + raise newException(EInvalidReply, "Reply has no file size.") + var fileSize: biggestInt + if reply.string.captureBetween('(', ')').parseBiggestInt(fileSize) == 0: + raise newException(EInvalidReply, "Reply has no file size.") + + await getFile(ftp, destFile, fileSize, onProgressChanged) + +proc doUpload(ftp: PAsyncFtpClient, file: TFile, + onProgressChanged: ProgressChangedProc) {.async.} = + assert ftp.dsockConnected + + let total = file.getFileSize() + var data = newStringOfCap(4000) + var progress = 0 + var progressInSecond = 0 + var countdownFut = sleepAsync(1000) + var sendFut: PFuture[void] = nil + while ftp.dsockConnected: + if sendFut == nil or sendFut.finished: + progress.inc(data.len) + progressInSecond.inc(data.len) + # TODO: Async file reading. + let len = file.readBuffer(addr(data[0]), 4000) + setLen(data, len) + if len == 0: + # File finished uploading. + ftp.dsock.close() + ftp.dsockConnected = false + + assertReply(await(ftp.expectReply()), "226") + else: + sendFut = ftp.dsock.send(data) + + if countdownFut.finished: + asyncCheck onProgressChanged(total, progress, progressInSecond.float) + progressInSecond = 0 + countdownFut = sleepAsync(1000) + + await countdownFut or sendFut + +proc storeFile*(ftp: PAsyncFtpClient, file, dest: string, + onProgressChanged = defaultOnProgressChanged) {.async.} = + ## Uploads ``file`` to ``dest`` on the remote FTP server. Usage of this + ## function asynchronously is recommended to view the progress of + ## the download. + ## The ``EvStore`` event is passed to the specified ``handleEvent`` function + ## when the upload is finished, and the ``filename`` field will be + ## equal to ``file``. + var destFile = open(file) + await ftp.pasv() + + let reply = await ftp.send("STOR " & dest.normalizePathSep) + assertReply reply, ["125", "150"] + + await doUpload(ftp, destFile, onProgressChanged) + +proc newAsyncFtpClient*(address: string, port = TPort(21), + user, pass = ""): PAsyncFtpClient = + ## Creates a new ``PAsyncFtpClient`` object. + new result + result.user = user + result.pass = pass + result.address = address + result.port = port + result.dsockConnected = false + result.csock = newAsyncSocket() + +when isMainModule: + var ftp = newAsyncFtpClient("example.com", user = "test", pass = "test") + proc main(ftp: PAsyncFtpClient) {.async.} = + await ftp.connect() + echo await ftp.pwd() + echo await ftp.listDirs() + await ftp.storeFile("payload.jpg", "payload.jpg") + await ftp.retrFile("payload.jpg", "payload2.jpg") + echo("Finished") + + waitFor main(ftp) diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index ee6658fd1..c8bd5cfc1 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -11,14 +11,14 @@ ## ## **Note:** This module is still largely experimental. -import strtabs, asyncnet, asyncdispatch, parseutils, parseurl, strutils +import strtabs, asyncnet, asyncdispatch, parseutils, uri, strutils type TRequest* = object client*: PAsyncSocket # TODO: Separate this into a Response object? reqMethod*: string headers*: PStringTable protocol*: tuple[orig: string, major, minor: int] - url*: TURL + url*: TUri hostname*: string ## The hostname of the client that made the request. body*: string @@ -97,7 +97,8 @@ proc sendStatus(client: PAsyncSocket, status: string): PFuture[void] = client.send("HTTP/1.1 " & status & "\c\L") proc processClient(client: PAsyncSocket, address: string, - callback: proc (request: TRequest): PFuture[void]) {.async.} = + callback: proc (request: TRequest): + PFuture[void] {.closure, gcsafe.}) {.async.} = while true: # GET /path HTTP/1.1 # Header: val @@ -135,7 +136,7 @@ proc processClient(client: PAsyncSocket, address: string, request.headers[kv.key] = kv.value request.reqMethod = reqMethod - request.url = parseUrl(path) + request.url = parseUri(path) try: request.protocol = protocol.parseProtocol() except EInvalidValue: @@ -184,7 +185,7 @@ proc processClient(client: PAsyncSocket, address: string, break proc serve*(server: PAsyncHttpServer, port: TPort, - callback: proc (request: TRequest): PFuture[void], + callback: proc (request: TRequest): PFuture[void] {.closure,gcsafe.}, address = "") {.async.} = ## Starts the process of listening for incoming HTTP connections on the ## specified address and port. @@ -199,19 +200,23 @@ proc serve*(server: PAsyncHttpServer, port: TPort, #var (address, client) = await server.socket.acceptAddr() var fut = await server.socket.acceptAddr() asyncCheck processClient(fut.client, fut.address, callback) + #echo(f.isNil) + #echo(f.repr) proc close*(server: PAsyncHttpServer) = ## Terminates the async http server instance. server.socket.close() when isMainModule: - var server = newAsyncHttpServer() - proc cb(req: TRequest) {.async.} = - #echo(req.reqMethod, " ", req.url) - #echo(req.headers) - let headers = {"Date": "Tue, 29 Apr 2014 23:40:08 GMT", - "Content-type": "text/plain; charset=utf-8"} - await req.respond(Http200, "Hello World", headers.newStringTable()) - - asyncCheck server.serve(TPort(5555), cb) - runForever() + proc main = + var server = newAsyncHttpServer() + proc cb(req: TRequest) {.async.} = + #echo(req.reqMethod, " ", req.url) + #echo(req.headers) + let headers = {"Date": "Tue, 29 Apr 2014 23:40:08 GMT", + "Content-type": "text/plain; charset=utf-8"} + await req.respond(Http200, "Hello World", headers.newStringTable()) + + asyncCheck server.serve(TPort(5555), cb) + runForever() + main() diff --git a/lib/pure/asyncio.nim b/lib/pure/asyncio.nim index c68ca4350..6b67bf4b5 100644 --- a/lib/pure/asyncio.nim +++ b/lib/pure/asyncio.nim @@ -671,25 +671,26 @@ when isMainModule: testRead(s, 2) disp.register(client) - var d = newDispatcher() - - var s = AsyncSocket() - s.connect("amber.tenthbit.net", TPort(6667)) - s.handleConnect = - proc (s: PAsyncSocket) = - testConnect(s, 1) - s.handleRead = - proc (s: PAsyncSocket) = - testRead(s, 1) - d.register(s) - - var server = AsyncSocket() - server.handleAccept = - proc (s: PAsyncSocket) = - testAccept(s, d, 78) - server.bindAddr(TPort(5555)) - server.listen() - d.register(server) - - while d.poll(-1): discard + proc main = + var d = newDispatcher() + + var s = AsyncSocket() + s.connect("amber.tenthbit.net", TPort(6667)) + s.handleConnect = + proc (s: PAsyncSocket) = + testConnect(s, 1) + s.handleRead = + proc (s: PAsyncSocket) = + testRead(s, 1) + d.register(s) + + var server = AsyncSocket() + server.handleAccept = + proc (s: PAsyncSocket) = + testAccept(s, d, 78) + server.bindAddr(TPort(5555)) + server.listen() + d.register(server) + while d.poll(-1): discard + main() diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim index 374ac77e3..5095d9461 100644 --- a/lib/pure/asyncnet.nim +++ b/lib/pure/asyncnet.nim @@ -36,9 +36,9 @@ ## let client = await server.accept() ## clients.add client ## -## processClient(client) +## asyncCheck processClient(client) ## -## serve() +## asyncCheck serve() ## runForever() ## ## @@ -135,13 +135,13 @@ proc send*(socket: PAsyncSocket, data: string, assert socket != nil result = send(socket.fd.TAsyncFD, data, flags) -proc acceptAddr*(socket: PAsyncSocket): +proc acceptAddr*(socket: PAsyncSocket, flags = {TSocketFlags.SafeDisconn}): PFuture[tuple[address: string, client: PAsyncSocket]] = ## Accepts a new connection. Returns a future containing the client socket ## corresponding to that connection and the remote address of the client. ## The future will complete when the connection is successfully accepted. - var retFuture = newFuture[tuple[address: string, client: PAsyncSocket]]() - var fut = acceptAddr(socket.fd.TAsyncFD) + var retFuture = newFuture[tuple[address: string, client: PAsyncSocket]]("asyncnet.acceptAddr") + var fut = acceptAddr(socket.fd.TAsyncFD, flags) fut.callback = proc (future: PFuture[tuple[address: string, client: TAsyncFD]]) = assert future.finished @@ -153,12 +153,13 @@ proc acceptAddr*(socket: PAsyncSocket): retFuture.complete(resultTup) return retFuture -proc accept*(socket: PAsyncSocket): PFuture[PAsyncSocket] = +proc accept*(socket: PAsyncSocket, + flags = {TSocketFlags.SafeDisconn}): PFuture[PAsyncSocket] = ## Accepts a new connection. Returns a future containing the client socket ## corresponding to that connection. ## The future will complete when the connection is successfully accepted. - var retFut = newFuture[PAsyncSocket]() - var fut = acceptAddr(socket) + var retFut = newFuture[PAsyncSocket]("asyncnet.accept") + var fut = acceptAddr(socket, flags) fut.callback = proc (future: PFuture[tuple[address: string, client: PAsyncSocket]]) = assert future.finished diff --git a/lib/pure/base64.nim b/lib/pure/base64.nim index 4e59a6ca6..7b3b0e6f5 100644 --- a/lib/pure/base64.nim +++ b/lib/pure/base64.nim @@ -58,7 +58,7 @@ template encodeInternal(s: expr, lineLen: int, newLine: string): stmt {.immediat if r+4 != result.len: setLen(result, r+4) else: - assert(r == result.len) + #assert(r == result.len) proc encode*[T:TInteger|char](s: openarray[T], lineLen = 75, newLine="\13\10"): string = ## encodes `s` into base64 representation. After `lineLen` characters, a diff --git a/lib/pure/collections/critbits.nim b/lib/pure/collections/critbits.nim index 40a02b651..1fde1f419 100644 --- a/lib/pure/collections/critbits.nim +++ b/lib/pure/collections/critbits.nim @@ -131,7 +131,7 @@ proc `[]=`*[T](c: var TCritBitTree[T], key: string, val: T) = var n = rawInsert(c, key) n.val = val -proc `[]`*[T](c: var TCritBitTree[T], key: string): T {.inline.} = +proc `[]`*[T](c: TCritBitTree[T], key: string): T {.inline.} = ## retrieves the value at ``c[key]``. If `key` is not in `t`, ## default empty value for the type `B` is returned ## and no exception is raised. One can check with ``hasKey`` whether the key diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index c50c4165b..2629e9f40 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -409,6 +409,23 @@ template mapIt*(varSeq, pred: expr) = let it {.inject.} = varSeq[i] varSeq[i] = pred +template newSeqWith*(len: int, init: expr): expr = + ## creates a new sequence, calling `init` to initialize each value. Example: + ## + ## .. code-block:: nimrod + ## var seq2D = newSeqWith(20, newSeq[bool](10)) + ## seq2D[0][0] = true + ## seq2D[1][0] = true + ## seq2D[0][1] = true + ## + ## import math + ## var seqRand = newSeqWith(20, random(10)) + ## echo seqRand + var result {.gensym.} = newSeq[type(init)](len) + for i in 0 .. <len: + result[i] = init + result + when isMainModule: import strutils block: # concat test @@ -557,4 +574,11 @@ when isMainModule: doAssert b.distribute(5, true)[4].len == 5 doAssert b.distribute(5, false)[4].len == 2 + block: # newSeqWith tests + var seq2D = newSeqWith(4, newSeq[bool](2)) + seq2D[0][0] = true + seq2D[1][0] = true + seq2D[0][1] = true + doAssert seq2D == @[@[true, true], @[true, false], @[false, false], @[false, false]] + echo "Finished doc tests" diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim index f1eed0004..22eff9c55 100644 --- a/lib/pure/collections/sets.nim +++ b/lib/pure/collections/sets.nim @@ -9,6 +9,10 @@ ## The ``sets`` module implements an efficient hash set and ordered hash set. ## +## Hash sets are different from the `built in set type +## <manual.html#set-type>`_. Sets allow you to store any value that can be +## `hashed <hashes.html>`_ and they don't contain duplicate entries. +## ## **Note**: The data types declared here have *value semantics*: This means ## that ``=`` performs a copy of the set. @@ -23,20 +27,69 @@ type TSlotEnum = enum seEmpty, seFilled, seDeleted TKeyValuePair[A] = tuple[slot: TSlotEnum, key: A] TKeyValuePairSeq[A] = seq[TKeyValuePair[A]] - TSet* {.final, myShallow.}[A] = object ## a generic hash set + TSet* {.final, myShallow.}[A] = object ## \ + ## A generic hash set. + ## + ## Use `init() <#init,TSet[A],int>`_ or `initSet[type]() <#initSet>`_ + ## before calling other procs on it. data: TKeyValuePairSeq[A] counter: int +proc isValid*[A](s: TSet[A]): bool = + ## Returns `true` if the set has been initialized with `initSet <#initSet>`_. + ## + ## Most operations over an uninitialized set will crash at runtime and + ## `assert <system.html#assert>`_ in debug builds. You can use this proc in + ## your own procs to verify that sets passed to your procs are correctly + ## initialized. Example: + ## + ## .. code-block :: nimrod + ## proc savePreferences(options: TSet[string]) = + ## assert options.isValid, "Pass an initialized set!" + ## # Do stuff here, may crash in release builds! + result = not s.data.isNil + proc len*[A](s: TSet[A]): int = - ## returns the number of keys in `s`. + ## Returns the number of keys in `s`. + ## + ## Due to an implementation detail you can call this proc on variables which + ## have not been initialized yet. The proc will return zero as the length + ## then. Example: + ## + ## .. code-block:: + ## + ## var values: TSet[int] + ## assert(not values.isValid) + ## assert values.len == 0 result = s.counter proc card*[A](s: TSet[A]): int = - ## alias for `len`. + ## Alias for `len() <#len,TSet[A]>`_. + ## + ## Card stands for the `cardinality + ## <http://en.wikipedia.org/wiki/Cardinality>`_ of a set. result = s.counter iterator items*[A](s: TSet[A]): A = - ## iterates over any key in the table `t`. + ## Iterates over keys in the set `s`. + ## + ## If you need a sequence with the keys you can use `sequtils.toSeq() + ## <sequtils.html#toSeq>`_ on the iterator. Usage example: + ## + ## .. code-block:: + ## type + ## pair = tuple[a, b: int] + ## var + ## a, b = initSet[pair]() + ## a.incl((2, 3)) + ## a.incl((3, 2)) + ## a.incl((2, 3)) + ## for x, y in a.items: + ## b.incl((x - 2, y + 1)) + ## assert a.len == 2 + ## echo b + ## # --> {(a: 1, b: 3), (a: 0, b: 4)} + assert s.isValid, "The set needs to be initialized." for h in 0..high(s.data): if s.data[h].slot == seFilled: yield s.data[h].key @@ -73,12 +126,24 @@ proc mget*[A](s: var TSet[A], key: A): var A = ## value as 'key' or raises the ``EInvalidKey`` exception. This is useful ## when one overloaded 'hash' and '==' but still needs reference semantics ## for sharing. + assert s.isValid, "The set needs to be initialized." var index = rawGet(s, key) if index >= 0: result = t.data[index].key else: raise newException(EInvalidKey, "key not found: " & $key) proc contains*[A](s: TSet[A], key: A): bool = - ## returns true iff `key` is in `s`. + ## Returns true iff `key` is in `s`. + ## + ## Example: + ## + ## .. code-block:: + ## var values = initSet[int]() + ## assert(not values.contains(2)) + ## values.incl(2) + ## assert values.contains(2) + ## values.excl(2) + ## assert(not values.contains(2)) + assert s.isValid, "The set needs to be initialized." var index = rawGet(s, key) result = index >= 0 @@ -109,38 +174,124 @@ template containsOrInclImpl() {.dirty.} = inc(s.counter) proc incl*[A](s: var TSet[A], key: A) = - ## includes an element `key` in `s`. + ## Includes an element `key` in `s`. + ## + ## This doesn't do anything if `key` is already in `s`. Example: + ## + ## .. code-block:: + ## var values = initSet[int]() + ## values.incl(2) + ## values.incl(2) + ## assert values.len == 1 + assert s.isValid, "The set needs to be initialized." inclImpl() proc incl*[A](s: var TSet[A], other: TSet[A]) = - ## includes everything in `other` in `s` + ## Includes all elements from `other` into `s`. + ## + ## Example: + ## + ## .. code-block:: + ## var values = initSet[int]() + ## values.incl(2) + ## var others = toSet([6, 7]) + ## values.incl(others) + ## assert values.len == 3 + assert s.isValid, "The set `s` needs to be initialized." + assert other.isValid, "The set `other` needs to be initialized." for item in other: incl(s, item) proc excl*[A](s: var TSet[A], key: A) = - ## excludes `key` from the set `s`. + ## Excludes `key` from the set `s`. + ## + ## This doesn't do anything if `key` is not found in `s`. Example: + ## + ## .. code-block:: + ## var s = toSet([2, 3, 6, 7]) + ## s.excl(2) + ## s.excl(2) + ## assert s.len == 3 + assert s.isValid, "The set needs to be initialized." var index = rawGet(s, key) if index >= 0: s.data[index].slot = seDeleted dec(s.counter) proc excl*[A](s: var TSet[A], other: TSet[A]) = - ## excludes everything in `other` from `s`. + ## Excludes everything in `other` from `s`. + ## + ## Example: + ## + ## .. code-block:: + ## var + ## numbers = toSet([1, 2, 3, 4, 5]) + ## even = toSet([2, 4, 6, 8]) + ## numbers.excl(even) + ## echo numbers + ## # --> {1, 3, 5} + assert s.isValid, "The set `s` needs to be initialized." + assert other.isValid, "The set `other` needs to be initialized." for item in other: excl(s, item) proc containsOrIncl*[A](s: var TSet[A], key: A): bool = - ## returns true if `s` contains `key`, otherwise `key` is included in `s` - ## and false is returned. + ## Includes `key` in the set `s` and tells if `key` was added to `s`. + ## + ## The difference with regards to the `incl() <#incl,TSet[A],A>`_ proc is + ## that this proc returns `true` if `key` was already present in `s`. The + ## proc will return false if `key` was added as a new value to `s` during + ## this call. Example: + ## + ## .. code-block:: + ## var values = initSet[int]() + ## assert values.containsOrIncl(2) == false + ## assert values.containsOrIncl(2) == true + assert s.isValid, "The set needs to be initialized." containsOrInclImpl() -proc initSet*[A](initialSize=64): TSet[A] = - ## creates a new hash set that is empty. `initialSize` needs to be - ## a power of two. +proc init*[A](s: var TSet[A], initialSize=64) = + ## Initializes a hash set. + ## + ## The `initialSize` parameter needs to be a power of too. You can use + ## `math.nextPowerOfTwo() <math.html#nextPowerOfTwo>`_ to guarantee that at + ## runtime. All set variables have to be initialized before you can use them + ## with other procs from this module with the exception of `isValid() + ## <#isValid,TSet[A]>`_ and `len() <#len,TSet[A]>`_. + ## + ## You can call this proc on a previously initialized hash set, which will + ## discard all its values. This might be more convenient than iterating over + ## existing values and calling `excl() <#excl,TSet[A],A>`_ on them. Example: + ## + ## .. code-block :: + ## var a: TSet[int] + ## a.init(4) + ## a.incl(2) + ## a.init + ## assert a.len == 0 and a.isValid assert isPowerOfTwo(initialSize) - result.counter = 0 - newSeq(result.data, initialSize) + s.counter = 0 + newSeq(s.data, initialSize) + +proc initSet*[A](initialSize=64): TSet[A] = + ## Wrapper around `init() <#init,TSet[A],int>`_ for initialization of hash + ## sets. + ## + ## Returns an empty hash set you can assign directly in ``var`` blocks in a + ## single line. Example: + ## + ## .. code-block :: + ## var a = initSet[int](4) + ## a.incl(2) + result.init(initialSize) proc toSet*[A](keys: openArray[A]): TSet[A] = - ## creates a new hash set that contains the given `keys`. + ## Creates a new hash set that contains the given `keys`. + ## + ## Example: + ## + ## .. code-block:: + ## var numbers = toSet([1, 2, 3, 4, 5]) + ## assert numbers.contains(2) + ## assert numbers.contains(4) result = initSet[A](nextPowerOfTwo(keys.len+10)) for key in items(keys): result.incl(key) @@ -152,57 +303,190 @@ template dollarImpl(): stmt {.dirty.} = result.add("}") proc `$`*[A](s: TSet[A]): string = - ## The `$` operator for hash sets. + ## Converts the set `s` to a string, mostly for logging purposes. + ## + ## Don't use this proc for serialization, the representation may change at + ## any moment and values are not escaped. Example: + ## + ## Example: + ## + ## .. code-block:: + ## echo toSet([2, 4, 5]) + ## # --> {2, 4, 5} + ## echo toSet(["no", "esc'aping", "is \" provided"]) + ## # --> {no, esc'aping, is " provided} + assert s.isValid, "The set needs to be initialized." dollarImpl() proc union*[A](s1, s2: TSet[A]): TSet[A] = - ## returns a new set of all items that are contained in at - ## least one of `s1` and `s2` + ## Returns the union of the sets `s1` and `s2`. + ## + ## The union of two sets is represented mathematically as *A ∪ B* and is the + ## set of all objects that are members of `s1`, `s2` or both. Example: + ## + ## .. code-block:: + ## var + ## a = toSet(["a", "b"]) + ## b = toSet(["b", "c"]) + ## c = union(a, b) + ## assert c == toSet(["a", "b", "c"]) + assert s1.isValid, "The set `s1` needs to be initialized." + assert s2.isValid, "The set `s2` needs to be initialized." result = s1 incl(result, s2) proc intersection*[A](s1, s2: TSet[A]): TSet[A] = - ## returns a new set of all items that are contained in both `s1` and `s2` + ## Returns the intersection of the sets `s1` and `s2`. + ## + ## The intersection of two sets is represented mathematically as *A ∩ B* and + ## is the set of all objects that are members of `s1` and `s2` at the same + ## time. Example: + ## + ## .. code-block:: + ## var + ## a = toSet(["a", "b"]) + ## b = toSet(["b", "c"]) + ## c = intersection(a, b) + ## assert c == toSet(["b"]) + assert s1.isValid, "The set `s1` needs to be initialized." + assert s2.isValid, "The set `s2` needs to be initialized." result = initSet[A](min(s1.data.len, s2.data.len)) for item in s1: if item in s2: incl(result, item) proc difference*[A](s1, s2: TSet[A]): TSet[A] = - ## returns a new set of all items that are contained in `s1`, but not in `s2` + ## Returns the difference of the sets `s1` and `s2`. + ## + ## The difference of two sets is represented mathematically as *A \ B* and is + ## the set of all objects that are members of `s1` and not members of `s2`. + ## Example: + ## + ## .. code-block:: + ## var + ## a = toSet(["a", "b"]) + ## b = toSet(["b", "c"]) + ## c = difference(a, b) + ## assert c == toSet(["a"]) + assert s1.isValid, "The set `s1` needs to be initialized." + assert s2.isValid, "The set `s2` needs to be initialized." result = initSet[A]() for item in s1: if not contains(s2, item): incl(result, item) proc symmetricDifference*[A](s1, s2: TSet[A]): TSet[A] = - ## returns a new set of all items that are contained in either - ## `s1` or `s2`, but not both + ## Returns the symmetric difference of the sets `s1` and `s2`. + ## + ## The symmetric difference of two sets is represented mathematically as *A △ + ## B* or *A ⊖ B* and is the set of all objects that are members of `s1` or + ## `s2` but not both at the same time. Example: + ## + ## .. code-block:: + ## var + ## a = toSet(["a", "b"]) + ## b = toSet(["b", "c"]) + ## c = symmetricDifference(a, b) + ## assert c == toSet(["a", "c"]) + assert s1.isValid, "The set `s1` needs to be initialized." + assert s2.isValid, "The set `s2` needs to be initialized." result = s1 for item in s2: if containsOrIncl(result, item): excl(result, item) proc `+`*[A](s1, s2: TSet[A]): TSet[A] {.inline.} = - ## alias for `union` + ## Alias for `union(s1, s2) <#union>`_. result = union(s1, s2) proc `*`*[A](s1, s2: TSet[A]): TSet[A] {.inline.} = - ## alias for `intersection` + ## Alias for `intersection(s1, s2) <#intersection>`_. result = intersection(s1, s2) proc `-`*[A](s1, s2: TSet[A]): TSet[A] {.inline.} = - ## alias for `difference` + ## Alias for `difference(s1, s2) <#difference>`_. result = difference(s1, s2) proc `-+-`*[A](s1, s2: TSet[A]): TSet[A] {.inline.} = - ## alias for `symmetricDifference` + ## Alias for `symmetricDifference(s1, s2) <#symmetricDifference>`_. result = symmetricDifference(s1, s2) proc disjoint*[A](s1, s2: TSet[A]): bool = - ## returns true iff `s1` and `s2` have no items in common + ## Returns true iff the sets `s1` and `s2` have no items in common. + ## + ## Example: + ## + ## .. code-block:: + ## var + ## a = toSet(["a", "b"]) + ## b = toSet(["b", "c"]) + ## assert disjoint(a, b) == false + ## assert disjoint(a, b - a) == true + assert s1.isValid, "The set `s1` needs to be initialized." + assert s2.isValid, "The set `s2` needs to be initialized." for item in s1: if item in s2: return false return true +proc `<`*[A](s, t: TSet[A]): bool = + ## Returns true if `s` is a strict or proper subset of `t`. + ## + ## A strict or proper subset `s` has all of its members in `t` but `t` has + ## more elements than `s`. Example: + ## + ## .. code-block:: + ## var + ## a = toSet(["a", "b"]) + ## b = toSet(["b", "c"]) + ## c = intersection(a, b) + ## assert c < a and c < b + ## assert((a < a) == false) + s.counter != t.counter and s <= t + +proc `<=`*[A](s, t: TSet[A]): bool = + ## Returns true if `s` is subset of `t`. + ## + ## A subset `s` has all of its members in `t` and `t` doesn't necessarily + ## have more members than `s`. That is, `s` can be equal to `t`. Example: + ## + ## .. code-block:: + ## var + ## a = toSet(["a", "b"]) + ## b = toSet(["b", "c"]) + ## c = intersection(a, b) + ## assert c <= a and c <= b + ## assert((a <= a)) + result = false + if s.counter > t.counter: return + result = true + for item in s: + if not(t.contains(item)): + result = false + return + +proc `==`*[A](s, t: TSet[A]): bool = + ## Returns true if both `s` and `t` have the same members and set size. + ## + ## Example: + ## + ## .. code-block:: + ## var + ## a = toSet([1, 2]) + ## b = toSet([1]) + ## b.incl(2) + ## assert a == b + s.counter == t.counter and s <= t + +proc map*[A, B](data: TSet[A], op: proc (x: A): B {.closure.}): TSet[B] = + ## Returns a new set after applying `op` on each of the elements of `data`. + ## + ## You can use this proc to transform the elements from a set. Example: + ## + ## .. code-block:: + ## var a = toSet([1, 2, 3]) + ## var b = a.map(proc (x: int): string = $x) + ## assert b == toSet(["1", "2", "3"]) + result = initSet[B]() + for item in data: result.incl(op(item)) + # ------------------------------ ordered set ------------------------------ type @@ -210,16 +494,48 @@ type slot: TSlotEnum, next: int, key: A] TOrderedKeyValuePairSeq[A] = seq[TOrderedKeyValuePair[A]] TOrderedSet* {. - final, myShallow.}[A] = object ## set that remembers insertion order + final, myShallow.}[A] = object ## \ + ## A generic hash set that remembers insertion order. + ## + ## Use `init() <#init,TOrderedSet[A],int>`_ or `initOrderedSet[type]() + ## <#initOrderedSet>`_ before calling other procs on it. data: TOrderedKeyValuePairSeq[A] counter, first, last: int +proc isValid*[A](s: TOrderedSet[A]): bool = + ## Returns `true` if the ordered set has been initialized with `initSet + ## <#initOrderedSet>`_. + ## + ## Most operations over an uninitialized ordered set will crash at runtime + ## and `assert <system.html#assert>`_ in debug builds. You can use this proc + ## in your own procs to verify that ordered sets passed to your procs are + ## correctly initialized. Example: + ## + ## .. code-block :: nimrod + ## proc saveTarotCards(cards: TOrderedSet[int]) = + ## assert cards.isValid, "Pass an initialized set!" + ## # Do stuff here, may crash in release builds! + result = not s.data.isNil + proc len*[A](s: TOrderedSet[A]): int {.inline.} = - ## returns the number of keys in `s`. + ## Returns the number of keys in `s`. + ## + ## Due to an implementation detail you can call this proc on variables which + ## have not been initialized yet. The proc will return zero as the length + ## then. Example: + ## + ## .. code-block:: + ## + ## var values: TOrderedSet[int] + ## assert(not values.isValid) + ## assert values.len == 0 result = s.counter proc card*[A](s: TOrderedSet[A]): int {.inline.} = - ## alias for `len`. + ## Alias for `len() <#len,TOrderedSet[A]>`_. + ## + ## Card stands for the `cardinality + ## <http://en.wikipedia.org/wiki/Cardinality>`_ of a set. result = s.counter template forAllOrderedPairs(yieldStmt: stmt) {.dirty, immediate.} = @@ -230,7 +546,24 @@ template forAllOrderedPairs(yieldStmt: stmt) {.dirty, immediate.} = h = nxt iterator items*[A](s: TOrderedSet[A]): A = - ## iterates over any key in the set `s` in insertion order. + ## Iterates over keys in the ordered set `s` in insertion order. + ## + ## If you need a sequence with the keys you can use `sequtils.toSeq() + ## <sequtils.html#toSeq>`_ on the iterator. Usage example: + ## + ## .. code-block:: + ## var a = initOrderedSet[int]() + ## for value in [9, 2, 1, 5, 1, 8, 4, 2]: + ## a.incl(value) + ## for value in a.items: + ## echo "Got ", value + ## # --> Got 9 + ## # --> Got 2 + ## # --> Got 1 + ## # --> Got 5 + ## # --> Got 8 + ## # --> Got 4 + assert s.isValid, "The set needs to be initialized." forAllOrderedPairs: yield s.data[h].key @@ -238,7 +571,16 @@ proc rawGet[A](s: TOrderedSet[A], key: A): int = rawGetImpl() proc contains*[A](s: TOrderedSet[A], key: A): bool = - ## returns true iff `key` is in `s`. + ## Returns true iff `key` is in `s`. + ## + ## Example: + ## + ## .. code-block:: + ## var values = initOrderedSet[int]() + ## assert(not values.contains(2)) + ## values.incl(2) + ## assert values.contains(2) + assert s.isValid, "The set needs to be initialized." var index = rawGet(s, key) result = index >= 0 @@ -264,53 +606,297 @@ proc enlarge[A](s: var TOrderedSet[A]) = swap(s.data, n) proc incl*[A](s: var TOrderedSet[A], key: A) = - ## includes an element `key` in `s`. + ## Includes an element `key` in `s`. + ## + ## This doesn't do anything if `key` is already in `s`. Example: + ## + ## .. code-block:: + ## var values = initOrderedSet[int]() + ## values.incl(2) + ## values.incl(2) + ## assert values.len == 1 + assert s.isValid, "The set needs to be initialized." inclImpl() proc incl*[A](s: var TSet[A], other: TOrderedSet[A]) = - ## includes everything in `other` in `s` + ## Includes all elements from `other` into `s`. + ## + ## Example: + ## + ## .. code-block:: + ## var values = initOrderedSet[int]() + ## values.incl(2) + ## var others = toOrderedSet([6, 7]) + ## values.incl(others) + ## assert values.len == 3 + assert s.isValid, "The set `s` needs to be initialized." + assert other.isValid, "The set `other` needs to be initialized." for item in other: incl(s, item) proc containsOrIncl*[A](s: var TOrderedSet[A], key: A): bool = - ## returns true if `s` contains `key`, otherwise `key` is included in `s` - ## and false is returned. + ## Includes `key` in the set `s` and tells if `key` was added to `s`. + ## + ## The difference with regards to the `incl() <#incl,TOrderedSet[A],A>`_ proc + ## is that this proc returns `true` if `key` was already present in `s`. The + ## proc will return false if `key` was added as a new value to `s` during + ## this call. Example: + ## + ## .. code-block:: + ## var values = initOrderedSet[int]() + ## assert values.containsOrIncl(2) == false + ## assert values.containsOrIncl(2) == true + assert s.isValid, "The set needs to be initialized." containsOrInclImpl() -proc initOrderedSet*[A](initialSize=64): TOrderedSet[A] = - ## creates a new ordered hash set that is empty. `initialSize` needs to be - ## a power of two. +proc init*[A](s: var TOrderedSet[A], initialSize=64) = + ## Initializes an ordered hash set. + ## + ## The `initialSize` parameter needs to be a power of too. You can use + ## `math.nextPowerOfTwo() <math.html#nextPowerOfTwo>`_ to guarantee that at + ## runtime. All set variables have to be initialized before you can use them + ## with other procs from this module with the exception of `isValid() + ## <#isValid,TOrderedSet[A]>`_ and `len() <#len,TOrderedSet[A]>`_. + ## + ## You can call this proc on a previously initialized ordered hash set to + ## discard its values. At the moment this is the only proc to remove elements + ## from an ordered hash set. Example: + ## + ## .. code-block :: + ## var a: TOrderedSet[int] + ## a.init(4) + ## a.incl(2) + ## a.init + ## assert a.len == 0 and a.isValid assert isPowerOfTwo(initialSize) - result.counter = 0 - result.first = -1 - result.last = -1 - newSeq(result.data, initialSize) + s.counter = 0 + s.first = -1 + s.last = -1 + newSeq(s.data, initialSize) + +proc initOrderedSet*[A](initialSize=64): TOrderedSet[A] = + ## Wrapper around `init() <#init,TOrderedSet[A],int>`_ for initialization of + ## ordered hash sets. + ## + ## Returns an empty ordered hash set you can assign directly in ``var`` + ## blocks in a single line. Example: + ## + ## .. code-block :: + ## var a = initOrderedSet[int](4) + ## a.incl(2) + result.init(initialSize) proc toOrderedSet*[A](keys: openArray[A]): TOrderedSet[A] = - ## creates a new ordered hash set that contains the given `keys`. + ## Creates a new ordered hash set that contains the given `keys`. + ## + ## Example: + ## + ## .. code-block:: + ## var numbers = toOrderedSet([1, 2, 3, 4, 5]) + ## assert numbers.contains(2) + ## assert numbers.contains(4) result = initOrderedSet[A](nextPowerOfTwo(keys.len+10)) for key in items(keys): result.incl(key) proc `$`*[A](s: TOrderedSet[A]): string = - ## The `$` operator for ordered hash sets. + ## Converts the ordered hash set `s` to a string, mostly for logging purposes. + ## + ## Don't use this proc for serialization, the representation may change at + ## any moment and values are not escaped. Example: + ## + ## Example: + ## + ## .. code-block:: + ## echo toOrderedSet([2, 4, 5]) + ## # --> {2, 4, 5} + ## echo toOrderedSet(["no", "esc'aping", "is \" provided"]) + ## # --> {no, esc'aping, is " provided} + assert s.isValid, "The set needs to be initialized." dollarImpl() -proc `<`*[A](s, t: TSet[A]): bool = - ## Is s a strict subset of t? - s.counter != t.counter and s <= t - -proc `<=`*[A](s, t: TSet[A]): bool = - ## Is s a subset of t? - result = false - if s.counter > t.counter: return - result = true - for item in s: - if not(t.contains(item)): - result = false - return - -proc `==`*[A](s, t: TSet[A]): bool = - s.counter == t.counter and s <= t - -proc map*[A, B](data: TSet[A], op: proc (x: A): B {.closure.}): TSet[B] = - result = initSet[B]() - for item in data: result.incl(op(item)) +proc `==`*[A](s, t: TOrderedSet[A]): bool = + ## Equality for ordered sets. + if s.counter != t.counter: return false + var h = s.first + var g = s.first + var compared = 0 + while h >= 0 and g >= 0: + var nxh = s.data[h].next + var nxg = t.data[g].next + if s.data[h].slot == seFilled and s.data[g].slot == seFilled: + if s.data[h].key == s.data[g].key: + inc compared + else: + return false + h = nxh + g = nxg + result = compared == s.counter + +proc testModule() = + ## Internal micro test to validate docstrings and such. + block isValidTest: + var options: TSet[string] + proc savePreferences(options: TSet[string]) = + assert options.isValid, "Pass an initialized set!" + options = initSet[string]() + options.savePreferences + + block lenTest: + var values: TSet[int] + assert(not values.isValid) + assert values.len == 0 + assert values.card == 0 + + block setIterator: + type pair = tuple[a, b: int] + var a, b = initSet[pair]() + a.incl((2, 3)) + a.incl((3, 2)) + a.incl((2, 3)) + for x, y in a.items: + b.incl((x - 2, y + 1)) + assert a.len == b.card + assert a.len == 2 + #echo b + + block setContains: + var values = initSet[int]() + assert(not values.contains(2)) + values.incl(2) + assert values.contains(2) + values.excl(2) + assert(not values.contains(2)) + + values.incl(4) + var others = toSet([6, 7]) + values.incl(others) + assert values.len == 3 + + values.init + assert values.containsOrIncl(2) == false + assert values.containsOrIncl(2) == true + var + a = toSet([1, 2]) + b = toSet([1]) + b.incl(2) + assert a == b + + block exclusions: + var s = toSet([2, 3, 6, 7]) + s.excl(2) + s.excl(2) + assert s.len == 3 + + var + numbers = toSet([1, 2, 3, 4, 5]) + even = toSet([2, 4, 6, 8]) + numbers.excl(even) + #echo numbers + # --> {1, 3, 5} + + block toSeqAndString: + var a = toSet([2, 4, 5]) + var b = initSet[int]() + for x in [2, 4, 5]: b.incl(x) + assert($a == $b) + #echo a + #echo toSet(["no", "esc'aping", "is \" provided"]) + + #block orderedToSeqAndString: + # echo toOrderedSet([2, 4, 5]) + # echo toOrderedSet(["no", "esc'aping", "is \" provided"]) + + block setOperations: + var + a = toSet(["a", "b"]) + b = toSet(["b", "c"]) + c = union(a, b) + assert c == toSet(["a", "b", "c"]) + var d = intersection(a, b) + assert d == toSet(["b"]) + var e = difference(a, b) + assert e == toSet(["a"]) + var f = symmetricDifference(a, b) + assert f == toSet(["a", "c"]) + assert d < a and d < b + assert((a < a) == false) + assert d <= a and d <= b + assert((a <= a)) + # Alias test. + assert a + b == toSet(["a", "b", "c"]) + assert a * b == toSet(["b"]) + assert a - b == toSet(["a"]) + assert a -+- b == toSet(["a", "c"]) + assert disjoint(a, b) == false + assert disjoint(a, b - a) == true + + block mapSet: + var a = toSet([1, 2, 3]) + var b = a.map(proc (x: int): string = $x) + assert b == toSet(["1", "2", "3"]) + + block isValidTest: + var cards: TOrderedSet[string] + proc saveTarotCards(cards: TOrderedSet[string]) = + assert cards.isValid, "Pass an initialized set!" + cards = initOrderedSet[string]() + cards.saveTarotCards + + block lenTest: + var values: TOrderedSet[int] + assert(not values.isValid) + assert values.len == 0 + assert values.card == 0 + + block setIterator: + type pair = tuple[a, b: int] + var a, b = initOrderedSet[pair]() + a.incl((2, 3)) + a.incl((3, 2)) + a.incl((2, 3)) + for x, y in a.items: + b.incl((x - 2, y + 1)) + assert a.len == b.card + assert a.len == 2 + + #block orderedSetIterator: + # var a = initOrderedSet[int]() + # for value in [9, 2, 1, 5, 1, 8, 4, 2]: + # a.incl(value) + # for value in a.items: + # echo "Got ", value + + block setContains: + var values = initOrderedSet[int]() + assert(not values.contains(2)) + values.incl(2) + assert values.contains(2) + + block toSeqAndString: + var a = toOrderedSet([2, 4, 5]) + var b = initOrderedSet[int]() + for x in [2, 4, 5]: b.incl(x) + assert($a == $b) + assert(a == b) # https://github.com/Araq/Nimrod/issues/1413 + + block initBlocks: + var a: TOrderedSet[int] + a.init(4) + a.incl(2) + a.init + assert a.len == 0 and a.isValid + a = initOrderedSet[int](4) + a.incl(2) + assert a.len == 1 + + var b: TSet[int] + b.init(4) + b.incl(2) + b.init + assert b.len == 0 and b.isValid + b = initSet[int](4) + b.incl(2) + assert b.len == 1 + + echo "Micro tests run successfully." + +when isMainModule and not defined(release): testModule() diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index ce9df09e1..dcf2ab481 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -246,7 +246,8 @@ template equalsImpl() = # different insertion orders mean different 'data' seqs, so we have # to use the slow route here: for key, val in s: - if not hasKey(t, key): return false + # prefix notation leads to automatic dereference in case of PTable + if not t.hasKey(key): return false if t[key] != val: return false return true @@ -332,7 +333,9 @@ proc `$`*[A, B](t: PTable[A, B]): string = dollarImpl() proc `==`*[A, B](s, t: PTable[A, B]): bool = - equalsImpl() + if isNil(s): result = isNil(t) + elif isNil(t): result = false + else: result = equalsImpl() proc newTableFrom*[A, B, C](collection: A, index: proc(x: B): C): PTable[C, B] = ## Index the collection with the proc provided. diff --git a/lib/pure/concurrency/threadpool.nim b/lib/pure/concurrency/threadpool.nim index fd1041918..e0a2ac678 100644 --- a/lib/pure/concurrency/threadpool.nim +++ b/lib/pure/concurrency/threadpool.nim @@ -9,6 +9,9 @@ ## Implements Nimrod's 'spawn'. +when not compileOption("threads"): + {.error: "Threadpool requires --threads:on option.".} + import cpuinfo, cpuload, locks {.push stackTrace:off.} @@ -134,7 +137,7 @@ proc await*(fv: FlowVarBase) = proc finished(fv: FlowVarBase) = doAssert fv.ai.isNil, "flowVar is still attached to an 'awaitAny'" # we have to protect against the rare cases where the owner of the flowVar - # simply disregards the flowVar and yet the "flowVarr" has not yet written + # simply disregards the flowVar and yet the "flowVar" has not yet written # anything to it: await(fv) if fv.data.isNil: return @@ -207,6 +210,7 @@ proc `^`*[T](fv: FlowVar[T]): T = ## blocks until the value is available and then returns this value. await(fv) when T is string or T is seq: + # XXX closures? deepCopy? result = cast[T](fv.data) else: result = fv.blob @@ -264,6 +268,10 @@ proc slave(w: ptr Worker) {.thread.} = w.shutdown = false atomicDec currentPoolSize +var + workers: array[MaxThreadPoolSize, TThread[ptr Worker]] + workersData: array[MaxThreadPoolSize, Worker] + proc setMinPoolSize*(size: range[1..MaxThreadPoolSize]) = ## sets the minimal thread pool size. The default value of this is 4. minPoolSize = size @@ -272,10 +280,10 @@ proc setMaxPoolSize*(size: range[1..MaxThreadPoolSize]) = ## sets the minimal thread pool size. The default value of this ## is ``MaxThreadPoolSize``. maxPoolSize = size - -var - workers: array[MaxThreadPoolSize, TThread[ptr Worker]] - workersData: array[MaxThreadPoolSize, Worker] + if currentPoolSize > maxPoolSize: + for i in maxPoolSize..currentPoolSize-1: + let w = addr(workersData[i]) + w.shutdown = true proc activateThread(i: int) {.noinline.} = workersData[i].taskArrived = createCondVar() diff --git a/lib/pure/cookies.nim b/lib/pure/cookies.nim index d1cf36a87..49bf92980 100644 --- a/lib/pure/cookies.nim +++ b/lib/pure/cookies.nim @@ -28,8 +28,9 @@ proc parseCookies*(s: string): PStringTable = if s[i] == '\0': break inc(i) # skip ';' -proc setCookie*(key, value: string, domain = "", path = "", - expires = "", noName = false): string = +proc setCookie*(key, value: string, domain = "", path = "", + expires = "", noName = false, + secure = false, httpOnly = false): string = ## Creates a command in the format of ## ``Set-Cookie: key=value; Domain=...; ...`` result = "" @@ -38,16 +39,20 @@ proc setCookie*(key, value: string, domain = "", path = "", if domain != "": result.add("; Domain=" & domain) if path != "": result.add("; Path=" & path) if expires != "": result.add("; Expires=" & expires) + if secure: result.add("; secure") + if httpOnly: result.add("; HttpOnly") proc setCookie*(key, value: string, expires: TTimeInfo, - domain = "", path = "", noName = false): string = + domain = "", path = "", noName = false, + secure = false, httpOnly = false): string = ## Creates a command in the format of ## ``Set-Cookie: key=value; Domain=...; ...`` ## ## **Note:** UTC is assumed as the timezone for ``expires``. return setCookie(key, value, domain, path, - format(expires, "ddd',' dd MMM yyyy HH:mm:ss 'UTC'"), noname) + format(expires, "ddd',' dd MMM yyyy HH:mm:ss 'UTC'"), + noname, secure, httpOnly) when isMainModule: var tim = TTime(int(getTime()) + 76 * (60 * 60 * 24)) @@ -55,5 +60,3 @@ when isMainModule: echo(setCookie("test", "value", tim.getGMTime())) echo parseCookies("uid=1; kp=2") - - \ No newline at end of file diff --git a/lib/pure/ftpclient.nim b/lib/pure/ftpclient.nim index 53f6688b9..e8d3f762e 100644 --- a/lib/pure/ftpclient.nim +++ b/lib/pure/ftpclient.nim @@ -10,6 +10,10 @@ include "system/inclrtl" import sockets, strutils, parseutils, times, os, asyncio +from asyncnet import nil +from rawsockets import nil +from asyncdispatch import PFuture + ## This module **partially** implements an FTP client as specified ## by `RFC 959 <http://tools.ietf.org/html/rfc959>`_. ## @@ -34,34 +38,32 @@ import sockets, strutils, parseutils, times, os, asyncio type - TFTPClient* = object of TObject - case isAsync: bool - of false: - csock: TSocket # Command connection socket - dsock: TSocket # Data connection socket - else: - dummyA, dummyB: pointer # workaround a Nimrod API issue - asyncCSock: PAsyncSocket - asyncDSock: PAsyncSocket + PFtpBase*[SockType] = ref TFtpBase[SockType] + TFtpBase*[SockType] = object + csock*: SockType + dsock*: SockType + when SockType is asyncio.PAsyncSocket: handleEvent*: proc (ftp: PAsyncFTPClient, ev: TFTPEvent){.closure,gcsafe.} disp: PDispatcher asyncDSockID: PDelegate - user, pass: string - address: string - port: TPort + user*, pass*: string + address*: string + when SockType is asyncnet.PAsyncSocket: + port*: rawsockets.TPort + else: + port*: TPort - jobInProgress: bool - job: ref TFTPJob + jobInProgress*: bool + job*: PFTPJob[SockType] - dsockConnected: bool - - PFTPClient* = ref TFTPClient + dsockConnected*: bool FTPJobType* = enum JRetrText, JRetr, JStore - TFTPJob = object - prc: proc (ftp: PFTPClient, async: bool): bool {.nimcall, gcsafe.} + PFtpJob[T] = ref TFtpJob[T] + TFTPJob[T] = object + prc: proc (ftp: PFTPBase[T], async: bool): bool {.nimcall, gcsafe.} case typ*: FTPJobType of JRetrText: lines: string @@ -75,8 +77,11 @@ type toStore: string # Data left to upload (Only used with async) else: nil + TFtpClient* = TFtpBase[TSocket] + PFtpClient* = ref TFTPClient + PAsyncFTPClient* = ref TAsyncFTPClient ## Async alternative to TFTPClient. - TAsyncFTPClient* = object of TFTPClient + TAsyncFTPClient* = TFtpBase[asyncio.PAsyncSocket] FTPEventType* = enum EvTransferProgress, EvLines, EvRetr, EvStore @@ -106,30 +111,30 @@ proc ftpClient*(address: string, port = TPort(21), result.address = address result.port = port - result.isAsync = false result.dsockConnected = false result.csock = socket() if result.csock == InvalidSocket: osError(osLastError()) -proc getDSock(ftp: PFTPClient): TSocket = - if ftp.isAsync: return ftp.asyncDSock else: return ftp.dsock +proc getDSock[T](ftp: PFTPBase[T]): TSocket = + return ftp.dsock -proc getCSock(ftp: PFTPClient): TSocket = - if ftp.isAsync: return ftp.asyncCSock else: return ftp.csock +proc getCSock[T](ftp: PFTPBase[T]): TSocket = + return ftp.csock template blockingOperation(sock: TSocket, body: stmt) {.immediate.} = - if ftp.isAsync: - sock.setBlocking(true) body - if ftp.isAsync: - sock.setBlocking(false) -proc expectReply(ftp: PFTPClient): TaintedString = +template blockingOperation(sock: asyncio.PAsyncSocket, body: stmt) {.immediate.} = + sock.setBlocking(true) + body + sock.setBlocking(false) + +proc expectReply[T](ftp: PFtpBase[T]): TaintedString = result = TaintedString"" blockingOperation(ftp.getCSock()): ftp.getCSock().readLine(result) -proc send*(ftp: PFTPClient, m: string): TaintedString = +proc send*[T](ftp: PFtpBase[T], m: string): TaintedString = ## Send a message to the server, and wait for a primary reply. ## ``\c\L`` is added for you. blockingOperation(ftp.getCSock()): @@ -149,8 +154,8 @@ proc assertReply(received: TaintedString, expected: varargs[string]) = "Expected reply '$1' got: $2" % [expected.join("' or '"), received.string]) -proc createJob(ftp: PFTPClient, - prc: proc (ftp: PFTPClient, async: bool): bool {. +proc createJob[T](ftp: PFtpBase[T], + prc: proc (ftp: PFtpBase[T], async: bool): bool {. nimcall,gcsafe.}, cmd: FTPJobType) = if ftp.jobInProgress: @@ -165,7 +170,7 @@ proc createJob(ftp: PFTPClient, of JRetr, JStore: ftp.job.toStore = "" -proc deleteJob(ftp: PFTPClient) = +proc deleteJob[T](ftp: PFtpBase[T]) = assert ftp.jobInProgress ftp.jobInProgress = false case ftp.job.typ @@ -173,12 +178,9 @@ proc deleteJob(ftp: PFTPClient) = ftp.job.lines = "" of JRetr, JStore: ftp.job.file.close() - if ftp.isAsync: - ftp.asyncDSock.close() - else: - ftp.dsock.close() + ftp.dsock.close() -proc handleTask(s: PAsyncSocket, ftp: PFTPClient) = +proc handleTask(s: PAsyncSocket, ftp: PAsyncFTPClient) = if ftp.jobInProgress: if ftp.job.typ in {JRetr, JStore}: if epochTime() - ftp.job.lastProgressReport >= 1.0: @@ -193,12 +195,12 @@ proc handleTask(s: PAsyncSocket, ftp: PFTPClient) = ftp.job.oneSecond = 0 ftp.handleEvent(PAsyncFTPClient(ftp), r) -proc handleWrite(s: PAsyncSocket, ftp: PFTPClient) = +proc handleWrite(s: PAsyncSocket, ftp: PAsyncFTPClient) = if ftp.jobInProgress: if ftp.job.typ == JStore: assert (not ftp.job.prc(ftp, true)) -proc handleConnect(s: PAsyncSocket, ftp: PFTPClient) = +proc handleConnect(s: PAsyncSocket, ftp: PAsyncFTPClient) = ftp.dsockConnected = true assert(ftp.jobInProgress) if ftp.job.typ == JStore: @@ -206,30 +208,32 @@ proc handleConnect(s: PAsyncSocket, ftp: PFTPClient) = else: s.delHandleWrite() -proc handleRead(s: PAsyncSocket, ftp: PFTPClient) = +proc handleRead(s: PAsyncSocket, ftp: PAsyncFTPClient) = assert ftp.jobInProgress assert ftp.job.typ != JStore # This can never return true, because it shouldn't check for code # 226 from csock. assert(not ftp.job.prc(ftp, true)) -proc pasv(ftp: PFTPClient) = +proc pasv[T](ftp: PFtpBase[T]) = ## Negotiate a data connection. - if not ftp.isAsync: + when T is TSocket: ftp.dsock = socket() if ftp.dsock == InvalidSocket: osError(osLastError()) - else: - ftp.asyncDSock = AsyncSocket() - ftp.asyncDSock.handleRead = + elif T is PAsyncSocket: + ftp.dsock = AsyncSocket() + ftp.dsock.handleRead = proc (s: PAsyncSocket) = handleRead(s, ftp) - ftp.asyncDSock.handleConnect = + ftp.dsock.handleConnect = proc (s: PAsyncSocket) = handleConnect(s, ftp) - ftp.asyncDSock.handleTask = + ftp.dsock.handleTask = proc (s: PAsyncSocket) = handleTask(s, ftp) - ftp.disp.register(ftp.asyncDSock) + ftp.disp.register(ftp.dsock) + else: + {.fatal: "Incorrect socket instantiation".} var pasvMsg = ftp.send("PASV").string.strip.TaintedString assertReply(pasvMsg, "227") @@ -238,23 +242,24 @@ proc pasv(ftp: PFTPClient) = var ip = nums[0.. -3] var port = nums[-2.. -1] var properPort = port[0].parseInt()*256+port[1].parseInt() - if ftp.isAsync: - ftp.asyncDSock.connect(ip.join("."), TPort(properPort.toU16)) + ftp.dsock.connect(ip.join("."), TPort(properPort.toU16)) + when T is PAsyncSocket: ftp.dsockConnected = False else: - ftp.dsock.connect(ip.join("."), TPort(properPort.toU16)) ftp.dsockConnected = True proc normalizePathSep(path: string): string = return replace(path, '\\', '/') -proc connect*(ftp: PFTPClient) = +proc connect*[T](ftp: PFtpBase[T]) = ## Connect to the FTP server specified by ``ftp``. - if ftp.isAsync: - blockingOperation(ftp.asyncCSock): - ftp.asyncCSock.connect(ftp.address, ftp.port) - else: + when T is PAsyncSocket: + blockingOperation(ftp.csock): + ftp.csock.connect(ftp.address, ftp.port) + elif T is TSocket: ftp.csock.connect(ftp.address, ftp.port) + else: + {.fatal: "Incorrect socket instantiation".} # TODO: Handle 120? or let user handle it. assertReply ftp.expectReply(), "220" @@ -279,25 +284,27 @@ proc cdup*(ftp: PFTPClient) = ## Changes the current directory to the parent of the current directory. assertReply ftp.send("CDUP"), "200" -proc getLines(ftp: PFTPClient, async: bool = false): bool = +proc getLines[T](ftp: PFtpBase[T], async: bool = false): bool = ## Downloads text data in ASCII mode ## Returns true if the download is complete. ## It doesn't if `async` is true, because it doesn't check for 226 then. if ftp.dsockConnected: var r = TaintedString"" - if ftp.isAsync: + when T is PAsyncSocket: if ftp.asyncDSock.readLine(r): if r.string == "": ftp.dsockConnected = false else: ftp.job.lines.add(r.string & "\n") - else: + elif T is TSocket: assert(not async) ftp.dsock.readLine(r) if r.string == "": ftp.dsockConnected = false else: ftp.job.lines.add(r.string & "\n") + else: + {.fatal: "Incorrect socket instantiation".} if not async: var readSocks: seq[TSocket] = @[ftp.getCSock()] @@ -307,14 +314,14 @@ proc getLines(ftp: PFTPClient, async: bool = false): bool = assertReply ftp.expectReply(), "226" return true -proc listDirs*(ftp: PFTPClient, dir: string = "", +proc listDirs*[T](ftp: PFtpBase[T], dir: string = "", async = false): seq[string] = ## Returns a list of filenames in the given directory. If ``dir`` is "", ## the current directory is used. If ``async`` is true, this ## function will return immediately and it will be your job to ## use asyncio's ``poll`` to progress this operation. - ftp.createJob(getLines, JRetrText) + ftp.createJob(getLines[T], JRetrText) ftp.pasv() assertReply ftp.send("NLST " & dir.normalizePathSep), ["125", "150"] @@ -384,12 +391,12 @@ proc chmod*(ftp: PFTPClient, path: string, assertReply ftp.send("SITE CHMOD " & perm & " " & path.normalizePathSep), "200" -proc list*(ftp: PFTPClient, dir: string = "", async = false): string = +proc list*[T](ftp: PFtpBase[T], dir: string = "", async = false): string = ## Lists all files in ``dir``. If ``dir`` is ``""``, uses the current ## working directory. If ``async`` is true, this function will return ## immediately and it will be your job to call asyncio's ## ``poll`` to progress this operation. - ftp.createJob(getLines, JRetrText) + ftp.createJob(getLines[T], JRetrText) ftp.pasv() assertReply(ftp.send("LIST" & " " & dir.normalizePathSep), ["125", "150"]) @@ -401,11 +408,11 @@ proc list*(ftp: PFTPClient, dir: string = "", async = false): string = else: return "" -proc retrText*(ftp: PFTPClient, file: string, async = false): string = +proc retrText*[T](ftp: PFtpBase[T], file: string, async = false): string = ## Retrieves ``file``. File must be ASCII text. ## If ``async`` is true, this function will return immediately and ## it will be your job to call asyncio's ``poll`` to progress this operation. - ftp.createJob(getLines, JRetrText) + ftp.createJob(getLines[T], JRetrText) ftp.pasv() assertReply ftp.send("RETR " & file.normalizePathSep), ["125", "150"] @@ -416,15 +423,17 @@ proc retrText*(ftp: PFTPClient, file: string, async = false): string = else: return "" -proc getFile(ftp: PFTPClient, async = false): bool = +proc getFile[T](ftp: PFtpBase[T], async = false): bool = if ftp.dsockConnected: var r = "".TaintedString var bytesRead = 0 var returned = false if async: - if not ftp.isAsync: raise newException(EFTP, "FTPClient must be async.") - bytesRead = ftp.AsyncDSock.recvAsync(r, BufferSize) - returned = bytesRead != -1 + when T is TSocket: + raise newException(EFTP, "FTPClient must be async.") + else: + bytesRead = ftp.dsock.recvAsync(r, BufferSize) + returned = bytesRead != -1 else: bytesRead = getDSock(ftp).recv(r, BufferSize) returned = true @@ -443,13 +452,13 @@ proc getFile(ftp: PFTPClient, async = false): bool = assertReply ftp.expectReply(), "226" return true -proc retrFile*(ftp: PFTPClient, file, dest: string, async = false) = +proc retrFile*[T](ftp: PFtpBase[T], file, dest: string, async = false) = ## Downloads ``file`` and saves it to ``dest``. Usage of this function ## asynchronously is recommended to view the progress of the download. ## The ``EvRetr`` event is passed to the specified ``handleEvent`` function ## when the download is finished, and the ``filename`` field will be equal ## to ``file``. - ftp.createJob(getFile, JRetr) + ftp.createJob(getFile[T], JRetr) ftp.job.file = open(dest, mode = fmWrite) ftp.pasv() var reply = ftp.send("RETR " & file.normalizePathSep) @@ -468,11 +477,11 @@ proc retrFile*(ftp: PFTPClient, file, dest: string, async = false) = while not ftp.job.prc(ftp, false): discard ftp.deleteJob() -proc doUpload(ftp: PFTPClient, async = false): bool = +proc doUpload[T](ftp: PFtpBase[T], async = false): bool = if ftp.dsockConnected: if ftp.job.toStore.len() > 0: assert(async) - let bytesSent = ftp.asyncDSock.sendAsync(ftp.job.toStore) + let bytesSent = ftp.dsock.sendAsync(ftp.job.toStore) if bytesSent == ftp.job.toStore.len: ftp.job.toStore = "" elif bytesSent != ftp.job.toStore.len and bytesSent != 0: @@ -485,7 +494,7 @@ proc doUpload(ftp: PFTPClient, async = false): bool = setLen(s, len) if len == 0: # File finished uploading. - if ftp.isAsync: ftp.asyncDSock.close() else: ftp.dsock.close() + ftp.dsock.close() ftp.dsockConnected = false if not async: @@ -496,7 +505,7 @@ proc doUpload(ftp: PFTPClient, async = false): bool = if not async: getDSock(ftp).send(s) else: - let bytesSent = ftp.asyncDSock.sendAsync(s) + let bytesSent = ftp.dsock.sendAsync(s) if bytesSent == 0: ftp.job.toStore.add(s) elif bytesSent != s.len: @@ -506,14 +515,14 @@ proc doUpload(ftp: PFTPClient, async = false): bool = ftp.job.progress.inc(len) ftp.job.oneSecond.inc(len) -proc store*(ftp: PFTPClient, file, dest: string, async = false) = +proc store*[T](ftp: PFtpBase[T], file, dest: string, async = false) = ## Uploads ``file`` to ``dest`` on the remote FTP server. Usage of this ## function asynchronously is recommended to view the progress of ## the download. ## The ``EvStore`` event is passed to the specified ``handleEvent`` function ## when the upload is finished, and the ``filename`` field will be ## equal to ``file``. - ftp.createJob(doUpload, JStore) + ftp.createJob(doUpload[T], JStore) ftp.job.file = open(file) ftp.job.total = ftp.job.file.getFileSize() ftp.job.lastProgressReport = epochTime() @@ -526,16 +535,12 @@ proc store*(ftp: PFTPClient, file, dest: string, async = false) = while not ftp.job.prc(ftp, false): discard ftp.deleteJob() -proc close*(ftp: PFTPClient) = +proc close*[T](ftp: PFTPBase[T]) = ## Terminates the connection to the server. assertReply ftp.send("QUIT"), "221" if ftp.jobInProgress: ftp.deleteJob() - if ftp.isAsync: - ftp.asyncCSock.close() - ftp.asyncDSock.close() - else: - ftp.csock.close() - ftp.dsock.close() + ftp.csock.close() + ftp.dsock.close() proc csockHandleRead(s: PAsyncSocket, ftp: PAsyncFTPClient) = if ftp.jobInProgress: @@ -572,66 +577,65 @@ proc asyncFTPClient*(address: string, port = TPort(21), dres.pass = pass dres.address = address dres.port = port - dres.isAsync = true dres.dsockConnected = false dres.handleEvent = handleEvent - dres.asyncCSock = AsyncSocket() - dres.asyncCSock.handleRead = + dres.csock = AsyncSocket() + dres.csock.handleRead = proc (s: PAsyncSocket) = csockHandleRead(s, dres) result = dres proc register*(d: PDispatcher, ftp: PAsyncFTPClient): PDelegate {.discardable.} = ## Registers ``ftp`` with dispatcher ``d``. - assert ftp.isAsync ftp.disp = d - return ftp.disp.register(ftp.asyncCSock) + return ftp.disp.register(ftp.csock) when isMainModule: - var d = newDispatcher() - let hev = - proc (ftp: PAsyncFTPClient, event: TFTPEvent) = - case event.typ - of EvStore: - echo("Upload finished!") - ftp.retrFile("payload.JPG", "payload2.JPG", async = true) - of EvTransferProgress: - var time: int64 = -1 - if event.speed != 0: - time = (event.bytesTotal - event.bytesFinished) div event.speed - echo(event.currentJob) - echo(event.speed div 1000, " kb/s. - ", - event.bytesFinished, "/", event.bytesTotal, - " - ", time, " seconds") - echo(d.len) - of EvRetr: - echo("Download finished!") - ftp.close() - echo d.len - else: assert(false) - var ftp = asyncFTPClient("picheta.me", user = "test", pass = "asf", handleEvent = hev) - - d.register(ftp) - d.len.echo() - ftp.connect() - echo "connected" - ftp.store("payload.JPG", "payload.JPG", async = true) - d.len.echo() - echo "uploading..." - while true: - if not d.poll(): break - + proc main = + var d = newDispatcher() + let hev = + proc (ftp: PAsyncFTPClient, event: TFTPEvent) = + case event.typ + of EvStore: + echo("Upload finished!") + ftp.retrFile("payload.jpg", "payload2.jpg", async = true) + of EvTransferProgress: + var time: int64 = -1 + if event.speed != 0: + time = (event.bytesTotal - event.bytesFinished) div event.speed + echo(event.currentJob) + echo(event.speed div 1000, " kb/s. - ", + event.bytesFinished, "/", event.bytesTotal, + " - ", time, " seconds") + echo(d.len) + of EvRetr: + echo("Download finished!") + ftp.close() + echo d.len + else: assert(false) + var ftp = asyncFTPClient("example.com", user = "foo", pass = "bar", handleEvent = hev) + + d.register(ftp) + d.len.echo() + ftp.connect() + echo "connected" + ftp.store("payload.jpg", "payload.jpg", async = true) + d.len.echo() + echo "uploading..." + while true: + if not d.poll(): break + main() when isMainModule and false: - var ftp = ftpClient("picheta.me", user = "asdasd", pass = "asfwq") + var ftp = ftpClient("example.com", user = "foo", pass = "bar") ftp.connect() echo ftp.pwd() echo ftp.list() echo("uploading") - ftp.store("payload.JPG", "payload.JPG", async = false) + ftp.store("payload.jpg", "payload.jpg", async = false) echo("Upload complete") - ftp.retrFile("payload.JPG", "payload2.JPG", async = false) + ftp.retrFile("payload.jpg", "payload2.jpg", async = false) echo("Download complete") sleep(5000) diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index 9bacc80d6..4db6ac6ed 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -654,8 +654,7 @@ when isMainModule: resp = await client.request("http://nimrod-lang.org/download.html") echo("Got response: ", resp.status) - asyncCheck main() - runForever() + waitFor main() else: #downloadFile("http://force7.de/nimrod/index.html", "nimrodindex.html") diff --git a/lib/pure/irc.nim b/lib/pure/irc.nim deleted file mode 100644 index 49d9a9a34..000000000 --- a/lib/pure/irc.nim +++ /dev/null @@ -1,503 +0,0 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2012 Dominik Picheta -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module implements an asynchronous IRC client. -## -## Currently this module requires at least some knowledge of the IRC protocol. -## It provides a function for sending raw messages to the IRC server, together -## with some basic functions like sending a message to a channel. -## It automizes the process of keeping the connection alive, so you don't -## need to reply to PING messages. In fact, the server is also PING'ed to check -## the amount of lag. -## -## .. code-block:: Nimrod -## -## var client = irc("picheta.me", joinChans = @["#bots"]) -## client.connect() -## while True: -## var event: TIRCEvent -## if client.poll(event): -## case event.typ -## of EvConnected: nil -## of EvDisconnected: -## client.reconnect() -## of EvMsg: -## # Write your message reading code here. -## -## **Warning:** The API of this module is unstable, and therefore is subject -## to change. - -include "system/inclrtl" - -import sockets, strutils, parseutils, times, asyncio, os - -type - TIRC* = object of TObject - address: string - port: TPort - nick, user, realname, serverPass: string - case isAsync: bool - of true: - handleEvent: proc (irc: PAsyncIRC, ev: TIRCEvent) {.closure, gcsafe.} - asyncSock: PAsyncSocket - myDispatcher: PDispatcher - of false: - dummyA: pointer - dummyB: pointer # workaround a Nimrod API issue - dummyC: pointer - sock: TSocket - status: TInfo - lastPing: float - lastPong: float - lag: float - channelsToJoin: seq[string] - msgLimit: bool - messageBuffer: seq[tuple[timeToSend: float, m: string]] - lastReconnect: float - - PIRC* = ref TIRC - - PAsyncIRC* = ref TAsyncIRC - TAsyncIRC* = object of TIRC - - TIRCMType* = enum - MUnknown, - MNumeric, - MPrivMsg, - MJoin, - MPart, - MMode, - MTopic, - MInvite, - MKick, - MQuit, - MNick, - MNotice, - MPing, - MPong, - MError - - TIRCEventType* = enum - EvMsg, EvConnected, EvDisconnected - TIRCEvent* = object ## IRC Event - case typ*: TIRCEventType - of EvConnected: - ## Connected to server. - ## Only occurs with AsyncIRC. - nil - of EvDisconnected: - ## Disconnected from the server - nil - of EvMsg: ## Message from the server - cmd*: TIRCMType ## Command (e.g. PRIVMSG) - nick*, user*, host*, servername*: string - numeric*: string ## Only applies to ``MNumeric`` - params*: seq[string] ## Parameters of the IRC message - origin*: string ## The channel/user that this msg originated from - raw*: string ## Raw IRC message - timestamp*: TTime ## UNIX epoch time the message was received - -proc send*(irc: PIRC, message: string, sendImmediately = false) = - ## Sends ``message`` as a raw command. It adds ``\c\L`` for you. - var sendMsg = true - if irc.msgLimit and not sendImmediately: - var timeToSend = epochTime() - if irc.messageBuffer.len() >= 3: - timeToSend = (irc.messageBuffer[irc.messageBuffer.len()-1][0] + 2.0) - - irc.messageBuffer.add((timeToSend, message)) - sendMsg = false - - if sendMsg: - try: - if irc.isAsync: - irc.asyncSock.send(message & "\c\L") - else: - irc.sock.send(message & "\c\L") - except EOS: - # Assuming disconnection of every EOS could be bad, - # but I can't exactly check for EBrokenPipe. - irc.status = SockClosed - -proc privmsg*(irc: PIRC, target, message: string) = - ## Sends ``message`` to ``target``. ``Target`` can be a channel, or a user. - irc.send("PRIVMSG $1 :$2" % [target, message]) - -proc notice*(irc: PIRC, target, message: string) = - ## Sends ``notice`` to ``target``. ``Target`` can be a channel, or a user. - irc.send("NOTICE $1 :$2" % [target, message]) - -proc join*(irc: PIRC, channel: string, key = "") = - ## Joins ``channel``. - ## - ## If key is not ``""``, then channel is assumed to be key protected and this - ## function will join the channel using ``key``. - if key == "": - irc.send("JOIN " & channel) - else: - irc.send("JOIN " & channel & " " & key) - -proc part*(irc: PIRC, channel, message: string) = - ## Leaves ``channel`` with ``message``. - irc.send("PART " & channel & " :" & message) - -proc close*(irc: PIRC) = - ## Closes connection to an IRC server. - ## - ## **Warning:** This procedure does not send a ``QUIT`` message to the server. - irc.status = SockClosed - if irc.isAsync: - irc.asyncSock.close() - else: - irc.sock.close() - -proc isNumber(s: string): bool = - ## Checks if `s` contains only numbers. - var i = 0 - while s[i] in {'0'..'9'}: inc(i) - result = i == s.len and s.len > 0 - -proc parseMessage(msg: string): TIRCEvent = - result.typ = EvMsg - result.cmd = MUnknown - result.raw = msg - result.timestamp = times.getTime() - var i = 0 - # Process the prefix - if msg[i] == ':': - inc(i) # Skip `:` - var nick = "" - i.inc msg.parseUntil(nick, {'!', ' '}, i) - result.nick = "" - result.serverName = "" - if msg[i] == '!': - result.nick = nick - inc(i) # Skip `!` - i.inc msg.parseUntil(result.user, {'@'}, i) - inc(i) # Skip `@` - i.inc msg.parseUntil(result.host, {' '}, i) - inc(i) # Skip ` ` - else: - result.serverName = nick - inc(i) # Skip ` ` - - # Process command - var cmd = "" - i.inc msg.parseUntil(cmd, {' '}, i) - - if cmd.isNumber: - result.cmd = MNumeric - result.numeric = cmd - else: - case cmd - of "PRIVMSG": result.cmd = MPrivMsg - of "JOIN": result.cmd = MJoin - of "PART": result.cmd = MPart - of "PONG": result.cmd = MPong - of "PING": result.cmd = MPing - of "MODE": result.cmd = MMode - of "TOPIC": result.cmd = MTopic - of "INVITE": result.cmd = MInvite - of "KICK": result.cmd = MKick - of "QUIT": result.cmd = MQuit - of "NICK": result.cmd = MNick - of "NOTICE": result.cmd = MNotice - of "ERROR": result.cmd = MError - else: result.cmd = MUnknown - - # Don't skip space here. It is skipped in the following While loop. - - # Params - result.params = @[] - var param = "" - while msg[i] != '\0' and msg[i] != ':': - inc(i) # Skip ` `. - i.inc msg.parseUntil(param, {' ', ':', '\0'}, i) - if param != "": - result.params.add(param) - param.setlen(0) - - if msg[i] == ':': - inc(i) # Skip `:`. - result.params.add(msg[i..msg.len-1]) - -proc connect*(irc: PIRC) = - ## Connects to an IRC server as specified by ``irc``. - assert(irc.address != "") - assert(irc.port != TPort(0)) - - irc.sock.connect(irc.address, irc.port) - - irc.status = SockConnected - - # Greet the server :) - if irc.serverPass != "": irc.send("PASS " & irc.serverPass, true) - irc.send("NICK " & irc.nick, true) - irc.send("USER $1 * 0 :$2" % [irc.user, irc.realname], true) - -proc reconnect*(irc: PIRC, timeout = 5000) = - ## Reconnects to an IRC server. - ## - ## ``Timeout`` specifies the time to wait in miliseconds between multiple - ## consecutive reconnections. - ## - ## This should be used when an ``EvDisconnected`` event occurs. - let secSinceReconnect = int(epochTime() - irc.lastReconnect) - if secSinceReconnect < timeout: - sleep(timeout - secSinceReconnect) - irc.sock = socket() - if irc.sock == InvalidSocket: osError(osLastError()) - irc.connect() - irc.lastReconnect = epochTime() - -proc irc*(address: string, port: TPort = 6667.TPort, - nick = "NimrodBot", - user = "NimrodBot", - realname = "NimrodBot", serverPass = "", - joinChans: seq[string] = @[], - msgLimit: bool = true): PIRC = - ## Creates a ``TIRC`` object. - new(result) - result.address = address - result.port = port - result.nick = nick - result.user = user - result.realname = realname - result.serverPass = serverPass - result.lastPing = epochTime() - result.lastPong = -1.0 - result.lag = -1.0 - result.channelsToJoin = joinChans - result.msgLimit = msgLimit - result.messageBuffer = @[] - result.status = SockIdle - result.sock = socket() - if result.sock == InvalidSocket: osError(osLastError()) - -proc processLine(irc: PIRC, line: string): TIRCEvent = - if line.len == 0: - irc.close() - result.typ = EvDisconnected - else: - result = parseMessage(line) - # Get the origin - result.origin = result.params[0] - if result.origin == irc.nick and - result.nick != "": result.origin = result.nick - - if result.cmd == MError: - irc.close() - result.typ = EvDisconnected - return - - if result.cmd == MPing: - irc.send("PONG " & result.params[0]) - if result.cmd == MPong: - irc.lag = epochTime() - parseFloat(result.params[result.params.high]) - irc.lastPong = epochTime() - if result.cmd == MNumeric: - if result.numeric == "001": - # Check the nickname. - if irc.nick != result.params[0]: - assert ' ' notin result.params[0] - irc.nick = result.params[0] - for chan in items(irc.channelsToJoin): - irc.join(chan) - if result.cmd == MNick: - if result.nick == irc.nick: - irc.nick = result.params[0] - -proc processOther(irc: PIRC, ev: var TIRCEvent): bool = - result = false - if epochTime() - irc.lastPing >= 20.0: - irc.lastPing = epochTime() - irc.send("PING :" & formatFloat(irc.lastPing), true) - - if epochTime() - irc.lastPong >= 120.0 and irc.lastPong != -1.0: - irc.close() - ev.typ = EvDisconnected # TODO: EvTimeout? - return true - - for i in 0..irc.messageBuffer.len-1: - if epochTime() >= irc.messageBuffer[0][0]: - irc.send(irc.messageBuffer[0].m, true) - irc.messageBuffer.delete(0) - else: - break # messageBuffer is guaranteed to be from the quickest to the - # later-est. - -proc poll*(irc: PIRC, ev: var TIRCEvent, - timeout: int = 500): bool = - ## This function parses a single message from the IRC server and returns - ## a TIRCEvent. - ## - ## This function should be called often as it also handles pinging - ## the server. - ## - ## This function provides a somewhat asynchronous IRC implementation, although - ## it should only be used for simple things for example an IRC bot which does - ## not need to be running many time critical tasks in the background. If you - ## require this, use the asyncio implementation. - - if not (irc.status == SockConnected): - # Do not close the socket here, it is already closed! - ev.typ = EvDisconnected - var line = TaintedString"" - var socks = @[irc.sock] - var ret = socks.select(timeout) - if ret == -1: osError(osLastError()) - if socks.len() != 0 and ret != 0: - irc.sock.readLine(line) - ev = irc.processLine(line.string) - result = true - - if processOther(irc, ev): result = true - -proc getLag*(irc: PIRC): float = - ## Returns the latency between this client and the IRC server in seconds. - ## - ## If latency is unknown, returns -1.0. - return irc.lag - -proc isConnected*(irc: PIRC): bool = - ## Returns whether this IRC client is connected to an IRC server. - return irc.status == SockConnected - -proc getNick*(irc: PIRC): string = - ## Returns the current nickname of the client. - return irc.nick - -# -- Asyncio dispatcher - -proc handleConnect(s: PAsyncSocket, irc: PAsyncIRC) = - # Greet the server :) - if irc.serverPass != "": irc.send("PASS " & irc.serverPass, true) - irc.send("NICK " & irc.nick, true) - irc.send("USER $1 * 0 :$2" % [irc.user, irc.realname], true) - irc.status = SockConnected - - var ev: TIRCEvent - ev.typ = EvConnected - irc.handleEvent(irc, ev) - -proc handleRead(s: PAsyncSocket, irc: PAsyncIRC) = - var line = "".TaintedString - var ret = s.readLine(line) - if ret: - if line == "": - var ev: TIRCEvent - irc.close() - ev.typ = EvDisconnected - irc.handleEvent(irc, ev) - else: - var ev = irc.processLine(line.string) - irc.handleEvent(irc, ev) - -proc handleTask(s: PAsyncSocket, irc: PAsyncIRC) = - var ev: TIRCEvent - if irc.processOther(ev): - irc.handleEvent(irc, ev) - -proc register*(d: PDispatcher, irc: PAsyncIRC) = - ## Registers ``irc`` with dispatcher ``d``. - irc.asyncSock.handleConnect = - proc (s: PAsyncSocket) = - handleConnect(s, irc) - irc.asyncSock.handleRead = - proc (s: PAsyncSocket) = - handleRead(s, irc) - irc.asyncSock.handleTask = - proc (s: PAsyncSocket) = - handleTask(s, irc) - d.register(irc.asyncSock) - irc.myDispatcher = d - -proc connect*(irc: PAsyncIRC) = - ## Equivalent of connect for ``TIRC`` but specifically created for asyncio. - assert(irc.address != "") - assert(irc.port != TPort(0)) - - irc.asyncSock.connect(irc.address, irc.port) - -proc reconnect*(irc: PAsyncIRC, timeout = 5000) = - ## Reconnects to an IRC server. - ## - ## ``Timeout`` specifies the time to wait in miliseconds between multiple - ## consecutive reconnections. - ## - ## This should be used when an ``EvDisconnected`` event occurs. - ## - ## When successfully reconnected an ``EvConnected`` event will occur. - let secSinceReconnect = int(epochTime() - irc.lastReconnect) - if secSinceReconnect < timeout: - sleep(timeout - secSinceReconnect) - irc.asyncSock = AsyncSocket() - irc.myDispatcher.register(irc) - irc.connect() - irc.lastReconnect = epochTime() - -proc asyncIRC*(address: string, port: TPort = 6667.TPort, - nick = "NimrodBot", - user = "NimrodBot", - realname = "NimrodBot", serverPass = "", - joinChans: seq[string] = @[], - msgLimit: bool = true, - ircEvent: proc (irc: PAsyncIRC, ev: TIRCEvent) {.closure,gcsafe.} - ): PAsyncIRC = - ## Use this function if you want to use asyncio's dispatcher. - ## - ## **Note:** Do **NOT** use this if you're writing a simple IRC bot which only - ## requires one task to be run, i.e. this should not be used if you want a - ## synchronous IRC client implementation, use ``irc`` for that. - - new(result) - result.isAsync = true - result.address = address - result.port = port - result.nick = nick - result.user = user - result.realname = realname - result.serverPass = serverPass - result.lastPing = epochTime() - result.lastPong = -1.0 - result.lag = -1.0 - result.channelsToJoin = joinChans - result.msgLimit = msgLimit - result.messageBuffer = @[] - result.handleEvent = ircEvent - result.asyncSock = AsyncSocket() - -when isMainModule: - #var m = parseMessage("ERROR :Closing Link: dom96.co.cc (Ping timeout: 252 seconds)") - #echo(repr(m)) - - - - var client = irc("amber.tenthbit.net", nick="TestBot1234", - joinChans = @["#flood"]) - client.connect() - while true: - var event: TIRCEvent - if client.poll(event): - case event.typ - of EvConnected: - discard - of EvDisconnected: - break - of EvMsg: - if event.cmd == MPrivMsg: - var msg = event.params[event.params.high] - if msg == "|test": client.privmsg(event.origin, "hello") - if msg == "|excessFlood": - for i in 0..10: - client.privmsg(event.origin, "TEST" & $i) - - #echo( repr(event) ) - #echo("Lag: ", formatFloat(client.getLag())) - - diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 508e564c5..a45900f29 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -621,9 +621,13 @@ proc `%`*(elements: openArray[PJsonNode]): PJsonNode = proc `==`* (a,b: PJsonNode): bool = ## Check two nodes for equality - if a.kind != b.kind: false + if a.isNil: + if b.isNil: return true + return false + elif b.isNil or a.kind != b.kind: + return false else: - case a.kind + return case a.kind of JString: a.str == b.str of JInt: diff --git a/lib/pure/math.nim b/lib/pure/math.nim index 2f7a696b9..8af09114b 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -219,7 +219,7 @@ when not defined(JS): proc randomize(seed: int) = srand(cint(seed)) - when defined(srand48): srand48(seed) + when declared(srand48): srand48(seed) proc random(max: int): int = result = int(rand()) mod max @@ -279,8 +279,13 @@ proc `mod`*(x, y: float): float = result = if y == 0.0: x else: x - y * (x/y).floor proc random*[T](x: TSlice[T]): T = + ## For a slice `a .. b` returns a value in the range `a .. b-1`. result = random(x.b - x.a) + x.a - + +proc random[T](a: openarray[T]): T = + ## returns a random element from the openarray `a`. + result = a[random(a.low..a.len)] + type TRunningStat* {.pure,final.} = object ## an accumulator for statistical data n*: int ## number of pushed data diff --git a/lib/pure/memfiles.nim b/lib/pure/memfiles.nim index 31fefc6c8..ffeb0beff 100644 --- a/lib/pure/memfiles.nim +++ b/lib/pure/memfiles.nim @@ -54,7 +54,7 @@ proc mapMem*(m: var TMemFile, mode: TFileMode = fmRead, nil, mappedSize, if readonly: PROT_READ else: PROT_READ or PROT_WRITE, - if readonly: MAP_PRIVATE else: MAP_SHARED, + if readonly: (MAP_PRIVATE or MAP_POPULATE) else: (MAP_SHARED or MAP_POPULATE), m.handle, offset) if result == cast[pointer](MAP_FAILED): osError(osLastError()) @@ -207,7 +207,7 @@ proc open*(filename: string, mode: TFileMode = fmRead, nil, result.size, if readonly: PROT_READ else: PROT_READ or PROT_WRITE, - if readonly: MAP_PRIVATE else: MAP_SHARED, + if readonly: (MAP_PRIVATE or MAP_POPULATE) else: (MAP_SHARED or MAP_POPULATE), result.handle, offset) diff --git a/lib/pure/net.nim b/lib/pure/net.nim index ddc2bbe2d..696527467 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -11,292 +11,10 @@ {.deadCodeElim: on.} import rawsockets, os, strutils, unsigned, parseutils, times -export TPort, `$` +export TPort, `$`, `==` const useWinVersion = defined(Windows) or defined(nimdoc) -type - IpAddressFamily* {.pure.} = enum ## Describes the type of an IP address - IPv6, ## IPv6 address - IPv4 ## IPv4 address - - TIpAddress* = object ## stores an arbitrary IP address - case family*: IpAddressFamily ## the type of the IP address (IPv4 or IPv6) - of IpAddressFamily.IPv6: - address_v6*: array[0..15, uint8] ## Contains the IP address in bytes in - ## case of IPv6 - of IpAddressFamily.IPv4: - address_v4*: array[0..3, uint8] ## Contains the IP address in bytes in - ## case of IPv4 - -proc IPv4_any*(): TIpAddress = - ## Returns the IPv4 any address, which can be used to listen on all available - ## network adapters - result = TIpAddress( - family: IpAddressFamily.IPv4, - address_v4: [0'u8, 0, 0, 0]) - -proc IPv4_loopback*(): TIpAddress = - ## Returns the IPv4 loopback address (127.0.0.1) - result = TIpAddress( - family: IpAddressFamily.IPv4, - address_v4: [127'u8, 0, 0, 1]) - -proc IPv4_broadcast*(): TIpAddress = - ## Returns the IPv4 broadcast address (255.255.255.255) - result = TIpAddress( - family: IpAddressFamily.IPv4, - address_v4: [255'u8, 255, 255, 255]) - -proc IPv6_any*(): TIpAddress = - ## Returns the IPv6 any address (::0), which can be used - ## to listen on all available network adapters - result = TIpAddress( - family: IpAddressFamily.IPv6, - address_v6: [0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - -proc IPv6_loopback*(): TIpAddress = - ## Returns the IPv6 loopback address (::1) - result = TIpAddress( - family: IpAddressFamily.IPv6, - address_v6: [0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]) - -proc `==`*(lhs, rhs: TIpAddress): bool = - ## Compares two IpAddresses for Equality. Returns two if the addresses are equal - if lhs.family != rhs.family: return false - if lhs.family == IpAddressFamily.IPv4: - for i in low(lhs.address_v4) .. high(lhs.address_v4): - if lhs.address_v4[i] != rhs.address_v4[i]: return false - else: # IPv6 - for i in low(lhs.address_v6) .. high(lhs.address_v6): - if lhs.address_v6[i] != rhs.address_v6[i]: return false - return true - -proc `$`*(address: TIpAddress): string = - ## Converts an TIpAddress into the textual representation - result = "" - case address.family - of IpAddressFamily.IPv4: - for i in 0 .. 3: - if i != 0: - result.add('.') - result.add($address.address_v4[i]) - of IpAddressFamily.IPv6: - var - currentZeroStart = -1 - currentZeroCount = 0 - biggestZeroStart = -1 - biggestZeroCount = 0 - # Look for the largest block of zeros - for i in 0..7: - var isZero = address.address_v6[i*2] == 0 and address.address_v6[i*2+1] == 0 - if isZero: - if currentZeroStart == -1: - currentZeroStart = i - currentZeroCount = 1 - else: - currentZeroCount.inc() - if currentZeroCount > biggestZeroCount: - biggestZeroCount = currentZeroCount - biggestZeroStart = currentZeroStart - else: - currentZeroStart = -1 - - if biggestZeroCount == 8: # Special case ::0 - result.add("::") - else: # Print address - var printedLastGroup = false - for i in 0..7: - var word:uint16 = (cast[uint16](address.address_v6[i*2])) shl 8 - word = word or cast[uint16](address.address_v6[i*2+1]) - - if biggestZeroCount != 0 and # Check if group is in skip group - (i >= biggestZeroStart and i < (biggestZeroStart + biggestZeroCount)): - if i == biggestZeroStart: # skip start - result.add("::") - printedLastGroup = false - else: - if printedLastGroup: - result.add(':') - var - afterLeadingZeros = false - mask = 0xF000'u16 - for j in 0'u16..3'u16: - var val = (mask and word) shr (4'u16*(3'u16-j)) - if val != 0 or afterLeadingZeros: - if val < 0xA: - result.add(chr(uint16(ord('0'))+val)) - else: # val >= 0xA - result.add(chr(uint16(ord('a'))+val-0xA)) - afterLeadingZeros = true - mask = mask shr 4 - printedLastGroup = true - -proc parseIPv4Address(address_str: string): TIpAddress = - ## Parses IPv4 adresses - ## Raises EInvalidValue on errors - var - byteCount = 0 - currentByte:uint16 = 0 - seperatorValid = false - - result.family = IpAddressFamily.IPv4 - - for i in 0 .. high(address_str): - if address_str[i] in strutils.Digits: # Character is a number - currentByte = currentByte * 10 + - cast[uint16](ord(address_str[i]) - ord('0')) - if currentByte > 255'u16: - raise newException(EInvalidValue, - "Invalid IP Address. Value is out of range") - seperatorValid = true - elif address_str[i] == '.': # IPv4 address separator - if not seperatorValid or byteCount >= 3: - raise newException(EInvalidValue, - "Invalid IP Address. The address consists of too many groups") - result.address_v4[byteCount] = cast[uint8](currentByte) - currentByte = 0 - byteCount.inc - seperatorValid = false - else: - raise newException(EInvalidValue, - "Invalid IP Address. Address contains an invalid character") - - if byteCount != 3 or not seperatorValid: - raise newException(EInvalidValue, "Invalid IP Address") - result.address_v4[byteCount] = cast[uint8](currentByte) - -proc parseIPv6Address(address_str: string): TIpAddress = - ## Parses IPv6 adresses - ## Raises EInvalidValue on errors - result.family = IpAddressFamily.IPv6 - if address_str.len < 2: - raise newException(EInvalidValue, "Invalid IP Address") - - var - groupCount = 0 - currentGroupStart = 0 - currentShort:uint32 = 0 - seperatorValid = true - dualColonGroup = -1 - lastWasColon = false - v4StartPos = -1 - byteCount = 0 - - for i,c in address_str: - if c == ':': - if not seperatorValid: - raise newException(EInvalidValue, - "Invalid IP Address. Address contains an invalid seperator") - if lastWasColon: - if dualColonGroup != -1: - raise newException(EInvalidValue, - "Invalid IP Address. Address contains more than one \"::\" seperator") - dualColonGroup = groupCount - seperatorValid = false - elif i != 0 and i != high(address_str): - if groupCount >= 8: - raise newException(EInvalidValue, - "Invalid IP Address. The address consists of too many groups") - result.address_v6[groupCount*2] = cast[uint8](currentShort shr 8) - result.address_v6[groupCount*2+1] = cast[uint8](currentShort and 0xFF) - currentShort = 0 - groupCount.inc() - if dualColonGroup != -1: seperatorValid = false - elif i == 0: # only valid if address starts with :: - if address_str[1] != ':': - raise newException(EInvalidValue, - "Invalid IP Address. Address may not start with \":\"") - else: # i == high(address_str) - only valid if address ends with :: - if address_str[high(address_str)-1] != ':': - raise newException(EInvalidValue, - "Invalid IP Address. Address may not end with \":\"") - lastWasColon = true - currentGroupStart = i + 1 - elif c == '.': # Switch to parse IPv4 mode - if i < 3 or not seperatorValid or groupCount >= 7: - raise newException(EInvalidValue, "Invalid IP Address") - v4StartPos = currentGroupStart - currentShort = 0 - seperatorValid = false - break - elif c in strutils.HexDigits: - if c in strutils.Digits: # Normal digit - currentShort = (currentShort shl 4) + cast[uint32](ord(c) - ord('0')) - elif c >= 'a' and c <= 'f': # Lower case hex - currentShort = (currentShort shl 4) + cast[uint32](ord(c) - ord('a')) + 10 - else: # Upper case hex - currentShort = (currentShort shl 4) + cast[uint32](ord(c) - ord('A')) + 10 - if currentShort > 65535'u32: - raise newException(EInvalidValue, - "Invalid IP Address. Value is out of range") - lastWasColon = false - seperatorValid = true - else: - raise newException(EInvalidValue, - "Invalid IP Address. Address contains an invalid character") - - - if v4StartPos == -1: # Don't parse v4. Copy the remaining v6 stuff - if seperatorValid: # Copy remaining data - if groupCount >= 8: - raise newException(EInvalidValue, - "Invalid IP Address. The address consists of too many groups") - result.address_v6[groupCount*2] = cast[uint8](currentShort shr 8) - result.address_v6[groupCount*2+1] = cast[uint8](currentShort and 0xFF) - groupCount.inc() - else: # Must parse IPv4 address - for i,c in address_str[v4StartPos..high(address_str)]: - if c in strutils.Digits: # Character is a number - currentShort = currentShort * 10 + cast[uint32](ord(c) - ord('0')) - if currentShort > 255'u32: - raise newException(EInvalidValue, - "Invalid IP Address. Value is out of range") - seperatorValid = true - elif c == '.': # IPv4 address separator - if not seperatorValid or byteCount >= 3: - raise newException(EInvalidValue, "Invalid IP Address") - result.address_v6[groupCount*2 + byteCount] = cast[uint8](currentShort) - currentShort = 0 - byteCount.inc() - seperatorValid = false - else: # Invalid character - raise newException(EInvalidValue, - "Invalid IP Address. Address contains an invalid character") - - if byteCount != 3 or not seperatorValid: - raise newException(EInvalidValue, "Invalid IP Address") - result.address_v6[groupCount*2 + byteCount] = cast[uint8](currentShort) - groupCount += 2 - - # Shift and fill zeros in case of :: - if groupCount > 8: - raise newException(EInvalidValue, - "Invalid IP Address. The address consists of too many groups") - elif groupCount < 8: # must fill - if dualColonGroup == -1: - raise newException(EInvalidValue, - "Invalid IP Address. The address consists of too few groups") - var toFill = 8 - groupCount # The number of groups to fill - var toShift = groupCount - dualColonGroup # Nr of known groups after :: - for i in 0..2*toShift-1: # shift - result.address_v6[15-i] = result.address_v6[groupCount*2-i-1] - for i in 0..2*toFill-1: # fill with 0s - result.address_v6[dualColonGroup*2+i] = 0 - elif dualColonGroup != -1: - raise newException(EInvalidValue, - "Invalid IP Address. The address consists of too many groups") - -proc parseIpAddress*(address_str: string): TIpAddress = - ## Parses an IP address - ## Raises EInvalidValue on error - if address_str == nil: - raise newException(EInvalidValue, "IP Address string is nil") - if address_str.contains(':'): - return parseIPv6Address(address_str) - else: - return parseIPv4Address(address_str) - when defined(ssl): import openssl @@ -361,7 +79,7 @@ proc isDisconnectionError*(flags: set[TSocketFlags], when useWinVersion: TSocketFlags.SafeDisconn in flags and lastError.int32 in {WSAECONNRESET, WSAECONNABORTED, WSAENETRESET, - WSAEDISCON} + WSAEDISCON, ERROR_NETNAME_DELETED} else: TSocketFlags.SafeDisconn in flags and lastError.int32 in {ECONNRESET, EPIPE, ENETRESET} @@ -569,8 +287,8 @@ proc bindAddr*(socket: PSocket, port = TPort(0), address = "") {. osError(osLastError()) dealloc(aiList) -proc acceptAddr*(server: PSocket, client: var PSocket, address: var string) {. - tags: [FReadIO].} = +proc acceptAddr*(server: PSocket, client: var PSocket, address: var string, + flags = {TSocketFlags.SafeDisconn}) {.tags: [FReadIO].} = ## Blocks until a connection is being made from a client. When a connection ## is made sets ``client`` to the client socket and ``address`` to the address ## of the connecting client. @@ -581,6 +299,11 @@ proc acceptAddr*(server: PSocket, client: var PSocket, address: var string) {. ## ## **Note**: ``client`` must be initialised (with ``new``), this function ## makes no effort to initialise the ``client`` variable. + ## + ## The ``accept`` call may result in an error if the connecting socket + ## disconnects during the duration of the ``accept``. If the ``SafeDisconn`` + ## flag is specified then this error will not be raised and instead + ## accept will be called again. assert(client != nil) var sockAddress: Tsockaddr_in var addrLen = sizeof(sockAddress).TSocklen @@ -589,6 +312,8 @@ proc acceptAddr*(server: PSocket, client: var PSocket, address: var string) {. if sock == osInvalidSocket: let err = osLastError() + if flags.isDisconnectionError(err): + acceptAddr(server, client, address, flags) osError(err) else: client.fd = sock @@ -658,15 +383,20 @@ when false: #defined(ssl): acceptAddrPlain(AcceptNoClient, AcceptSuccess): doHandshake() -proc accept*(server: PSocket, client: var PSocket) {.tags: [FReadIO].} = +proc accept*(server: PSocket, client: var PSocket, + flags = {TSocketFlags.SafeDisconn}) {.tags: [FReadIO].} = ## Equivalent to ``acceptAddr`` but doesn't return the address, only the ## socket. ## ## **Note**: ``client`` must be initialised (with ``new``), this function ## makes no effort to initialise the ``client`` variable. - + ## + ## The ``accept`` call may result in an error if the connecting socket + ## disconnects during the duration of the ``accept``. If the ``SafeDisconn`` + ## flag is specified then this error will not be raised and instead + ## accept will be called again. var addrDummy = "" - acceptAddr(server, client, addrDummy) + acceptAddr(server, client, addrDummy, flags) proc close*(socket: PSocket) = ## Closes a socket. @@ -1173,3 +903,285 @@ proc isSSL*(socket: PSocket): bool = return socket.isSSL proc getFD*(socket: PSocket): TSocketHandle = return socket.fd ## Returns the socket's file descriptor + +type + IpAddressFamily* {.pure.} = enum ## Describes the type of an IP address + IPv6, ## IPv6 address + IPv4 ## IPv4 address + + TIpAddress* = object ## stores an arbitrary IP address + case family*: IpAddressFamily ## the type of the IP address (IPv4 or IPv6) + of IpAddressFamily.IPv6: + address_v6*: array[0..15, uint8] ## Contains the IP address in bytes in + ## case of IPv6 + of IpAddressFamily.IPv4: + address_v4*: array[0..3, uint8] ## Contains the IP address in bytes in + ## case of IPv4 + +proc IPv4_any*(): TIpAddress = + ## Returns the IPv4 any address, which can be used to listen on all available + ## network adapters + result = TIpAddress( + family: IpAddressFamily.IPv4, + address_v4: [0'u8, 0, 0, 0]) + +proc IPv4_loopback*(): TIpAddress = + ## Returns the IPv4 loopback address (127.0.0.1) + result = TIpAddress( + family: IpAddressFamily.IPv4, + address_v4: [127'u8, 0, 0, 1]) + +proc IPv4_broadcast*(): TIpAddress = + ## Returns the IPv4 broadcast address (255.255.255.255) + result = TIpAddress( + family: IpAddressFamily.IPv4, + address_v4: [255'u8, 255, 255, 255]) + +proc IPv6_any*(): TIpAddress = + ## Returns the IPv6 any address (::0), which can be used + ## to listen on all available network adapters + result = TIpAddress( + family: IpAddressFamily.IPv6, + address_v6: [0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + +proc IPv6_loopback*(): TIpAddress = + ## Returns the IPv6 loopback address (::1) + result = TIpAddress( + family: IpAddressFamily.IPv6, + address_v6: [0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]) + +proc `==`*(lhs, rhs: TIpAddress): bool = + ## Compares two IpAddresses for Equality. Returns two if the addresses are equal + if lhs.family != rhs.family: return false + if lhs.family == IpAddressFamily.IPv4: + for i in low(lhs.address_v4) .. high(lhs.address_v4): + if lhs.address_v4[i] != rhs.address_v4[i]: return false + else: # IPv6 + for i in low(lhs.address_v6) .. high(lhs.address_v6): + if lhs.address_v6[i] != rhs.address_v6[i]: return false + return true + +proc `$`*(address: TIpAddress): string = + ## Converts an TIpAddress into the textual representation + result = "" + case address.family + of IpAddressFamily.IPv4: + for i in 0 .. 3: + if i != 0: + result.add('.') + result.add($address.address_v4[i]) + of IpAddressFamily.IPv6: + var + currentZeroStart = -1 + currentZeroCount = 0 + biggestZeroStart = -1 + biggestZeroCount = 0 + # Look for the largest block of zeros + for i in 0..7: + var isZero = address.address_v6[i*2] == 0 and address.address_v6[i*2+1] == 0 + if isZero: + if currentZeroStart == -1: + currentZeroStart = i + currentZeroCount = 1 + else: + currentZeroCount.inc() + if currentZeroCount > biggestZeroCount: + biggestZeroCount = currentZeroCount + biggestZeroStart = currentZeroStart + else: + currentZeroStart = -1 + + if biggestZeroCount == 8: # Special case ::0 + result.add("::") + else: # Print address + var printedLastGroup = false + for i in 0..7: + var word:uint16 = (cast[uint16](address.address_v6[i*2])) shl 8 + word = word or cast[uint16](address.address_v6[i*2+1]) + + if biggestZeroCount != 0 and # Check if group is in skip group + (i >= biggestZeroStart and i < (biggestZeroStart + biggestZeroCount)): + if i == biggestZeroStart: # skip start + result.add("::") + printedLastGroup = false + else: + if printedLastGroup: + result.add(':') + var + afterLeadingZeros = false + mask = 0xF000'u16 + for j in 0'u16..3'u16: + var val = (mask and word) shr (4'u16*(3'u16-j)) + if val != 0 or afterLeadingZeros: + if val < 0xA: + result.add(chr(uint16(ord('0'))+val)) + else: # val >= 0xA + result.add(chr(uint16(ord('a'))+val-0xA)) + afterLeadingZeros = true + mask = mask shr 4 + printedLastGroup = true + +proc parseIPv4Address(address_str: string): TIpAddress = + ## Parses IPv4 adresses + ## Raises EInvalidValue on errors + var + byteCount = 0 + currentByte:uint16 = 0 + seperatorValid = false + + result.family = IpAddressFamily.IPv4 + + for i in 0 .. high(address_str): + if address_str[i] in strutils.Digits: # Character is a number + currentByte = currentByte * 10 + + cast[uint16](ord(address_str[i]) - ord('0')) + if currentByte > 255'u16: + raise newException(EInvalidValue, + "Invalid IP Address. Value is out of range") + seperatorValid = true + elif address_str[i] == '.': # IPv4 address separator + if not seperatorValid or byteCount >= 3: + raise newException(EInvalidValue, + "Invalid IP Address. The address consists of too many groups") + result.address_v4[byteCount] = cast[uint8](currentByte) + currentByte = 0 + byteCount.inc + seperatorValid = false + else: + raise newException(EInvalidValue, + "Invalid IP Address. Address contains an invalid character") + + if byteCount != 3 or not seperatorValid: + raise newException(EInvalidValue, "Invalid IP Address") + result.address_v4[byteCount] = cast[uint8](currentByte) + +proc parseIPv6Address(address_str: string): TIpAddress = + ## Parses IPv6 adresses + ## Raises EInvalidValue on errors + result.family = IpAddressFamily.IPv6 + if address_str.len < 2: + raise newException(EInvalidValue, "Invalid IP Address") + + var + groupCount = 0 + currentGroupStart = 0 + currentShort:uint32 = 0 + seperatorValid = true + dualColonGroup = -1 + lastWasColon = false + v4StartPos = -1 + byteCount = 0 + + for i,c in address_str: + if c == ':': + if not seperatorValid: + raise newException(EInvalidValue, + "Invalid IP Address. Address contains an invalid seperator") + if lastWasColon: + if dualColonGroup != -1: + raise newException(EInvalidValue, + "Invalid IP Address. Address contains more than one \"::\" seperator") + dualColonGroup = groupCount + seperatorValid = false + elif i != 0 and i != high(address_str): + if groupCount >= 8: + raise newException(EInvalidValue, + "Invalid IP Address. The address consists of too many groups") + result.address_v6[groupCount*2] = cast[uint8](currentShort shr 8) + result.address_v6[groupCount*2+1] = cast[uint8](currentShort and 0xFF) + currentShort = 0 + groupCount.inc() + if dualColonGroup != -1: seperatorValid = false + elif i == 0: # only valid if address starts with :: + if address_str[1] != ':': + raise newException(EInvalidValue, + "Invalid IP Address. Address may not start with \":\"") + else: # i == high(address_str) - only valid if address ends with :: + if address_str[high(address_str)-1] != ':': + raise newException(EInvalidValue, + "Invalid IP Address. Address may not end with \":\"") + lastWasColon = true + currentGroupStart = i + 1 + elif c == '.': # Switch to parse IPv4 mode + if i < 3 or not seperatorValid or groupCount >= 7: + raise newException(EInvalidValue, "Invalid IP Address") + v4StartPos = currentGroupStart + currentShort = 0 + seperatorValid = false + break + elif c in strutils.HexDigits: + if c in strutils.Digits: # Normal digit + currentShort = (currentShort shl 4) + cast[uint32](ord(c) - ord('0')) + elif c >= 'a' and c <= 'f': # Lower case hex + currentShort = (currentShort shl 4) + cast[uint32](ord(c) - ord('a')) + 10 + else: # Upper case hex + currentShort = (currentShort shl 4) + cast[uint32](ord(c) - ord('A')) + 10 + if currentShort > 65535'u32: + raise newException(EInvalidValue, + "Invalid IP Address. Value is out of range") + lastWasColon = false + seperatorValid = true + else: + raise newException(EInvalidValue, + "Invalid IP Address. Address contains an invalid character") + + + if v4StartPos == -1: # Don't parse v4. Copy the remaining v6 stuff + if seperatorValid: # Copy remaining data + if groupCount >= 8: + raise newException(EInvalidValue, + "Invalid IP Address. The address consists of too many groups") + result.address_v6[groupCount*2] = cast[uint8](currentShort shr 8) + result.address_v6[groupCount*2+1] = cast[uint8](currentShort and 0xFF) + groupCount.inc() + else: # Must parse IPv4 address + for i,c in address_str[v4StartPos..high(address_str)]: + if c in strutils.Digits: # Character is a number + currentShort = currentShort * 10 + cast[uint32](ord(c) - ord('0')) + if currentShort > 255'u32: + raise newException(EInvalidValue, + "Invalid IP Address. Value is out of range") + seperatorValid = true + elif c == '.': # IPv4 address separator + if not seperatorValid or byteCount >= 3: + raise newException(EInvalidValue, "Invalid IP Address") + result.address_v6[groupCount*2 + byteCount] = cast[uint8](currentShort) + currentShort = 0 + byteCount.inc() + seperatorValid = false + else: # Invalid character + raise newException(EInvalidValue, + "Invalid IP Address. Address contains an invalid character") + + if byteCount != 3 or not seperatorValid: + raise newException(EInvalidValue, "Invalid IP Address") + result.address_v6[groupCount*2 + byteCount] = cast[uint8](currentShort) + groupCount += 2 + + # Shift and fill zeros in case of :: + if groupCount > 8: + raise newException(EInvalidValue, + "Invalid IP Address. The address consists of too many groups") + elif groupCount < 8: # must fill + if dualColonGroup == -1: + raise newException(EInvalidValue, + "Invalid IP Address. The address consists of too few groups") + var toFill = 8 - groupCount # The number of groups to fill + var toShift = groupCount - dualColonGroup # Nr of known groups after :: + for i in 0..2*toShift-1: # shift + result.address_v6[15-i] = result.address_v6[groupCount*2-i-1] + for i in 0..2*toFill-1: # fill with 0s + result.address_v6[dualColonGroup*2+i] = 0 + elif dualColonGroup != -1: + raise newException(EInvalidValue, + "Invalid IP Address. The address consists of too many groups") + +proc parseIpAddress*(address_str: string): TIpAddress = + ## Parses an IP address + ## Raises EInvalidValue on error + if address_str == nil: + raise newException(EInvalidValue, "IP Address string is nil") + if address_str.contains(':'): + return parseIPv6Address(address_str) + else: + return parseIPv4Address(address_str) diff --git a/lib/pure/nimprof.nim b/lib/pure/nimprof.nim index ab7cd1944..6f94d0656 100644 --- a/lib/pure/nimprof.nim +++ b/lib/pure/nimprof.nim @@ -26,7 +26,7 @@ const withThreads = compileOption("threads") tickCountCorrection = 50_000 -when not defined(system.TStackTrace): +when not declared(system.TStackTrace): type TStackTrace = array [0..20, cstring] # We use a simple hash table of bounded size to keep track of the stack traces: @@ -146,7 +146,7 @@ proc `//`(a, b: int): string = result = format("$1/$2 = $3%", a, b, formatFloat(a / b * 100.0, ffDefault, 2)) proc writeProfile() {.noconv.} = - when defined(system.TStackTrace): + when declared(system.TStackTrace): system.profilerHook = nil const filename = "profile_results.txt" echo "writing " & filename & "..." @@ -189,16 +189,16 @@ var disabled: int proc disableProfiling*() = - when defined(system.TStackTrace): + when declared(system.TStackTrace): atomicDec disabled system.profilerHook = nil proc enableProfiling*() = - when defined(system.TStackTrace): + when declared(system.TStackTrace): if atomicInc(disabled) >= 0: system.profilerHook = hook -when defined(system.TStackTrace): +when declared(system.TStackTrace): system.profilerHook = hook addQuitProc(writeProfile) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index a7f4f7d91..cfff58eb0 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -365,8 +365,9 @@ when defined(windows): template getFilename(f: expr): expr = $f.cFilename proc skipFindData(f: TWIN32_FIND_DATA): bool {.inline.} = + # Note - takes advantage of null delimiter in the cstring const dot = ord('.') - result = f.cFileName[0].int == dot and(f.cFileName[1].int == 0 or + result = f.cFileName[0].int == dot and (f.cFileName[1].int == 0 or f.cFileName[1].int == dot and f.cFileName[2].int == 0) proc existsFile*(filename: string): bool {.rtl, extern: "nos$1", @@ -955,11 +956,12 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1", ## ## If this fails, `EOS` is raised. On the Windows platform this proc will ## copy the source file's attributes into dest. On other platforms you need - ## to use getFilePermissions and setFilePermissions to copy them by hand (or - ## use the convenience copyFileWithPermissions() proc), otherwise `dest` will - ## inherit the default permissions of a newly created file for the user. If - ## `dest` already exists, the file attributes will be preserved and the - ## content overwritten. + ## to use `getFilePermissions() <#getFilePermissions>`_ and + ## `setFilePermissions() <#setFilePermissions>`_ to copy them by hand (or use + ## the convenience `copyFileWithPermissions() <#copyFileWithPermissions>`_ + ## proc), otherwise `dest` will inherit the default permissions of a newly + ## created file for the user. If `dest` already exists, the file attributes + ## will be preserved and the content overwritten. when defined(Windows): when useWinUnicode: let s = newWideCString(source) @@ -996,7 +998,7 @@ proc moveFile*(source, dest: string) {.rtl, extern: "nos$1", if c_rename(source, dest) != 0'i32: raise newException(EOS, $strerror(errno)) -when not defined(ENOENT) and not defined(Windows): +when not declared(ENOENT) and not defined(Windows): when NoFakeVars: const ENOENT = cint(2) # 2 on most systems including Solaris else: @@ -1363,7 +1365,13 @@ proc createDir*(dir: string) {.rtl, extern: "nos$1", tags: [FWriteDir].} = proc copyDir*(source, dest: string) {.rtl, extern: "nos$1", tags: [FWriteIO, FReadIO].} = - ## Copies a directory from `source` to `dest`. If this fails, `EOS` is raised. + ## Copies a directory from `source` to `dest`. + ## + ## If this fails, `EOS` is raised. On the Windows platform this proc will + ## copy the attributes from `source` into `dest`. On other platforms created + ## files and directories will inherit the default permissions of a newly + ## created file/directory for the user. To preserve attributes recursively on + ## these platforms use `copyDirWithPermissions() <#copyDirWithPermissions>`_. createDir(dest) for kind, path in walkDir(source): var noSource = path.substr(source.len()+1) @@ -1450,7 +1458,8 @@ proc parseCmdLine*(c: string): seq[string] {. var a = "" while true: setLen(a, 0) - while c[i] == ' ' or c[i] == '\t': inc(i) + # eat all delimiting whitespace + while c[i] == ' ' or c[i] == '\t' or c [i] == '\l' or c [i] == '\r' : inc(i) when defined(windows): # parse a single argument according to the above rules: if c[i] == '\0': break @@ -1507,14 +1516,17 @@ proc copyFileWithPermissions*(source, dest: string, ignorePermissionErrors = true) = ## Copies a file from `source` to `dest` preserving file permissions. ## - ## This is a wrapper proc around copyFile, getFilePermissions and - ## setFilePermissions on non Windows platform. On windows this proc is just a - ## wrapper for copyFile since that proc already copies attributes. + ## This is a wrapper proc around `copyFile() <#copyFile>`_, + ## `getFilePermissions() <#getFilePermissions>`_ and `setFilePermissions() + ## <#setFilePermissions>`_ on non Windows platform. On Windows this proc is + ## just a wrapper for `copyFile() <#copyFile>`_ since that proc already + ## copies attributes. ## - ## On non windows systems permissions are copied after the file itself has + ## On non Windows systems permissions are copied after the file itself has ## been copied, which won't happen atomically and could lead to a race - ## condition. If ignorePermissionErrors is true, errors while reading/setting - ## file attributes will be ignored, otherwise will raise `OSError`. + ## condition. If `ignorePermissionErrors` is true, errors while + ## reading/setting file attributes will be ignored, otherwise will raise + ## `OSError`. copyFile(source, dest) when not defined(Windows): try: @@ -1523,6 +1535,37 @@ proc copyFileWithPermissions*(source, dest: string, if not ignorePermissionErrors: raise +proc copyDirWithPermissions*(source, dest: string, + ignorePermissionErrors = true) {.rtl, extern: "nos$1", + tags: [FWriteIO, FReadIO].} = + ## Copies a directory from `source` to `dest` preserving file permissions. + ## + ## If this fails, `EOS` is raised. This is a wrapper proc around `copyDir() + ## <#copyDir>`_ and `copyFileWithPermissions() <#copyFileWithPermissions>`_ + ## on non Windows platforms. On Windows this proc is just a wrapper for + ## `copyDir() <#copyDir>`_ since that proc already copies attributes. + ## + ## On non Windows systems permissions are copied after the file or directory + ## itself has been copied, which won't happen atomically and could lead to a + ## race condition. If `ignorePermissionErrors` is true, errors while + ## reading/setting file attributes will be ignored, otherwise will raise + ## `OSError`. + createDir(dest) + when not defined(Windows): + try: + setFilePermissions(dest, getFilePermissions(source)) + except: + if not ignorePermissionErrors: + raise + for kind, path in walkDir(source): + var noSource = path.substr(source.len()+1) + case kind + of pcFile: + copyFileWithPermissions(path, dest / noSource, ignorePermissionErrors) + of pcDir: + copyDirWithPermissions(path, dest / noSource, ignorePermissionErrors) + else: discard + proc inclFilePermissions*(filename: string, permissions: set[TFilePermission]) {. rtl, extern: "nos$1", tags: [FReadDir, FWriteDir].} = @@ -1573,11 +1616,11 @@ when defined(nimdoc): ## ## **Availability**: On Posix there is no portable way to get the command ## line from a DLL and thus the proc isn't defined in this environment. You - ## can test for its availability with `defined() <system.html#defined>`_. + ## can test for its availability with `declared() <system.html#declared>`_. ## Example: ## ## .. code-block:: nimrod - ## when defined(paramCount): + ## when declared(paramCount): ## # Use paramCount() here ## else: ## # Do something else! @@ -1596,11 +1639,11 @@ when defined(nimdoc): ## ## **Availability**: On Posix there is no portable way to get the command ## line from a DLL and thus the proc isn't defined in this environment. You - ## can test for its availability with `defined() <system.html#defined>`_. + ## can test for its availability with `declared() <system.html#declared>`_. ## Example: ## ## .. code-block:: nimrod - ## when defined(paramStr): + ## when declared(paramStr): ## # Use paramStr() here ## else: ## # Do something else! @@ -1640,7 +1683,7 @@ elif not defined(createNimRtl): # Docstring in nimdoc block. result = cmdCount-1 -when defined(paramCount) or defined(nimdoc): +when declared(paramCount) or defined(nimdoc): proc commandLineParams*(): seq[TaintedString] = ## Convenience proc which returns the command line parameters. ## @@ -1649,11 +1692,11 @@ when defined(paramCount) or defined(nimdoc): ## ## **Availability**: On Posix there is no portable way to get the command ## line from a DLL and thus the proc isn't defined in this environment. You - ## can test for its availability with `defined() <system.html#defined>`_. + ## can test for its availability with `declared() <system.html#declared>`_. ## Example: ## ## .. code-block:: nimrod - ## when defined(commandLineParams): + ## when declared(commandLineParams): ## # Use commandLineParams() here ## else: ## # Do something else! @@ -1672,7 +1715,7 @@ when defined(linux) or defined(solaris) or defined(bsd) or defined(aix): when not (defined(windows) or defined(macosx)): proc getApplHeuristic(): string = - when defined(paramStr): + when declared(paramStr): result = string(paramStr(0)) # POSIX guaranties that this contains the executable # as it has been executed by the calling process @@ -1819,12 +1862,12 @@ proc expandTilde*(path: string): string = when defined(Windows): type - DeviceId = int32 - FileId = int64 + DeviceId* = int32 + FileId* = int64 else: type - DeviceId = TDev - FileId = TIno + DeviceId* = TDev + FileId* = TIno type FileInfo* = object @@ -1867,6 +1910,7 @@ template rawToFormalFileInfo(rawInfo, formalInfo): expr = if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32: formalInfo.kind = succ(result.kind) + else: template checkAndIncludeMode(rawMode, formalMode: expr) = if (rawInfo.st_mode and rawMode) != 0'i32: @@ -1953,4 +1997,26 @@ proc getFileInfo*(path: string, followSymlink = true): FileInfo = osError(osLastError()) rawToFormalFileInfo(rawInfo, result) +proc isHidden*(path: string): bool = + ## Determines whether a given path is hidden or not. Returns false if the + ## file doesn't exist. The given path must be accessible from the current + ## working directory of the program. + ## + ## On Windows, a file is hidden if the file's 'hidden' attribute is set. + ## On Unix-like systems, a file is hidden if it starts with a '.' (period) + ## and is not *just* '.' or '..' ' ." + when defined(Windows): + wrapUnary(attributes, getFileAttributesW, path) + if attributes != -1'i32: + result = (attributes and FILE_ATTRIBUTE_HIDDEN) != 0'i32 + else: + if fileExists(path): + let + fileName = extractFilename(path) + nameLen = len(fileName) + if nameLen == 2: + result = (fileName[0] == '.') and (fileName[1] != '.') + elif nameLen > 2: + result = (fileName[0] == '.') and (fileName[3] != '.') + {.pop.} diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 04a0c2403..3c181bf53 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -643,7 +643,7 @@ elif not defined(useNimRtl): data.workingDir = workingDir - when defined(posix_spawn) and not defined(useFork) and + when declared(posix_spawn) and not defined(useFork) and not defined(useClone) and not defined(linux): pid = startProcessAuxSpawn(data) else: @@ -763,7 +763,7 @@ elif not defined(useNimRtl): discard write(data.pErrorPipe[writeIdx], addr error, sizeof(error)) exitnow(1) - when defined(macosx): + when defined(macosx) or defined(freebsd): var environ {.importc.}: cstringArray proc startProcessAfterFork(data: ptr TStartProcessData) = @@ -793,7 +793,7 @@ elif not defined(useNimRtl): discard fcntl(data.pErrorPipe[writeIdx], F_SETFD, FD_CLOEXEC) if data.optionPoUsePath: - when defined(macosx): + when defined(macosx) or defined(freebsd): # MacOSX doesn't have execvpe, so we need workaround. # On MacOSX we can arrive here only from fork, so this is safe: environ = data.sysEnv diff --git a/lib/pure/parseopt.nim b/lib/pure/parseopt.nim index 68ae537c7..f43853fe6 100644 --- a/lib/pure/parseopt.nim +++ b/lib/pure/parseopt.nim @@ -37,7 +37,7 @@ type ## or the argument, ``value`` is not "" if ## the option was given a value -when defined(os.paramCount): +when declared(os.paramCount): # we cannot provide this for NimRtl creation on Posix, because we can't # access the command line arguments then! @@ -127,7 +127,7 @@ proc cmdLineRest*(p: TOptParser): TaintedString {. ## retrieves the rest of the command line that has not been parsed yet. result = strip(substr(p.cmd, p.pos, len(p.cmd) - 1)).TaintedString -when defined(initOptParser): +when declared(initOptParser): iterator getopt*(): tuple[kind: TCmdLineKind, key, val: TaintedString] = ## This is an convenience iterator for iterating over the command line. diff --git a/lib/pure/parseopt2.nim b/lib/pure/parseopt2.nim index 5e79d8a18..7638171d1 100644 --- a/lib/pure/parseopt2.nim +++ b/lib/pure/parseopt2.nim @@ -119,7 +119,7 @@ proc cmdLineRest*(p: TOptParser): TaintedString {.rtl, extern: "npo$1", deprecat type TGetoptResult* = tuple[kind: TCmdLineKind, key, val: TaintedString] -when defined(paramCount): +when declared(paramCount): iterator getopt*(): TGetoptResult = ## This is an convenience iterator for iterating over the command line. ## This uses the TOptParser object. Example: diff --git a/lib/pure/pegs.nim b/lib/pure/pegs.nim index 68b1ab223..efe169c1d 100644 --- a/lib/pure/pegs.nim +++ b/lib/pure/pegs.nim @@ -870,7 +870,7 @@ template `=~`*(s: string, pattern: TPeg): bool = ## echo("syntax error") ## bind maxSubpatterns - when not definedInScope(matches): + when not declaredInScope(matches): var matches {.inject.}: array[0..MaxSubpatterns-1, string] match(s, pattern, matches) diff --git a/lib/pure/rawsockets.nim b/lib/pure/rawsockets.nim index d96741846..fea09dfa2 100644 --- a/lib/pure/rawsockets.nim +++ b/lib/pure/rawsockets.nim @@ -22,7 +22,7 @@ const useWinVersion = defined(Windows) or defined(nimdoc) when useWinVersion: import winlean export WSAEWOULDBLOCK, WSAECONNRESET, WSAECONNABORTED, WSAENETRESET, - WSAEDISCON + WSAEDISCON, ERROR_NETNAME_DELETED else: import posix export fcntl, F_GETFL, O_NONBLOCK, F_SETFL, EAGAIN, EWOULDBLOCK, MSG_NOSIGNAL, diff --git a/lib/pure/ropes.nim b/lib/pure/ropes.nim index 4a6c3f530..eb3792bce 100644 --- a/lib/pure/ropes.nim +++ b/lib/pure/ropes.nim @@ -58,8 +58,8 @@ proc newRope(data: string): PRope = result.data = data var - cache: PRope # the root of the cache tree - N: PRope # dummy rope needed for splay algorithm + cache {.threadvar.}: PRope # the root of the cache tree + N {.threadvar.}: PRope # dummy rope needed for splay algorithm when countCacheMisses: var misses, hits: int diff --git a/lib/pure/subexes.nim b/lib/pure/subexes.nim index 92797744a..ed87610d6 100644 --- a/lib/pure/subexes.nim +++ b/lib/pure/subexes.nim @@ -84,7 +84,8 @@ proc getFormatArg(p: var TFormatParser, a: openArray[string]): int = if result >=% a.len: raiseInvalidFormat("index out of bounds: " & $result) p.i = i -proc scanDollar(p: var TFormatParser, a: openarray[string], s: var string) +proc scanDollar(p: var TFormatParser, a: openarray[string], s: var string) {. + noSideEffect.} proc emitChar(p: var TFormatParser, x: var string, ch: char) {.inline.} = x.add(ch) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 498511899..ebc3b9fdb 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -63,44 +63,44 @@ elif defined(windows): elif defined(JS): type TTime* {.final, importc.} = object - getDay: proc (): int {.tags: [], raises: [].} - getFullYear: proc (): int {.tags: [], raises: [].} - getHours: proc (): int {.tags: [], raises: [].} - getMilliseconds: proc (): int {.tags: [], raises: [].} - getMinutes: proc (): int {.tags: [], raises: [].} - getMonth: proc (): int {.tags: [], raises: [].} - getSeconds: proc (): int {.tags: [], raises: [].} - getTime: proc (): int {.tags: [], raises: [].} - getTimezoneOffset: proc (): int {.tags: [], raises: [].} - getDate: proc (): int {.tags: [], raises: [].} - getUTCDate: proc (): int {.tags: [], raises: [].} - getUTCFullYear: proc (): int {.tags: [], raises: [].} - getUTCHours: proc (): int {.tags: [], raises: [].} - getUTCMilliseconds: proc (): int {.tags: [], raises: [].} - getUTCMinutes: proc (): int {.tags: [], raises: [].} - getUTCMonth: proc (): int {.tags: [], raises: [].} - getUTCSeconds: proc (): int {.tags: [], raises: [].} - getUTCDay: proc (): int {.tags: [], raises: [].} - getYear: proc (): int {.tags: [], raises: [].} - parse: proc (s: cstring): TTime {.tags: [], raises: [].} - setDate: proc (x: int) {.tags: [], raises: [].} - setFullYear: proc (x: int) {.tags: [], raises: [].} - setHours: proc (x: int) {.tags: [], raises: [].} - setMilliseconds: proc (x: int) {.tags: [], raises: [].} - setMinutes: proc (x: int) {.tags: [], raises: [].} - setMonth: proc (x: int) {.tags: [], raises: [].} - setSeconds: proc (x: int) {.tags: [], raises: [].} - setTime: proc (x: int) {.tags: [], raises: [].} - setUTCDate: proc (x: int) {.tags: [], raises: [].} - setUTCFullYear: proc (x: int) {.tags: [], raises: [].} - setUTCHours: proc (x: int) {.tags: [], raises: [].} - setUTCMilliseconds: proc (x: int) {.tags: [], raises: [].} - setUTCMinutes: proc (x: int) {.tags: [], raises: [].} - setUTCMonth: proc (x: int) {.tags: [], raises: [].} - setUTCSeconds: proc (x: int) {.tags: [], raises: [].} - setYear: proc (x: int) {.tags: [], raises: [].} - toGMTString: proc (): cstring {.tags: [], raises: [].} - toLocaleString: proc (): cstring {.tags: [], raises: [].} + getDay: proc (): int {.tags: [], raises: [], gcsafe.} + getFullYear: proc (): int {.tags: [], raises: [], gcsafe.} + getHours: proc (): int {.tags: [], raises: [], gcsafe.} + getMilliseconds: proc (): int {.tags: [], raises: [], gcsafe.} + getMinutes: proc (): int {.tags: [], raises: [], gcsafe.} + getMonth: proc (): int {.tags: [], raises: [], gcsafe.} + getSeconds: proc (): int {.tags: [], raises: [], gcsafe.} + getTime: proc (): int {.tags: [], raises: [], gcsafe.} + getTimezoneOffset: proc (): int {.tags: [], raises: [], gcsafe.} + getDate: proc (): int {.tags: [], raises: [], gcsafe.} + getUTCDate: proc (): int {.tags: [], raises: [], gcsafe.} + getUTCFullYear: proc (): int {.tags: [], raises: [], gcsafe.} + getUTCHours: proc (): int {.tags: [], raises: [], gcsafe.} + getUTCMilliseconds: proc (): int {.tags: [], raises: [], gcsafe.} + getUTCMinutes: proc (): int {.tags: [], raises: [], gcsafe.} + getUTCMonth: proc (): int {.tags: [], raises: [], gcsafe.} + getUTCSeconds: proc (): int {.tags: [], raises: [], gcsafe.} + getUTCDay: proc (): int {.tags: [], raises: [], gcsafe.} + getYear: proc (): int {.tags: [], raises: [], gcsafe.} + parse: proc (s: cstring): TTime {.tags: [], raises: [], gcsafe.} + setDate: proc (x: int) {.tags: [], raises: [], gcsafe.} + setFullYear: proc (x: int) {.tags: [], raises: [], gcsafe.} + setHours: proc (x: int) {.tags: [], raises: [], gcsafe.} + setMilliseconds: proc (x: int) {.tags: [], raises: [], gcsafe.} + setMinutes: proc (x: int) {.tags: [], raises: [], gcsafe.} + setMonth: proc (x: int) {.tags: [], raises: [], gcsafe.} + setSeconds: proc (x: int) {.tags: [], raises: [], gcsafe.} + setTime: proc (x: int) {.tags: [], raises: [], gcsafe.} + setUTCDate: proc (x: int) {.tags: [], raises: [], gcsafe.} + setUTCFullYear: proc (x: int) {.tags: [], raises: [], gcsafe.} + setUTCHours: proc (x: int) {.tags: [], raises: [], gcsafe.} + setUTCMilliseconds: proc (x: int) {.tags: [], raises: [], gcsafe.} + setUTCMinutes: proc (x: int) {.tags: [], raises: [], gcsafe.} + setUTCMonth: proc (x: int) {.tags: [], raises: [], gcsafe.} + setUTCSeconds: proc (x: int) {.tags: [], raises: [], gcsafe.} + setYear: proc (x: int) {.tags: [], raises: [], gcsafe.} + toGMTString: proc (): cstring {.tags: [], raises: [], gcsafe.} + toLocaleString: proc (): cstring {.tags: [], raises: [], gcsafe.} type TTimeInfo* = object of TObject ## represents a time in different parts diff --git a/lib/pure/typetraits.nim b/lib/pure/typetraits.nim index e7bd363cf..3203ee699 100644 --- a/lib/pure/typetraits.nim +++ b/lib/pure/typetraits.nim @@ -11,7 +11,26 @@ ## working with types proc name*(t: typedesc): string {.magic: "TypeTrait".} - ## Returns the name of the given type + ## Returns the name of the given type. + ## + ## Example: + ## + ## .. code-block:: + ## + ## import typetraits + ## + ## proc `$`*[T](some:typedesc[T]): string = name(T) + ## + ## template test(x): stmt = + ## echo "type: ", type(x), ", value: ", x + ## + ## test 42 + ## # --> type: int, value: 42 + ## test "Foo" + ## # --> type: string, value: Foo + ## test(@['A','B']) + ## # --> type: seq[char], value: @[A, B] + proc arity*(t: typedesc): int {.magic: "TypeTrait".} - ## Returns the arity of the given type \ No newline at end of file + ## Returns the arity of the given type diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index f5640a1b4..7cc95f0ad 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -19,7 +19,7 @@ import macros -when defined(stdout): +when declared(stdout): import os when not defined(ECMAScript): @@ -99,7 +99,7 @@ template fail* = when not defined(ECMAScript): if AbortOnError: quit(1) - when defined(TestStatusIMPL): + when declared(TestStatusIMPL): TestStatusIMPL = FAILED else: program_result += 1 @@ -188,7 +188,7 @@ macro expect*(exceptions: varargs[expr], body: stmt): stmt {.immediate.} = result = getAst(expectBody(errorTypes, exp.lineinfo, body)) -when defined(stdout): +when declared(stdout): ## Reading settings var envOutLvl = os.getEnv("NIMTEST_OUTPUT_LVL").string diff --git a/lib/pure/xmldom.nim b/lib/pure/xmldom.nim index 47e94243e..d63b6c5dd 100644 --- a/lib/pure/xmldom.nim +++ b/lib/pure/xmldom.nim @@ -45,7 +45,7 @@ const DocumentFragmentNode* = 11 # Nodes which are childless - Not sure about AttributeNode - childlessObjects = {DocumentNode, AttributeNode, TextNode, + childlessObjects = {DocumentNode, AttributeNode, TextNode, CDataSectionNode, ProcessingInstructionNode, CommentNode} # Illegal characters illegalChars = {'>', '<', '&', '"'} @@ -69,21 +69,21 @@ type FOwnerDocument: PDocument # Read-Only FParentNode: PNode # Read-Only prefix*: string # Setting this should change some values... TODO! - + PElement* = ref Element Element = object of Node FTagName: string # Read-only - + PCharacterData* = ref CharacterData CharacterData = object of Node data*: string - + PDocument* = ref Document Document = object of Node FImplementation: PDOMImplementation # Read-only FDocumentElement: PElement # Read-only - - PAttr* = ref Attr + + PAttr* = ref Attr Attr = object of Node FName: string # Read-only FSpecified: bool # Read-only @@ -95,13 +95,13 @@ type PText* = ref Text Text = object of CharacterData - + PComment* = ref comment Comment = object of CharacterData - + PCDataSection* = ref CDataSection CDataSection = object of Text - + PProcessingInstruction* = ref ProcessingInstruction ProcessingInstruction = object of Node data*: string @@ -111,8 +111,8 @@ type proc getDOM*(): PDOMImplementation = ## Returns a DOMImplementation new(result) - result.Features = @[(name: "core", version: "2.0"), - (name: "core", version: "1.0"), + result.Features = @[(name: "core", version: "2.0"), + (name: "core", version: "1.0"), (name: "XML", version: "2.0")] proc createDocument*(dom: PDOMImplementation, namespaceURI: string, qualifiedName: string): PDocument = @@ -121,28 +121,28 @@ proc createDocument*(dom: PDOMImplementation, namespaceURI: string, qualifiedNam new(doc) doc.FNamespaceURI = namespaceURI doc.FImplementation = dom - + var elTag: PElement new(elTag) elTag.FTagName = qualifiedName elTag.FNodeName = qualifiedName doc.FDocumentElement = elTag doc.FNodeType = DocumentNode - + return doc - + proc createDocument*(dom: PDOMImplementation, n: PElement): PDocument = ## Creates an XML Document object of the specified type with its document element. - + # This procedure is not in the specification, it's provided for the parser. var doc: PDocument new(doc) doc.FDocumentElement = n doc.FImplementation = dom doc.FNodeType = DocumentNode - + return doc - + proc hasFeature*(dom: PDOMImplementation, feature: string, version: string = ""): bool = ## Returns ``true`` if this ``version`` of the DomImplementation implements ``feature``, otherwise ``false`` for iName, iVersion in items(dom.Features): @@ -157,11 +157,11 @@ proc hasFeature*(dom: PDOMImplementation, feature: string, version: string = "") # Document # Attributes - + proc implementation*(doc: PDocument): PDOMImplementation = return doc.FImplementation - -proc documentElement*(doc: PDocument): PElement = + +proc documentElement*(doc: PDocument): PElement = return doc.FDocumentElement # Internal procedures @@ -175,13 +175,13 @@ proc findNodes(nl: PNode, name: string): seq[PNode] = if i.FNodeType == ElementNode: if i.FNodeName == name or name == "*": r.add(i) - + if not isNil(i.childNodes): if i.childNodes.len() != 0: r.add(findNodes(i, name)) - + return r - + proc findNodesNS(nl: PNode, namespaceURI: string, localName: string): seq[PNode] = # Made for getElementsByTagNameNS var r: seq[PNode] = @[] @@ -192,23 +192,23 @@ proc findNodesNS(nl: PNode, namespaceURI: string, localName: string): seq[PNode] if i.FNodeType == ElementNode: if (i.FNamespaceURI == namespaceURI or namespaceURI == "*") and (i.FLocalName == localName or localName == "*"): r.add(i) - + if not isNil(i.childNodes): if i.childNodes.len() != 0: r.add(findNodesNS(i, namespaceURI, localName)) - + return r - + #Procedures proc createAttribute*(doc: PDocument, name: string): PAttr = ## Creates an Attr of the given name. Note that the Attr instance can then be set on an Element using the setAttributeNode method. - ## To create an attribute with a qualified name and namespace URI, use the createAttributeNS method. - + ## To create an attribute with a qualified name and namespace URI, use the createAttributeNS method. + # Check if name contains illegal characters if illegalChars in name: raise newException(EInvalidCharacterErr, "Invalid character") - + var AttrNode: PAttr new(AttrNode) AttrNode.FName = name @@ -222,21 +222,21 @@ proc createAttribute*(doc: PDocument, name: string): PAttr = proc createAttributeNS*(doc: PDocument, namespaceURI: string, qualifiedName: string): PAttr = ## Creates an attribute of the given qualified name and namespace URI - + # Check if name contains illegal characters if illegalChars in namespaceURI or illegalChars in qualifiedName: raise newException(EInvalidCharacterErr, "Invalid character") # Exceptions if qualifiedName.contains(':'): - if namespaceURI == nil: + if isNil(namespaceURI): raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil") elif qualifiedName.split(':')[0].toLower() == "xml" and namespaceURI != "http://www.w3.org/XML/1998/namespace": - raise newException(ENamespaceErr, + raise newException(ENamespaceErr, "When the namespace prefix is \"xml\" namespaceURI has to be \"http://www.w3.org/XML/1998/namespace\"") elif qualifiedName.split(':')[1].toLower() == "xmlns" and namespaceURI != "http://www.w3.org/2000/xmlns/": - raise newException(ENamespaceErr, + raise newException(ENamespaceErr, "When the namespace prefix is \"xmlns\" namespaceURI has to be \"http://www.w3.org/2000/xmlns/\"") - + var AttrNode: PAttr new(AttrNode) AttrNode.FName = qualifiedName @@ -250,7 +250,7 @@ proc createAttributeNS*(doc: PDocument, namespaceURI: string, qualifiedName: str AttrNode.prefix = nil AttrNode.FLocalName = qualifiedName AttrNode.value = "" - + AttrNode.FNodeType = AttributeNode return AttrNode @@ -265,12 +265,12 @@ proc createCDATASection*(doc: PDocument, data: string): PCDATASection = return CData proc createComment*(doc: PDocument, data: string): PComment = - ## Creates a Comment node given the specified string. + ## Creates a Comment node given the specified string. var Comm: PComment new(Comm) Comm.data = data Comm.nodeValue = data - + Comm.FNodeType = CommentNode return Comm @@ -282,11 +282,11 @@ proc createDocumentFragment*(doc: PDocument): PDocumentFragment = proc createElement*(doc: PDocument, tagName: string): PElement = ## Creates an element of the type specified. - + # Check if name contains illegal characters if illegalChars in tagName: raise newException(EInvalidCharacterErr, "Invalid character") - + var elNode: PElement new(elNode) elNode.FTagName = tagName @@ -296,24 +296,24 @@ proc createElement*(doc: PDocument, tagName: string): PElement = elNode.FNamespaceURI = nil elNode.childNodes = @[] elNode.attributes = @[] - + elNode.FNodeType = ElementNode - + return elNode proc createElementNS*(doc: PDocument, namespaceURI: string, qualifiedName: string): PElement = ## Creates an element of the given qualified name and namespace URI. if qualifiedName.contains(':'): - if namespaceURI == nil: + if isNIl(namespaceURI): raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil") elif qualifiedName.split(':')[0].toLower() == "xml" and namespaceURI != "http://www.w3.org/XML/1998/namespace": - raise newException(ENamespaceErr, + raise newException(ENamespaceErr, "When the namespace prefix is \"xml\" namespaceURI has to be \"http://www.w3.org/XML/1998/namespace\"") - + # Check if name contains illegal characters if illegalChars in namespaceURI or illegalChars in qualifiedName: raise newException(EInvalidCharacterErr, "Invalid character") - + var elNode: PElement new(elNode) elNode.FTagName = qualifiedName @@ -327,18 +327,18 @@ proc createElementNS*(doc: PDocument, namespaceURI: string, qualifiedName: strin elNode.FNamespaceURI = namespaceURI elNode.childNodes = @[] elNode.attributes = @[] - + elNode.FNodeType = ElementNode - + return elNode -proc createProcessingInstruction*(doc: PDocument, target: string, data: string): PProcessingInstruction = - ## Creates a ProcessingInstruction node given the specified name and data strings. - +proc createProcessingInstruction*(doc: PDocument, target: string, data: string): PProcessingInstruction = + ## Creates a ProcessingInstruction node given the specified name and data strings. + #Check if name contains illegal characters if illegalChars in target: raise newException(EInvalidCharacterErr, "Invalid character") - + var PI: PProcessingInstruction new(PI) PI.FTarget = target @@ -347,13 +347,13 @@ proc createProcessingInstruction*(doc: PDocument, target: string, data: string): return PI proc createTextNode*(doc: PDocument, data: string): PText = #Propably TextNode - ## Creates a Text node given the specified string. + ## Creates a Text node given the specified string. var txtNode: PText new(txtNode) txtNode.data = data txtNode.nodeValue = data txtNode.FNodeName = "#text" - + txtNode.FNodeType = TextNode return txtNode @@ -363,22 +363,22 @@ discard """proc getElementById*(doc: PDocument, elementId: string): PElement = proc getElementsByTagName*(doc: PDocument, tagName: string): seq[PNode] = ## Returns a NodeList of all the Elements with a given tag name in - ## the order in which they are encountered in a preorder traversal of the Document tree. + ## the order in which they are encountered in a preorder traversal of the Document tree. var result: seq[PNode] = @[] if doc.FDocumentElement.FNodeName == tagName or tagName == "*": result.add(doc.FDocumentElement) - + result.add(doc.FDocumentElement.findNodes(tagName)) return result - + proc getElementsByTagNameNS*(doc: PDocument, namespaceURI: string, localName: string): seq[PNode] = ## Returns a NodeList of all the Elements with a given localName and namespaceURI - ## in the order in which they are encountered in a preorder traversal of the Document tree. + ## in the order in which they are encountered in a preorder traversal of the Document tree. var result: seq[PNode] = @[] if doc.FDocumentElement.FLocalName == localName or localName == "*": if doc.FDocumentElement.FNamespaceURI == namespaceURI or namespaceURI == "*": result.add(doc.FDocumentElement) - + result.add(doc.FDocumentElement.findNodesNS(namespaceURI, localName)) return result @@ -406,7 +406,7 @@ proc importNode*(doc: PDocument, importedNode: PNode, deep: bool): PNode = if deep: for i in low(tmp.len())..high(tmp.len()): n.childNodes.add(importNode(doc, tmp[i], deep)) - + return n of ElementNode: var n: PNode @@ -414,7 +414,7 @@ proc importNode*(doc: PDocument, importedNode: PNode, deep: bool): PNode = n = importedNode n.FOwnerDocument = doc n.FParentNode = nil - + var tmpA: seq[PAttr] = n.attributes n.attributes = @[] # Import the Element node's attributes @@ -426,7 +426,7 @@ proc importNode*(doc: PDocument, importedNode: PNode, deep: bool): PNode = if deep: for i in low(tmp.len())..high(tmp.len()): n.childNodes.add(importNode(doc, tmp[i], deep)) - + return n of ProcessingInstructionNode, TextNode, CDataSectionNode, CommentNode: var n: PNode @@ -437,27 +437,27 @@ proc importNode*(doc: PDocument, importedNode: PNode, deep: bool): PNode = return n else: raise newException(ENotSupportedErr, "The type of node being imported is not supported") - + # Node # Attributes - + proc firstChild*(n: PNode): PNode = ## Returns this node's first child - if n.childNodes.len() > 0: + if not isNil(n.childNodes) and n.childNodes.len() > 0: return n.childNodes[0] else: return nil - + proc lastChild*(n: PNode): PNode = ## Returns this node's last child - if n.childNodes.len() > 0: + if not isNil(n.childNodes) and n.childNodes.len() > 0: return n.childNodes[n.childNodes.len() - 1] else: return nil - + proc localName*(n: PNode): string = ## Returns this nodes local name @@ -465,15 +465,17 @@ proc localName*(n: PNode): string = proc namespaceURI*(n: PNode): string = ## Returns this nodes namespace URI - + return n.FNamespaceURI - -proc `namespaceURI=`*(n: PNode, value: string) = + +proc `namespaceURI=`*(n: PNode, value: string) = n.FNamespaceURI = value proc nextSibling*(n: PNode): PNode = ## Returns the next sibling of this node + if isNil(n.FParentNode) or isNil(n.FParentNode.childNodes): + return nil var nLow: int = low(n.FParentNode.childNodes) var nHigh: int = high(n.FParentNode.childNodes) for i in nLow..nHigh: @@ -500,17 +502,19 @@ proc parentNode*(n: PNode): PNode = ## Returns the parent node of this node return n.FParentNode - + proc previousSibling*(n: PNode): PNode = ## Returns the previous sibling of this node + if isNil(n.FParentNode) or isNil(n.FParentNode.childNodes): + return nil var nLow: int = low(n.FParentNode.childNodes) var nHigh: int = high(n.FParentNode.childNodes) for i in nLow..nHigh: if n.FParentNode.childNodes[i] == n: return n.FParentNode.childNodes[i - 1] return nil - + proc `prefix=`*(n: PNode, value: string) = ## Modifies the prefix of this node @@ -519,13 +523,13 @@ proc `prefix=`*(n: PNode, value: string) = if illegalChars in value: raise newException(EInvalidCharacterErr, "Invalid character") - if n.FNamespaceURI == nil: + if isNil(n.FNamespaceURI): raise newException(ENamespaceErr, "namespaceURI cannot be nil") elif value.toLower() == "xml" and n.FNamespaceURI != "http://www.w3.org/XML/1998/namespace": - raise newException(ENamespaceErr, + raise newException(ENamespaceErr, "When the namespace prefix is \"xml\" namespaceURI has to be \"http://www.w3.org/XML/1998/namespace\"") elif value.toLower() == "xmlns" and n.FNamespaceURI != "http://www.w3.org/2000/xmlns/": - raise newException(ENamespaceErr, + raise newException(ENamespaceErr, "When the namespace prefix is \"xmlns\" namespaceURI has to be \"http://www.w3.org/2000/xmlns/\"") elif value.toLower() == "xmlns" and n.FNodeType == AttributeNode: raise newException(ENamespaceErr, "An AttributeNode cannot have a prefix of \"xmlns\"") @@ -543,33 +547,33 @@ proc `prefix=`*(n: PNode, value: string) = proc appendChild*(n: PNode, newChild: PNode) = ## Adds the node newChild to the end of the list of children of this node. ## If the newChild is already in the tree, it is first removed. - + # Check if n contains newChild - if not IsNil(n.childNodes): + if not isNil(n.childNodes): for i in low(n.childNodes)..high(n.childNodes): if n.childNodes[i] == newChild: raise newException(EHierarchyRequestErr, "The node to append is already in this nodes children.") - + # Check if newChild is from this nodes document if n.FOwnerDocument != newChild.FOwnerDocument: raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") - + if n == newChild: raise newException(EHierarchyRequestErr, "You can't add a node into itself") - + if n.nodeType in childlessObjects: raise newException(ENoModificationAllowedErr, "Cannot append children to a childless node") - + if isNil(n.childNodes): n.childNodes = @[] - + newChild.FParentNode = n for i in low(n.childNodes)..high(n.childNodes): if n.childNodes[i] == newChild: n.childNodes[i] = newChild - + n.childNodes.add(newChild) -proc cloneNode*(n: PNode, deep: bool): PNode = +proc cloneNode*(n: PNode, deep: bool): PNode = ## Returns a duplicate of this node, if ``deep`` is `true`, Element node's children are copied case n.FNodeType of AttributeNode: @@ -586,7 +590,7 @@ proc cloneNode*(n: PNode, deep: bool): PNode = # Import the childNodes var tmp: seq[PNode] = n.childNodes n.childNodes = @[] - if deep: + if deep and not isNil(tmp): for i in low(tmp.len())..high(tmp.len()): n.childNodes.add(cloneNode(tmp[i], deep)) return newNode @@ -597,34 +601,39 @@ proc cloneNode*(n: PNode, deep: bool): PNode = return newNode proc hasAttributes*(n: PNode): bool = - ## Returns whether this node (if it is an element) has any attributes. - return n.attributes.len() > 0 + ## Returns whether this node (if it is an element) has any attributes. + return not isNil(n.attributes) and n.attributes.len() > 0 -proc hasChildNodes*(n: PNode): bool = +proc hasChildNodes*(n: PNode): bool = ## Returns whether this node has any children. - return n.childNodes.len() > 0 + return not isNil(n.childNodes) and n.childNodes.len() > 0 proc insertBefore*(n: PNode, newChild: PNode, refChild: PNode): PNode = ## Inserts the node ``newChild`` before the existing child node ``refChild``. ## If ``refChild`` is nil, insert ``newChild`` at the end of the list of children. - + # Check if newChild is from this nodes document if n.FOwnerDocument != newChild.FOwnerDocument: raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") - + + if isNil(n.childNodes): + n.ChildNodes = @[] + for i in low(n.childNodes)..high(n.childNodes): if n.childNodes[i] == refChild: n.childNodes.insert(newChild, i - 1) - return + return + + n.ChildNodes.add(newChild) proc isSupported*(n: PNode, feature: string, version: string): bool = - ## Tests whether the DOM implementation implements a specific - ## feature and that feature is supported by this node. + ## Tests whether the DOM implementation implements a specific + ## feature and that feature is supported by this node. return n.FOwnerDocument.FImplementation.hasFeature(feature, version) proc isEmpty(s: string): bool = - if s == "" or s == nil: + if isNil(s) or s == "": return True for i in items(s): if i != ' ': @@ -635,18 +644,18 @@ proc normalize*(n: PNode) = ## Merges all seperated TextNodes together, and removes any empty TextNodes var curTextNode: PNode = nil var i: int = 0 - + var newChildNodes: seq[PNode] = @[] while True: - if i >= n.childNodes.len: + if isNil(n.childNodes) or i >= n.childNodes.len: break if n.childNodes[i].nodeType == TextNode: - + #If the TextNode is empty, remove it if PText(n.childNodes[i]).data.isEmpty(): inc(i) - - if curTextNode == nil: + + if isNil(curTextNode): curTextNode = n.childNodes[i] else: PText(curTextNode).data.add(PText(n.childNodes[i]).data) @@ -656,35 +665,37 @@ proc normalize*(n: PNode) = newChildNodes.add(curTextNode) newChildNodes.add(n.childNodes[i]) curTextNode = nil - + inc(i) n.childNodes = newChildNodes proc removeChild*(n: PNode, oldChild: PNode): PNode = ## Removes the child node indicated by ``oldChild`` from the list of children, and returns it. - for i in low(n.childNodes)..high(n.childNodes): - if n.childNodes[i] == oldChild: - result = n.childNodes[i] - n.childNodes.delete(i) - return result - + if not isNil(n.childNodes): + for i in low(n.childNodes)..high(n.childNodes): + if n.childNodes[i] == oldChild: + result = n.childNodes[i] + n.childNodes.delete(i) + return result + raise newException(ENotFoundErr, "Node not found") - + proc replaceChild*(n: PNode, newChild: PNode, oldChild: PNode): PNode = ## Replaces the child node ``oldChild`` with ``newChild`` in the list of children, and returns the ``oldChild`` node. - + # Check if newChild is from this nodes document if n.FOwnerDocument != newChild.FOwnerDocument: raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") - - for i in low(n.childNodes)..high(n.childNodes): - if n.childNodes[i] == oldChild: - result = n.childNodes[i] - n.childNodes[i] = newChild - return result - + + if not isNil(n.childNodes): + for i in low(n.childNodes)..high(n.childNodes): + if n.childNodes[i] == oldChild: + result = n.childNodes[i] + n.childNodes[i] = newChild + return result + raise newException(ENotFoundErr, "Node not found") - + # NamedNodeMap proc getNamedItem*(NList: seq[PNode], name: string): PNode = @@ -693,22 +704,22 @@ proc getNamedItem*(NList: seq[PNode], name: string): PNode = if i.nodeName() == name: return i return nil - + proc getNamedItem*(NList: seq[PAttr], name: string): PAttr = ## Retrieves a node specified by ``name``. If this node cannot be found returns ``nil`` for i in items(NList): if i.nodeName() == name: return i return nil - + proc getNamedItemNS*(NList: seq[PNode], namespaceURI: string, localName: string): PNode = ## Retrieves a node specified by ``localName`` and ``namespaceURI``. If this node cannot be found returns ``nil`` for i in items(NList): if i.namespaceURI() == namespaceURI and i.localName() == localName: return i return nil - -proc getNamedItemNS*(NList: seq[PAttr], namespaceURI: string, localName: string): PAttr = + +proc getNamedItemNS*(NList: seq[PAttr], namespaceURI: string, localName: string): PAttr = ## Retrieves a node specified by ``localName`` and ``namespaceURI``. If this node cannot be found returns ``nil`` for i in items(NList): if i.NamespaceURI() == namespaceURI and i.LocalName() == localName: @@ -716,7 +727,7 @@ proc getNamedItemNS*(NList: seq[PAttr], namespaceURI: string, localName: string) return nil proc item*(NList: seq[PNode], index: int): PNode = - ## Returns the ``index`` th item in the map. + ## Returns the ``index`` th item in the map. ## If ``index`` is greater than or equal to the number of nodes in this map, this returns ``nil``. if index >= NList.len(): return nil else: return NList[index] @@ -729,9 +740,9 @@ proc removeNamedItem*(NList: var seq[PNode], name: string): PNode = result = NList[i] NList.delete(i) return result - + raise newException(ENotFoundErr, "Node not found") - + proc removeNamedItemNS*(NList: var seq[PNode], namespaceURI: string, localName: string): PNode = ## Removes a node specified by local name and namespace URI for i in low(NList)..high(NList): @@ -739,7 +750,7 @@ proc removeNamedItemNS*(NList: var seq[PNode], namespaceURI: string, localName: result = NList[i] NList.delete(i) return result - + raise newException(ENotFoundErr, "Node not found") proc setNamedItem*(NList: var seq[PNode], arg: PNode): PNode = @@ -751,9 +762,9 @@ proc setNamedItem*(NList: var seq[PNode], arg: PNode): PNode = if NList[0].FOwnerDocument != arg.FOwnerDocument: raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") #Exceptions End - + var item: PNode = NList.getNamedItem(arg.NodeName()) - if item == nil: + if isNil(item): NList.add(arg) return nil else: @@ -765,22 +776,22 @@ proc setNamedItem*(NList: var seq[PNode], arg: PNode): PNode = break NList[index] = arg return item # Return the replaced node - + proc setNamedItem*(NList: var seq[PAttr], arg: PAttr): PAttr = ## Adds ``arg`` as a ``Node`` to the ``NList`` ## If a node with the same name is already present in this map, it is replaced by the new one. - if not IsNil(NList): + if not isNil(NList): if NList.len() > 0: # Check if newChild is from this nodes document if NList[0].FOwnerDocument != arg.FOwnerDocument: raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") - - if arg.FOwnerElement != nil: + + if not isNil(arg.FOwnerElement): raise newException(EInuseAttributeErr, "This attribute is in use by another element, use cloneNode") - + # Exceptions end var item: PAttr = NList.getNamedItem(arg.nodeName()) - if item == nil: + if isNil(item): NList.add(arg) return nil else: @@ -792,18 +803,18 @@ proc setNamedItem*(NList: var seq[PAttr], arg: PAttr): PAttr = break NList[index] = arg return item # Return the replaced node - + proc setNamedItemNS*(NList: var seq[PNode], arg: PNode): PNode = ## Adds a node using its ``namespaceURI`` and ``localName`` - if not IsNil(NList): + if not isNil(NList): if NList.len() > 0: # Check if newChild is from this nodes document if NList[0].FOwnerDocument != arg.FOwnerDocument: raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") #Exceptions end - + var item: PNode = NList.getNamedItemNS(arg.namespaceURI(), arg.localName()) - if item == nil: + if isNil(item): NList.add(arg) return nil else: @@ -815,7 +826,7 @@ proc setNamedItemNS*(NList: var seq[PNode], arg: PNode): PNode = break NList[index] = arg return item # Return the replaced node - + proc setNamedItemNS*(NList: var seq[PAttr], arg: PAttr): PAttr = ## Adds a node using its ``namespaceURI`` and ``localName`` if not isNil(NList): @@ -823,13 +834,13 @@ proc setNamedItemNS*(NList: var seq[PAttr], arg: PAttr): PAttr = # Check if newChild is from this nodes document if NList[0].FOwnerDocument != arg.FOwnerDocument: raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") - - if arg.FOwnerElement != nil: + + if not isNil(arg.FOwnerElement): raise newException(EInuseAttributeErr, "This attribute is in use by another element, use cloneNode") - + # Exceptions end var item: PAttr = NList.getNamedItemNS(arg.namespaceURI(), arg.localName()) - if item == nil: + if isNil(item): NList.add(arg) return nil else: @@ -841,8 +852,8 @@ proc setNamedItemNS*(NList: var seq[PAttr], arg: PAttr): PAttr = break NList[index] = arg return item # Return the replaced node - -# CharacterData - Decided to implement this, + +# CharacterData - Decided to implement this, # Didn't add the procedures, because you can just edit .data # Attr @@ -851,13 +862,13 @@ proc name*(a: PAttr): string = ## Returns the name of the Attribute return a.FName - + proc specified*(a: PAttr): bool = ## Specifies whether this attribute was specified in the original document return a.FSpecified - -proc ownerElement*(a: PAttr): PElement = + +proc ownerElement*(a: PAttr): PElement = ## Returns this Attributes owner element return a.FOwnerElement @@ -873,27 +884,35 @@ proc tagName*(el: PElement): string = # Procedures proc getAttribute*(el: PElement, name: string): string = ## Retrieves an attribute value by ``name`` + if isNil(el.attributes): + return nil var attribute = el.attributes.getNamedItem(name) - if attribute != nil: + if not isNil(attribute): return attribute.value else: return nil proc getAttributeNS*(el: PElement, namespaceURI: string, localName: string): string = ## Retrieves an attribute value by ``localName`` and ``namespaceURI`` + if isNil(el.attributes): + return nil var attribute = el.attributes.getNamedItemNS(namespaceURI, localName) - if attribute != nil: + if not isNil(attribute): return attribute.value else: return nil - + proc getAttributeNode*(el: PElement, name: string): PAttr = ## Retrieves an attribute node by ``name`` ## To retrieve an attribute node by qualified name and namespace URI, use the `getAttributeNodeNS` method + if isNil(el.attributes): + return nil return el.attributes.getNamedItem(name) proc getAttributeNodeNS*(el: PElement, namespaceURI: string, localName: string): PAttr = ## Retrieves an `Attr` node by ``localName`` and ``namespaceURI`` + if isNil(el.attributes): + return nil return el.attributes.getNamedItemNS(namespaceURI, localName) proc getElementsByTagName*(el: PElement, name: string): seq[PNode] = @@ -909,103 +928,110 @@ proc getElementsByTagNameNS*(el: PElement, namespaceURI: string, localName: stri result = el.findNodesNS(namespaceURI, localName) proc hasAttribute*(el: PElement, name: string): bool = - ## Returns ``true`` when an attribute with a given ``name`` is specified - ## on this element , ``false`` otherwise. - return el.attributes.getNamedItem(name) != nil + ## Returns ``true`` when an attribute with a given ``name`` is specified + ## on this element , ``false`` otherwise. + if isNil(el.attributes): + return false + return not isNil(el.attributes.getNamedItem(name)) proc hasAttributeNS*(el: PElement, namespaceURI: string, localName: string): bool = ## Returns ``true`` when an attribute with a given ``localName`` and - ## ``namespaceURI`` is specified on this element , ``false`` otherwise - return el.attributes.getNamedItemNS(namespaceURI, localName) != nil + ## ``namespaceURI`` is specified on this element , ``false`` otherwise + if isNil(el.attributes): + return false + return not isNil(el.attributes.getNamedItemNS(namespaceURI, localName)) proc removeAttribute*(el: PElement, name: string) = ## Removes an attribute by ``name`` - for i in low(el.attributes)..high(el.attributes): - if el.attributes[i].FName == name: - el.attributes.delete(i) - + if not isNil(el.attributes): + for i in low(el.attributes)..high(el.attributes): + if el.attributes[i].FName == name: + el.attributes.delete(i) + proc removeAttributeNS*(el: PElement, namespaceURI: string, localName: string) = ## Removes an attribute by ``localName`` and ``namespaceURI`` - for i in low(el.attributes)..high(el.attributes): - if el.attributes[i].FNamespaceURI == namespaceURI and - el.attributes[i].FLocalName == localName: - el.attributes.delete(i) - + if not isNil(el.attributes): + for i in low(el.attributes)..high(el.attributes): + if el.attributes[i].FNamespaceURI == namespaceURI and + el.attributes[i].FLocalName == localName: + el.attributes.delete(i) + proc removeAttributeNode*(el: PElement, oldAttr: PAttr): PAttr = ## Removes the specified attribute node ## If the attribute node cannot be found raises ``ENotFoundErr`` - for i in low(el.attributes)..high(el.attributes): - if el.attributes[i] == oldAttr: - result = el.attributes[i] - el.attributes.delete(i) - return result - + if not isNil(el.attributes): + for i in low(el.attributes)..high(el.attributes): + if el.attributes[i] == oldAttr: + result = el.attributes[i] + el.attributes.delete(i) + return result + raise newException(ENotFoundErr, "oldAttr is not a member of el's Attributes") proc setAttributeNode*(el: PElement, newAttr: PAttr): PAttr = ## Adds a new attribute node, if an attribute with the same `nodeName` is ## present, it is replaced by the new one and the replaced attribute is ## returned, otherwise ``nil`` is returned. - + # Check if newAttr is from this nodes document if el.FOwnerDocument != newAttr.FOwnerDocument: - raise newException(EWrongDocumentErr, + raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") - - if newAttr.FOwnerElement != nil: - raise newException(EInuseAttributeErr, + + if not isNil(newAttr.FOwnerElement): + raise newException(EInuseAttributeErr, "This attribute is in use by another element, use cloneNode") # Exceptions end - + if isNil(el.attributes): el.attributes = @[] return el.attributes.setNamedItem(newAttr) - + proc setAttributeNodeNS*(el: PElement, newAttr: PAttr): PAttr = - ## Adds a new attribute node, if an attribute with the localName and + ## Adds a new attribute node, if an attribute with the localName and ## namespaceURI of ``newAttr`` is present, it is replaced by the new one ## and the replaced attribute is returned, otherwise ``nil`` is returned. - + # Check if newAttr is from this nodes document if el.FOwnerDocument != newAttr.FOwnerDocument: - raise newException(EWrongDocumentErr, + raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") - - if newAttr.FOwnerElement != nil: - raise newException(EInuseAttributeErr, + + if not isNil(newAttr.FOwnerElement): + raise newException(EInuseAttributeErr, "This attribute is in use by another element, use cloneNode") # Exceptions end - + if isNil(el.attributes): el.attributes = @[] return el.attributes.setNamedItemNS(newAttr) proc setAttribute*(el: PElement, name: string, value: string) = ## Adds a new attribute, as specified by ``name`` and ``value`` - ## If an attribute with that name is already present in the element, its + ## If an attribute with that name is already present in the element, its ## value is changed to be that of the value parameter - ## Raises the EInvalidCharacterErr if the specified ``name`` contains + ## Raises the EInvalidCharacterErr if the specified ``name`` contains ## illegal characters var AttrNode = el.FOwnerDocument.createAttribute(name) # Check if name contains illegal characters if illegalChars in name: raise newException(EInvalidCharacterErr, "Invalid character") - + discard el.setAttributeNode(AttrNode) # Set the info later, the setAttributeNode checks # if FOwnerElement is nil, and if it isn't it raises an exception AttrNode.FOwnerElement = el AttrNode.FSpecified = True AttrNode.value = value - + proc setAttributeNS*(el: PElement, namespaceURI, localName, value: string) = - ## Adds a new attribute, as specified by ``namespaceURI``, ``localName`` + ## Adds a new attribute, as specified by ``namespaceURI``, ``localName`` ## and ``value``. - + # Check if name contains illegal characters if illegalChars in namespaceURI or illegalChars in localName: raise newException(EInvalidCharacterErr, "Invalid character") - + var AttrNode = el.FOwnerDocument.createAttributeNS(namespaceURI, localName) - + discard el.setAttributeNodeNS(AttrNode) # Set the info later, the setAttributeNode checks # if FOwnerElement is nil, and if it isn't it raises an exception @@ -1013,19 +1039,19 @@ proc setAttributeNS*(el: PElement, namespaceURI, localName, value: string) = AttrNode.FSpecified = True AttrNode.value = value -# Text +# Text proc splitData*(TextNode: PText, offset: int): PText = - ## Breaks this node into two nodes at the specified offset, + ## Breaks this node into two nodes at the specified offset, ## keeping both in the tree as siblings. - + if offset > TextNode.data.len(): raise newException(EIndexSizeErr, "Index out of bounds") - + var left: string = TextNode.data.substr(0, offset) TextNode.data = left var right: string = TextNode.data.substr(offset, TextNode.data.len()) - - if TextNode.FParentNode != nil: + + if not isNil(TextNode.FParentNode) and not isNil(TextNode.FParentNode.childNodes): for i in low(TextNode.FParentNode.childNodes)..high(TextNode.FParentNode.childNodes): if TextNode.FParentNode.childNodes[i] == TextNode: var newNode: PText = TextNode.FOwnerDocument.createTextNode(right) @@ -1042,10 +1068,10 @@ proc target*(PI: PProcessingInstruction): string = return PI.FTarget - + # --Other stuff-- # Writer -proc addEscaped(s: string): string = +proc addEscaped(s: string): string = result = "" for c in items(s): case c @@ -1057,10 +1083,11 @@ proc addEscaped(s: string): string = proc nodeToXml(n: PNode, indent: int = 0): string = result = repeatChar(indent, ' ') & "<" & n.nodeName - for i in items(n.Attributes): - result.add(" " & i.name & "=\"" & addEscaped(i.value) & "\"") - - if n.childNodes.len() == 0: + if not isNil(n.attributes): + for i in items(n.attributes): + result.add(" " & i.name & "=\"" & addEscaped(i.value) & "\"") + + if isNil(n.childNodes) or n.childNodes.len() == 0: result.add("/>") # No idea why this doesn't need a \n :O else: # End the beginning of this tag diff --git a/lib/pure/xmltree.nim b/lib/pure/xmltree.nim index 95b48a850..1af7db7d5 100644 --- a/lib/pure/xmltree.nim +++ b/lib/pure/xmltree.nim @@ -151,6 +151,8 @@ proc addEscaped*(result: var string, s: string) = of '>': result.add(">") of '&': result.add("&") of '"': result.add(""") + of '\'': result.add("'") + of '/': result.add("/") else: result.add(c) proc escape*(s: string): string = @@ -164,6 +166,8 @@ proc escape*(s: string): string = ## ``>`` ``>`` ## ``&`` ``&`` ## ``"`` ``"`` + ## ``'`` ``'`` + ## ``/`` ``/`` ## ------------ ------------------- result = newStringOfCap(s.len) addEscaped(result, s) diff --git a/lib/system.nim b/lib/system.nim index 2fb08563a..0d8f63bd4 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -13,6 +13,21 @@ ## Each module implicitly imports the System module; it must not be listed ## explicitly. Because of this there cannot be a user-defined module named ## ``system``. +## +## Exception hierarchy +## =================== +## +## For visual convenience here is the exception inheritance hierarchy +## represented as a tree: +## +## .. include:: ../doc/exception_hierarchy_fragment.txt +## +## Module system +## ============= +## + +# That lonesome header above is to prevent :idx: entries from being mentioned +# in the global index as part of the previous header (Exception hierarchy). type int* {.magic: Int.} ## default integer type; bitwidth depends on @@ -83,16 +98,8 @@ type proc defined*(x: expr): bool {.magic: "Defined", noSideEffect.} ## Special compile-time procedure that checks whether `x` is - ## defined. `x` has to be an identifier or a qualified identifier. - ## This can be used to check whether a library provides a certain - ## feature or not: - ## - ## .. code-block:: Nimrod - ## when not defined(strutils.toUpper): - ## # provide our own toUpper proc here, because strutils is - ## # missing it. - ## - ## You can also check external symbols introduced through the compiler's + ## defined. + ## `x` is an external symbol introduced through the compiler's ## `-d:x switch <nimrodc.html#compile-time-symbols>`_ to enable build time ## conditionals: ## @@ -101,13 +108,28 @@ proc defined*(x: expr): bool {.magic: "Defined", noSideEffect.} ## # Do here programmer friendly expensive sanity checks. ## # Put here the normal code +proc declared*(x: expr): bool {.magic: "Defined", noSideEffect.} + ## Special compile-time procedure that checks whether `x` is + ## declared. `x` has to be an identifier or a qualified identifier. + ## This can be used to check whether a library provides a certain + ## feature or not: + ## + ## .. code-block:: Nimrod + ## when not defined(strutils.toUpper): + ## # provide our own toUpper proc here, because strutils is + ## # missing it. + when defined(useNimRtl): {.deadCodeElim: on.} proc definedInScope*(x: expr): bool {. + magic: "DefinedInScope", noSideEffect, deprecated.} + ## **Deprecated since version 0.9.6**: Use ``declaredInScope`` instead. + +proc declaredInScope*(x: expr): bool {. magic: "DefinedInScope", noSideEffect.} ## Special compile-time procedure that checks whether `x` is - ## defined in the current scope. `x` has to be an identifier. + ## declared in the current scope. `x` has to be an identifier. proc `not` *(x: bool): bool {.magic: "Not", noSideEffect.} ## Boolean not; returns true iff ``x == false``. @@ -301,9 +323,11 @@ type FWriteIO* = object of FIO ## Effect describing a write IO operation. FExecIO* = object of FIO ## Effect describing an executing IO operation. - E_Base* {.compilerproc.} = object of TObject ## base exception class; - ## each exception has to - ## inherit from `E_Base`. + E_Base* {.compilerproc.} = object of TObject ## \ + ## Base exception class. + ## + ## Each exception has to inherit from `E_Base`. See the full `exception + ## hierarchy`_. parent: ref E_Base ## parent exception (can be used as a stack) name: cstring ## The exception's name is its Nimrod identifier. ## This field is filled automatically in the @@ -313,99 +337,142 @@ type ## is bad style. trace: string - EAsynch* = object of E_Base ## Abstract exception class for - ## *asynchronous exceptions* (interrupts). - ## This is rarely needed: Most - ## exception types inherit from `ESynch` - ESynch* = object of E_Base ## Abstract exception class for - ## *synchronous exceptions*. Most exceptions - ## should be inherited (directly or indirectly) - ## from ESynch. - ESystem* = object of ESynch ## Abstract class for exceptions that the runtime - ## system raises. - EIO* = object of ESystem ## raised if an IO error occured. - EOS* = object of ESystem ## raised if an operating system service failed. + EAsynch* = object of E_Base ## \ + ## Abstract exception class for *asynchronous exceptions* (interrupts). + ## + ## This is rarely needed: most exception types inherit from `ESynch + ## <#ESynch>`_. See the full `exception hierarchy`_. + EControlC* = object of EAsynch ## \ + ## Raised for Ctrl+C key presses in console applications. + ## + ## See the full `exception hierarchy`_. + ESynch* = object of E_Base ## \ + ## Abstract exception class for *synchronous exceptions*. + ## + ## Most exceptions should be inherited (directly or indirectly) from + ## `ESynch` instead of from `EAsynch <#EAsynch>`_. See the full `exception + ## hierarchy`_. + ESystem* = object of ESynch ## \ + ## Abstract class for exceptions that the runtime system raises. + ## + ## See the full `exception hierarchy`_. + EIO* = object of ESystem ## \ + ## Raised if an IO error occured. + ## + ## See the full `exception hierarchy`_. + EOS* = object of ESystem ## \ + ## Raised if an operating system service failed. + ## + ## See the full `exception hierarchy`_. errorCode*: int32 ## OS-defined error code describing this error. - EInvalidLibrary* = object of EOS ## raised if a dynamic library - ## could not be loaded. - EResourceExhausted* = object of ESystem ## raised if a resource request - ## could not be fullfilled. - EArithmetic* = object of ESynch ## raised if any kind of arithmetic - ## error occured. - EDivByZero* {.compilerproc.} = - object of EArithmetic ## is the exception class for integer divide-by-zero - ## errors. - EOverflow* {.compilerproc.} = - object of EArithmetic ## is the exception class for integer calculations - ## whose results are too large to fit in the - ## provided bits. - - EAccessViolation* {.compilerproc.} = - object of ESynch ## the exception class for invalid memory access errors - - EAssertionFailed* {.compilerproc.} = - object of ESynch ## is the exception class for Assert - ## procedures that is raised if the - ## assertion proves wrong - - EControlC* = object of EAsynch ## is the exception class for Ctrl+C - ## key presses in console applications. - - EInvalidValue* = object of ESynch ## is the exception class for string - ## and object conversion errors. - EInvalidKey* = object of EInvalidValue ## is the exception class if a key - ## cannot be found in a table. - - EOutOfMemory* = object of ESystem ## is the exception class for - ## unsuccessful attempts to allocate - ## memory. - - EInvalidIndex* = object of ESynch ## is raised if an array index is out - ## of bounds. - EInvalidField* = object of ESynch ## is raised if a record field is not - ## accessible because its dicriminant's - ## value does not fit. - - EOutOfRange* = object of ESynch ## is raised if a range check error - ## occurred. - - EStackOverflow* = object of ESystem ## is raised if the hardware stack - ## used for subroutine calls overflowed. - - ENoExceptionToReraise* = object of ESynch ## is raised if there is no - ## exception to reraise. - - EInvalidObjectAssignment* = - object of ESynch ## is raised if an object gets assigned to its - ## parent's object. - - EInvalidObjectConversion* = - object of ESynch ## is raised if an object is converted to an incompatible - ## object type. - - EFloatingPoint* = object of ESynch ## base class for floating point exceptions - EFloatInvalidOp* {.compilerproc.} = - object of EFloatingPoint ## Invalid operation according to IEEE: Raised by - ## 0.0/0.0, for example. - EFloatDivByZero* {.compilerproc.} = - object of EFloatingPoint ## Division by zero. Divisor is zero and dividend - ## is a finite nonzero number. - EFloatOverflow* {.compilerproc.} = - object of EFloatingPoint ## Overflow. Operation produces a result - ## that exceeds the range of the exponent - EFloatUnderflow* {.compilerproc.} = - object of EFloatingPoint ## Underflow. Operation produces a result - ## that is too small to be represented as - ## a normal number - EFloatInexact* {.compilerproc.} = - object of EFloatingPoint ## Inexact. Operation produces a result - ## that cannot be represented with infinite - ## precision -- for example, 2.0 / 3.0, log(1.1) - ## NOTE: Nimrod currently does not detect these! - EDeadThread* = - object of ESynch ## is raised if it is attempted to send a message to a - ## dead thread. - + EInvalidLibrary* = object of EOS ## \ + ## Raised if a dynamic library could not be loaded. + ## + ## See the full `exception hierarchy`_. + EResourceExhausted* = object of ESystem ## \ + ## Raised if a resource request could not be fullfilled. + ## + ## See the full `exception hierarchy`_. + EArithmetic* = object of ESynch ## \ + ## Raised if any kind of arithmetic error occured. + ## + ## See the full `exception hierarchy`_. + EDivByZero* {.compilerproc.} = object of EArithmetic ## \ + ## Raised for runtime integer divide-by-zero errors. + ## + ## See the full `exception hierarchy`_. + EOverflow* {.compilerproc.} = object of EArithmetic ## \ + ## Raised for runtime integer overflows. + ## + ## This happens for calculations whose results are too large to fit in the + ## provided bits. See the full `exception hierarchy`_. + EAccessViolation* {.compilerproc.} = object of ESynch ## \ + ## Raised for invalid memory access errors + ## + ## See the full `exception hierarchy`_. + EAssertionFailed* {.compilerproc.} = object of ESynch ## \ + ## Raised when assertion is proved wrong. + ## + ## Usually the result of using the `assert() template <#assert>`_. See the + ## full `exception hierarchy`_. + EInvalidValue* = object of ESynch ## \ + ## Raised for string and object conversion errors. + EInvalidKey* = object of EInvalidValue ## \ + ## Raised if a key cannot be found in a table. + ## + ## Mostly used by the `tables <tables.html>`_ module, it can also be raised + ## by other collection modules like `sets <sets.html>`_ or `strtabs + ## <strtabs.html>`_. See the full `exception hierarchy`_. + EOutOfMemory* = object of ESystem ## \ + ## Raised for unsuccessful attempts to allocate memory. + ## + ## See the full `exception hierarchy`_. + EInvalidIndex* = object of ESynch ## \ + ## Raised if an array index is out of bounds. + ## + ## See the full `exception hierarchy`_. + EInvalidField* = object of ESynch ## \ + ## Raised if a record field is not accessible because its dicriminant's + ## value does not fit. + ## + ## See the full `exception hierarchy`_. + EOutOfRange* = object of ESynch ## \ + ## Raised if a range check error occurred. + ## + ## See the full `exception hierarchy`_. + EStackOverflow* = object of ESystem ## \ + ## Raised if the hardware stack used for subroutine calls overflowed. + ## + ## See the full `exception hierarchy`_. + ENoExceptionToReraise* = object of ESynch ## \ + ## Raised if there is no exception to reraise. + ## + ## See the full `exception hierarchy`_. + EInvalidObjectAssignment* = object of ESynch ## \ + ## Raised if an object gets assigned to its parent's object. + ## + ## See the full `exception hierarchy`_. + EInvalidObjectConversion* = object of ESynch ## \ + ## Raised if an object is converted to an incompatible object type. + ## + ## See the full `exception hierarchy`_. + EFloatingPoint* = object of ESynch ## \ + ## Base class for floating point exceptions. + ## + ## See the full `exception hierarchy`_. + EFloatInvalidOp* {.compilerproc.} = object of EFloatingPoint ## \ + ## Raised by invalid operations according to IEEE. + ## + ## Raised by ``0.0/0.0``, for example. See the full `exception + ## hierarchy`_. + EFloatDivByZero* {.compilerproc.} = object of EFloatingPoint ## \ + ## Raised by division by zero. + ## + ## Divisor is zero and dividend is a finite nonzero number. See the full + ## `exception hierarchy`_. + EFloatOverflow* {.compilerproc.} = object of EFloatingPoint ## \ + ## Raised for overflows. + ## + ## The operation produced a result that exceeds the range of the exponent. + ## See the full `exception hierarchy`_. + EFloatUnderflow* {.compilerproc.} = object of EFloatingPoint ## \ + ## Raised for underflows. + ## + ## The operation produced a result that is too small to be represented as a + ## normal number. See the full `exception hierarchy`_. + EFloatInexact* {.compilerproc.} = object of EFloatingPoint ## \ + ## Raised for inexact results. + ## + ## The operation produced a result that cannot be represented with infinite + ## precision -- for example: ``2.0 / 3.0, log(1.1)`` + ## + ## **NOTE**: Nimrod currently does not detect these! See the full + ## `exception hierarchy`_. + EDeadThread* = object of ESynch ## \ + ## Raised if it is attempted to send a message to a dead thread. + ## + ## See the full `exception hierarchy`_. + TResult* = enum Failure, Success proc sizeof*[T](x: T): Natural {.magic: "SizeOf", noSideEffect.} @@ -783,7 +850,7 @@ proc contains*[T](s: TSlice[T], value: T): bool {.noSideEffect, inline.} = result = s.a <= value and value <= s.b template `in` * (x, y: expr): expr {.immediate.} = contains(y, x) - ## Suger for contains + ## Sugar for contains ## ## .. code-block:: Nimrod ## assert(1 in (1..3) == true) @@ -814,9 +881,9 @@ proc `of` *[T, S](x: T, y: S): bool {.magic: "Of", noSideEffect.} ## Checks if `x` has a type of `y` ## ## .. code-block:: Nimrod - ## assert(EFloatingPoint is EBase) - ## assert(EIO is ESystem) - ## assert(EDivByZero is EBase) + ## assert(EFloatingPoint of EBase) + ## assert(EIO of ESystem) + ## assert(EDivByZero of EBase) proc cmp*[T](x, y: T): int {.procvar.} = ## Generic compare proc. Returns a value < 0 iff x < y, a value > 0 iff x > y @@ -1052,7 +1119,7 @@ proc add *[T](x: var seq[T], y: openArray[T]) {.noSideEffect.} = ## containers should also call their adding proc `add` for consistency. ## Generic code becomes much easier to write if the Nimrod naming scheme is ## respected. - var xl = x.len + let xl = x.len setLen(x, xl + y.len) for i in 0..high(y): x[xl+i] = y[i] @@ -1066,20 +1133,20 @@ proc shallowCopy*[T](x: var T, y: T) {.noSideEffect, magic: "ShallowCopy".} proc del*[T](x: var seq[T], i: int) {.noSideEffect.} = ## deletes the item at index `i` by putting ``x[high(x)]`` into position `i`. ## This is an O(1) operation. - var xl = x.len + let xl = x.len shallowCopy(x[i], x[xl-1]) setLen(x, xl-1) proc delete*[T](x: var seq[T], i: int) {.noSideEffect.} = ## deletes the item at index `i` by moving ``x[i+1..]`` by one position. ## This is an O(n) operation. - var xl = x.len + let xl = x.len for j in i..xl-2: shallowCopy(x[j], x[j+1]) setLen(x, xl-1) proc insert*[T](x: var seq[T], item: T, i = 0) {.noSideEffect.} = ## inserts `item` into `x` at position `i`. - var xl = x.len + let xl = x.len setLen(x, xl+1) var j = xl-1 while j >= i: @@ -1930,16 +1997,16 @@ when not defined(nimrodVM) and hostOS != "standalone": ## returns an informative string about the GC's activity. This may be useful ## for tweaking. - proc GC_ref*[T](x: ref T) {.magic: "GCref".} - proc GC_ref*[T](x: seq[T]) {.magic: "GCref".} - proc GC_ref*(x: string) {.magic: "GCref".} + proc GC_ref*[T](x: ref T) {.magic: "GCref", gcsafe.} + proc GC_ref*[T](x: seq[T]) {.magic: "GCref", gcsafe.} + proc GC_ref*(x: string) {.magic: "GCref", gcsafe.} ## marks the object `x` as referenced, so that it will not be freed until ## it is unmarked via `GC_unref`. If called n-times for the same object `x`, ## n calls to `GC_unref` are needed to unmark `x`. - proc GC_unref*[T](x: ref T) {.magic: "GCunref".} - proc GC_unref*[T](x: seq[T]) {.magic: "GCunref".} - proc GC_unref*(x: string) {.magic: "GCunref".} + proc GC_unref*[T](x: ref T) {.magic: "GCunref", gcsafe.} + proc GC_unref*[T](x: seq[T]) {.magic: "GCunref", gcsafe.} + proc GC_unref*(x: string) {.magic: "GCunref", gcsafe.} ## see the documentation of `GC_ref`. template accumulateResult*(iter: expr) = @@ -2026,19 +2093,25 @@ elif hostOS != "standalone": {.pop.} proc echo*[T](x: varargs[T, `$`]) {.magic: "Echo", tags: [FWriteIO], gcsafe.} - ## special built-in that takes a variable number of arguments. Each argument + ## Writes and flushes the parameters to the standard output. + ## + ## Special built-in that takes a variable number of arguments. Each argument ## is converted to a string via ``$``, so it works for user-defined ## types that have an overloaded ``$`` operator. - ## It is roughly equivalent to ``writeln(stdout, x); flush(stdout)``, but + ## It is roughly equivalent to ``writeln(stdout, x); flushFile(stdout)``, but ## available for the JavaScript target too. + ## ## Unlike other IO operations this is guaranteed to be thread-safe as - ## ``echo`` is very often used for debugging convenience. + ## ``echo`` is very often used for debugging convenience. If you want to use + ## ``echo`` inside a `proc without side effects + ## <manual.html#nosideeffect-pragma>`_ you can use `debugEcho <#debugEcho>`_ + ## instead. proc debugEcho*[T](x: varargs[T, `$`]) {.magic: "Echo", noSideEffect, tags: [], raises: [].} - ## Same as ``echo``, but as a special semantic rule, ``debugEcho`` pretends - ## to be free of side effects, so that it can be used for debugging routines - ## marked as ``noSideEffect``. + ## Same as `echo <#echo>`_, but as a special semantic rule, ``debugEcho`` + ## pretends to be free of side effects, so that it can be used for debugging + ## routines marked as `noSideEffect <manual.html#nosideeffect-pragma>`_. template newException*(exceptn: typedesc, message: string): expr = ## creates an exception object of type ``exceptn`` and sets its ``msg`` field @@ -2052,7 +2125,7 @@ template newException*(exceptn: typedesc, message: string): expr = when hostOS == "standalone": include panicoverride -when not defined(sysFatal): +when not declared(sysFatal): template sysFatal(exceptn: typedesc, message: string) = when hostOS == "standalone": panic(message) @@ -2104,11 +2177,17 @@ when not defined(JS): #and not defined(NimrodVM): # WARNING: This is very fragile! An array size of 8 does not work on my # Linux 64bit system. -- That's because the stack direction is the other # way round. - when defined(setStackBottom): + when declared(setStackBottom): var locals {.volatile.}: pointer locals = addr(locals) setStackBottom(locals) + proc initStackBottomWith(locals: pointer) {.inline, compilerproc.} = + # We need to keep initStackBottom around for now to avoid + # bootstrapping problems. + when declared(setStackBottom): + setStackBottom(locals) + var strDesc: TNimType @@ -2377,7 +2456,7 @@ when not defined(JS): #and not defined(NimrodVM): hasRaiseAction: bool raiseAction: proc (e: ref E_Base): bool {.closure.} - when defined(initAllocator): + when declared(initAllocator): initAllocator() when hasThreadSupport: include "system/syslocks" @@ -2494,11 +2573,11 @@ when not defined(JS): #and not defined(NimrodVM): include "system/assign" include "system/repr" - proc getCurrentException*(): ref E_Base {.compilerRtl, inl.} = + proc getCurrentException*(): ref E_Base {.compilerRtl, inl, gcsafe.} = ## retrieves the current exception; if there is none, nil is returned. result = currException - proc getCurrentExceptionMsg*(): string {.inline.} = + proc getCurrentExceptionMsg*(): string {.inline, gcsafe.} = ## retrieves the error message that was attached to the current ## exception; if there is none, "" is returned. var e = getCurrentException() @@ -2950,7 +3029,7 @@ proc compiles*(x): bool {.magic: "Compiles", noSideEffect.} = ## echo "'+' for integers is available" discard -when defined(initDebugger): +when declared(initDebugger): initDebugger() when hostOS != "standalone": @@ -2991,18 +3070,12 @@ proc locals*(): TObject {.magic: "Locals", noSideEffect.} = ## # -> B is 1 discard -proc deepCopy*[T](x: T): T {.magic: "DeepCopy", noSideEffect.} = - ## performs a deep copy of `x`. This is also used by the code generator - ## for the implementation of ``spawn``. - discard +when hostOS != "standalone" and not defined(NimrodVM) and not defined(JS): + proc deepCopy*[T](x: var T, y: T) {.noSideEffect, magic: "DeepCopy".} = + ## performs a deep copy of `x`. This is also used by the code generator + ## for the implementation of ``spawn``. + discard -{.pop.} #{.push warning[GcMem]: off.} + include "system/deepcopy" -when not defined(booting): - type - semistatic*[T] = static[T] | T - # indicates a param of proc specialized for each static value, - # but also accepting run-time values - - template isStatic*(x): expr = compiles(static(x)) - # checks whether `x` is a value known at compile-time +{.pop.} #{.push warning[GcMem]: off.} diff --git a/lib/system/ansi_c.nim b/lib/system/ansi_c.nim index 511a006d3..e012697ae 100644 --- a/lib/system/ansi_c.nim +++ b/lib/system/ansi_c.nim @@ -39,7 +39,7 @@ var c_stderr {.importc: "stderr", nodecl.}: C_TextFileStar # constants faked as variables: -when not defined(SIGINT): +when not declared(SIGINT): when NoFakeVars: when defined(windows): const @@ -132,7 +132,7 @@ proc c_realloc(p: pointer, newsize: int): pointer {. importc: "realloc", header: "<stdlib.h>".} when hostOS != "standalone": - when not defined(errno): + when not declared(errno): when defined(NimrodVM): var vmErrnoWrapper {.importc.}: ptr cint template errno: expr = diff --git a/lib/system/arithm.nim b/lib/system/arithm.nim index d9b3aebac..7672947cd 100644 --- a/lib/system/arithm.nim +++ b/lib/system/arithm.nim @@ -114,63 +114,69 @@ when asmVersion and not defined(gcc) and not defined(llvm_gcc): proc addInt(a, b: int): int {.compilerProc, asmNoStackFrame.} = # a in eax, and b in edx asm """ - mov eax, `a` - add eax, `b` + mov eax, ecx + add eax, edx jno theEnd call `raiseOverflow` theEnd: + ret """ proc subInt(a, b: int): int {.compilerProc, asmNoStackFrame.} = asm """ - mov eax, `a` - sub eax, `b` + mov eax, ecx + sub eax, edx jno theEnd call `raiseOverflow` theEnd: + ret """ proc negInt(a: int): int {.compilerProc, asmNoStackFrame.} = asm """ - mov eax, `a` + mov eax, ecx neg eax jno theEnd call `raiseOverflow` theEnd: + ret """ proc divInt(a, b: int): int {.compilerProc, asmNoStackFrame.} = asm """ - mov eax, `a` - mov ecx, `b` + mov eax, ecx + mov ecx, edx xor edx, edx idiv ecx jno theEnd call `raiseOverflow` theEnd: + ret """ proc modInt(a, b: int): int {.compilerProc, asmNoStackFrame.} = asm """ - mov eax, `a` - mov ecx, `b` + mov eax, ecx + mov ecx, edx xor edx, edx idiv ecx jno theEnd call `raiseOverflow` theEnd: mov eax, edx + ret """ proc mulInt(a, b: int): int {.compilerProc, asmNoStackFrame.} = asm """ - mov eax, `a` - mov ecx, `b` + mov eax, ecx + mov ecx, edx xor edx, edx imul ecx jno theEnd call `raiseOverflow` theEnd: + ret """ elif false: # asmVersion and (defined(gcc) or defined(llvm_gcc)): @@ -241,26 +247,26 @@ elif false: # asmVersion and (defined(gcc) or defined(llvm_gcc)): """ # Platform independent versions of the above (slower!) -when not defined(addInt): +when not declared(addInt): proc addInt(a, b: int): int {.compilerProc, inline.} = result = a +% b if (result xor a) >= 0 or (result xor b) >= 0: return result raiseOverflow() -when not defined(subInt): +when not declared(subInt): proc subInt(a, b: int): int {.compilerProc, inline.} = result = a -% b if (result xor a) >= 0 or (result xor not b) >= 0: return result raiseOverflow() -when not defined(negInt): +when not declared(negInt): proc negInt(a: int): int {.compilerProc, inline.} = if a != low(int): return -a raiseOverflow() -when not defined(divInt): +when not declared(divInt): proc divInt(a, b: int): int {.compilerProc, inline.} = if b == 0: raiseDivByZero() @@ -268,13 +274,13 @@ when not defined(divInt): raiseOverflow() return a div b -when not defined(modInt): +when not declared(modInt): proc modInt(a, b: int): int {.compilerProc, inline.} = if b == 0: raiseDivByZero() return a mod b -when not defined(mulInt): +when not declared(mulInt): # # This code has been inspired by Python's source code. # The native int product x*y is either exactly right or *way* off, being diff --git a/lib/system/assign.nim b/lib/system/assign.nim index 2ae945fb1..0e27eb57f 100644 --- a/lib/system/assign.nim +++ b/lib/system/assign.nim @@ -89,14 +89,10 @@ proc genericAssignAux(dest, src: pointer, mt: PNimType, shallow: bool) = copyMem(dest, src, mt.size) # copy raw bits proc genericAssign(dest, src: pointer, mt: PNimType) {.compilerProc.} = - GC_disable() genericAssignAux(dest, src, mt, false) - GC_enable() proc genericShallowAssign(dest, src: pointer, mt: PNimType) {.compilerProc.} = - GC_disable() genericAssignAux(dest, src, mt, true) - GC_enable() when false: proc debugNimType(t: PNimType) = diff --git a/lib/system/atomics.nim b/lib/system/atomics.nim index 43b3f0438..695a5f63e 100644 --- a/lib/system/atomics.nim +++ b/lib/system/atomics.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -## Atomic operations for Nimrod. +# Atomic operations for Nimrod. {.push stackTrace:off.} const someGcc = defined(gcc) or defined(llvm_gcc) or defined(clang) @@ -174,7 +174,7 @@ proc atomicInc*(memLoc: var int, x: int = 1): int = proc atomicDec*(memLoc: var int, x: int = 1): int = when defined(gcc) and hasThreadSupport: - when defined(atomic_sub_fetch): + when declared(atomic_sub_fetch): result = atomic_sub_fetch(memLoc.addr, x, ATOMIC_RELAXED) else: result = atomic_add_fetch(memLoc.addr, -x, ATOMIC_RELAXED) @@ -201,14 +201,14 @@ when (defined(x86) or defined(amd64)) and (defined(gcc) or defined(llvm_gcc)): {.emit: """asm volatile("pause" ::: "memory");""".} elif (defined(x86) or defined(amd64)) and defined(vcc): proc cpuRelax {.importc: "YieldProcessor", header: "<windows.h>".} -elif defined(intelc): +elif defined(icl): proc cpuRelax {.importc: "_mm_pause", header: "xmmintrin.h".} elif false: from os import sleep proc cpuRelax {.inline.} = os.sleep(1) -when not defined(fence) and hasThreadSupport: +when not declared(fence) and hasThreadSupport: # XXX fixme proc fence*() {.inline.} = var dummy: bool diff --git a/lib/system/channels.nim b/lib/system/channels.nim index e5535dbdc..df46922e4 100644 --- a/lib/system/channels.nim +++ b/lib/system/channels.nim @@ -14,7 +14,7 @@ ## **Note:** The current implementation of message passing is slow and does ## not work with cyclic data structures. -when not defined(NimString): +when not declared(NimString): {.error: "You must not import this module explicitly".} type diff --git a/lib/system/deepcopy.nim b/lib/system/deepcopy.nim new file mode 100644 index 000000000..e7eb1cdb4 --- /dev/null +++ b/lib/system/deepcopy.nim @@ -0,0 +1,141 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2014 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) {.gcsafe.} +proc genericDeepCopyAux(dest, src: pointer, n: ptr TNimNode) {.gcsafe.} = + var + d = cast[TAddress](dest) + s = cast[TAddress](src) + case n.kind + of nkSlot: + genericDeepCopyAux(cast[pointer](d +% n.offset), + cast[pointer](s +% n.offset), n.typ) + of nkList: + for i in 0..n.len-1: + genericDeepCopyAux(dest, src, n.sons[i]) + of nkCase: + var dd = selectBranch(dest, n) + var m = selectBranch(src, n) + # reset if different branches are in use; note different branches also + # imply that's not self-assignment (``x = x``)! + if m != dd and dd != nil: + genericResetAux(dest, dd) + copyMem(cast[pointer](d +% n.offset), cast[pointer](s +% n.offset), + n.typ.size) + if m != nil: + genericDeepCopyAux(dest, src, m) + of nkNone: sysAssert(false, "genericDeepCopyAux") + +proc copyDeepString(src: NimString): NimString {.inline.} = + if src != nil: + result = rawNewString(src.space) + result.len = src.len + c_memcpy(result.data, src.data, (src.len + 1) * sizeof(char)) + +proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) = + var + d = cast[TAddress](dest) + s = cast[TAddress](src) + sysAssert(mt != nil, "genericDeepCopyAux 2") + case mt.kind + of tyString: + var x = cast[PPointer](dest) + var s2 = cast[PPointer](s)[] + if s2 == nil: + unsureAsgnRef(x, s2) + else: + unsureAsgnRef(x, copyDeepString(cast[NimString](s2))) + of tySequence: + var s2 = cast[PPointer](src)[] + var seq = cast[PGenericSeq](s2) + var x = cast[PPointer](dest) + if s2 == nil: + unsureAsgnRef(x, s2) + return + sysAssert(dest != nil, "genericDeepCopyAux 3") + unsureAsgnRef(x, newSeq(mt, seq.len)) + var dst = cast[TAddress](cast[PPointer](dest)[]) + for i in 0..seq.len-1: + genericDeepCopyAux( + cast[pointer](dst +% i*% mt.base.size +% GenericSeqSize), + cast[pointer](cast[TAddress](s2) +% i *% mt.base.size +% + GenericSeqSize), + mt.base) + of tyObject: + # we need to copy m_type field for tyObject, as it could be empty for + # sequence reallocations: + var pint = cast[ptr PNimType](dest) + pint[] = cast[ptr PNimType](src)[] + if mt.base != nil: + genericDeepCopyAux(dest, src, mt.base) + genericDeepCopyAux(dest, src, mt.node) + of tyTuple: + genericDeepCopyAux(dest, src, mt.node) + of tyArray, tyArrayConstr: + for i in 0..(mt.size div mt.base.size)-1: + genericDeepCopyAux(cast[pointer](d +% i*% mt.base.size), + cast[pointer](s +% i*% mt.base.size), mt.base) + of tyRef: + if mt.base.deepCopy != nil: + let z = mt.base.deepCopy(cast[PPointer](src)[]) + unsureAsgnRef(cast[PPointer](dest), z) + else: + # we modify the header of the cell temporarily; instead of the type + # field we store a forwarding pointer. XXX This is bad when the cloning + # fails due to OOM etc. + let s2 = cast[PPointer](src)[] + if s2 == nil: + unsureAsgnRef(cast[PPointer](dest), s2) + return + when declared(usrToCell): + # unfortunately we only have cycle detection for our native GCs. + let x = usrToCell(s2) + let forw = cast[int](x.typ) + if (forw and 1) == 1: + # we stored a forwarding pointer, so let's use that: + let z = cast[pointer](forw and not 1) + unsureAsgnRef(cast[PPointer](dest), z) + else: + let realType = x.typ + let z = newObj(realType, realType.base.size) + + unsureAsgnRef(cast[PPointer](dest), z) + x.typ = cast[PNimType](cast[int](z) or 1) + genericDeepCopyAux(z, s2, realType.base) + x.typ = realType + else: + let realType = mt + let z = newObj(realType, realType.base.size) + unsureAsgnRef(cast[PPointer](dest), z) + genericDeepCopyAux(z, s2, realType.base) + of tyPtr: + # no cycle check here, but also not really required + if mt.base.deepCopy != nil: + cast[PPointer](dest)[] = mt.base.deepCopy(cast[PPointer](s)[]) + else: + cast[PPointer](dest)[] = cast[PPointer](s)[] + else: + copyMem(dest, src, mt.size) + +proc genericDeepCopy(dest, src: pointer, mt: PNimType) {.compilerProc.} = + genericDeepCopyAux(dest, src, mt) + +proc genericSeqDeepCopy(dest, src: pointer, mt: PNimType) {.compilerProc.} = + # also invoked for 'string' + var src = src + genericDeepCopy(dest, addr(src), mt) + +proc genericDeepCopyOpenArray(dest, src: pointer, len: int, + mt: PNimType) {.compilerproc.} = + var + d = cast[TAddress](dest) + s = cast[TAddress](src) + for i in 0..len-1: + genericDeepCopy(cast[pointer](d +% i*% mt.base.size), + cast[pointer](s +% i*% mt.base.size), mt.base) diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index e1a5a958f..3c5436afb 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -38,11 +38,11 @@ proc chckRangeF(x, a, b: float): float {.inline, compilerproc, gcsafe.} proc chckNil(p: pointer) {.noinline, compilerproc, gcsafe.} var - framePtr {.rtlThreadVar.}: PFrame - excHandler {.rtlThreadVar.}: PSafePoint + framePtr {.threadvar.}: PFrame + excHandler {.threadvar.}: PSafePoint # list of exception handlers # a global variable for the root of all try blocks - currException {.rtlThreadVar.}: ref E_Base + currException {.threadvar.}: ref E_Base proc popFrame {.compilerRtl, inl.} = framePtr = framePtr.prev @@ -307,7 +307,7 @@ when not defined(noSignalHandler): action("SIGBUS: Illegal storage access. (Attempt to read from nil?)\n") else: block platformSpecificSignal: - when defined(SIGPIPE): + when declared(SIGPIPE): if s == SIGPIPE: action("SIGPIPE: Pipe closed.\n") break platformSpecificSignal @@ -336,7 +336,7 @@ when not defined(noSignalHandler): c_signal(SIGFPE, signalHandler) c_signal(SIGILL, signalHandler) c_signal(SIGBUS, signalHandler) - when defined(SIGPIPE): + when declared(SIGPIPE): c_signal(SIGPIPE, signalHandler) registerSignalHandler() # call it in initialization section diff --git a/lib/system/gc.nim b/lib/system/gc.nim index 3b85fe600..0c1fc7748 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -30,7 +30,7 @@ const # cycles instead of the complex # algorithm -when withRealTime and not defined(getTicks): +when withRealTime and not declared(getTicks): include "system/timers" when defined(memProfiler): proc nimProfile(requestedSize: int) @@ -413,7 +413,7 @@ proc addNewObjToZCT(res: PCell, gch: var TGcHeap) {.inline.} = {.push stackTrace: off, profiler:off.} proc gcInvariant*() = sysAssert(allocInv(gch.region), "injected") - when defined(markForDebug): + when declared(markForDebug): markForDebug(gch) {.pop.} diff --git a/lib/system/gc2.nim b/lib/system/gc2.nim index 31c99a601..132da9885 100644 --- a/lib/system/gc2.nim +++ b/lib/system/gc2.nim @@ -26,7 +26,7 @@ const # this seems to be a good value withRealTime = defined(useRealtimeGC) -when withRealTime and not defined(getTicks): +when withRealTime and not declared(getTicks): include "system/timers" when defined(memProfiler): proc nimProfile(requestedSize: int) diff --git a/lib/system/hti.nim b/lib/system/hti.nim index 64174e60f..ef8f50831 100644 --- a/lib/system/hti.nim +++ b/lib/system/hti.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -when defined(NimString): +when declared(NimString): # we are in system module: {.pragma: codegenType, compilerproc.} else: @@ -86,6 +86,7 @@ type node: ptr TNimNode # valid for tyRecord, tyObject, tyTuple, tyEnum finalizer: pointer # the finalizer for the type marker: proc (p: pointer, op: int) {.nimcall, gcsafe.} # marker proc for GC + deepcopy: proc (p: pointer): pointer {.nimcall, gcsafe.} PNimType = ptr TNimType # node.len may be the ``first`` element of a set diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim index 8766906e3..423f63e2a 100644 --- a/lib/system/jssys.nim +++ b/lib/system/jssys.nim @@ -515,7 +515,7 @@ proc isFatPointer(ti: PNimType): bool = proc nimCopy(x: pointer, ti: PNimType): pointer {.compilerproc.} -proc nimCopyAux(dest, src: Pointer, n: ptr TNimNode) {.compilerproc.} = +proc nimCopyAux(dest, src: pointer, n: ptr TNimNode) {.compilerproc.} = case n.kind of nkNone: sysAssert(false, "nimCopyAux") of nkSlot: @@ -566,7 +566,7 @@ proc nimCopy(x: pointer, ti: PNimType): pointer = else: result = x -proc genericReset(x: Pointer, ti: PNimType): pointer {.compilerproc.} = +proc genericReset(x: pointer, ti: PNimType): pointer {.compilerproc.} = case ti.kind of tyPtr, tyRef, tyVar, tyNil: if not isFatPointer(ti): diff --git a/lib/system/repr.nim b/lib/system/repr.nim index f8f949668..8e1bc5f26 100644 --- a/lib/system/repr.nim +++ b/lib/system/repr.nim @@ -121,7 +121,7 @@ proc reprSet(p: pointer, typ: PNimType): string {.compilerRtl.} = type TReprClosure {.final.} = object # we cannot use a global variable here # as this wouldn't be thread-safe - when defined(TCellSet): + when declared(TCellSet): marked: TCellSet recdepth: int # do not recurse endlessly indent: int # indentation @@ -130,16 +130,16 @@ when not defined(useNimRtl): proc initReprClosure(cl: var TReprClosure) = # Important: cellsets does not lock the heap when doing allocations! We # have to do it here ... - when hasThreadSupport and hasSharedHeap and defined(heapLock): + when hasThreadSupport and hasSharedHeap and declared(heapLock): AcquireSys(HeapLock) - when defined(TCellSet): + when declared(TCellSet): init(cl.marked) cl.recdepth = -1 # default is to display everything! cl.indent = 0 proc deinitReprClosure(cl: var TReprClosure) = - when defined(TCellSet): deinit(cl.marked) - when hasThreadSupport and hasSharedHeap and defined(heapLock): + when declared(TCellSet): deinit(cl.marked) + when hasThreadSupport and hasSharedHeap and declared(heapLock): ReleaseSys(HeapLock) proc reprBreak(result: var string, cl: TReprClosure) = @@ -201,7 +201,7 @@ when not defined(useNimRtl): proc reprRef(result: var string, p: pointer, typ: PNimType, cl: var TReprClosure) = # we know that p is not nil here: - when defined(TCellSet): + when declared(TCellSet): when defined(boehmGC) or defined(nogc): var cell = cast[PCell](p) else: @@ -221,7 +221,7 @@ when not defined(useNimRtl): dec(cl.recdepth) case typ.kind of tySet: reprSetAux(result, p, typ) - of tyArray: reprArray(result, p, typ, cl) + of tyArray, tyArrayConstr: reprArray(result, p, typ, cl) of tyTuple: reprRecord(result, p, typ, cl) of tyObject: var t = cast[ptr PNimType](p)[] @@ -275,7 +275,7 @@ when not defined(useNimRtl): cl: TReprClosure initReprClosure(cl) result = "" - if typ.kind in {tyObject, tyTuple, tyArray, tySet}: + if typ.kind in {tyObject, tyTuple, tyArray, tyArrayConstr, tySet}: reprAux(result, p, typ, cl) else: var p = p diff --git a/lib/system/sysspawn.nim b/lib/system/sysspawn.nim index 95cdba65d..5161104a9 100644 --- a/lib/system/sysspawn.nim +++ b/lib/system/sysspawn.nim @@ -9,7 +9,7 @@ ## Implements Nimrod's 'spawn'. -when not defined(NimString): +when not declared(NimString): {.error: "You must not import this module explicitly".} {.push stackTrace:off.} diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim index b3dc9c14e..bc79bb254 100644 --- a/lib/system/sysstr.nim +++ b/lib/system/sysstr.nim @@ -32,7 +32,7 @@ proc eqStrings(a, b: NimString): bool {.inline, compilerProc.} = return a.len == b.len and c_memcmp(a.data, b.data, a.len * sizeof(char)) == 0'i32 -when defined(allocAtomic): +when declared(allocAtomic): template allocStr(size: expr): expr = cast[NimString](allocAtomic(size)) else: @@ -85,7 +85,7 @@ proc copyStringRC1(src: NimString): NimString {.compilerRtl.} = if src != nil: var s = src.space if s < 8: s = 7 - when defined(newObjRC1): + when declared(newObjRC1): result = cast[NimString](newObjRC1(addr(strDesc), sizeof(TGenericSeq) + s+1)) else: @@ -258,7 +258,7 @@ proc nimFloatToStr(f: float): string {.compilerproc.} = if buf[i] == ',': buf[i] = '.' hasDot = true - elif buf[i] in {'e', 'E', '.'}: + elif buf[i] in {'a'..'z', 'A'..'Z', '.'}: hasDot = true if not hasDot: buf[n] = '.' diff --git a/lib/system/threads.nim b/lib/system/threads.nim index d3b3aa457..4717659e5 100644 --- a/lib/system/threads.nim +++ b/lib/system/threads.nim @@ -39,7 +39,7 @@ ## createThread(thr[i], threadFunc, (i*10, i*10+5)) ## joinThreads(thr) -when not defined(NimString): +when not declared(NimString): {.error: "You must not import this module explicitly".} const @@ -267,7 +267,7 @@ when not defined(boehmgc) and not hasSharedHeap: proc deallocOsPages() template threadProcWrapperBody(closure: expr) {.immediate.} = - when defined(globalsSlot): ThreadVarSetValue(globalsSlot, closure) + when declared(globalsSlot): ThreadVarSetValue(globalsSlot, closure) var t = cast[ptr TThread[TArg]](closure) when useStackMaskHack: var tls: TThreadLocalStorage @@ -275,13 +275,13 @@ template threadProcWrapperBody(closure: expr) {.immediate.} = # init the GC for this thread: setStackBottom(addr(t)) initGC() - when defined(registerThread): + when declared(registerThread): t.stackBottom = addr(t) registerThread(t) when TArg is void: t.dataFn() else: t.dataFn(t.data) - when defined(registerThread): unregisterThread(t) - when defined(deallocOsPages): deallocOsPages() + when declared(registerThread): unregisterThread(t) + when declared(deallocOsPages): deallocOsPages() # Since an unhandled exception terminates the whole process (!), there is # no need for a ``try finally`` here, nor would it be correct: The current # exception is tried to be re-raised by the code-gen after the ``finally``! @@ -332,7 +332,7 @@ when false: discard TerminateThread(t.sys, 1'i32) else: discard pthread_cancel(t.sys) - when defined(registerThread): unregisterThread(addr(t)) + when declared(registerThread): unregisterThread(addr(t)) t.dataFn = nil proc createThread*[TArg](t: var TThread[TArg], diff --git a/lib/system/widestrs.nim b/lib/system/widestrs.nim index e2a5d87e9..cd64ff410 100644 --- a/lib/system/widestrs.nim +++ b/lib/system/widestrs.nim @@ -7,10 +7,10 @@ # distribution, for details about the copyright. # -## Nimrod support for C/C++'s `wide strings`:idx:. This is part of the system -## module! Do not import it directly! +# Nimrod support for C/C++'s `wide strings`:idx:. This is part of the system +# module! Do not import it directly! -when not defined(NimString): +when not declared(NimString): {.error: "You must not import this module explicitly".} type @@ -103,7 +103,7 @@ proc newWideCString*(source: cstring, L: int): WideCString = proc newWideCString*(s: cstring): WideCString = if s.isNil: return nil - when not defined(c_strlen): + when not declared(c_strlen): proc c_strlen(a: cstring): int {. header: "<string.h>", noSideEffect, importc: "strlen".} diff --git a/lib/windows/ole2.nim b/lib/windows/ole2.nim deleted file mode 100644 index ec0ab8f5d..000000000 --- a/lib/windows/ole2.nim +++ /dev/null @@ -1,208 +0,0 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2006 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -import - windows - -const - GUID_NULL*: TGUID = (D1: 0x00000000, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000]) - IID_IUnknown*: TGUID = (D1: 0x00000000, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IClassFactory*: TGUID = (D1: 0x00000001, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IMarshal*: TGUID = (D1: 0x00000003, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IMalloc*: TGUID = (D1: 0x00000002, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IStdMarshalInfo*: TGUID = (D1: 0x00000018, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IExternalConnection*: TGUID = (D1: 0x00000019, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IEnumUnknown*: TGUID = (D1: 0x00000100, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IBindCtx*: TGUID = (D1: 0x0000000E, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IEnumMoniker*: TGUID = (D1: 0x00000102, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IRunnableObject*: TGUID = (D1: 0x00000126, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IRunningObjectTable*: TGUID = (D1: 0x00000010, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IPersist*: TGUID = (D1: 0x0000010C, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IPersistStream*: TGUID = (D1: 0x00000109, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IMoniker*: TGUID = (D1: 0x0000000F, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IEnumString*: TGUID = (D1: 0x00000101, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IStream*: TGUID = (D1: 0x0000000C, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IEnumStatStg*: TGUID = (D1: 0x0000000D, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IStorage*: TGUID = (D1: 0x0000000B, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IPersistFile*: TGUID = (D1: 0x0000010B, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IPersistStorage*: TGUID = (D1: 0x0000010A, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_ILockBytes*: TGUID = (D1: 0x0000000A, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IEnumFormatEtc*: TGUID = (D1: 0x00000103, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IEnumStatData*: TGUID = (D1: 0x00000105, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IRootStorage*: TGUID = (D1: 0x00000012, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IAdviseSink*: TGUID = (D1: 0x0000010F, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IAdviseSink2*: TGUID = (D1: 0x00000125, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IDataObject*: TGUID = (D1: 0x0000010E, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IDataAdviseHolder*: TGUID = (D1: 0x00000110, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IMessageFilter*: TGUID = (D1: 0x00000016, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IRpcChannelBuffer*: TGUID = (D1: 0xD5F56B60, D2: 0x0000593B, - D3: 0x0000101A, D4: [0x000000B5, 0x00000069, 0x00000008, 0x00000000, - 0x0000002B, 0x0000002D, 0x000000BF, 0x0000007A]) - IID_IRpcProxyBuffer*: TGUID = (D1: 0xD5F56A34, D2: 0x0000593B, D3: 0x0000101A, D4: [ - 0x000000B5, 0x00000069, 0x00000008, 0x00000000, 0x0000002B, 0x0000002D, - 0x000000BF, 0x0000007A]) - IID_IRpcStubBuffer*: TGUID = (D1: 0xD5F56AFC, D2: 0x0000593B, D3: 0x0000101A, D4: [ - 0x000000B5, 0x00000069, 0x00000008, 0x00000000, 0x0000002B, 0x0000002D, - 0x000000BF, 0x0000007A]) - IID_IPSFactoryBuffer*: TGUID = (D1: 0xD5F569D0, D2: 0x0000593B, - D3: 0x0000101A, D4: [0x000000B5, 0x00000069, 0x00000008, 0x00000000, - 0x0000002B, 0x0000002D, 0x000000BF, 0x0000007A]) - IID_ICreateTypeInfo*: TGUID = (D1: 0x00020405, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_ICreateTypeLib*: TGUID = (D1: 0x00020406, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IDispatch*: TGUID = (D1: 0x00020400, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IEnumVariant*: TGUID = (D1: 0x00020404, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_ITypeComp*: TGUID = (D1: 0x00020403, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_ITypeInfo*: TGUID = (D1: 0x00020401, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_ITypeLib*: TGUID = (D1: 0x00020402, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IErrorInfo*: TGUID = (D1: 0x1CF2B120, D2: 0x0000547D, D3: 0x0000101B, D4: [ - 0x0000008E, 0x00000065, 0x00000008, 0x00000000, 0x0000002B, 0x0000002B, - 0x000000D1, 0x00000019]) - IID_ICreateErrorInfo*: TGUID = (D1: 0x22F03340, D2: 0x0000547D, - D3: 0x0000101B, D4: [0x0000008E, 0x00000065, 0x00000008, 0x00000000, - 0x0000002B, 0x0000002B, 0x000000D1, 0x00000019]) - IID_ISupportErrorInfo*: TGUID = (D1: 0xDF0B3D60, D2: 0x0000548F, - D3: 0x0000101B, D4: [0x0000008E, 0x00000065, 0x00000008, 0x00000000, - 0x0000002B, 0x0000002B, 0x000000D1, 0x00000019]) - IID_IOleAdviseHolder*: TGUID = (D1: 0x00000111, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IOleCache*: TGUID = (D1: 0x0000011E, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IOleCache2*: TGUID = (D1: 0x00000128, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IOleCacheControl*: TGUID = (D1: 0x00000129, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IParseDisplayName*: TGUID = (D1: 0x0000011A, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IOleContainer*: TGUID = (D1: 0x0000011B, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IOleClientSite*: TGUID = (D1: 0x00000118, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IOleObject*: TGUID = (D1: 0x00000112, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IOleWindow*: TGUID = (D1: 0x00000114, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IOleLink*: TGUID = (D1: 0x0000011D, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IOleItemContainer*: TGUID = (D1: 0x0000011C, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IOleInPlaceUIWindow*: TGUID = (D1: 0x00000115, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IOleInPlaceActiveObject*: TGUID = (D1: 0x00000117, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IOleInPlaceFrame*: TGUID = (D1: 0x00000116, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IOleInPlaceObject*: TGUID = (D1: 0x00000113, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IOleInPlaceSite*: TGUID = (D1: 0x00000119, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IViewObject*: TGUID = (D1: 0x0000010D, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IViewObject2*: TGUID = (D1: 0x00000127, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IDropSource*: TGUID = (D1: 0x00000121, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IDropTarget*: TGUID = (D1: 0x00000122, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IEnumOleVerb*: TGUID = (D1: 0x00000104, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index dcae6ffaf..09696b67f 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -664,6 +664,7 @@ const WSAEDISCON* = 10101 WSAENETRESET* = 10052 WSAETIMEDOUT* = 10060 + ERROR_NETNAME_DELETED* = 64 proc CreateIoCompletionPort*(FileHandle: THANDLE, ExistingCompletionPort: THANDLE, CompletionKey: DWORD, diff --git a/lib/wrappers/sdl/sdl_ttf.nim b/lib/wrappers/sdl/sdl_ttf.nim index f501e31d8..45247df4d 100644 --- a/lib/wrappers/sdl/sdl_ttf.nim +++ b/lib/wrappers/sdl/sdl_ttf.nim @@ -333,11 +333,5 @@ proc VERSION*(X: var sdl.Tversion) = X.patch = PATCHLEVEL -when not (defined(Workaround_RenderText_Solid)): - proc RenderText_Solid*(font: PFont, text: cstring, fg: TColor): PSurface{. - cdecl, importc: "TTF_RenderText_Solid", dynlib: ttfLibName.} -else: - proc RenderText_Solid(font: PFont, text: cstring, fg: TColor): PSurface = - var Black: TColor # initialized to zero - result = RenderText_Shaded(font, text, fg, Black) - +proc RenderText_Solid*(font: PFont, text: cstring, fg: TColor): PSurface{. + cdecl, importc: "TTF_RenderText_Solid", dynlib: ttfLibName.} diff --git a/lib/wrappers/zmq.nim b/lib/wrappers/zmq.nim deleted file mode 100644 index 9826ab813..000000000 --- a/lib/wrappers/zmq.nim +++ /dev/null @@ -1,322 +0,0 @@ -# Nimrod wrapper of 0mq -# Generated by c2nim with modifications and enhancement from Andreas Rumpf -# Original licence follows: - -# -# Copyright (c) 2007-2011 iMatix Corporation -# Copyright (c) 2007-2011 Other contributors as noted in the AUTHORS file -# -# This file is part of 0MQ. -# -# 0MQ is free software; you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# 0MQ is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# - -# Generated from zmq version 2.1.5 - -## Nimrod 0mq wrapper. This file contains the low level C wrappers as well as -## some higher level constructs. The higher level constructs are easily -## recognizable because they are the only ones that have documentation. -## -## Example of a client: -## -## .. code-block:: nimrod -## import zmq -## -## var connection = zmq.open("tcp://localhost:5555", server=false) -## echo("Connecting...") -## for i in 0..10: -## echo("Sending hello...", i) -## send(connection, "Hello") -## var reply = receive(connection) -## echo("Received ...", reply) -## close(connection) -## -## Example of a server: -## -## .. code-block:: nimrod -## -## import zmq -## var connection = zmq.open("tcp://*:5555", server=true) -## while True: -## var request = receive(connection) -## echo("Received: ", request) -## send(connection, "World") -## close(connection) - -{.deadCodeElim: on.} -when defined(windows): - const - zmqdll* = "zmq.dll" -elif defined(macosx): - const - zmqdll* = "libzmq.dylib" -else: - const - zmqdll* = "libzmq.so" - -# A number random enough not to collide with different errno ranges on -# different OSes. The assumption is that error_t is at least 32-bit type. -const - HAUSNUMERO* = 156384712 - # On Windows platform some of the standard POSIX errnos are not defined. - ENOTSUP* = (HAUSNUMERO + 1) - EPROTONOSUPPORT* = (HAUSNUMERO + 2) - ENOBUFS* = (HAUSNUMERO + 3) - ENETDOWN* = (HAUSNUMERO + 4) - EADDRINUSE* = (HAUSNUMERO + 5) - EADDRNOTAVAIL* = (HAUSNUMERO + 6) - ECONNREFUSED* = (HAUSNUMERO + 7) - EINPROGRESS* = (HAUSNUMERO + 8) - # Native 0MQ error codes. - EFSM* = (HAUSNUMERO + 51) - ENOCOMPATPROTO* = (HAUSNUMERO + 52) - ETERM* = (HAUSNUMERO + 53) - EMTHREAD* = (HAUSNUMERO + 54) - # Maximal size of "Very Small Message". VSMs are passed by value - # to avoid excessive memory allocation/deallocation. - # If VMSs larger than 255 bytes are required, type of 'vsm_size' - # field in msg_t structure should be modified accordingly. - MAX_VSM_SIZE* = 30 - - POLLIN* = 1 - POLLOUT* = 2 - POLLERR* = 4 - - STREAMER* = 1 - FORWARDER* = 2 - QUEUE* = 3 - - PAIR* = 0 - PUB* = 1 - SUB* = 2 - REQ* = 3 - REP* = 4 - DEALER* = 5 - ROUTER* = 6 - PULL* = 7 - PUSH* = 8 - XPUB* = 9 - XSUB* = 10 - XREQ* = DEALER # Old alias, remove in 3.x - XREP* = ROUTER # Old alias, remove in 3.x - UPSTREAM* = PULL # Old alias, remove in 3.x - DOWNSTREAM* = PUSH # Old alias, remove in 3.x - -type - # Message types. These integers may be stored in 'content' member of the - # message instead of regular pointer to the data. - TMsgTypes* = enum - DELIMITER = 31, - VSM = 32 - # Message flags. MSG_SHARED is strictly speaking not a message flag - # (it has no equivalent in the wire format), however, making it a flag - # allows us to pack the stucture tighter and thus improve performance. - TMsgFlags* = enum - MSG_MORE = 1, - MSG_SHARED = 128, - MSG_MASK = 129 # Merges all the flags - # A message. Note that 'content' is not a pointer to the raw data. - # Rather it is pointer to zmq::msg_content_t structure - # (see src/msg_content.hpp for its definition). - TMsg*{.pure, final.} = object - content*: pointer - flags*: char - vsm_size*: char - vsm_data*: array[0..MAX_VSM_SIZE - 1, char] - - TFreeFn = proc (data, hint: pointer) {.noconv.} - - TContext {.final, pure.} = object - PContext* = ptr TContext - - # Socket Types - TSocket {.final, pure.} = object - PSocket* = ptr TSocket - - # Socket options. - TSockOptions* = enum - HWM = 1, - SWAP = 3, - AFFINITY = 4, - IDENTITY = 5, - SUBSCRIBE = 6, - UNSUBSCRIBE = 7, - RATE = 8, - RECOVERY_IVL = 9, - MCAST_LOOP = 10, - SNDBUF = 11, - RCVBUF = 12, - RCVMORE = 13, - FD = 14, - EVENTS = 15, - theTYPE = 16, - LINGER = 17, - RECONNECT_IVL = 18, - BACKLOG = 19, - RECOVERY_IVL_MSEC = 20, # opt. recovery time, reconcile in 3.x - RECONNECT_IVL_MAX = 21 - - # Send/recv options. - TSendRecvOptions* = enum - NOBLOCK, SNDMORE - - TPollItem*{.pure, final.} = object - socket*: PSocket - fd*: cint - events*: cshort - revents*: cshort - -# Run-time API version detection - -proc version*(major: var cint, minor: var cint, patch: var cint){.cdecl, - importc: "zmq_version", dynlib: zmqdll.} -#**************************************************************************** -# 0MQ errors. -#**************************************************************************** - -# This function retrieves the errno as it is known to 0MQ library. The goal -# of this function is to make the code 100% portable, including where 0MQ -# compiled with certain CRT library (on Windows) is linked to an -# application that uses different CRT library. - -proc errno*(): cint{.cdecl, importc: "zmq_errno", dynlib: zmqdll.} -# Resolves system errors and 0MQ errors to human-readable string. - -proc strerror*(errnum: cint): cstring {.cdecl, importc: "zmq_strerror", - dynlib: zmqdll.} -#**************************************************************************** -# 0MQ message definition. -#**************************************************************************** - -proc msg_init*(msg: var TMsg): cint{.cdecl, importc: "zmq_msg_init", - dynlib: zmqdll.} -proc msg_init*(msg: var TMsg, size: int): cint{.cdecl, - importc: "zmq_msg_init_size", dynlib: zmqdll.} -proc msg_init*(msg: var TMsg, data: cstring, size: int, - ffn: TFreeFn, hint: pointer): cint{.cdecl, - importc: "zmq_msg_init_data", dynlib: zmqdll.} -proc msg_close*(msg: var TMsg): cint {.cdecl, importc: "zmq_msg_close", - dynlib: zmqdll.} -proc msg_move*(dest, src: var TMsg): cint{.cdecl, - importc: "zmq_msg_move", dynlib: zmqdll.} -proc msg_copy*(dest, src: var TMsg): cint{.cdecl, - importc: "zmq_msg_copy", dynlib: zmqdll.} -proc msg_data*(msg: var TMsg): cstring {.cdecl, importc: "zmq_msg_data", - dynlib: zmqdll.} -proc msg_size*(msg: var TMsg): int {.cdecl, importc: "zmq_msg_size", - dynlib: zmqdll.} - -#**************************************************************************** -# 0MQ infrastructure (a.k.a. context) initialisation & termination. -#**************************************************************************** - -proc init*(io_threads: cint): PContext {.cdecl, importc: "zmq_init", - dynlib: zmqdll.} -proc term*(context: PContext): cint {.cdecl, importc: "zmq_term", - dynlib: zmqdll.} -#**************************************************************************** -# 0MQ socket definition. -#**************************************************************************** - -proc socket*(context: PContext, theType: cint): PSocket {.cdecl, - importc: "zmq_socket", dynlib: zmqdll.} -proc close*(s: PSocket): cint{.cdecl, importc: "zmq_close", dynlib: zmqdll.} -proc setsockopt*(s: PSocket, option: cint, optval: pointer, - optvallen: int): cint {.cdecl, importc: "zmq_setsockopt", - dynlib: zmqdll.} -proc getsockopt*(s: PSocket, option: cint, optval: pointer, - optvallen: ptr int): cint{.cdecl, - importc: "zmq_getsockopt", dynlib: zmqdll.} -proc bindAddr*(s: PSocket, address: cstring): cint{.cdecl, importc: "zmq_bind", - dynlib: zmqdll.} -proc connect*(s: PSocket, address: cstring): cint{.cdecl, - importc: "zmq_connect", dynlib: zmqdll.} -proc send*(s: PSocket, msg: var TMsg, flags: cint): cint{.cdecl, - importc: "zmq_send", dynlib: zmqdll.} -proc recv*(s: PSocket, msg: var TMsg, flags: cint): cint{.cdecl, - importc: "zmq_recv", dynlib: zmqdll.} -#**************************************************************************** -# I/O multiplexing. -#**************************************************************************** - -proc poll*(items: ptr TPollItem, nitems: cint, timeout: int): cint{. - cdecl, importc: "zmq_poll", dynlib: zmqdll.} - -#**************************************************************************** -# Built-in devices -#**************************************************************************** - -proc device*(device: cint, insocket, outsocket: PSocket): cint{. - cdecl, importc: "zmq_device", dynlib: zmqdll.} - -type - EZmq* = object of ESynch ## exception that is raised if something fails - TConnection* {.pure, final.} = object ## a connection - c*: PContext ## the embedded context - s*: PSocket ## the embedded socket - - TConnectionMode* = enum ## connection mode - conPAIR = 0, - conPUB = 1, - conSUB = 2, - conREQ = 3, - conREP = 4, - conDEALER = 5, - conROUTER = 6, - conPULL = 7, - conPUSH = 8, - conXPUB = 9, - conXSUB = 10 - -proc zmqError*() {.noinline, noreturn.} = - ## raises EZmq with error message from `zmq.strerror`. - var e: ref EZmq - new(e) - e.msg = $strerror(errno()) - raise e - -proc open*(address: string, server: bool, mode: TConnectionMode = conDEALER, - numthreads = 4): TConnection = - ## opens a new connection. If `server` is true, it uses `bindAddr` for the - ## underlying socket, otherwise it opens the socket with `connect`. - result.c = init(cint(numthreads)) - if result.c == nil: zmqError() - result.s = socket(result.c, cint(ord(mode))) - if result.s == nil: zmqError() - if server: - if bindAddr(result.s, address) != 0'i32: zmqError() - else: - if connect(result.s, address) != 0'i32: zmqError() - -proc close*(c: TConnection) = - ## closes the connection. - if close(c.s) != 0'i32: zmqError() - if term(c.c) != 0'i32: zmqError() - -proc send*(c: TConnection, msg: string) = - ## sends a message over the connection. - var m: TMsg - if msg_init(m, msg.len) != 0'i32: zmqError() - copyMem(msg_data(m), cstring(msg), msg.len) - if send(c.s, m, 0'i32) != 0'i32: zmqError() - discard msg_close(m) - -proc receive*(c: TConnection): string = - ## receives a message from a connection. - var m: TMsg - if msg_init(m) != 0'i32: zmqError() - if recv(c.s, m, 0'i32) != 0'i32: zmqError() - result = newString(msg_size(m)) - copyMem(addr(result[0]), msg_data(m), result.len) - discard msg_close(m) |