diff options
Diffstat (limited to 'lib/pure')
-rw-r--r-- | lib/pure/asyncio2.nim | 639 | ||||
-rw-r--r-- | lib/pure/dynlib.nim | 10 | ||||
-rw-r--r-- | lib/pure/net.nim | 17 | ||||
-rw-r--r-- | lib/pure/pegs.nim | 8 | ||||
-rw-r--r-- | lib/pure/selectors.nim | 355 | ||||
-rw-r--r-- | lib/pure/sockets2.nim | 16 | ||||
-rw-r--r-- | lib/pure/times.nim | 242 |
7 files changed, 886 insertions, 401 deletions
diff --git a/lib/pure/asyncio2.nim b/lib/pure/asyncio2.nim index cdb4a6f49..12d4cb5a3 100644 --- a/lib/pure/asyncio2.nim +++ b/lib/pure/asyncio2.nim @@ -7,9 +7,7 @@ # distribution, for details about the copyright. # -import os, oids, tables, strutils - -import winlean +import os, oids, tables, strutils, macros import sockets2, net @@ -23,14 +21,13 @@ import sockets2, net # -- Futures type - PFutureVoid* = ref object of PObject - cbVoid: proc () {.closure.} + PFutureBase* = ref object of PObject + cb: proc () {.closure.} finished: bool - PFuture*[T] = ref object of PFutureVoid + PFuture*[T] = ref object of PFutureBase value: T error: ref EBase - cb: proc (future: PFuture[T]) {.closure.} proc newFuture*[T](): PFuture[T] = ## Creates a new future. @@ -39,42 +36,38 @@ proc newFuture*[T](): PFuture[T] = proc complete*[T](future: PFuture[T], val: T) = ## Completes ``future`` with value ``val``. - assert(not future.finished) + assert(not future.finished, "Future already finished, cannot finish twice.") assert(future.error == nil) future.value = val future.finished = true if future.cb != nil: - future.cb(future) - if future.cbVoid != nil: - future.cbVoid() + future.cb() proc fail*[T](future: PFuture[T], error: ref EBase) = ## Completes ``future`` with ``error``. - assert(not future.finished) + assert(not future.finished, "Future already finished, cannot finish twice.") future.finished = true future.error = error if future.cb != nil: - future.cb(future) + future.cb() -proc `callback=`*[T](future: PFuture[T], - cb: proc (future: PFuture[T]) {.closure.}) = +proc `callback=`*(future: PFutureBase, cb: proc () {.closure.}) = ## Sets the callback proc to be called when the future completes. ## ## If future has already completed then ``cb`` will be called immediately. + ## + ## **Note**: You most likely want the other ``callback`` setter which + ## passes ``future`` as a param to the callback. future.cb = cb if future.finished: - future.cb(future) + future.cb() -proc `callbackVoid=`*(future: PFutureVoid, cb: proc () {.closure.}) = - ## Sets the **void** callback proc to be called when the future completes. +proc `callback=`*[T](future: PFuture[T], + cb: proc (future: PFuture[T]) {.closure.}) = + ## Sets the callback proc to be called when the future completes. ## ## If future has already completed then ``cb`` will be called immediately. - ## - ## **Note**: This is used for the ``await`` functionality, you most likely - ## want to use ``callback``. - future.cbVoid = cb - if future.finished: - future.cbVoid() + future.callback = proc () = cb(future) proc read*[T](future: PFuture[T]): T = ## Retrieves the value of ``future``. Future must be finished otherwise @@ -98,16 +91,21 @@ proc failed*[T](future: PFuture[T]): bool = ## Determines whether ``future`` completed with an error. future.error != nil -when defined(windows): +# TODO: Get rid of register. Do it implicitly. + +when defined(windows) or defined(nimdoc): + import winlean type TCompletionKey = dword TCompletionData* = object sock: TSocketHandle - cb: proc (sock: TSocketHandle, errcode: TOSErrorCode) {.closure.} + cb: proc (sock: TSocketHandle, bytesTransferred: DWORD, + errcode: TOSErrorCode) {.closure.} PDispatcher* = ref object ioPort: THandle + hasHandles: bool TCustomOverlapped = object Internal*: DWORD @@ -129,9 +127,13 @@ when defined(windows): if CreateIOCompletionPort(sock.THandle, p.ioPort, cast[TCompletionKey](sock), 1) == 0: OSError(OSLastError()) + p.hasHandles = true proc poll*(p: PDispatcher, timeout = 500) = ## Waits for completion events and processes them. + if not p.hasHandles: + raise newException(EInvalidValue, "No handles registered in dispatcher.") + let llTimeout = if timeout == -1: winlean.INFINITE else: timeout.int32 @@ -145,16 +147,19 @@ when defined(windows): # TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html var customOverlapped = cast[PCustomOverlapped](lpOverlapped) if res: + # This is useful for ensuring the reliability of the overlapped struct. assert customOverlapped.data.sock == lpCompletionKey.TSocketHandle - customOverlapped.data.cb(customOverlapped.data.sock, TOSErrorCode(-1)) + customOverlapped.data.cb(customOverlapped.data.sock, + lpNumberOfBytesTransferred, TOSErrorCode(-1)) dealloc(customOverlapped) else: let errCode = OSLastError() if lpOverlapped != nil: assert customOverlapped.data.sock == lpCompletionKey.TSocketHandle + customOverlapped.data.cb(customOverlapped.data.sock, + lpNumberOfBytesTransferred, errCode) dealloc(customOverlapped) - customOverlapped.data.cb(customOverlapped.data.sock, errCode) else: if errCode.int32 == WAIT_TIMEOUT: # Timed out @@ -252,11 +257,12 @@ when defined(windows): # http://blogs.msdn.com/b/oldnewthing/archive/2011/02/02/10123392.aspx var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped))) ol.data = TCompletionData(sock: socket, cb: - proc (sock: TSocketHandle, errcode: TOSErrorCode) = - if errcode == TOSErrorCode(-1): - retFuture.complete(0) - else: - retFuture.fail(newException(EOS, osErrorMsg(errcode))) + proc (sock: TSocketHandle, bytesCount: DWord, errcode: TOSErrorCode) = + if not retFuture.finished: + if errcode == TOSErrorCode(-1): + retFuture.complete(0) + else: + retFuture.fail(newException(EOS, osErrorMsg(errcode))) ) var ret = connectEx(socket, it.ai_addr, sizeof(TSockAddrIn).cint, @@ -265,7 +271,9 @@ when defined(windows): # Request to connect completed immediately. success = true retFuture.complete(0) - dealloc(ol) + # We don't deallocate ``ol`` here because even though this completed + # immediately poll will still be notified about its completion and it will + # free ``ol``. break else: lastError = OSLastError() @@ -283,9 +291,13 @@ when defined(windows): retFuture.fail(newException(EOS, osErrorMsg(lastError))) return retFuture - proc recv*(p: PDispatcher, socket: TSocketHandle, size: int): PFuture[string] = + proc recv*(p: PDispatcher, socket: TSocketHandle, size: int, + flags: int = 0): PFuture[string] = ## Reads ``size`` bytes from ``socket``. Returned future will complete once - ## all of the requested data is read. + ## all of the requested data is read. If socket is disconnected during the + ## recv operation then the future may complete with only a part of the + ## requested data read. If socket is disconnected and no data is available + ## to be read then the future will complete with a value of ``""``. var retFuture = newFuture[string]() @@ -293,31 +305,50 @@ when defined(windows): dataBuf.buf = newString(size) dataBuf.len = size - var bytesReceived, flags: DWord + var bytesReceived: DWord + var flagsio = flags.dword var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped))) ol.data = TCompletionData(sock: socket, cb: - proc (sock: TSocketHandle, errcode: TOSErrorCode) = - if errcode == TOSErrorCode(-1): - var data = newString(size) - copyMem(addr data[0], addr dataBuf.buf[0], size) - retFuture.complete($data) - else: - retFuture.fail(newException(EOS, osErrorMsg(errcode))) + proc (sock: TSocketHandle, bytesCount: DWord, errcode: TOSErrorCode) = + if not retFuture.finished: + if errcode == TOSErrorCode(-1): + if bytesCount == 0 and dataBuf.buf[0] == '\0': + retFuture.complete("") + else: + var data = newString(size) + copyMem(addr data[0], addr dataBuf.buf[0], size) + retFuture.complete($data) + else: + retFuture.fail(newException(EOS, osErrorMsg(errcode))) ) - + let ret = WSARecv(socket, addr dataBuf, 1, addr bytesReceived, - addr flags, cast[POverlapped](ol), nil) + addr flagsio, cast[POverlapped](ol), nil) if ret == -1: let err = OSLastError() if err.int32 != ERROR_IO_PENDING: retFuture.fail(newException(EOS, osErrorMsg(err))) dealloc(ol) + elif ret == 0 and bytesReceived == 0 and dataBuf.buf[0] == '\0': + # We have to ensure that the buffer is empty because WSARecv will tell + # us immediatelly when it was disconnected, even when there is still + # data in the buffer. + # We want to give the user as much data as we can. So we only return + # the empty string (which signals a disconnection) when there is + # nothing left to read. + retFuture.complete("") + # TODO: "For message-oriented sockets, where a zero byte message is often + # allowable, a failure with an error code of WSAEDISCON is used to + # indicate graceful closure." + # ~ http://msdn.microsoft.com/en-us/library/ms741688%28v=vs.85%29.aspx else: # Request to read completed immediately. var data = newString(size) copyMem(addr data[0], addr dataBuf.buf[0], size) retFuture.complete($data) - dealloc(ol) + # We don't deallocate ``ol`` here because even though this completed + # immediately poll will still be notified about its completion and it will + # free ``ol``. return retFuture proc send*(p: PDispatcher, socket: TSocketHandle, data: string): PFuture[int] = @@ -332,11 +363,12 @@ when defined(windows): var bytesReceived, flags: DWord var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped))) ol.data = TCompletionData(sock: socket, cb: - proc (sock: TSocketHandle, errcode: TOSErrorCode) = - if errcode == TOSErrorCode(-1): - retFuture.complete(0) - else: - retFuture.fail(newException(EOS, osErrorMsg(errcode))) + proc (sock: TSocketHandle, bytesCount: DWord, errcode: TOSErrorCode) = + if not retFuture.finished: + if errcode == TOSErrorCode(-1): + retFuture.complete(0) + else: + retFuture.fail(newException(EOS, osErrorMsg(errcode))) ) let ret = WSASend(socket, addr dataBuf, 1, addr bytesReceived, @@ -348,7 +380,9 @@ when defined(windows): dealloc(ol) else: retFuture.complete(0) - dealloc(ol) + # We don't deallocate ``ol`` here because even though this completed + # immediately poll will still be notified about its completion and it will + # free ``ol``. return retFuture proc acceptAddr*(p: PDispatcher, socket: TSocketHandle): @@ -390,11 +424,12 @@ when defined(windows): var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped))) ol.data = TCompletionData(sock: socket, cb: - proc (sock: TSocketHandle, errcode: TOSErrorCode) = - if errcode == TOSErrorCode(-1): - completeAccept() - else: - retFuture.fail(newException(EOS, osErrorMsg(errcode))) + proc (sock: TSocketHandle, bytesCount: DWord, errcode: TOSErrorCode) = + if not retFuture.finished: + if errcode == TOSErrorCode(-1): + completeAccept() + else: + retFuture.fail(newException(EOS, osErrorMsg(errcode))) ) # http://msdn.microsoft.com/en-us/library/windows/desktop/ms737524%28v=vs.85%29.aspx @@ -411,70 +446,472 @@ when defined(windows): dealloc(ol) else: completeAccept() - dealloc(ol) + # We don't deallocate ``ol`` here because even though this completed + # immediately poll will still be notified about its completion and it will + # free ``ol``. return retFuture - proc accept*(p: PDispatcher, socket: TSocketHandle): PFuture[TSocketHandle] = - ## 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[TSocketHandle]() - var fut = p.acceptAddr(socket) - fut.callback = - proc (future: PFuture[tuple[address: string, client: TSocketHandle]]) = - assert future.finished - if future.failed: - retFut.fail(future.error) - else: - retFut.complete(future.read.client) - return retFut - initAll() else: - # TODO: Selectors. + import selectors + from posix import EINTR, EAGAIN, EINPROGRESS, EWOULDBLOCK, MSG_PEEK + type + TCallback = proc (sock: TSocketHandle): bool {.closure.} + + PData* = ref object of PObject + sock: TSocketHandle + readCBs: seq[TCallback] + writeCBs: seq[TCallback] + PDispatcher* = ref object + selector: PSelector + + proc newDispatcher*(): PDispatcher = + new result + result.selector = newSelector() + + proc update(p: PDispatcher, sock: TSocketHandle, events: set[TEvent]) = + assert sock in p.selector + echo("Update: ", events) + if events == {}: + discard p.selector.unregister(sock) + else: + discard p.selector.update(sock, events) + + proc addRead(p: PDispatcher, sock: TSocketHandle, cb: TCallback) = + if sock notin p.selector: + var data = PData(sock: sock, readCBs: @[cb], writeCBs: @[]) + p.selector.register(sock, {EvRead}, data.PObject) + else: + p.selector[sock].data.PData.readCBs.add(cb) + p.update(sock, p.selector[sock].events + {EvRead}) + + proc addWrite(p: PDispatcher, sock: TSocketHandle, cb: TCallback) = + if sock notin p.selector: + var data = PData(sock: sock, readCBs: @[], writeCBs: @[cb]) + p.selector.register(sock, {EvWrite}, data.PObject) + else: + p.selector[sock].data.PData.writeCBs.add(cb) + p.update(sock, p.selector[sock].events + {EvWrite}) + + proc poll*(p: PDispatcher, timeout = 500) = + for info in p.selector.select(timeout): + let data = PData(info.key.data) + assert data.sock == info.key.fd + echo("R: ", data.readCBs.len, " W: ", data.writeCBs.len, ". ", info.events) + + if EvRead in info.events: + var newReadCBs: seq[TCallback] = @[] + for cb in data.readCBs: + if not cb(data.sock): + # Callback wants to be called again. + newReadCBs.add(cb) + data.readCBs = newReadCBs + + if EvWrite in info.events: + var newWriteCBs: seq[TCallback] = @[] + for cb in data.writeCBs: + if not cb(data.sock): + # Callback wants to be called again. + newWriteCBs.add(cb) + data.writeCBs = newWriteCBs + + var newEvents: set[TEvent] + if data.readCBs.len != 0: newEvents = {EvRead} + if data.writeCBs.len != 0: newEvents = newEvents + {EvWrite} + p.update(data.sock, newEvents) + + proc connect*(p: PDispatcher, socket: TSocketHandle, address: string, port: TPort, + af = AF_INET): PFuture[int] = + var retFuture = newFuture[int]() + + proc cb(sock: TSocketHandle): bool = + # We have connected. + retFuture.complete(0) + return true + + var aiList = getAddrInfo(address, port, af) + var success = false + var lastError: TOSErrorCode + var it = aiList + while it != nil: + var ret = connect(socket, it.ai_addr, it.ai_addrlen.TSocklen) + if ret == 0: + # Request to connect completed immediately. + success = true + retFuture.complete(0) + break + else: + lastError = osLastError() + if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS: + success = true + addWrite(p, socket, cb) + break + else: + success = false + it = it.ai_next + + dealloc(aiList) + if not success: + retFuture.fail(newException(EOS, osErrorMsg(lastError))) + return retFuture + + proc recv*(p: PDispatcher, socket: TSocketHandle, size: int, + flags: int = 0): PFuture[string] = + var retFuture = newFuture[string]() + + var readBuffer = newString(size) + var sizeRead = 0 + + proc cb(sock: TSocketHandle): bool = + result = true + let netSize = size - sizeRead + let res = recv(sock, addr readBuffer[sizeRead], netSize, flags.cint) + if res < 0: + let lastError = osLastError() + if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}: + retFuture.fail(newException(EOS, osErrorMsg(lastError))) + else: + result = false # We still want this callback to be called. + elif res == 0: + # Disconnected + if sizeRead == 0: + retFuture.complete("") + else: + readBuffer.setLen(sizeRead) + retFuture.complete(readBuffer) + else: + sizeRead.inc(res) + if res != netSize: + result = false # We want to read all the data requested. + else: + retFuture.complete(readBuffer) + + addRead(p, socket, cb) + return retFuture + + proc send*(p: PDispatcher, socket: TSocketHandle, data: string): PFuture[int] = + var retFuture = newFuture[int]() + + var written = 0 + + proc cb(sock: TSocketHandle): bool = + result = true + let netSize = data.len-written + var d = data.cstring + let res = send(sock, addr d[written], netSize, 0.cint) + if res < 0: + let lastError = osLastError() + if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}: + retFuture.fail(newException(EOS, osErrorMsg(lastError))) + else: + result = false # We still want this callback to be called. + else: + written.inc(res) + if res != netSize: + result = false # We still have data to send. + else: + retFuture.complete(0) + addWrite(p, socket, cb) + return retFuture + + + proc acceptAddr*(p: PDispatcher, socket: TSocketHandle): + PFuture[tuple[address: string, client: TSocketHandle]] = + var retFuture = newFuture[tuple[address: string, client: TSocketHandle]]() + proc cb(sock: TSocketHandle): bool = + result = true + var sockAddress: Tsockaddr_in + var addrLen = sizeof(sockAddress).TSocklen + var client = accept(sock, cast[ptr TSockAddr](addr(sockAddress)), + addr(addrLen)) + if client == osInvalidSocket: + let lastError = osLastError() + assert lastError.int32 notin {EWOULDBLOCK, EAGAIN} + if lastError.int32 == EINTR: + return false + else: + retFuture.fail(newException(EOS, osErrorMsg(lastError))) + else: + retFuture.complete(($inet_ntoa(sockAddress.sin_addr), client)) + addRead(p, socket, cb) + return retFuture + +proc accept*(p: PDispatcher, socket: TSocketHandle): PFuture[TSocketHandle] = + ## 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[TSocketHandle]() + var fut = p.acceptAddr(socket) + fut.callback = + proc (future: PFuture[tuple[address: string, client: TSocketHandle]]) = + assert future.finished + if future.failed: + retFut.fail(future.error) + else: + retFut.complete(future.read.client) + return retFut + +# -- Await Macro + +template createCb*(cbName, varNameIterSym, retFutureSym: expr): stmt {.immediate, dirty.} = + proc cbName {.closure.} = + if not varNameIterSym.finished: + var next = varNameIterSym() + if next == nil: + assert retFutureSym.finished, "Async procedure's return Future was not finished." + else: + next.callback = cbName + +template createVar(futSymName: string, asyncProc: PNimrodNode, + valueReceiver: expr) {.immediate, dirty.} = + # TODO: Used template here due to bug #926 + result = newNimNode(nnkStmtList) + var futSym = newIdentNode(futSymName) #genSym(nskVar, "future") + result.add newVarStmt(futSym, asyncProc) # -> var future<x> = y + result.add newNimNode(nnkYieldStmt).add(futSym) # -> yield future<x> + valueReceiver = newDotExpr(futSym, newIdentNode("read")) # -> future<x>.read + +proc processBody(node, retFutureSym: PNimrodNode): PNimrodNode {.compileTime.} = + result = node + case node.kind + of nnkReturnStmt: + result = newNimNode(nnkStmtList) + result.add newCall(newIdentNode("complete"), retFutureSym, + if node[0].kind == nnkEmpty: newIdentNode("result") else: node[0]) + result.add newNimNode(nnkYieldStmt).add(newNilLit()) + of nnkCommand: + if node[0].ident == !"await": + case node[1].kind + of nnkIdent: + # await x + result = newNimNode(nnkYieldStmt).add(node[1]) # -> yield x + of nnkCall: + # await foo(p, x) + var futureValue: PNimrodNode + createVar("future" & $node[1][0].toStrLit, node[1], futureValue) + result.add futureValue + 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": + # foo await x + var newCommand = node + createVar("future" & $node[0].ident, node[1][0], newCommand[1]) + result.add newCommand + + of nnkVarSection, nnkLetSection: + case node[0][2].kind + of nnkCommand: + if node[0][2][0].ident == !"await": + # var x = await y + var newVarSection = node # TODO: Should this use copyNimNode? + createVar("future" & $node[0][0].ident, node[0][2][1], + newVarSection[0][2]) + result.add newVarSection + else: discard + of nnkAsgn: + case node[1].kind + of nnkCommand: + if node[1][0].ident == !"await": + # x = await y + var newAsgn = node + createVar("future" & $node[0].ident, node[1][1], newAsgn[1]) + result.add newAsgn + else: discard + of nnkDiscardStmt: + # discard await x + if node[0][0].ident == !"await": + var dummy = newNimNode(nnkStmtList) + createVar("futureDiscard_" & $toStrLit(node[0][1]), node[0][1], dummy) + else: discard + + for i in 0 .. <result.len: + result[i] = processBody(result[i], retFutureSym) + #echo(treeRepr(result)) + +proc getName(node: PNimrodNode): string {.compileTime.} = + case node.kind + of nnkPostfix: + return $node[1].ident + of nnkIdent: + return $node.ident + else: + assert false + +macro async*(prc: stmt): stmt {.immediate.} = + expectKind(prc, nnkProcDef) + + hint("Processing " & prc[0].getName & " as an async proc.") + + # Verify that the return type is a PFuture[T] + if prc[3][0].kind == nnkIdent: + error("Expected return type of 'PFuture' got '" & $prc[3][0] & "'") + elif prc[3][0].kind == nnkBracketExpr: + if $prc[3][0][0] != "PFuture": + error("Expected return type of 'PFuture' got '" & $prc[3][0][0] & "'") + + # TODO: Why can't I use genSym? I get illegal capture errors for Syms. + # TODO: It seems genSym is broken. Change all usages back to genSym when fixed + + var outerProcBody = newNimNode(nnkStmtList) + + # -> var retFuture = newFuture[T]() + var retFutureSym = newIdentNode("retFuture") #genSym(nskVar, "retFuture") + outerProcBody.add( + newVarStmt(retFutureSym, + newCall( + newNimNode(nnkBracketExpr).add( + newIdentNode("newFuture"), + prc[3][0][1])))) # Get type from return type of this proc. + + # -> iterator nameIter(): PFutureBase {.closure.} = + # -> var result: T + # -> <proc_body> + # -> complete(retFuture, result) + var iteratorNameSym = newIdentNode($prc[0].getName & "Iter") #genSym(nskIterator, $prc[0].ident & "Iter") + var procBody = prc[6].processBody(retFutureSym) + procBody.insert(0, newNimNode(nnkVarSection).add( + newIdentDefs(newIdentNode("result"), prc[3][0][1]))) # -> var result: T + procBody.add( + newCall(newIdentNode("complete"), + retFutureSym, newIdentNode("result"))) # -> complete(retFuture, result) + + var closureIterator = newProc(iteratorNameSym, [newIdentNode("PFutureBase")], + procBody, nnkIteratorDef) + closureIterator[4] = newNimNode(nnkPragma).add(newIdentNode("closure")) + outerProcBody.add(closureIterator) + + # -> var nameIterVar = nameIter + # -> var first = nameIterVar() + var varNameIterSym = newIdentNode($prc[0].getName & "IterVar") #genSym(nskVar, $prc[0].ident & "IterVar") + var varNameIter = newVarStmt(varNameIterSym, iteratorNameSym) + outerProcBody.add varNameIter + var varFirstSym = genSym(nskVar, "first") + var varFirst = newVarStmt(varFirstSym, newCall(varNameIterSym)) + outerProcBody.add varFirst + + # -> createCb(cb, nameIter, retFuture) + var cbName = newIdentNode("cb") + var procCb = newCall("createCb", cbName, varNameIterSym, retFutureSym) + outerProcBody.add procCb + + # -> first.callback = cb + outerProcBody.add newAssignment( + newDotExpr(varFirstSym, newIdentNode("callback")), + cbName) + + # -> return retFuture + outerProcBody.add newNimNode(nnkReturnStmt).add(retFutureSym) + + result = prc + + # Remove the 'async' pragma. + for i in 0 .. <result[4].len: + if result[4][i].ident == !"async": + result[4].del(i) + + result[6] = outerProcBody + + echo(toStrLit(result)) + +proc recvLine*(p: PDispatcher, socket: TSocketHandle): PFuture[string] {.async.} = + ## Reads a line of data from ``socket``. Returned future will complete once + ## a full line is read or an error occurs. + ## + ## If a full line is read ``\r\L`` is not + ## added to ``line``, however if solely ``\r\L`` is read then ``line`` + ## will be set to it. + ## + ## If the socket is disconnected, ``line`` will be set to ``""``. + + template addNLIfEmpty(): stmt = + if result.len == 0: + result.add("\c\L") + + result = "" + var c = "" + while true: + c = await p.recv(socket, 1) + if c.len == 0: + return + if c == "\r": + c = await p.recv(socket, 1, MSG_PEEK) + if c.len > 0 and c == "\L": + discard await p.recv(socket, 1) + addNLIfEmpty() + return + elif c == "\L": + addNLIfEmpty() + return + add(result.string, c) when isMainModule: var p = newDispatcher() var sock = socket() - #sock.setBlocking false - p.register(sock) - - when true: - - var f = p.connect(sock, "irc.freenode.org", TPort(6667)) - f.callback = - proc (future: PFuture[int]) = - echo("Connected in future!") - echo(future.read) - for i in 0 .. 50: - var recvF = p.recv(sock, 10) - recvF.callback = - proc (future: PFuture[string]) = - echo("Read: ", future.read) + sock.setBlocking false + + + when false: + # Await tests + proc main(p: PDispatcher): PFuture[int] {.async.} = + discard await p.connect(sock, "irc.freenode.net", TPort(6667)) + while true: + var line = await p.recvLine(sock) + echo("Line is: ", line.repr) + if line == "": + echo "Disconnected" + break + + proc peekTest(p: PDispatcher): PFuture[int] {.async.} = + discard await p.connect(sock, "localhost", TPort(6667)) + while true: + var line = await p.recv(sock, 1, MSG_PEEK) + var line2 = await p.recv(sock, 1) + echo(line.repr) + echo(line2.repr) + echo("---") + if line2 == "": break + sleep(500) + + var f = main(p) + else: + when false: - sock.bindAddr(TPort(6667)) - sock.listen() - proc onAccept(future: PFuture[TSocketHandle]) = - echo "Accepted" - var t = p.send(future.read, "test\c\L") - t.callback = + var f = p.connect(sock, "irc.freenode.org", TPort(6667)) + f.callback = proc (future: PFuture[int]) = + echo("Connected in future!") echo(future.read) - + for i in 0 .. 50: + var recvF = p.recv(sock, 10) + recvF.callback = + proc (future: PFuture[string]) = + echo("Read ", future.read.len, ": ", future.read.repr) + + else: + + sock.bindAddr(TPort(6667)) + sock.listen() + proc onAccept(future: PFuture[TSocketHandle]) = + echo "Accepted" + var t = p.send(future.read, "test\c\L") + t.callback = + proc (future: PFuture[int]) = + echo(future.read) + + var f = p.accept(sock) + f.callback = onAccept + var f = p.accept(sock) f.callback = onAccept - - var f = p.accept(sock) - f.callback = onAccept while true: p.poll() - echo "polled" diff --git a/lib/pure/dynlib.nim b/lib/pure/dynlib.nim index 3ed00fdb2..889912052 100644 --- a/lib/pure/dynlib.nim +++ b/lib/pure/dynlib.nim @@ -14,7 +14,7 @@ type TLibHandle* = pointer ## a handle to a dynamically loaded library -proc loadLib*(path: string): TLibHandle +proc loadLib*(path: string, global_symbols=false): TLibHandle ## loads a library from `path`. Returns nil if the library could not ## be loaded. @@ -53,6 +53,7 @@ when defined(posix): # var RTLD_NOW {.importc: "RTLD_NOW", header: "<dlfcn.h>".}: int + RTLD_GLOBAL {.importc: "RTLD_GLOBAL", header: "<dlfcn.h>".}: int proc dlclose(lib: TLibHandle) {.importc, header: "<dlfcn.h>".} proc dlopen(path: CString, mode: int): TLibHandle {. @@ -60,7 +61,10 @@ when defined(posix): proc dlsym(lib: TLibHandle, name: cstring): pointer {. importc, header: "<dlfcn.h>".} - proc loadLib(path: string): TLibHandle = return dlopen(path, RTLD_NOW) + proc loadLib(path: string, global_symbols=false): TLibHandle = + var flags = RTLD_NOW + if global_symbols: flags = flags or RTLD_GLOBAL + return dlopen(path, flags) proc loadLib(): TLibHandle = return dlopen(nil, RTLD_NOW) proc unloadLib(lib: TLibHandle) = dlclose(lib) proc symAddr(lib: TLibHandle, name: cstring): pointer = @@ -81,7 +85,7 @@ elif defined(windows) or defined(dos): proc getProcAddress(lib: THINSTANCE, name: cstring): pointer {. importc: "GetProcAddress", header: "<windows.h>", stdcall.} - proc loadLib(path: string): TLibHandle = + proc loadLib(path: string, global_symbols=false): TLibHandle = result = cast[TLibHandle](winLoadLibrary(path)) proc loadLib(): TLibHandle = result = cast[TLibHandle](winLoadLibrary(nil)) diff --git a/lib/pure/net.nim b/lib/pure/net.nim index bdcae677e..0ec007009 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -37,4 +37,19 @@ proc bindAddr*(socket: TSocket, port = TPort(0), address = "") {. if bindAddr(socket, aiList.ai_addr, aiList.ai_addrlen.TSocklen) < 0'i32: dealloc(aiList) osError(osLastError()) - dealloc(aiList) \ No newline at end of file + dealloc(aiList) + +proc setBlocking*(s: TSocket, blocking: bool) {.tags: [].} = + ## Sets blocking mode on socket + when defined(Windows): + var mode = clong(ord(not blocking)) # 1 for non-blocking, 0 for blocking + if ioctlsocket(s, FIONBIO, addr(mode)) == -1: + osError(osLastError()) + else: # BSD sockets + var x: int = fcntl(s, F_GETFL, 0) + if x == -1: + osError(osLastError()) + else: + var mode = if blocking: x and not O_NONBLOCK else: x or O_NONBLOCK + if fcntl(s, F_SETFL, mode) == -1: + osError(osLastError()) \ No newline at end of file diff --git a/lib/pure/pegs.nim b/lib/pure/pegs.nim index 70b617393..68b1ab223 100644 --- a/lib/pure/pegs.nim +++ b/lib/pure/pegs.nim @@ -836,9 +836,11 @@ iterator findAll*(s: string, pattern: TPeg, start = 0): string = while i < s.len: c.ml = 0 var L = rawMatch(s, pattern, i, c) - if L < 0: break - yield substr(s, i, i+L-1) - inc(i, L) + if L < 0: + inc(i, 1) + else: + yield substr(s, i, i+L-1) + inc(i, L) proc findAll*(s: string, pattern: TPeg, start = 0): seq[string] {. nosideEffect, rtl, extern: "npegs$1".} = diff --git a/lib/pure/selectors.nim b/lib/pure/selectors.nim index 83c158da1..6482a01a6 100644 --- a/lib/pure/selectors.nim +++ b/lib/pure/selectors.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2013 Dominik Picheta +# (c) Copyright 2014 Dominik Picheta # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -9,212 +9,211 @@ # TODO: Docs. -import tables, os, unsigned -when defined(windows): - import winlean -else: - import posix +import tables, os, unsigned, hashes + +when defined(linux): import posix, epoll +elif defined(windows): import winlean + +proc hash*(x: TSocketHandle): THash {.borrow.} type TEvent* = enum EvRead, EvWrite - TSelectorKey* = object - fd: cint - events: set[TEvent] - data: PObject - - TReadyInfo* = tuple[key: TSelectorKey, events: set[TEvent]] - - PSelector* = ref object of PObject ## Selector interface. - fds*: TTable[cint, TSelectorKey] - registerImpl*: proc (s: PSelector, fd: cint, events: set[TEvent], - data: PObject): TSelectorKey {.nimcall, tags: [FWriteIO].} - unregisterImpl*: proc (s: PSelector, fd: cint): TSelectorKey {.nimcall, tags: [FWriteIO].} - selectImpl*: proc (s: PSelector, timeout: int): seq[TReadyInfo] {.nimcall, tags: [FReadIO].} - closeImpl*: proc (s: PSelector) {.nimcall.} - -template initSelector(r: expr) = - new r - r.fds = initTable[cint, TSelectorKey]() - -proc register*(s: PSelector, fd: cint, events: set[TEvent], data: PObject): - TSelectorKey = - if not s.registerImpl.isNil: result = s.registerImpl(s, fd, events, data) - -proc unregister*(s: PSelector, fd: cint): TSelectorKey = - ## - ## **Note:** For the ``epoll`` implementation the resulting ``TSelectorKey`` - ## will only have the ``fd`` field set. This is an optimisation and may - ## change in the future if a viable use case is presented. - if not s.unregisterImpl.isNil: result = s.unregisterImpl(s, fd) - -proc select*(s: PSelector, timeout = 500): seq[TReadyInfo] = - ## - ## The ``events`` field of the returned ``key`` contains the original events - ## for which the ``fd`` was bound. This is contrary to the ``events`` field - ## of the ``TReadyInfo`` tuple which determines which events are ready - ## on the ``fd``. - - if not s.selectImpl.isNil: result = s.selectImpl(s, timeout) + PSelectorKey* = ref object + fd*: TSocketHandle + events*: set[TEvent] ## The events which ``fd`` listens for. + data*: PObject ## User object. -proc close*(s: PSelector) = - if not s.closeImpl.isNil: s.closeImpl(s) + TReadyInfo* = tuple[key: PSelectorKey, events: set[TEvent]] -# ---- Select() ---------------------------------------------------------------- - -type - PSelectSelector* = ref object of PSelector ## Implementation of select() - -proc ssRegister(s: PSelector, fd: cint, events: set[TEvent], - data: PObject): TSelectorKey = - if s.fds.hasKey(fd): - raise newException(EInvalidValue, "FD already exists in selector.") - var sk = TSelectorKey(fd: fd, events: events, data: data) - s.fds[fd] = sk - result = sk - -proc ssUnregister(s: PSelector, fd: cint): TSelectorKey = - result = s.fds[fd] - s.fds.del(fd) - -proc ssClose(s: PSelector) = nil - -proc timeValFromMilliseconds(timeout: int): TTimeVal = - if timeout != -1: - var seconds = timeout div 1000 - result.tv_sec = seconds.int32 - result.tv_usec = ((timeout - seconds * 1000) * 1000).int32 - -proc createFdSet(rd, wr: var TFdSet, fds: TTable[cint, TSelectorKey], - m: var int) = - FD_ZERO(rd); FD_ZERO(wr) - for k, v in pairs(fds): - if EvRead in v.events: - m = max(m, int(k)) - FD_SET(k, rd) - if EvWrite in v.events: - m = max(m, int(k)) - FD_SET(k, wr) - -proc getReadyFDs(rd, wr: var TFdSet, fds: TTable[cint, TSelectorKey]): - seq[TReadyInfo] = - result = @[] - for k, v in pairs(fds): - var events: set[TEvent] = {} - if FD_ISSET(k, rd) != 0'i32: - events = events + {EvRead} - if FD_ISSET(k, wr) != 0'i32: - events = events + {EvWrite} - result.add((v, events)) - -proc select(fds: TTable[cint, TSelectorKey], timeout = 500): - seq[TReadyInfo] = - var tv {.noInit.}: TTimeVal = timeValFromMilliseconds(timeout) - - var rd, wr: TFdSet - var m = 0 - createFdSet(rd, wr, fds, m) - - var retCode = 0 - if timeout != -1: - retCode = int(select(cint(m+1), addr(rd), addr(wr), nil, addr(tv))) - else: - retCode = int(select(cint(m+1), addr(rd), addr(wr), nil, nil)) - - if retCode < 0: - OSError(OSLastError()) - elif retCode == 0: - return @[] - else: - return getReadyFDs(rd, wr, fds) - -proc ssSelect(s: PSelector, timeout: int): seq[TReadyInfo] = - result = select(s.fds, timeout) - -proc newSelectSelector*(): PSelectSelector = - initSelector(result) - result.registerImpl = ssRegister - result.unregisterImpl = ssUnregister - result.selectImpl = ssSelect - result.closeImpl = ssClose - -# ---- Epoll ------------------------------------------------------------------- - -when defined(linux): - import epoll +when defined(linux) or defined(nimdoc): type - PEpollSelector* = ref object of PSelector + PSelector* = ref object epollFD: cint events: array[64, ptr epoll_event] + fds: TTable[TSocketHandle, PSelectorKey] - TDataWrapper = object - fd: cint - boundEvents: set[TEvent] ## The events which ``fd`` listens for. - data: PObject ## User object. - - proc esRegister(s: PSelector, fd: cint, events: set[TEvent], - data: PObject): TSelectorKey = - var es = PEpollSelector(s) - var event: epoll_event + proc createEventStruct(events: set[TEvent], fd: TSocketHandle): epoll_event = if EvRead in events: - event.events = EPOLLIN + result.events = EPOLLIN if EvWrite in events: - event.events = event.events or EPOLLOUT - - var dw = cast[ptr TDataWrapper](alloc0(sizeof(TDataWrapper))) # TODO: This needs to be dealloc'd - dw.fd = fd - dw.boundEvents = events - dw.data = data - event.data.thePtr = dw + result.events = result.events or EPOLLOUT + result.data.fd = fd.cint + + proc register*(s: PSelector, fd: TSocketHandle, events: set[TEvent], + data: PObject): PSelectorKey {.discardable.} = + ## Registers file descriptor ``fd`` to selector ``s`` with a set of TEvent + ## ``events``. + if s.fds.hasKey(fd): + raise newException(EInvalidValue, "File descriptor already exists.") - if epoll_ctl(es.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0: + var event = createEventStruct(events, fd) + + if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0: OSError(OSLastError()) - - result = TSelectorKey(fd: fd, events: events, data: data) - proc esUnregister(s: PSelector, fd: cint): TSelectorKey = - # We cannot find out the information about this ``fd`` from the epoll - # context. As such I will simply return an almost empty TSelectorKey. - var es = PEpollSelector(s) - if epoll_ctl(es.epollFD, EPOLL_CTL_DEL, fd, nil) != 0: + var key = PSelectorKey(fd: fd, events: events, data: data) + + s.fds[fd] = key + result = key + + proc update*(s: PSelector, fd: TSocketHandle, + events: set[TEvent]): PSelectorKey {.discardable.} = + ## Updates the events which ``fd`` wants notifications for. + if not s.fds.hasKey(fd): + raise newException(EInvalidValue, "File descriptor not found.") + var event = createEventStruct(events, fd) + + s.fds[fd].events = events + echo("About to update") + if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fd, addr(event)) != 0: OSError(OSLastError()) - # We could fill in the ``fds`` TTable to get the info, but that wouldn't - # be nice for our memory. - result = TSelectorKey(fd: fd, events: {}, data: nil) - - proc esClose(s: PSelector) = - var es = PEpollSelector(s) - if es.epollFD.close() != 0: OSError(OSLastError()) - dealloc(addr es.events) # TODO: Test this + echo("finished updating") + result = s.fds[fd] - proc esSelect(s: PSelector, timeout: int): seq[TReadyInfo] = + proc unregister*(s: PSelector, fd: TSocketHandle): PSelectorKey {.discardable.} = + if not s.fds.hasKey(fd): + raise newException(EInvalidValue, "File descriptor not found.") + if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fd, nil) != 0: + OSError(OSLastError()) + result = s.fds[fd] + s.fds.del(fd) + + proc close*(s: PSelector) = + if s.epollFD.close() != 0: OSError(OSLastError()) + dealloc(addr s.events) # TODO: Test this + + proc select*(s: PSelector, timeout: int): seq[TReadyInfo] = + ## + ## The ``events`` field of the returned ``key`` contains the original events + ## for which the ``fd`` was bound. This is contrary to the ``events`` field + ## of the ``TReadyInfo`` tuple which determines which events are ready + ## on the ``fd``. result = @[] - var es = PEpollSelector(s) - let evNum = epoll_wait(es.epollFD, es.events[0], 64.cint, timeout.cint) + let evNum = epoll_wait(s.epollFD, s.events[0], 64.cint, timeout.cint) if evNum < 0: OSError(OSLastError()) if evNum == 0: return @[] for i in 0 .. <evNum: var evSet: set[TEvent] = {} - if (es.events[i].events and EPOLLIN) != 0: evSet = evSet + {EvRead} - if (es.events[i].events and EPOLLOUT) != 0: evSet = evSet + {EvWrite} - let dw = cast[ptr TDataWrapper](es.events[i].data.thePtr) + if (s.events[i].events and EPOLLIN) != 0: evSet = evSet + {EvRead} + if (s.events[i].events and EPOLLOUT) != 0: evSet = evSet + {EvWrite} - let selectorKey = TSelectorKey(fd: dw.fd, events: dw.boundEvents, - data: dw.data) + let selectorKey = s.fds[s.events[i].data.fd.TSocketHandle] result.add((selectorKey, evSet)) - proc newEpollSelector*(): PEpollSelector = + proc newSelector*(): PSelector = new result result.epollFD = epoll_create(64) result.events = cast[array[64, ptr epoll_event]](alloc0(sizeof(epoll_event)*64)) + result.fds = initTable[TSocketHandle, PSelectorKey]() if result.epollFD < 0: OSError(OSLastError()) - result.registerImpl = esRegister - result.unregisterImpl = esUnregister - result.closeImpl = esClose - result.selectImpl = esSelect + + proc contains*(s: PSelector, fd: TSocketHandle): bool = + ## Determines whether selector contains a file descriptor. + return s.fds.hasKey(fd) + + proc `[]`*(s: PSelector, fd: TSocketHandle): PSelectorKey = + ## Retrieves the selector key for ``fd``. + return s.fds[fd] + +elif defined(windows): + type + PSelector* = ref object + fds: TTable[TSocketHandle, PSelectorKey] + + proc register*(s: PSelector, fd: TSocketHandle, events: set[TEvent], + data: PObject): PSelectorKey {.discardable.} = + if s.fds.hasKey(fd): + raise newException(EInvalidValue, "File descriptor already exists.") + var sk = PSelectorKey(fd: fd, events: events, data: data) + s.fds[fd] = sk + result = sk + + proc update*(s: PSelector, fd: TSocketHandle, + events: set[TEvent]): PSelectorKey {.discardable.} = + ## Updates the events which ``fd`` wants notifications for. + if not s.fds.hasKey(fd): + raise newException(EInvalidValue, "File descriptor not found.") + + s.fds[fd].events = events + result = s.fds[fd] + + proc unregister*(s: PSelector, fd: TSocketHandle): PSelectorKey {.discardable.} = + result = s.fds[fd] + s.fds.del(fd) + + proc close*(s: PSelector) = nil + + proc timeValFromMilliseconds(timeout: int): TTimeVal = + if timeout != -1: + var seconds = timeout div 1000 + result.tv_sec = seconds.int32 + result.tv_usec = ((timeout - seconds * 1000) * 1000).int32 + + proc createFdSet(rd, wr: var TFdSet, fds: TTable[TSocketHandle, PSelectorKey], + m: var int) = + FD_ZERO(rd); FD_ZERO(wr) + for k, v in pairs(fds): + if EvRead in v.events: + m = max(m, int(k)) + FD_SET(k, rd) + if EvWrite in v.events: + m = max(m, int(k)) + FD_SET(k, wr) + + proc getReadyFDs(rd, wr: var TFdSet, fds: TTable[TSocketHandle, PSelectorKey]): + seq[TReadyInfo] = + result = @[] + for k, v in pairs(fds): + var events: set[TEvent] = {} + if FD_ISSET(k, rd) != 0'i32: + events = events + {EvRead} + if FD_ISSET(k, wr) != 0'i32: + events = events + {EvWrite} + result.add((v, events)) + + proc select(fds: TTable[TSocketHandle, PSelectorKey], timeout = 500): + seq[TReadyInfo] = + var tv {.noInit.}: TTimeVal = timeValFromMilliseconds(timeout) + + var rd, wr: TFdSet + var m = 0 + createFdSet(rd, wr, fds, m) + + var retCode = 0 + if timeout != -1: + retCode = int(select(TSocketHandle(m+1), addr(rd), addr(wr), nil, addr(tv))) + else: + retCode = int(select(TSocketHandle(m+1), addr(rd), addr(wr), nil, nil)) + + if retCode < 0: + OSError(OSLastError()) + elif retCode == 0: + return @[] + else: + return getReadyFDs(rd, wr, fds) + + proc select*(s: PSelector, timeout: int): seq[TReadyInfo] = + result = select(s.fds, timeout) + + proc newSelector*(): PSelector = + new result + result.fds = initTable[TSocketHandle, PSelectorKey]() + + proc contains*(s: PSelector, fd: TSocketHandle): bool = + return s.fds.hasKey(fd) + + proc `[]`*(s: PSelector, fd: TSocketHandle): PSelectorKey = + return s.fds[fd] + +elif defined(bsd) or defined(macosx): + # TODO: kqueue + {.error: "Sorry your platform is not supported yet.".} +else: + {.error: "Sorry your platform is not supported.".} when isMainModule: # Select() @@ -224,11 +223,12 @@ when isMainModule: sock: TSocket var sock = socket() + sock.setBlocking(false) sock.connect("irc.freenode.net", TPort(6667)) - var selector = newEpollSelector() + var selector = newSelector() var data = PSockWrapper(sock: sock) - let key = selector.register(sock.getFD.cint, {EvRead}, data) + let key = selector.register(sock.getFD, {EvWrite}, data) var i = 0 while true: let ready = selector.select(1000) @@ -236,6 +236,7 @@ when isMainModule: if ready.len > 0: echo ready[0].events i.inc if i == 6: + assert selector.unregister(sock.getFD).fd == sock.getFD selector.close() break diff --git a/lib/pure/sockets2.nim b/lib/pure/sockets2.nim index 22624bbad..031217b90 100644 --- a/lib/pure/sockets2.nim +++ b/lib/pure/sockets2.nim @@ -17,11 +17,13 @@ when hostos == "solaris": when defined(Windows): import winlean + export ioctlsocket else: import posix + export fcntl, F_GETFL, O_NONBLOCK, F_SETFL export TSocketHandle, TSockaddr_in, TAddrinfo, INADDR_ANY, TSockAddr, TSockLen, - inet_ntoa + inet_ntoa, recv, `==`, connect, send, accept type @@ -63,10 +65,10 @@ type when defined(windows): let - OSInvalidSocket* = winlean.INVALID_SOCKET + osInvalidSocket* = winlean.INVALID_SOCKET else: let - OSInvalidSocket* = posix.INVALID_SOCKET + osInvalidSocket* = posix.INVALID_SOCKET proc `==`*(a, b: TPort): bool {.borrow.} ## ``==`` for ports. @@ -89,7 +91,7 @@ when defined(posix): of AF_UNIX: result = posix.AF_UNIX of AF_INET: result = posix.AF_INET of AF_INET6: result = posix.AF_INET6 - else: nil + else: discard proc toInt(typ: TType): cint = case typ @@ -97,7 +99,7 @@ when defined(posix): of SOCK_DGRAM: result = posix.SOCK_DGRAM of SOCK_SEQPACKET: result = posix.SOCK_SEQPACKET of SOCK_RAW: result = posix.SOCK_RAW - else: nil + else: discard proc toInt(p: TProtocol): cint = case p @@ -107,7 +109,7 @@ when defined(posix): of IPPROTO_IPV6: result = posix.IPPROTO_IPV6 of IPPROTO_RAW: result = posix.IPPROTO_RAW of IPPROTO_ICMP: result = posix.IPPROTO_ICMP - else: nil + else: discard else: proc toInt(domain: TDomain): cint = @@ -199,4 +201,4 @@ proc htons*(x: int16): int16 = when defined(Windows): var wsa: TWSADATA - if WSAStartup(0x0101'i16, addr wsa) != 0: OSError(OSLastError()) \ No newline at end of file + if WSAStartup(0x0101'i16, addr wsa) != 0: OSError(OSLastError()) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index de6c4e4fa..2fce235e2 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -557,6 +557,119 @@ proc `$`*(m: TMonth): string = "November", "December"] return lookup[m] +proc format_token(info: TTimeInfo, token: string, buf: var string) = + ## Helper of the format proc to parse individual tokens. + ## + ## Pass the found token in the user input string, and the buffer where the + ## final string is being built. This has to be a var value because certain + ## formatting tokens require modifying the previous characters. + case token + of "d": + buf.add($info.monthday) + of "dd": + if info.monthday < 10: + buf.add("0") + buf.add($info.monthday) + of "ddd": + buf.add(($info.weekday)[0 .. 2]) + of "dddd": + buf.add($info.weekday) + of "h": + buf.add($(if info.hour > 12: info.hour - 12 else: info.hour)) + of "hh": + let amerHour = if info.hour > 12: info.hour - 12 else: info.hour + if amerHour < 10: + buf.add('0') + buf.add($amerHour) + of "H": + buf.add($info.hour) + of "HH": + if info.hour < 10: + buf.add('0') + buf.add($info.hour) + of "m": + buf.add($info.minute) + of "mm": + if info.minute < 10: + buf.add('0') + buf.add($info.minute) + of "M": + buf.add($(int(info.month)+1)) + of "MM": + if info.month < mOct: + buf.add('0') + buf.add($(int(info.month)+1)) + of "MMM": + buf.add(($info.month)[0..2]) + of "MMMM": + buf.add($info.month) + of "s": + buf.add($info.second) + of "ss": + if info.second < 10: + buf.add('0') + buf.add($info.second) + of "t": + if info.hour >= 12: + buf.add('P') + else: buf.add('A') + of "tt": + if info.hour >= 12: + buf.add("PM") + else: buf.add("AM") + of "y": + var fr = ($info.year).len()-1 + if fr < 0: fr = 0 + buf.add(($info.year)[fr .. ($info.year).len()-1]) + of "yy": + var fr = ($info.year).len()-2 + if fr < 0: fr = 0 + var fyear = ($info.year)[fr .. ($info.year).len()-1] + if fyear.len != 2: fyear = repeatChar(2-fyear.len(), '0') & fyear + buf.add(fyear) + of "yyy": + var fr = ($info.year).len()-3 + if fr < 0: fr = 0 + var fyear = ($info.year)[fr .. ($info.year).len()-1] + if fyear.len != 3: fyear = repeatChar(3-fyear.len(), '0') & fyear + buf.add(fyear) + of "yyyy": + var fr = ($info.year).len()-4 + if fr < 0: fr = 0 + var fyear = ($info.year)[fr .. ($info.year).len()-1] + if fyear.len != 4: fyear = repeatChar(4-fyear.len(), '0') & fyear + buf.add(fyear) + of "yyyyy": + var fr = ($info.year).len()-5 + if fr < 0: fr = 0 + var fyear = ($info.year)[fr .. ($info.year).len()-1] + if fyear.len != 5: fyear = repeatChar(5-fyear.len(), '0') & fyear + buf.add(fyear) + of "z": + let hrs = (info.timezone div 60) div 60 + buf.add($hrs) + of "zz": + let hrs = (info.timezone div 60) div 60 + + buf.add($hrs) + if hrs.abs < 10: + var atIndex = buf.len-(($hrs).len-(if hrs < 0: 1 else: 0)) + buf.insert("0", atIndex) + of "zzz": + let hrs = (info.timezone div 60) div 60 + + buf.add($hrs & ":00") + if hrs.abs < 10: + var atIndex = buf.len-(($hrs & ":00").len-(if hrs < 0: 1 else: 0)) + buf.insert("0", atIndex) + of "ZZZ": + buf.add(info.tzname) + of "": + discard + else: + raise newException(EInvalidValue, "Invalid format string: " & token) + + proc format*(info: TTimeInfo, f: string): string = ## This function formats `info` as specified by `f`. The following format ## specifiers are available: @@ -591,8 +704,11 @@ proc format*(info: TTimeInfo, f: string): string = ## ZZZ Displays the name of the timezone. ``GMT -> GMT``, ``EST -> EST`` ## ========== ================================================================================= ================================================ ## - ## Other strings can be inserted by putting them in ``''``. For example ``hh'->'mm`` will give ``01->56``. - ## The following characters can be inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]`` ``,`` + ## Other strings can be inserted by putting them in ``''``. For example + ## ``hh'->'mm`` will give ``01->56``. The following characters can be + ## inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]`` + ## ``,``. However you don't need to necessarily separate format specifiers, a + ## unambiguous format string like ``yyyyMMddhhmmss`` is valid too. result = "" var i = 0 @@ -600,112 +716,8 @@ proc format*(info: TTimeInfo, f: string): string = while true: case f[i] of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',': - case currentF - of "d": - result.add($info.monthday) - of "dd": - if info.monthday < 10: - result.add("0") - result.add($info.monthday) - of "ddd": - result.add(($info.weekday)[0 .. 2]) - of "dddd": - result.add($info.weekday) - of "h": - result.add($(if info.hour > 12: info.hour - 12 else: info.hour)) - of "hh": - let amerHour = if info.hour > 12: info.hour - 12 else: info.hour - if amerHour < 10: - result.add('0') - result.add($amerHour) - of "H": - result.add($info.hour) - of "HH": - if info.hour < 10: - result.add('0') - result.add($info.hour) - of "m": - result.add($info.minute) - of "mm": - if info.minute < 10: - result.add('0') - result.add($info.minute) - of "M": - result.add($(int(info.month)+1)) - of "MM": - if info.month < mOct: - result.add('0') - result.add($(int(info.month)+1)) - of "MMM": - result.add(($info.month)[0..2]) - of "MMMM": - result.add($info.month) - of "s": - result.add($info.second) - of "ss": - if info.second < 10: - result.add('0') - result.add($info.second) - of "t": - if info.hour >= 12: - result.add('P') - else: result.add('A') - of "tt": - if info.hour >= 12: - result.add("PM") - else: result.add("AM") - of "y": - var fr = ($info.year).len()-1 - if fr < 0: fr = 0 - result.add(($info.year)[fr .. ($info.year).len()-1]) - of "yy": - var fr = ($info.year).len()-2 - if fr < 0: fr = 0 - var fyear = ($info.year)[fr .. ($info.year).len()-1] - if fyear.len != 2: fyear = repeatChar(2-fyear.len(), '0') & fyear - result.add(fyear) - of "yyy": - var fr = ($info.year).len()-3 - if fr < 0: fr = 0 - var fyear = ($info.year)[fr .. ($info.year).len()-1] - if fyear.len != 3: fyear = repeatChar(3-fyear.len(), '0') & fyear - result.add(fyear) - of "yyyy": - var fr = ($info.year).len()-4 - if fr < 0: fr = 0 - var fyear = ($info.year)[fr .. ($info.year).len()-1] - if fyear.len != 4: fyear = repeatChar(4-fyear.len(), '0') & fyear - result.add(fyear) - of "yyyyy": - var fr = ($info.year).len()-5 - if fr < 0: fr = 0 - var fyear = ($info.year)[fr .. ($info.year).len()-1] - if fyear.len != 5: fyear = repeatChar(5-fyear.len(), '0') & fyear - result.add(fyear) - of "z": - let hrs = (info.timezone div 60) div 60 - result.add($hrs) - of "zz": - let hrs = (info.timezone div 60) div 60 - - result.add($hrs) - if hrs.abs < 10: - var atIndex = result.len-(($hrs).len-(if hrs < 0: 1 else: 0)) - result.insert("0", atIndex) - of "zzz": - let hrs = (info.timezone div 60) div 60 - - result.add($hrs & ":00") - if hrs.abs < 10: - var atIndex = result.len-(($hrs & ":00").len-(if hrs < 0: 1 else: 0)) - result.insert("0", atIndex) - of "ZZZ": - result.add(info.tzname) - of "": - discard - else: - raise newException(EInvalidValue, "Invalid format string: " & currentF) - + format_token(info, currentF, result) + currentF = "" if f[i] == '\0': break @@ -716,7 +728,15 @@ proc format*(info: TTimeInfo, f: string): string = inc(i) else: result.add(f[i]) - else: currentF.add(f[i]) + else: + # Check if the letter being added matches previous accumulated buffer. + if currentF.len < 1 or currentF[high(currentF)] == f[i]: + currentF.add(f[i]) + else: + format_token(info, currentF, result) + dec(i) # Move position back to re-process the character separately. + currentF = "" + inc(i) {.pop.} @@ -727,11 +747,15 @@ when isMainModule: var t = getGMTime(fromSeconds(2147483647)) echo t.format("ddd dd MMM hh:mm:ss ZZZ yyyy") + echo t.format("ddd ddMMMhhmmssZZZyyyy") assert t.format("ddd dd MMM hh:mm:ss ZZZ yyyy") == "Tue 19 Jan 03:14:07 UTC 2038" + assert t.format("ddd ddMMMhh:mm:ssZZZyyyy") == "Tue 19Jan03:14:07UTC2038" assert t.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & " ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") == "19 19 Tue Tuesday 3 03 3 03 14 14 1 01 Jan January 7 07 A AM 8 38 038 2038 02038 0 00 00:00 UTC" + + assert t.format("yyyyMMddhhmmss") == "20380119031407" var t2 = getGMTime(fromSeconds(160070789)) # Mon 27 Jan 16:06:29 GMT 1975 assert t2.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & |