summary refs log tree commit diff stats
path: root/lib/pure
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure')
-rw-r--r--lib/pure/asyncio2.nim639
-rw-r--r--lib/pure/dynlib.nim10
-rw-r--r--lib/pure/net.nim17
-rw-r--r--lib/pure/pegs.nim8
-rw-r--r--lib/pure/selectors.nim355
-rw-r--r--lib/pure/sockets2.nim16
-rw-r--r--lib/pure/times.nim242
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" &