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/asyncdispatch.nim10
-rw-r--r--lib/pure/collections/queues.nim2
-rw-r--r--lib/pure/collections/rtarrays.nim2
-rw-r--r--lib/pure/collections/sets.nim6
-rw-r--r--lib/pure/collections/tables.nim33
-rw-r--r--lib/pure/htmlparser.nim6
-rw-r--r--lib/pure/httpcore.nim8
-rw-r--r--lib/pure/ioselectors.nim255
-rw-r--r--lib/pure/ioselects/ioselectors_epoll.nim461
-rw-r--r--lib/pure/ioselects/ioselectors_kqueue.nim439
-rw-r--r--lib/pure/ioselects/ioselectors_poll.nim295
-rw-r--r--lib/pure/ioselects/ioselectors_select.nim416
-rw-r--r--lib/pure/json.nim7
-rw-r--r--lib/pure/logging.nim2
-rw-r--r--lib/pure/nativesockets.nim9
-rw-r--r--lib/pure/nimprof.nim4
-rw-r--r--lib/pure/os.nim6
-rw-r--r--lib/pure/ospaths.nim2
-rw-r--r--lib/pure/osproc.nim2
-rw-r--r--lib/pure/parsexml.nim21
-rw-r--r--lib/pure/pegs.nim4
-rw-r--r--lib/pure/strutils.nim447
-rw-r--r--lib/pure/terminal.nim30
-rw-r--r--lib/pure/times.nim6
-rw-r--r--lib/pure/unicode.nim134
25 files changed, 2434 insertions, 173 deletions
diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim
index be6522c36..15d9ab02e 100644
--- a/lib/pure/asyncdispatch.nim
+++ b/lib/pure/asyncdispatch.nim
@@ -1699,8 +1699,10 @@ proc processBody(node, retFutureSym: NimNode,
       else:
         result.add newCall(newIdentNode("complete"), retFutureSym)
     else:
-      result.add newCall(newIdentNode("complete"), retFutureSym,
-        node[0].processBody(retFutureSym, subTypeIsVoid, tryStmt))
+      let x = node[0].processBody(retFutureSym, subTypeIsVoid, tryStmt)
+      if x.kind == nnkYieldStmt: result.add x
+      else:
+        result.add newCall(newIdentNode("complete"), retFutureSym, x)
 
     result.add newNimNode(nnkReturnStmt, node).add(newNilLit())
     return # Don't process the children of this return stmt
@@ -1896,7 +1898,7 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
 
   # -> createCb(retFuture)
   #var cbName = newIdentNode("cb")
-  var procCb = newCall(bindSym"createCb", retFutureSym, iteratorNameSym,
+  var procCb = getAst createCb(retFutureSym, iteratorNameSym,
                        newStrLitNode(prc[0].getName))
   outerProcBody.add procCb
 
@@ -1931,6 +1933,8 @@ macro async*(prc: stmt): stmt {.immediate.} =
       result.add asyncSingleProc(oneProc)
   else:
     result = asyncSingleProc(prc)
+  when defined(nimDumpAsync):
+    echo repr result
 
 proc recvLine*(socket: AsyncFD): Future[string] {.async.} =
   ## Reads a line of data from ``socket``. Returned future will complete once
diff --git a/lib/pure/collections/queues.nim b/lib/pure/collections/queues.nim
index 911816518..399e4d413 100644
--- a/lib/pure/collections/queues.nim
+++ b/lib/pure/collections/queues.nim
@@ -152,11 +152,13 @@ proc add*[T](q: var Queue[T], item: T) =
   q.data[q.wr] = item
   q.wr = (q.wr + 1) and q.mask
 
+proc default[T](t: typedesc[T]): T {.inline.} = discard
 proc pop*[T](q: var Queue[T]): T {.inline, discardable.} =
   ## Remove and returns the first (oldest) element of the queue `q`.
   emptyCheck(q)
   dec q.count
   result = q.data[q.rd]
+  q.data[q.rd] = default(type(result))
   q.rd = (q.rd + 1) and q.mask
 
 proc enqueue*[T](q: var Queue[T], item: T) =
diff --git a/lib/pure/collections/rtarrays.nim b/lib/pure/collections/rtarrays.nim
index 9d8085643..2abe9d1f8 100644
--- a/lib/pure/collections/rtarrays.nim
+++ b/lib/pure/collections/rtarrays.nim
@@ -18,7 +18,7 @@ type
   RtArray*[T] = object  ##
     L: Natural
     spart: seq[T]
-    apart: array [ArrayPartSize, T]
+    apart: array[ArrayPartSize, T]
   UncheckedArray* {.unchecked.}[T] = array[0..100_000_000, T]
 
 template usesSeqPart(x): expr = x.L > ArrayPartSize
diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim
index e2081e5bf..20e06aaae 100644
--- a/lib/pure/collections/sets.nim
+++ b/lib/pure/collections/sets.nim
@@ -58,7 +58,7 @@ proc isValid*[A](s: HashSet[A]): bool =
   ## initialized. Example:
   ##
   ## .. code-block ::
-  ##   proc savePreferences(options: Set[string]) =
+  ##   proc savePreferences(options: HashSet[string]) =
   ##     assert options.isValid, "Pass an initialized set!"
   ##     # Do stuff here, may crash in release builds!
   result = not s.data.isNil
@@ -72,7 +72,7 @@ proc len*[A](s: HashSet[A]): int =
   ##
   ## .. code-block::
   ##
-  ##   var values: Set[int]
+  ##   var values: HashSet[int]
   ##   assert(not values.isValid)
   ##   assert values.len == 0
   result = s.counter
@@ -338,7 +338,7 @@ proc init*[A](s: var HashSet[A], initialSize=64) =
   ## existing values and calling `excl() <#excl,TSet[A],A>`_ on them. Example:
   ##
   ## .. code-block ::
-  ##   var a: Set[int]
+  ##   var a: HashSet[int]
   ##   a.init(4)
   ##   a.incl(2)
   ##   a.init
diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim
index e454a43cb..941516956 100644
--- a/lib/pure/collections/tables.nim
+++ b/lib/pure/collections/tables.nim
@@ -16,6 +16,39 @@
 ## semantics, this means that ``=`` performs a copy of the hash table.
 ## For **reference** semantics use the ``Ref`` variant: ``TableRef``,
 ## ``OrderedTableRef``, ``CountTableRef``.
+## To give an example, when `a` is a Table, then `var b = a` gives `b`
+## as a new independent table. b is initialised with the contents of `a`.
+## Changing `b` does not affect `a` and vice versa:
+##
+## .. code-block::
+##   import tables
+##
+##   var
+##     a = {1: "one", 2: "two"}.toTable  # creates a Table
+##     b = a
+##
+##   echo a, b  # output: {1: one, 2: two}{1: one, 2: two}
+##
+##   b[3] = "three"
+##   echo a, b  # output: {1: one, 2: two}{1: one, 2: two, 3: three}
+##   echo a == b  # output: false
+##
+## On the other hand, when `a` is a TableRef instead, then changes to `b` also affect `a`.
+## Both `a` and `b` reference the same data structure:
+##
+## .. code-block::
+##   import tables
+##
+##   var
+##     a = {1: "one", 2: "two"}.newTable  # creates a TableRef
+##     b = a
+##
+##   echo a, b  # output: {1: one, 2: two}{1: one, 2: two}
+##
+##   b[3] = "three"
+##   echo a, b  # output: {1: one, 2: two, 3: three}{1: one, 2: two, 3: three}
+##   echo a == b  # output: true
+##
 ##
 ## If you are using simple standard types like ``int`` or ``string`` for the
 ## keys of the table you won't have any problems, but as soon as you try to use
diff --git a/lib/pure/htmlparser.nim b/lib/pure/htmlparser.nim
index d620e816e..fd58bed25 100644
--- a/lib/pure/htmlparser.nim
+++ b/lib/pure/htmlparser.nim
@@ -433,7 +433,7 @@ proc htmlTag*(n: XmlNode): HtmlTag =
 proc htmlTag*(s: string): HtmlTag =
   ## converts `s` to a ``HtmlTag``. If `s` is no HTML tag, ``tagUnknown`` is
   ## returned.
-  let s = if allLower(s): s else: s.toLower
+  let s = if allLower(s): s else: toLowerAscii(s)
   result = toHtmlTag(s)
 
 proc entityToUtf8*(entity: string): string =
@@ -513,13 +513,13 @@ proc parse(x: var XmlParser, errors: var seq[string]): XmlNode =
     errors.add(errorMsg(x))
     next(x)
   of xmlElementStart:
-    result = newElement(x.elemName.toLower)
+    result = newElement(toLowerAscii(x.elemName))
     next(x)
     untilElementEnd(x, result, errors)
   of xmlElementEnd:
     errors.add(errorMsg(x, "unexpected ending tag: " & x.elemName))
   of xmlElementOpen:
-    result = newElement(x.elemName.toLower)
+    result = newElement(toLowerAscii(x.elemName))
     next(x)
     result.attrs = newStringTable()
     while true:
diff --git a/lib/pure/httpcore.nim b/lib/pure/httpcore.nim
index c4d575833..562d16c19 100644
--- a/lib/pure/httpcore.nim
+++ b/lib/pure/httpcore.nim
@@ -24,6 +24,7 @@ type
     Http200 = "200 OK",
     Http201 = "201 Created",
     Http202 = "202 Accepted",
+    Http203 = "203 Non-Authoritative Information",
     Http204 = "204 No Content",
     Http205 = "205 Reset Content",
     Http206 = "206 Partial Content",
@@ -52,6 +53,13 @@ type
     Http416 = "416 Requested Range Not Satisfiable",
     Http417 = "417 Expectation Failed",
     Http418 = "418 I'm a teapot",
+    Http421 = "421 Misdirected Request",
+    Http422 = "422 Unprocessable Entity",
+    Http426 = "426 Upgrade Required",
+    Http428 = "428 Precondition Required",
+    Http429 = "429 Too Many Requests",
+    Http431 = "431 Request Header Fields Too Large",
+    Http451 = "451 Unavailable For Legal Reasons",
     Http500 = "500 Internal Server Error",
     Http501 = "501 Not Implemented",
     Http502 = "502 Bad Gateway",
diff --git a/lib/pure/ioselectors.nim b/lib/pure/ioselectors.nim
new file mode 100644
index 000000000..a5d5d2c01
--- /dev/null
+++ b/lib/pure/ioselectors.nim
@@ -0,0 +1,255 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2016 Eugene Kabanov
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## This module allows high-level and efficient I/O multiplexing.
+##
+## Supported OS primitives: ``epoll``, ``kqueue``, ``poll`` and
+## Windows ``select``.
+##
+## To use threadsafe version of this module, it needs to be compiled
+## with both ``-d:threadsafe`` and ``--threads:on`` options.
+##
+## Supported features: files, sockets, pipes, timers, processes, signals
+## and user events.
+##
+## Fully supported OS: MacOSX, FreeBSD, OpenBSD, NetBSD, Linux.
+##
+## Partially supported OS: Windows (only sockets and user events),
+## Solaris (files, sockets, handles and user events).
+##
+## TODO: ``/dev/poll``, ``event ports`` and filesystem events.
+
+import os
+
+const hasThreadSupport = compileOption("threads") and defined(threadsafe)
+
+const supportedPlatform = defined(macosx) or defined(freebsd) or
+                          defined(netbsd) or defined(openbsd) or
+                          defined(linux)
+
+const bsdPlatform = defined(macosx) or defined(freebsd) or
+                    defined(netbsd) or defined(openbsd)
+
+
+when defined(nimdoc):
+  type
+    Selector*[T] = ref object
+      ## An object which holds descriptors to be checked for read/write status
+
+    Event* {.pure.} = enum
+      ## An enum which hold event types
+      Read,    ## Descriptor is available for read
+      Write,   ## Descriptor is available for write
+      Timer,   ## Timer descriptor is completed
+      Signal,  ## Signal is raised
+      Process, ## Process is finished
+      Vnode,   ## Currently not supported
+      User,    ## User event is raised
+      Error    ## Error happens while waiting, for descriptor
+
+    ReadyKey*[T] = object
+      ## An object which holds result for descriptor
+      fd* : int ## file/socket descriptor
+      events*: set[Event] ## set of events
+      data*: T ## application-defined data
+
+    SelectEvent* = object
+      ## An object which holds user defined event
+
+  proc newSelector*[T](): Selector[T] =
+    ## Creates a new selector
+
+  proc close*[T](s: Selector[T]) =
+    ## Closes selector
+
+  proc registerHandle*[T](s: Selector[T], fd: SocketHandle, events: set[Event],
+                          data: T) =
+    ## Registers file/socket descriptor ``fd`` to selector ``s``
+    ## with events set in ``events``. The ``data`` is application-defined
+    ## data, which to be passed when event happens.
+
+  proc updateHandle*[T](s: Selector[T], fd: SocketHandle, events: set[Event]) =
+    ## Update file/socket descriptor ``fd``, registered in selector
+    ## ``s`` with new events set ``event``.
+
+  proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool,
+                         data: T): int {.discardable.} =
+    ## Registers timer notification with ``timeout`` in milliseconds
+    ## to selector ``s``.
+    ## If ``oneshot`` is ``true`` timer will be notified only once.
+    ## Set ``oneshot`` to ``false`` if your want periodic notifications.
+    ## The ``data`` is application-defined data, which to be passed, when
+    ## time limit expired.
+
+  proc registerSignal*[T](s: Selector[T], signal: int,
+                          data: T): int {.discardable.} =
+    ## Registers Unix signal notification with ``signal`` to selector
+    ## ``s``. The ``data`` is application-defined data, which to be
+    ## passed, when signal raises.
+    ##
+    ## This function is not supported for ``Windows``.
+
+  proc registerProcess*[T](s: Selector[T], pid: int,
+                           data: T): int {.discardable.} =
+    ## Registers process id (pid) notification when process has
+    ## exited to selector ``s``.
+    ## The ``data`` is application-defined data, which to be passed, when
+    ## process with ``pid`` has exited.
+
+  proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
+    ## Registers selector event ``ev`` to selector ``s``.
+    ## ``data`` application-defined data, which to be passed, when
+    ## ``ev`` happens.
+
+  proc newSelectEvent*(): SelectEvent =
+    ## Creates new event ``SelectEvent``.
+
+  proc setEvent*(ev: SelectEvent) =
+    ## Trigger event ``ev``.
+
+  proc close*(ev: SelectEvent) =
+    ## Closes selector event ``ev``.
+
+  proc unregister*[T](s: Selector[T], ev: SelectEvent) =
+    ## Unregisters event ``ev`` from selector ``s``.
+
+  proc unregister*[T](s: Selector[T], fd: int|SocketHandle|cint) =
+    ## Unregisters file/socket descriptor ``fd`` from selector ``s``.
+
+  proc flush*[T](s: Selector[T]) =
+    ## Flushes all changes was made to kernel pool/queue.
+    ## This function is usefull only for BSD and MacOS, because
+    ## kqueue supports bulk changes to be made.
+    ## On Linux/Windows and other Posix compatible operation systems,
+    ## ``flush`` is alias for `discard`.
+
+  proc selectInto*[T](s: Selector[T], timeout: int,
+                      results: var openarray[ReadyKey[T]]): int =
+    ## Process call waiting for events registered in selector ``s``.
+    ## The ``timeout`` argument specifies the minimum number of milliseconds
+    ## the function will be blocked, if no events are not ready. Specifying a
+    ## timeout of ``-1`` causes function to block indefinitely.
+    ## All available events will be stored in ``results`` array.
+    ##
+    ## Function returns number of triggered events.
+
+  proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] =
+    ## Process call waiting for events registered in selector ``s``.
+    ## The ``timeout`` argument specifies the minimum number of milliseconds
+    ## the function will be blocked, if no events are not ready. Specifying a
+    ## timeout of -1 causes function to block indefinitely.
+    ##
+    ## Function returns sequence of triggered events.
+
+  template isEmpty*[T](s: Selector[T]): bool =
+    ## Returns ``true``, if there no registered events or descriptors
+    ## in selector.
+
+  template withData*[T](s: Selector[T], fd: SocketHandle, value,
+                        body: untyped) =
+    ## retrieves the application-data assigned with descriptor ``fd``
+    ## to ``value``. This ``value`` can be modified in the scope of
+    ## the ``withData`` call.
+    ##
+    ## .. code-block:: nim
+    ##
+    ##   s.withData(fd, value) do:
+    ##     # block is executed only if ``fd`` registered in selector ``s``
+    ##     value.uid = 1000
+    ##
+
+  template withData*[T](s: Selector[T], fd: SocketHandle, value,
+                        body1, body2: untyped) =
+    ## retrieves the application-data assigned with descriptor ``fd``
+    ## to ``value``. This ``value`` can be modified in the scope of
+    ## the ``withData`` call.
+    ##
+    ## .. code-block:: nim
+    ##
+    ##   s.withData(fd, value) do:
+    ##     # block is executed only if ``fd`` registered in selector ``s``.
+    ##     value.uid = 1000
+    ##   do:
+    ##     # block is executed if ``fd`` not registered in selector ``s``.
+    ##     raise
+    ##
+
+else:
+  when hasThreadSupport:
+    import locks
+
+    type
+      SharedArray {.unchecked.}[T] = array[0..100, T]
+
+    proc allocSharedArray[T](nsize: int): ptr SharedArray[T] =
+      result = cast[ptr SharedArray[T]](allocShared0(sizeof(T) * nsize))
+
+    proc deallocSharedArray[T](sa: ptr SharedArray[T]) =
+      deallocShared(cast[pointer](sa))
+  type
+    Event* {.pure.} = enum
+      Read, Write, Timer, Signal, Process, Vnode, User, Error, Oneshot
+
+    ReadyKey*[T] = object
+      fd* : int
+      events*: set[Event]
+      data*: T
+
+    SelectorKey[T] = object
+      ident: int
+      events: set[Event]
+      param: int
+      key: ReadyKey[T]
+
+  when not defined(windows):
+    import posix
+    proc setNonBlocking(fd: cint) {.inline.} =
+      var x = fcntl(fd, F_GETFL, 0)
+      if x == -1:
+        raiseOSError(osLastError())
+      else:
+        var mode = x or O_NONBLOCK
+        if fcntl(fd, F_SETFL, mode) == -1:
+          raiseOSError(osLastError())
+
+    template setKey(s, pident, pkeyfd, pevents, pparam, pdata) =
+      var skey = addr(s.fds[pident])
+      skey.ident = pident
+      skey.events = pevents
+      skey.param = pparam
+      skey.key.fd = pkeyfd
+      skey.key.data = pdata
+
+  when supportedPlatform:
+    template blockSignals(newmask: var Sigset, oldmask: var Sigset) =
+      when hasThreadSupport:
+        if posix.pthread_sigmask(SIG_BLOCK, newmask, oldmask) == -1:
+          raiseOSError(osLastError())
+      else:
+        if posix.sigprocmask(SIG_BLOCK, newmask, oldmask) == -1:
+          raiseOSError(osLastError())
+
+    template unblockSignals(newmask: var Sigset, oldmask: var Sigset) =
+      when hasThreadSupport:
+        if posix.pthread_sigmask(SIG_UNBLOCK, newmask, oldmask) == -1:
+          raiseOSError(osLastError())
+      else:
+        if posix.sigprocmask(SIG_UNBLOCK, newmask, oldmask) == -1:
+          raiseOSError(osLastError())
+
+  when defined(linux):
+    include ioselects/ioselectors_epoll
+  elif bsdPlatform:
+    include ioselects/ioselectors_kqueue
+  elif defined(windows):
+    include ioselects/ioselectors_select
+  elif defined(solaris):
+    include ioselects/ioselectors_poll # need to replace it with event ports
+  else:
+    include ioselects/ioselectors_poll
diff --git a/lib/pure/ioselects/ioselectors_epoll.nim b/lib/pure/ioselects/ioselectors_epoll.nim
new file mode 100644
index 000000000..92b2cdc07
--- /dev/null
+++ b/lib/pure/ioselects/ioselectors_epoll.nim
@@ -0,0 +1,461 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2016 Eugene Kabanov
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+# This module implements Linux epoll().
+
+import posix, times
+
+# Maximum number of events that can be returned
+const MAX_EPOLL_RESULT_EVENTS = 64
+
+type
+  SignalFdInfo* {.importc: "struct signalfd_siginfo",
+                  header: "<sys/signalfd.h>", pure, final.} = object
+    ssi_signo*: uint32
+    ssi_errno*: int32
+    ssi_code*: int32
+    ssi_pid*: uint32
+    ssi_uid*: uint32
+    ssi_fd*: int32
+    ssi_tid*: uint32
+    ssi_band*: uint32
+    ssi_overrun*: uint32
+    ssi_trapno*: uint32
+    ssi_status*: int32
+    ssi_int*: int32
+    ssi_ptr*: uint64
+    ssi_utime*: uint64
+    ssi_stime*: uint64
+    ssi_addr*: uint64
+    pad* {.importc: "__pad".}: array[0..47, uint8]
+
+  eventFdData {.importc: "eventfd_t",
+                header: "<sys/eventfd.h>", pure, final.} = uint64
+  epoll_data {.importc: "union epoll_data", header: "<sys/epoll.h>",
+               pure, final.} = object
+    u64 {.importc: "u64".}: uint64
+  epoll_event {.importc: "struct epoll_event",
+                header: "<sys/epoll.h>", pure, final.} = object
+    events: uint32 # Epoll events
+    data: epoll_data # User data variable
+
+const
+  EPOLL_CTL_ADD = 1          # Add a file descriptor to the interface.
+  EPOLL_CTL_DEL = 2          # Remove a file descriptor from the interface.
+  EPOLL_CTL_MOD = 3          # Change file descriptor epoll_event structure.
+  EPOLLIN = 0x00000001
+  EPOLLOUT = 0x00000004
+  EPOLLERR = 0x00000008
+  EPOLLHUP = 0x00000010
+  EPOLLRDHUP = 0x00002000
+  EPOLLONESHOT = 1 shl 30
+
+proc epoll_create(size: cint): cint
+     {.importc: "epoll_create", header: "<sys/epoll.h>".}
+proc epoll_ctl(epfd: cint; op: cint; fd: cint; event: ptr epoll_event): cint
+     {.importc: "epoll_ctl", header: "<sys/epoll.h>".}
+proc epoll_wait(epfd: cint; events: ptr epoll_event; maxevents: cint;
+                 timeout: cint): cint
+     {.importc: "epoll_wait", header: "<sys/epoll.h>".}
+proc timerfd_create(clock_id: ClockId, flags: cint): cint
+     {.cdecl, importc: "timerfd_create", header: "<sys/timerfd.h>".}
+proc timerfd_settime(ufd: cint, flags: cint,
+                      utmr: var Itimerspec, otmr: var Itimerspec): cint
+     {.cdecl, importc: "timerfd_settime", header: "<sys/timerfd.h>".}
+proc signalfd(fd: cint, mask: var Sigset, flags: cint): cint
+     {.cdecl, importc: "signalfd", header: "<sys/signalfd.h>".}
+proc eventfd(count: cuint, flags: cint): cint
+     {.cdecl, importc: "eventfd", header: "<sys/eventfd.h>".}
+proc ulimit(cmd: cint): clong
+     {.importc: "ulimit", header: "<ulimit.h>", varargs.}
+
+when hasThreadSupport:
+  type
+    SelectorImpl[T] = object
+      epollFD : cint
+      maxFD : int
+      fds: ptr SharedArray[SelectorKey[T]]
+      count: int
+    Selector*[T] = ptr SelectorImpl[T]
+else:
+  type
+    SelectorImpl[T] = object
+      epollFD : cint
+      maxFD : int
+      fds: seq[SelectorKey[T]]
+      count: int
+    Selector*[T] = ref SelectorImpl[T]
+type
+  SelectEventImpl = object
+    efd: cint
+  SelectEvent* = ptr SelectEventImpl
+
+proc newSelector*[T](): Selector[T] =
+  var maxFD = int(ulimit(4, 0))
+  doAssert(maxFD > 0)
+
+  var epollFD = epoll_create(MAX_EPOLL_RESULT_EVENTS)
+  if epollFD < 0:
+    raiseOsError(osLastError())
+
+  when hasThreadSupport:
+    result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T])))
+    result.epollFD = epollFD
+    result.maxFD = maxFD
+    result.fds = allocSharedArray[SelectorKey[T]](maxFD)
+  else:
+    result = Selector[T]()
+    result.epollFD = epollFD
+    result.maxFD = maxFD
+    result.fds = newSeq[SelectorKey[T]](maxFD)
+
+proc close*[T](s: Selector[T]) =
+  if posix.close(s.epollFD) != 0:
+    raiseOSError(osLastError())
+  when hasThreadSupport:
+    deallocSharedArray(s.fds)
+    deallocShared(cast[pointer](s))
+
+proc newSelectEvent*(): SelectEvent =
+  let fdci = eventfd(0, 0)
+  if fdci == -1:
+    raiseOSError(osLastError())
+  setNonBlocking(fdci)
+  result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl)))
+  result.efd = fdci
+
+proc setEvent*(ev: SelectEvent) =
+  var data : uint64 = 1
+  if posix.write(ev.efd, addr data, sizeof(uint64)) == -1:
+    raiseOSError(osLastError())
+
+proc close*(ev: SelectEvent) =
+  discard posix.close(ev.efd)
+  deallocShared(cast[pointer](ev))
+
+template checkFd(s, f) =
+  if f >= s.maxFD:
+    raise newException(ValueError, "Maximum file descriptors exceeded")
+
+proc registerHandle*[T](s: Selector[T], fd: SocketHandle,
+                        events: set[Event], data: T) =
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  doAssert(s.fds[fdi].ident == 0)
+  s.setKey(fdi, fdi, events, 0, data)
+  if events != {}:
+    var epv = epoll_event(events: EPOLLRDHUP)
+    epv.data.u64 = fdi.uint
+    if Event.Read in events: epv.events = epv.events or EPOLLIN
+    if Event.Write in events: epv.events = epv.events or EPOLLOUT
+    if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1:
+      raiseOSError(osLastError())
+    inc(s.count)
+
+proc updateHandle*[T](s: Selector[T], fd: SocketHandle, events: set[Event]) =
+  let maskEvents = {Event.Timer, Event.Signal, Event.Process, Event.Vnode,
+                    Event.User, Event.Oneshot, Event.Error}
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  var pkey = addr(s.fds[fdi])
+  doAssert(pkey.ident != 0)
+  doAssert(pkey.events * maskEvents == {})
+  if pkey.events != events:
+    var epv = epoll_event(events: EPOLLRDHUP)
+    epv.data.u64 = fdi.uint
+
+    if Event.Read in events: epv.events = epv.events or EPOLLIN
+    if Event.Write in events: epv.events = epv.events or EPOLLOUT
+
+    if pkey.events == {}:
+      if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1:
+        raiseOSError(osLastError())
+      inc(s.count)
+    else:
+      if events != {}:
+        if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fdi.cint, addr epv) == -1:
+          raiseOSError(osLastError())
+      else:
+        if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
+          raiseOSError(osLastError())
+        dec(s.count)
+    pkey.events = events
+
+proc unregister*[T](s: Selector[T], fd: int|SocketHandle) =
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  var pkey = addr(s.fds[fdi])
+  doAssert(pkey.ident != 0)
+
+  if pkey.events != {}:
+    if pkey.events * {Event.Read, Event.Write} != {}:
+      var epv = epoll_event()
+      if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
+        raiseOSError(osLastError())
+      dec(s.count)
+    elif Event.Timer in pkey.events:
+      var epv = epoll_event()
+      if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
+        raiseOSError(osLastError())
+      discard posix.close(fdi.cint)
+      dec(s.count)
+    elif Event.Signal in pkey.events:
+      var epv = epoll_event()
+      if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
+        raiseOSError(osLastError())
+      var nmask, omask: Sigset
+      discard sigemptyset(nmask)
+      discard sigemptyset(omask)
+      discard sigaddset(nmask, cint(s.fds[fdi].param))
+      unblockSignals(nmask, omask)
+      discard posix.close(fdi.cint)
+      dec(s.count)
+    elif Event.Process in pkey.events:
+      var epv = epoll_event()
+      if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
+        raiseOSError(osLastError())
+      var nmask, omask: Sigset
+      discard sigemptyset(nmask)
+      discard sigemptyset(omask)
+      discard sigaddset(nmask, SIGCHLD)
+      unblockSignals(nmask, omask)
+      discard posix.close(fdi.cint)
+      dec(s.count)
+  pkey.ident = 0
+  pkey.events = {}
+
+proc unregister*[T](s: Selector[T], ev: SelectEvent) =
+  let fdi = int(ev.efd)
+  s.checkFd(fdi)
+  var pkey = addr(s.fds[fdi])
+  doAssert(pkey.ident != 0)
+  doAssert(Event.User in pkey.events)
+  pkey.ident = 0
+  pkey.events = {}
+  var epv = epoll_event()
+  if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
+    raiseOSError(osLastError())
+  dec(s.count)
+
+proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool,
+                       data: T): int {.discardable.} =
+  var
+    new_ts: Itimerspec
+    old_ts: Itimerspec
+  let fdi = timerfd_create(CLOCK_MONOTONIC, 0).int
+  if fdi == -1:
+    raiseOSError(osLastError())
+  setNonBlocking(fdi.cint)
+
+  s.checkFd(fdi)
+  doAssert(s.fds[fdi].ident == 0)
+
+  var events = {Event.Timer}
+  var epv = epoll_event(events: EPOLLIN or EPOLLRDHUP)
+  epv.data.u64 = fdi.uint
+  if oneshot:
+    new_ts.it_interval.tv_sec = 0.Time
+    new_ts.it_interval.tv_nsec = 0
+    new_ts.it_value.tv_sec = (timeout div 1_000).Time
+    new_ts.it_value.tv_nsec = (timeout %% 1_000) * 1_000_000
+    incl(events, Event.Oneshot)
+    epv.events = epv.events or EPOLLONESHOT
+  else:
+    new_ts.it_interval.tv_sec = (timeout div 1000).Time
+    new_ts.it_interval.tv_nsec = (timeout %% 1_000) * 1_000_000
+    new_ts.it_value.tv_sec = new_ts.it_interval.tv_sec
+    new_ts.it_value.tv_nsec = new_ts.it_interval.tv_nsec
+
+  if timerfd_settime(fdi.cint, cint(0), new_ts, old_ts) == -1:
+    raiseOSError(osLastError())
+  if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1:
+    raiseOSError(osLastError())
+  s.setKey(fdi, fdi, events, 0, data)
+  inc(s.count)
+  result = fdi
+
+proc registerSignal*[T](s: Selector[T], signal: int,
+                        data: T): int {.discardable.} =
+  var
+    nmask: Sigset
+    omask: Sigset
+
+  discard sigemptyset(nmask)
+  discard sigemptyset(omask)
+  discard sigaddset(nmask, cint(signal))
+  blockSignals(nmask, omask)
+
+  let fdi = signalfd(-1, nmask, 0).int
+  if fdi == -1:
+    raiseOSError(osLastError())
+  setNonBlocking(fdi.cint)
+
+  s.checkFd(fdi)
+  doAssert(s.fds[fdi].ident == 0)
+
+  var epv = epoll_event(events: EPOLLIN or EPOLLRDHUP)
+  epv.data.u64 = fdi.uint
+  if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1:
+    raiseOSError(osLastError())
+  s.setKey(fdi, signal, {Event.Signal}, signal, data)
+  inc(s.count)
+  result = fdi
+
+proc registerProcess*[T](s: Selector, pid: int,
+                         data: T): int {.discardable.} =
+  var
+    nmask: Sigset
+    omask: Sigset
+
+  discard sigemptyset(nmask)
+  discard sigemptyset(omask)
+  discard sigaddset(nmask, posix.SIGCHLD)
+  blockSignals(nmask, omask)
+
+  let fdi = signalfd(-1, nmask, 0).int
+  if fdi == -1:
+    raiseOSError(osLastError())
+  setNonBlocking(fdi.cint)
+
+  s.checkFd(fdi)
+  doAssert(s.fds[fdi].ident == 0)
+
+  var epv = epoll_event(events: EPOLLIN or EPOLLRDHUP)
+  epv.data.u64 = fdi.uint
+  epv.events = EPOLLIN or EPOLLRDHUP
+  if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1:
+    raiseOSError(osLastError())
+  s.setKey(fdi, pid, {Event.Process, Event.Oneshot}, pid, data)
+  inc(s.count)
+  result = fdi
+
+proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
+  let fdi = int(ev.efd)
+  doAssert(s.fds[fdi].ident == 0)
+  s.setKey(fdi, fdi, {Event.User}, 0, data)
+  var epv = epoll_event(events: EPOLLIN or EPOLLRDHUP)
+  epv.data.u64 = ev.efd.uint
+  if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, ev.efd, addr epv) == -1:
+    raiseOSError(osLastError())
+  inc(s.count)
+
+proc flush*[T](s: Selector[T]) =
+  discard
+
+proc selectInto*[T](s: Selector[T], timeout: int,
+                    results: var openarray[ReadyKey[T]]): int =
+  var
+    resTable: array[MAX_EPOLL_RESULT_EVENTS, epoll_event]
+    maxres = MAX_EPOLL_RESULT_EVENTS
+    events: set[Event] = {}
+    i, k: int
+
+  if maxres > len(results):
+    maxres = len(results)
+
+  let count = epoll_wait(s.epollFD, addr(resTable[0]), maxres.cint,
+                         timeout.cint)
+  if count < 0:
+    result = 0
+    let err = osLastError()
+    if cint(err) != EINTR:
+      raiseOSError(err)
+  elif count == 0:
+    result = 0
+  else:
+    i = 0
+    k = 0
+    while i < count:
+      let fdi = int(resTable[i].data.u64)
+      let pevents = resTable[i].events
+      var skey = addr(s.fds[fdi])
+      doAssert(skey.ident != 0)
+      events = {}
+
+      if (pevents and EPOLLERR) != 0 or (pevents and EPOLLHUP) != 0:
+        events.incl(Event.Error)
+      if (pevents and EPOLLOUT) != 0:
+        events.incl(Event.Write)
+      if (pevents and EPOLLIN) != 0:
+        if Event.Read in skey.events:
+          events.incl(Event.Read)
+        elif Event.Timer in skey.events:
+          var data: uint64 = 0
+          if posix.read(fdi.cint, addr data, sizeof(uint64)) != sizeof(uint64):
+            raiseOSError(osLastError())
+          events = {Event.Timer}
+        elif Event.Signal in skey.events:
+          var data = SignalFdInfo()
+          if posix.read(fdi.cint, addr data,
+                        sizeof(SignalFdInfo)) != sizeof(SignalFdInfo):
+            raiseOsError(osLastError())
+          events = {Event.Signal}
+        elif Event.Process in skey.events:
+          var data = SignalFdInfo()
+          if posix.read(fdi.cint, addr data,
+                        sizeof(SignalFdInfo)) != sizeof(SignalFdInfo):
+            raiseOsError(osLastError())
+          if cast[int](data.ssi_pid) == skey.param:
+            events = {Event.Process}
+          else:
+            inc(i)
+            continue
+        elif Event.User in skey.events:
+          var data: uint = 0
+          if posix.read(fdi.cint, addr data, sizeof(uint)) != sizeof(uint):
+            let err = osLastError()
+            if err == OSErrorCode(EAGAIN):
+              inc(i)
+              continue
+            else:
+              raiseOSError(err)
+          events = {Event.User}
+
+      skey.key.events = events
+      results[k] = skey.key
+      inc(k)
+
+      if Event.Oneshot in skey.events:
+        var epv = epoll_event()
+        if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
+          raiseOSError(osLastError())
+        discard posix.close(fdi.cint)
+        skey.ident = 0
+        skey.events = {}
+        dec(s.count)
+      inc(i)
+    result = k
+
+proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] =
+  result = newSeq[ReadyKey[T]](MAX_EPOLL_RESULT_EVENTS)
+  let count = selectInto(s, timeout, result)
+  result.setLen(count)
+
+template isEmpty*[T](s: Selector[T]): bool =
+  (s.count == 0)
+
+template withData*[T](s: Selector[T], fd: SocketHandle, value,
+                        body: untyped) =
+  mixin checkFd
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  if s.fds[fdi].ident != 0:
+    var value = addr(s.fds[fdi].key.data)
+    body
+
+template withData*[T](s: Selector[T], fd: SocketHandle, value, body1,
+                        body2: untyped) =
+  mixin checkFd
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  if s.fds[fdi].ident != 0:
+    var value = addr(s.fds[fdi].key.data)
+    body1
+  else:
+    body2
diff --git a/lib/pure/ioselects/ioselectors_kqueue.nim b/lib/pure/ioselects/ioselectors_kqueue.nim
new file mode 100644
index 000000000..29a201863
--- /dev/null
+++ b/lib/pure/ioselects/ioselectors_kqueue.nim
@@ -0,0 +1,439 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2016 Eugene Kabanov
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+#  This module implements BSD kqueue().
+
+import posix, times, kqueue
+
+const
+  # Maximum number of cached changes.
+  MAX_KQUEUE_CHANGE_EVENTS = 64
+  # Maximum number of events that can be returned.
+  MAX_KQUEUE_RESULT_EVENTS = 64
+
+when defined(macosx) or defined(freebsd):
+  when defined(macosx):
+    const MAX_DESCRIPTORS_ID = 29 # KERN_MAXFILESPERPROC (MacOS)
+  else:
+    const MAX_DESCRIPTORS_ID = 27 # KERN_MAXFILESPERPROC (FreeBSD)
+  proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr int,
+              newp: pointer, newplen: int): cint
+       {.importc: "sysctl",header: """#include <sys/types.h>
+                                      #include <sys/sysctl.h>"""}
+elif defined(netbsd) or defined(openbsd):
+  # OpenBSD and NetBSD don't have KERN_MAXFILESPERPROC, so we are using
+  # KERN_MAXFILES, because KERN_MAXFILES is always bigger,
+  # than KERN_MAXFILESPERPROC.
+  const MAX_DESCRIPTORS_ID = 7 # KERN_MAXFILES
+  proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr int,
+              newp: pointer, newplen: int): cint
+       {.importc: "sysctl",header: """#include <sys/param.h>
+                                      #include <sys/sysctl.h>"""}
+
+when hasThreadSupport:
+  type
+    SelectorImpl[T] = object
+      kqFD : cint
+      maxFD : int
+      changesTable: array[MAX_KQUEUE_CHANGE_EVENTS, KEvent]
+      changesCount: int
+      fds: ptr SharedArray[SelectorKey[T]]
+      count: int
+      changesLock: Lock
+    Selector*[T] = ptr SelectorImpl[T]
+else:
+  type
+    SelectorImpl[T] = object
+      kqFD : cint
+      maxFD : int
+      changesTable: array[MAX_KQUEUE_CHANGE_EVENTS, KEvent]
+      changesCount: int
+      fds: seq[SelectorKey[T]]
+      count: int
+    Selector*[T] = ref SelectorImpl[T]
+
+type
+  SelectEventImpl = object
+    rfd: cint
+    wfd: cint
+# SelectEvent is declared as `ptr` to be placed in `shared memory`,
+# so you can share one SelectEvent handle between threads.
+type SelectEvent* = ptr SelectEventImpl
+
+proc newSelector*[T](): Selector[T] =
+  var maxFD = 0.cint
+  var size = sizeof(cint)
+  var namearr = [1.cint, MAX_DESCRIPTORS_ID.cint]
+  # Obtain maximum number of file descriptors for process
+  if sysctl(addr(namearr[0]), 2, cast[pointer](addr maxFD), addr size,
+            nil, 0) != 0:
+    raiseOsError(osLastError())
+
+  var kqFD = kqueue()
+  if kqFD < 0:
+    raiseOsError(osLastError())
+
+  when hasThreadSupport:
+    result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T])))
+    result.kqFD = kqFD
+    result.maxFD = maxFD.int
+    result.fds = allocSharedArray[SelectorKey[T]](maxFD)
+    initLock(result.changesLock)
+  else:
+    result = Selector[T]()
+    result.kqFD = kqFD
+    result.maxFD = maxFD.int
+    result.fds = newSeq[SelectorKey[T]](maxFD)
+
+proc close*[T](s: Selector[T]) =
+  if posix.close(s.kqFD) != 0:
+    raiseOSError(osLastError())
+  when hasThreadSupport:
+    deinitLock(s.changesLock)
+    deallocSharedArray(s.fds)
+    deallocShared(cast[pointer](s))
+
+proc newSelectEvent*(): SelectEvent =
+  var fds: array[2, cint]
+  if posix.pipe(fds) == -1:
+    raiseOSError(osLastError())
+  setNonBlocking(fds[0])
+  setNonBlocking(fds[1])
+  result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl)))
+  result.rfd = fds[0]
+  result.wfd = fds[1]
+
+proc setEvent*(ev: SelectEvent) =
+  var data: uint64 = 1
+  if posix.write(ev.wfd, addr data, sizeof(uint64)) != sizeof(uint64):
+    raiseOSError(osLastError())
+
+proc close*(ev: SelectEvent) =
+  discard posix.close(cint(ev.rfd))
+  discard posix.close(cint(ev.wfd))
+  deallocShared(cast[pointer](ev))
+
+template checkFd(s, f) =
+  if f >= s.maxFD:
+    raise newException(ValueError, "Maximum file descriptors exceeded")
+
+when hasThreadSupport:
+  template withChangeLock[T](s: Selector[T], body: untyped) =
+    acquire(s.changesLock)
+    {.locks: [s.changesLock].}:
+      try:
+        body
+      finally:
+        release(s.changesLock)
+else:
+  template withChangeLock(s, body: untyped) =
+    body
+
+template modifyKQueue[T](s: Selector[T], nident: uint, nfilter: cshort,
+                         nflags: cushort, nfflags: cuint, ndata: int,
+                         nudata: pointer) =
+  mixin withChangeLock
+  s.withChangeLock():
+    s.changesTable[s.changesCount] = KEvent(ident: nident,
+                                            filter: nfilter, flags: nflags,
+                                            fflags: nfflags, data: ndata,
+                                            udata: nudata)
+    inc(s.changesCount)
+    if s.changesCount == MAX_KQUEUE_CHANGE_EVENTS:
+      if kevent(s.kqFD, addr(s.changesTable[0]), cint(s.changesCount),
+                nil, 0, nil) == -1:
+        raiseOSError(osLastError())
+      s.changesCount = 0
+
+proc registerHandle*[T](s: Selector[T], fd: SocketHandle,
+                        events: set[Event], data: T) =
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  doAssert(s.fds[fdi].ident == 0)
+  s.setKey(fdi, fdi, events, 0, data)
+  if events != {}:
+    if Event.Read in events:
+      modifyKQueue(s, fdi.uint, EVFILT_READ, EV_ADD, 0, 0, nil)
+      inc(s.count)
+    if Event.Write in events:
+      modifyKQueue(s, fdi.uint, EVFILT_WRITE, EV_ADD, 0, 0, nil)
+      inc(s.count)
+
+proc updateHandle*[T](s: Selector[T], fd: SocketHandle,
+                      events: set[Event]) =
+  let maskEvents = {Event.Timer, Event.Signal, Event.Process, Event.Vnode,
+                    Event.User, Event.Oneshot, Event.Error}
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  var pkey = addr(s.fds[fdi])
+  doAssert(pkey.ident != 0)
+  doAssert(pkey.events * maskEvents == {})
+
+  if pkey.events != events:
+    if (Event.Read in pkey.events) and (Event.Read notin events):
+      modifyKQueue(s, fdi.uint, EVFILT_READ, EV_DELETE, 0, 0, nil)
+      dec(s.count)
+    if (Event.Write in pkey.events) and (Event.Write notin events):
+      modifyKQueue(s, fdi.uint, EVFILT_WRITE, EV_DELETE, 0, 0, nil)
+      dec(s.count)
+    if (Event.Read notin pkey.events) and (Event.Read in events):
+      modifyKQueue(s, fdi.uint, EVFILT_READ, EV_ADD, 0, 0, nil)
+      inc(s.count)
+    if (Event.Write notin pkey.events) and (Event.Write in events):
+      modifyKQueue(s, fdi.uint, EVFILT_WRITE, EV_ADD, 0, 0, nil)
+      inc(s.count)
+    pkey.events = events
+
+proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool,
+                       data: T): int {.discardable.} =
+  var fdi = posix.socket(posix.AF_INET, posix.SOCK_STREAM,
+                         posix.IPPROTO_TCP).int
+  if fdi == -1:
+    raiseOsError(osLastError())
+
+  s.checkFd(fdi)
+  doAssert(s.fds[fdi].ident == 0)
+
+  let events = if oneshot: {Event.Timer, Event.Oneshot} else: {Event.Timer}
+  let flags: cushort = if oneshot: EV_ONESHOT or EV_ADD else: EV_ADD
+
+  s.setKey(fdi, fdi, events, 0, data)
+  # EVFILT_TIMER on Open/Net(BSD) has granularity of only milliseconds,
+  # but MacOS and FreeBSD allow use `0` as `fflags` to use milliseconds
+  # too
+  modifyKQueue(s, fdi.uint, EVFILT_TIMER, flags, 0, cint(timeout), nil)
+  inc(s.count)
+  result = fdi
+
+proc registerSignal*[T](s: Selector[T], signal: int,
+                        data: T): int {.discardable.} =
+  var fdi = posix.socket(posix.AF_INET, posix.SOCK_STREAM,
+                         posix.IPPROTO_TCP).int
+  if fdi == -1:
+    raiseOsError(osLastError())
+
+  s.checkFd(fdi)
+  doAssert(s.fds[fdi].ident == 0)
+
+  s.setKey(fdi, signal, {Event.Signal}, signal, data)
+  var nmask, omask: Sigset
+  discard sigemptyset(nmask)
+  discard sigemptyset(omask)
+  discard sigaddset(nmask, cint(signal))
+  blockSignals(nmask, omask)
+  # to be compatible with linux semantic we need to "eat" signals
+  posix.signal(cint(signal), SIG_IGN)
+  modifyKQueue(s, signal.uint, EVFILT_SIGNAL, EV_ADD, 0, 0,
+               cast[pointer](fdi))
+  inc(s.count)
+  result = fdi
+
+proc registerProcess*[T](s: Selector[T], pid: int,
+                             data: T): int {.discardable.} =
+  var fdi = posix.socket(posix.AF_INET, posix.SOCK_STREAM,
+                         posix.IPPROTO_TCP).int
+  if fdi == -1:
+    raiseOsError(osLastError())
+
+  s.checkFd(fdi)
+  doAssert(s.fds[fdi].ident == 0)
+
+  var kflags: cushort = EV_ONESHOT or EV_ADD
+  setKey(s, fdi, pid, {Event.Process, Event.Oneshot}, pid, data)
+  modifyKQueue(s, pid.uint, EVFILT_PROC, kflags, NOTE_EXIT, 0,
+               cast[pointer](fdi))
+  inc(s.count)
+  result = fdi
+
+proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
+  let fdi = ev.rfd.int
+  doAssert(s.fds[fdi].ident == 0)
+  setKey(s, fdi, fdi, {Event.User}, 0, data)
+  modifyKQueue(s, fdi.uint, EVFILT_READ, EV_ADD, 0, 0, nil)
+  inc(s.count)
+
+proc unregister*[T](s: Selector[T], fd: int|SocketHandle) =
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  var pkey = addr(s.fds[fdi])
+  doAssert(pkey.ident != 0)
+
+  if pkey.events != {}:
+    if pkey.events * {Event.Read, Event.Write} != {}:
+      if Event.Read in pkey.events:
+        modifyKQueue(s, fdi.uint, EVFILT_READ, EV_DELETE, 0, 0, nil)
+        dec(s.count)
+      if Event.Write in pkey.events:
+        modifyKQueue(s, fdi.uint, EVFILT_WRITE, EV_DELETE, 0, 0, nil)
+        dec(s.count)
+    elif Event.Timer in pkey.events:
+      discard posix.close(cint(pkey.key.fd))
+      modifyKQueue(s, fdi.uint, EVFILT_TIMER, EV_DELETE, 0, 0, nil)
+      dec(s.count)
+    elif Event.Signal in pkey.events:
+      var nmask, omask: Sigset
+      var signal = cint(pkey.param)
+      discard sigemptyset(nmask)
+      discard sigemptyset(omask)
+      discard sigaddset(nmask, signal)
+      unblockSignals(nmask, omask)
+      posix.signal(signal, SIG_DFL)
+      discard posix.close(cint(pkey.key.fd))
+      modifyKQueue(s, fdi.uint, EVFILT_SIGNAL, EV_DELETE, 0, 0, nil)
+      dec(s.count)
+    elif Event.Process in pkey.events:
+      discard posix.close(cint(pkey.key.fd))
+      modifyKQueue(s, fdi.uint, EVFILT_PROC, EV_DELETE, 0, 0, nil)
+      dec(s.count)
+    elif Event.User in pkey.events:
+      modifyKQueue(s, fdi.uint, EVFILT_READ, EV_DELETE, 0, 0, nil)
+      dec(s.count)
+  pkey.ident = 0
+  pkey.events = {}
+
+proc unregister*[T](s: Selector[T], ev: SelectEvent) =
+  let fdi = int(ev.rfd)
+  s.checkFd(fdi)
+  var pkey = addr(s.fds[fdi])
+  doAssert(pkey.ident != 0)
+  doAssert(Event.User in pkey.events)
+  pkey.ident = 0
+  pkey.events = {}
+  modifyKQueue(s, fdi.uint, EVFILT_READ, EV_DELETE, 0, 0, nil)
+  dec(s.count)
+
+proc flush*[T](s: Selector[T]) =
+  s.withChangeLock():
+    var tv = Timespec()
+    if kevent(s.kqFD, addr(s.changesTable[0]), cint(s.changesCount),
+              nil, 0, addr tv) == -1:
+      raiseOSError(osLastError())
+    s.changesCount = 0
+
+proc selectInto*[T](s: Selector[T], timeout: int,
+                    results: var openarray[ReadyKey[T]]): int =
+  var
+    tv: Timespec
+    resTable: array[MAX_KQUEUE_RESULT_EVENTS, KEvent]
+    ptv = addr tv
+    maxres = MAX_KQUEUE_RESULT_EVENTS
+
+  if timeout != -1:
+    if timeout >= 1000:
+      tv.tv_sec = (timeout div 1_000).Time
+      tv.tv_nsec = (timeout %% 1_000) * 1_000_000
+    else:
+      tv.tv_sec = 0.Time
+      tv.tv_nsec = timeout * 1_000_000
+  else:
+    ptv = nil
+
+  if maxres > len(results):
+    maxres = len(results)
+
+  var count = 0
+  s.withChangeLock():
+    count = kevent(s.kqFD, addr(s.changesTable[0]), cint(s.changesCount),
+                   addr(resTable[0]), cint(maxres), ptv)
+    s.changesCount = 0
+
+  if count < 0:
+    result = 0
+    let err = osLastError()
+    if cint(err) != EINTR:
+      raiseOSError(err)
+  elif count == 0:
+    result = 0
+  else:
+    var i = 0
+    var k = 0
+    var pkey: ptr SelectorKey[T]
+    while i < count:
+      let kevent = addr(resTable[i])
+      if (kevent.flags and EV_ERROR) == 0:
+        case kevent.filter:
+        of EVFILT_READ:
+          pkey = addr(s.fds[kevent.ident.int])
+          pkey.key.events = {Event.Read}
+          if Event.User in pkey.events:
+            var data: uint64 = 0
+            if posix.read(kevent.ident.cint, addr data,
+                          sizeof(uint64)) != sizeof(uint64):
+              let err = osLastError()
+              if err == OSErrorCode(EAGAIN):
+                # someone already consumed event data
+                inc(i)
+                continue
+              else:
+                raiseOSError(osLastError())
+            pkey.key.events = {Event.User}
+        of EVFILT_WRITE:
+          pkey = addr(s.fds[kevent.ident.int])
+          pkey.key.events = {Event.Write}
+        of EVFILT_TIMER:
+          pkey = addr(s.fds[kevent.ident.int])
+          if Event.Oneshot in pkey.events:
+            if posix.close(cint(pkey.ident)) == -1:
+              raiseOSError(osLastError())
+            pkey.ident = 0
+            pkey.events = {}
+            dec(s.count)
+          pkey.key.events = {Event.Timer}
+        of EVFILT_VNODE:
+          pkey = addr(s.fds[kevent.ident.int])
+          pkey.key.events = {Event.Vnode}
+        of EVFILT_SIGNAL:
+          pkey = addr(s.fds[cast[int](kevent.udata)])
+          pkey.key.events = {Event.Signal}
+        of EVFILT_PROC:
+          pkey = addr(s.fds[cast[int](kevent.udata)])
+          if posix.close(cint(pkey.ident)) == -1:
+            raiseOSError(osLastError())
+          pkey.ident = 0
+          pkey.events = {}
+          dec(s.count)
+          pkey.key.events = {Event.Process}
+        else:
+          raise newException(ValueError, "Unsupported kqueue filter in queue")
+
+        if (kevent.flags and EV_EOF) != 0:
+          pkey.key.events.incl(Event.Error)
+
+        results[k] = pkey.key
+        inc(k)
+      inc(i)
+    result = k
+
+proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] =
+  result = newSeq[ReadyKey[T]](MAX_KQUEUE_RESULT_EVENTS)
+  let count = selectInto(s, timeout, result)
+  result.setLen(count)
+
+template isEmpty*[T](s: Selector[T]): bool =
+  (s.count == 0)
+
+template withData*[T](s: Selector[T], fd: SocketHandle, value,
+                        body: untyped) =
+  mixin checkFd
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  if s.fds[fdi].ident != 0:
+    var value = addr(s.fds[fdi].key.data)
+    body
+
+template withData*[T](s: Selector[T], fd: SocketHandle, value, body1,
+                        body2: untyped) =
+  mixin checkFd
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  if s.fds[fdi].ident != 0:
+    var value = addr(s.fds[fdi].key.data)
+    body1
+  else:
+    body2
diff --git a/lib/pure/ioselects/ioselectors_poll.nim b/lib/pure/ioselects/ioselectors_poll.nim
new file mode 100644
index 000000000..d2a0a1273
--- /dev/null
+++ b/lib/pure/ioselects/ioselectors_poll.nim
@@ -0,0 +1,295 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2016 Eugene Kabanov
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+# This module implements Posix poll().
+
+import posix, times
+
+# Maximum number of events that can be returned
+const MAX_POLL_RESULT_EVENTS = 64
+
+when hasThreadSupport:
+  type
+    SelectorImpl[T] = object
+      maxFD : int
+      pollcnt: int
+      fds: ptr SharedArray[SelectorKey[T]]
+      pollfds: ptr SharedArray[TPollFd]
+      count: int
+      lock: Lock
+    Selector*[T] = ptr SelectorImpl[T]
+else:
+  type
+    SelectorImpl[T] = object
+      maxFD : int
+      pollcnt: int
+      fds: seq[SelectorKey[T]]
+      pollfds: seq[TPollFd]
+      count: int
+    Selector*[T] = ref SelectorImpl[T]
+
+type
+  SelectEventImpl = object
+    rfd: cint
+    wfd: cint
+  SelectEvent* = ptr SelectEventImpl
+
+var RLIMIT_NOFILE {.importc: "RLIMIT_NOFILE",
+                    header: "<sys/resource.h>".}: cint
+type
+  rlimit {.importc: "struct rlimit",
+           header: "<sys/resource.h>", pure, final.} = object
+    rlim_cur: int
+    rlim_max: int
+proc getrlimit(resource: cint, rlp: var rlimit): cint
+     {.importc: "getrlimit",header: "<sys/resource.h>".}
+
+when hasThreadSupport:
+  template withPollLock[T](s: Selector[T], body: untyped) =
+    acquire(s.lock)
+    {.locks: [s.lock].}:
+      try:
+        body
+      finally:
+        release(s.lock)
+else:
+  template withPollLock(s, body: untyped) =
+    body
+
+proc newSelector*[T](): Selector[T] =
+  var a = rlimit()
+  if getrlimit(RLIMIT_NOFILE, a) != 0:
+    raiseOsError(osLastError())
+  var maxFD = int(a.rlim_max)
+
+  when hasThreadSupport:
+    result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T])))
+    result.maxFD = maxFD
+    result.fds = allocSharedArray[SelectorKey[T]](maxFD)
+    result.pollfds = allocSharedArray[TPollFd](maxFD)
+    initLock(result.lock)
+  else:
+    result = Selector[T]()
+    result.maxFD = maxFD
+    result.fds = newSeq[SelectorKey[T]](maxFD)
+    result.pollfds = newSeq[TPollFd](maxFD)
+
+proc close*[T](s: Selector[T]) =
+  when hasThreadSupport:
+    deinitLock(s.lock)
+    deallocSharedArray(s.fds)
+    deallocSharedArray(s.pollfds)
+    deallocShared(cast[pointer](s))
+
+template pollAdd[T](s: Selector[T], sock: cint, events: set[Event]) =
+  withPollLock(s):
+    var pollev: cshort = 0
+    if Event.Read in events: pollev = pollev or POLLIN
+    if Event.Write in events: pollev = pollev or POLLOUT
+    s.pollfds[s.pollcnt].fd = cint(sock)
+    s.pollfds[s.pollcnt].events = pollev
+    inc(s.count)
+    inc(s.pollcnt)
+
+template pollUpdate[T](s: Selector[T], sock: cint, events: set[Event]) =
+  withPollLock(s):
+    var i = 0
+    var pollev: cshort = 0
+    if Event.Read in events: pollev = pollev or POLLIN
+    if Event.Write in events: pollev = pollev or POLLOUT
+
+    while i < s.pollcnt:
+      if s.pollfds[i].fd == sock:
+        s.pollfds[i].events = pollev
+        break
+      inc(i)
+
+    if i == s.pollcnt:
+      raise newException(ValueError, "Descriptor is not registered in queue")
+
+template pollRemove[T](s: Selector[T], sock: cint) =
+  withPollLock(s):
+    var i = 0
+    while i < s.pollcnt:
+      if s.pollfds[i].fd == sock:
+        if i == s.pollcnt - 1:
+          s.pollfds[i].fd = 0
+          s.pollfds[i].events = 0
+          s.pollfds[i].revents = 0
+        else:
+          while i < (s.pollcnt - 1):
+            s.pollfds[i].fd = s.pollfds[i + 1].fd
+            s.pollfds[i].events = s.pollfds[i + 1].events
+            inc(i)
+        break
+      inc(i)
+    dec(s.pollcnt)
+    dec(s.count)
+
+template checkFd(s, f) =
+  if f >= s.maxFD:
+    raise newException(ValueError, "Maximum file descriptors exceeded")
+
+proc registerHandle*[T](s: Selector[T], fd: SocketHandle,
+                        events: set[Event], data: T) =
+  var fdi = int(fd)
+  s.checkFd(fdi)
+  doAssert(s.fds[fdi].ident == 0)
+  s.setKey(fdi, fdi, events, 0, data)
+  if events != {}: s.pollAdd(fdi.cint, events)
+
+proc updateHandle*[T](s: Selector[T], fd: SocketHandle,
+                      events: set[Event]) =
+  let maskEvents = {Event.Timer, Event.Signal, Event.Process, Event.Vnode,
+                    Event.User, Event.Oneshot, Event.Error}
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  var pkey = addr(s.fds[fdi])
+  doAssert(pkey.ident != 0)
+  doAssert(pkey.events * maskEvents == {})
+
+  if pkey.events != events:
+    if pkey.events == {}:
+      s.pollAdd(fd.cint, events)
+    else:
+      if events != {}:
+        s.pollUpdate(fd.cint, events)
+      else:
+        s.pollRemove(fd.cint)
+    pkey.events = events
+
+proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
+  var fdi = int(ev.rfd)
+  doAssert(s.fds[fdi].ident == 0)
+  var events = {Event.User}
+  setKey(s, fdi, fdi, events, 0, data)
+  events.incl(Event.Read)
+  s.pollAdd(fdi.cint, events)
+
+proc flush*[T](s: Selector[T]) = discard
+
+proc unregister*[T](s: Selector[T], fd: int|SocketHandle) =
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  var pkey = addr(s.fds[fdi])
+  doAssert(pkey.ident != 0)
+  pkey.ident = 0
+  pkey.events = {}
+  s.pollRemove(fdi.cint)
+
+proc unregister*[T](s: Selector[T], ev: SelectEvent) =
+  let fdi = int(ev.rfd)
+  s.checkFd(fdi)
+  var pkey = addr(s.fds[fdi])
+  doAssert(pkey.ident != 0)
+  doAssert(Event.User in pkey.events)
+  pkey.ident = 0
+  pkey.events = {}
+  s.pollRemove(fdi.cint)
+
+proc newSelectEvent*(): SelectEvent =
+  var fds: array[2, cint]
+  if posix.pipe(fds) == -1:
+    raiseOSError(osLastError())
+  setNonBlocking(fds[0])
+  setNonBlocking(fds[1])
+  result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl)))
+  result.rfd = fds[0]
+  result.wfd = fds[1]
+
+proc setEvent*(ev: SelectEvent) =
+  var data: uint64 = 1
+  if posix.write(ev.wfd, addr data, sizeof(uint64)) != sizeof(uint64):
+    raiseOSError(osLastError())
+
+proc close*(ev: SelectEvent) =
+  discard posix.close(cint(ev.rfd))
+  discard posix.close(cint(ev.wfd))
+  deallocShared(cast[pointer](ev))
+
+proc selectInto*[T](s: Selector[T], timeout: int,
+                    results: var openarray[ReadyKey[T]]): int =
+  var maxres = MAX_POLL_RESULT_EVENTS
+  if maxres > len(results):
+    maxres = len(results)
+
+  s.withPollLock():
+    let count = posix.poll(addr(s.pollfds[0]), Tnfds(s.pollcnt), timeout)
+    if count < 0:
+      result = 0
+      let err = osLastError()
+      if err.cint == EINTR:
+        discard
+      else:
+        raiseOSError(osLastError())
+    elif count == 0:
+      result = 0
+    else:
+      var i = 0
+      var k = 0
+      var rindex = 0
+      while (i < s.pollcnt) and (k < count) and (rindex < maxres):
+        let revents = s.pollfds[i].revents
+        if revents != 0:
+          let fd = s.pollfds[i].fd
+          var skey = addr(s.fds[fd])
+          skey.key.events = {}
+
+          if (revents and POLLIN) != 0:
+            skey.key.events.incl(Event.Read)
+            if Event.User in skey.events:
+              var data: uint64 = 0
+              if posix.read(fd, addr data, sizeof(int)) != sizeof(int):
+                let err = osLastError()
+                if err != OSErrorCode(EAGAIN):
+                  raiseOSError(osLastError())
+                else:
+                  # someone already consumed event data
+                  inc(i)
+                  continue
+              skey.key.events = {Event.User}
+          if (revents and POLLOUT) != 0:
+            skey.key.events.incl(Event.Write)
+          if (revents and POLLERR) != 0 or (revents and POLLHUP) != 0 or
+             (revents and POLLNVAL) != 0:
+            skey.key.events.incl(Event.Error)
+          results[rindex] = skey.key
+          s.pollfds[i].revents = 0
+          inc(rindex)
+          inc(k)
+        inc(i)
+      result = k
+
+proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] =
+  result = newSeq[ReadyKey[T]](MAX_POLL_RESULT_EVENTS)
+  let count = selectInto(s, timeout, result)
+  result.setLen(count)
+
+template isEmpty*[T](s: Selector[T]): bool =
+  (s.count == 0)
+
+template withData*[T](s: Selector[T], fd: SocketHandle, value,
+                        body: untyped) =
+  mixin checkFd
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  if s.fds[fdi].ident != 0:
+    var value = addr(s.fds[fdi].key.data)
+    body
+
+template withData*[T](s: Selector[T], fd: SocketHandle, value, body1,
+                        body2: untyped) =
+  mixin checkFd
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  if s.fds[fdi].ident != 0:
+    var value = addr(s.fds[fdi].key.data)
+    body1
+  else:
+    body2
diff --git a/lib/pure/ioselects/ioselectors_select.nim b/lib/pure/ioselects/ioselectors_select.nim
new file mode 100644
index 000000000..f8099f9a0
--- /dev/null
+++ b/lib/pure/ioselects/ioselectors_select.nim
@@ -0,0 +1,416 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2016 Eugene Kabanov
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+# This module implements Posix and Windows select().
+
+import times, nativesockets
+
+when defined(windows):
+  import winlean
+  when defined(gcc):
+    {.passL: "-lws2_32".}
+  elif defined(vcc):
+    {.passL: "ws2_32.lib".}
+  const platformHeaders = """#include <winsock2.h>
+                             #include <windows.h>"""
+  const EAGAIN = WSAEWOULDBLOCK
+else:
+  const platformHeaders = """#include <sys/select.h>
+                             #include <sys/time.h>
+                             #include <sys/types.h>
+                             #include <unistd.h>"""
+type
+  Fdset {.importc: "fd_set", header: platformHeaders, pure, final.} = object
+var
+  FD_SETSIZE {.importc: "FD_SETSIZE", header: platformHeaders.}: cint
+
+proc IOFD_SET(fd: SocketHandle, fdset: ptr Fdset)
+     {.cdecl, importc: "FD_SET", header: platformHeaders, inline.}
+proc IOFD_CLR(fd: SocketHandle, fdset: ptr Fdset)
+     {.cdecl, importc: "FD_CLR", header: platformHeaders, inline.}
+proc IOFD_ZERO(fdset: ptr Fdset)
+     {.cdecl, importc: "FD_ZERO", header: platformHeaders, inline.}
+
+when defined(windows):
+  proc IOFD_ISSET(fd: SocketHandle, fdset: ptr Fdset): cint
+       {.stdcall, importc: "FD_ISSET", header: platformHeaders, inline.}
+  proc ioselect(nfds: cint, readFds, writeFds, exceptFds: ptr Fdset,
+                timeout: ptr Timeval): cint
+       {.stdcall, importc: "select", header: platformHeaders.}
+else:
+  proc IOFD_ISSET(fd: SocketHandle, fdset: ptr Fdset): cint
+       {.cdecl, importc: "FD_ISSET", header: platformHeaders, inline.}
+  proc ioselect(nfds: cint, readFds, writeFds, exceptFds: ptr Fdset,
+                timeout: ptr Timeval): cint
+       {.cdecl, importc: "select", header: platformHeaders.}
+
+when hasThreadSupport:
+  type
+    SelectorImpl[T] = object
+      rSet: FdSet
+      wSet: FdSet
+      eSet: FdSet
+      maxFD: int
+      fds: ptr SharedArray[SelectorKey[T]]
+      count: int
+      lock: Lock
+    Selector*[T] = ptr SelectorImpl[T]
+else:
+  type
+    SelectorImpl[T] = object
+      rSet: FdSet
+      wSet: FdSet
+      eSet: FdSet
+      maxFD: int
+      fds: seq[SelectorKey[T]]
+      count: int
+    Selector*[T] = ref SelectorImpl[T]
+
+type
+  SelectEventImpl = object
+    rsock: SocketHandle
+    wsock: SocketHandle
+  SelectEvent* = ptr SelectEventImpl
+
+when hasThreadSupport:
+  template withSelectLock[T](s: Selector[T], body: untyped) =
+    acquire(s.lock)
+    {.locks: [s.lock].}:
+      try:
+        body
+      finally:
+        release(s.lock)
+else:
+  template withSelectLock[T](s: Selector[T], body: untyped) =
+    body
+
+proc newSelector*[T](): Selector[T] =
+  when hasThreadSupport:
+    result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T])))
+    result.fds = allocSharedArray[SelectorKey[T]](FD_SETSIZE)
+    initLock result.lock
+  else:
+    result = Selector[T]()
+    result.fds = newSeq[SelectorKey[T]](FD_SETSIZE)
+
+  IOFD_ZERO(addr result.rSet)
+  IOFD_ZERO(addr result.wSet)
+  IOFD_ZERO(addr result.eSet)
+
+proc close*[T](s: Selector[T]) =
+  when hasThreadSupport:
+    deallocSharedArray(s.fds)
+    deallocShared(cast[pointer](s))
+
+when defined(windows):
+  proc newSelectEvent*(): SelectEvent =
+    var ssock = newNativeSocket()
+    var wsock = newNativeSocket()
+    var rsock: SocketHandle = INVALID_SOCKET
+    var saddr = Sockaddr_in()
+
+    saddr.sin_family = winlean.AF_INET
+    saddr.sin_port = 0
+    saddr.sin_addr.s_addr = INADDR_ANY
+    if bindAddr(ssock, cast[ptr SockAddr](addr(saddr)),
+                sizeof(saddr).SockLen) < 0'i32:
+      raiseOSError(osLastError())
+
+    if winlean.listen(ssock, 1) == -1:
+      raiseOSError(osLastError())
+
+    var namelen = sizeof(saddr).SockLen
+    if getsockname(ssock, cast[ptr SockAddr](addr(saddr)),
+                   addr(namelen)) == -1'i32:
+      raiseOSError(osLastError())
+
+    saddr.sin_addr.s_addr = 0x0100007F
+    if winlean.connect(wsock, cast[ptr SockAddr](addr(saddr)),
+                       sizeof(saddr).SockLen) == -1:
+      raiseOSError(osLastError())
+    namelen = sizeof(saddr).SockLen
+    rsock = winlean.accept(ssock, cast[ptr SockAddr](addr(saddr)),
+                           cast[ptr SockLen](addr(namelen)))
+    if rsock == SocketHandle(-1):
+      raiseOSError(osLastError())
+
+    if winlean.closesocket(ssock) == -1:
+      raiseOSError(osLastError())
+
+    var mode = clong(1)
+    if ioctlsocket(rsock, FIONBIO, addr(mode)) == -1:
+      raiseOSError(osLastError())
+    mode = clong(1)
+    if ioctlsocket(wsock, FIONBIO, addr(mode)) == -1:
+      raiseOSError(osLastError())
+
+    result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl)))
+    result.rsock = rsock
+    result.wsock = wsock
+
+  proc setEvent*(ev: SelectEvent) =
+    var data: int = 1
+    if winlean.send(ev.wsock, cast[pointer](addr data),
+                    cint(sizeof(int)), 0) != sizeof(int):
+      raiseOSError(osLastError())
+
+  proc close*(ev: SelectEvent) =
+    discard winlean.closesocket(ev.rsock)
+    discard winlean.closesocket(ev.wsock)
+    deallocShared(cast[pointer](ev))
+
+else:
+  proc newSelectEvent*(): SelectEvent =
+    var fds: array[2, cint]
+    if posix.pipe(fds) == -1:
+      raiseOSError(osLastError())
+    setNonBlocking(fds[0])
+    setNonBlocking(fds[1])
+    result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl)))
+    result.rsock = SocketHandle(fds[0])
+    result.wsock = SocketHandle(fds[1])
+
+  proc setEvent*(ev: SelectEvent) =
+    var data: uint64 = 1
+    if posix.write(cint(ev.wsock), addr data, sizeof(uint64)) != sizeof(uint64):
+      raiseOSError(osLastError())
+
+  proc close*(ev: SelectEvent) =
+    discard posix.close(cint(ev.rsock))
+    discard posix.close(cint(ev.wsock))
+    deallocShared(cast[pointer](ev))
+
+proc setKey[T](s: Selector[T], fd: SocketHandle, events: set[Event], data: T) =
+  var i = 0
+  let fdi = int(fd)
+  while i < FD_SETSIZE:
+    if s.fds[i].ident == 0:
+      var pkey = addr(s.fds[i])
+      pkey.ident = fdi
+      pkey.events = events
+      pkey.key.fd = fd.int
+      pkey.key.events = {}
+      pkey.key.data = data
+      break
+    inc(i)
+  if i == FD_SETSIZE:
+    raise newException(ValueError, "Maximum numbers of fds exceeded")
+
+proc getKey[T](s: Selector[T], fd: SocketHandle): ptr SelectorKey[T] =
+  var i = 0
+  let fdi = int(fd)
+  while i < FD_SETSIZE:
+    if s.fds[i].ident == fdi:
+      result = addr(s.fds[i])
+      break
+    inc(i)
+  doAssert(i < FD_SETSIZE, "Descriptor not registered in queue")
+
+proc delKey[T](s: Selector[T], fd: SocketHandle) =
+  var i = 0
+  while i < FD_SETSIZE:
+    if s.fds[i].ident == fd.int:
+      s.fds[i].ident = 0
+      s.fds[i].events = {}
+      break
+    inc(i)
+  doAssert(i < FD_SETSIZE, "Descriptor not registered in queue")
+
+proc registerHandle*[T](s: Selector[T], fd: SocketHandle,
+                        events: set[Event], data: T) =
+  when not defined(windows):
+    let fdi = int(fd)
+  s.withSelectLock():
+    s.setKey(fd, events, data)
+    when not defined(windows):
+      if fdi > s.maxFD: s.maxFD = fdi
+    if Event.Read in events:
+      IOFD_SET(fd, addr s.rSet)
+      inc(s.count)
+    if Event.Write in events:
+      IOFD_SET(fd, addr s.wSet)
+      IOFD_SET(fd, addr s.eSet)
+      inc(s.count)
+
+proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
+  when not defined(windows):
+    let fdi = int(ev.rsock)
+  s.withSelectLock():
+    s.setKey(ev.rsock, {Event.User}, data)
+    when not defined(windows):
+      if fdi > s.maxFD: s.maxFD = fdi
+    IOFD_SET(ev.rsock, addr s.rSet)
+    inc(s.count)
+
+proc updateHandle*[T](s: Selector[T], fd: SocketHandle,
+                      events: set[Event]) =
+  let maskEvents = {Event.Timer, Event.Signal, Event.Process, Event.Vnode,
+                    Event.User, Event.Oneshot, Event.Error}
+  s.withSelectLock():
+    var pkey = s.getKey(fd)
+    doAssert(pkey.events * maskEvents == {})
+    if pkey.events != events:
+      if (Event.Read in pkey.events) and (Event.Read notin events):
+        IOFD_CLR(fd, addr s.rSet)
+        dec(s.count)
+      if (Event.Write in pkey.events) and (Event.Write notin events):
+        IOFD_CLR(fd, addr s.wSet)
+        IOFD_CLR(fd, addr s.eSet)
+        dec(s.count)
+      if (Event.Read notin pkey.events) and (Event.Read in events):
+        IOFD_SET(fd, addr s.rSet)
+        inc(s.count)
+      if (Event.Write notin pkey.events) and (Event.Write in events):
+        IOFD_SET(fd, addr s.wSet)
+        IOFD_SET(fd, addr s.eSet)
+        inc(s.count)
+      pkey.events = events
+
+proc unregister*[T](s: Selector[T], fd: SocketHandle) =
+  s.withSelectLock():
+    var pkey = s.getKey(fd)
+    if Event.Read in pkey.events:
+      IOFD_CLR(fd, addr s.rSet)
+      dec(s.count)
+    if Event.Write in pkey.events:
+      IOFD_CLR(fd, addr s.wSet)
+      IOFD_CLR(fd, addr s.eSet)
+      dec(s.count)
+    s.delKey(fd)
+
+proc unregister*[T](s: Selector[T], ev: SelectEvent) =
+  let fd = ev.rsock
+  s.withSelectLock():
+    IOFD_CLR(fd, addr s.rSet)
+    dec(s.count)
+    s.delKey(fd)
+
+proc selectInto*[T](s: Selector[T], timeout: int,
+                    results: var openarray[ReadyKey[T]]): int =
+  var tv = Timeval()
+  var ptv = addr tv
+  var rset, wset, eset: FdSet
+
+  if timeout != -1:
+    tv.tv_sec = timeout.int32 div 1_000
+    tv.tv_usec = (timeout.int32 %% 1_000) * 1_000
+  else:
+    ptv = nil
+
+  s.withSelectLock():
+    rset = s.rSet
+    wset = s.wSet
+    eset = s.eSet
+
+  var count = ioselect(cint(s.maxFD) + 1, addr(rset), addr(wset),
+                       addr(eset), ptv)
+  if count < 0:
+    result = 0
+    when defined(windows):
+      raiseOSError(osLastError())
+    else:
+      let err = osLastError()
+      if cint(err) != EINTR:
+        raiseOSError(err)
+  elif count == 0:
+    result = 0
+  else:
+    var rindex = 0
+    var i = 0
+    var k = 0
+
+    while (i < FD_SETSIZE) and (k < count):
+      if s.fds[i].ident != 0:
+        var flag = false
+        var pkey = addr(s.fds[i])
+        pkey.key.events = {}
+        let fd = SocketHandle(pkey.ident)
+        if IOFD_ISSET(fd, addr rset) != 0:
+          if Event.User in pkey.events:
+            var data: uint64 = 0
+            if recv(fd, cast[pointer](addr(data)),
+                    sizeof(uint64).cint, 0) != sizeof(uint64):
+              let err = osLastError()
+              if cint(err) != EAGAIN:
+                raiseOSError(err)
+              else:
+                inc(i)
+                inc(k)
+                continue
+            else:
+              flag = true
+              pkey.key.events = {Event.User}
+          else:
+            flag = true
+            pkey.key.events = {Event.Read}
+        if IOFD_ISSET(fd, addr wset) != 0:
+          pkey.key.events.incl(Event.Write)
+          if IOFD_ISSET(fd, addr eset) != 0:
+            pkey.key.events.incl(Event.Error)
+          flag = true
+        if flag:
+          results[rindex] = pkey.key
+          inc(rindex)
+          inc(k)
+      inc(i)
+    result = rindex
+
+proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] =
+  result = newSeq[ReadyKey[T]](FD_SETSIZE)
+  var count = selectInto(s, timeout, result)
+  result.setLen(count)
+
+proc flush*[T](s: Selector[T]) = discard
+
+template isEmpty*[T](s: Selector[T]): bool =
+  (s.count == 0)
+
+when hasThreadSupport:
+  template withSelectLock[T](s: Selector[T], body: untyped) =
+    acquire(s.lock)
+    {.locks: [s.lock].}:
+      try:
+        body
+      finally:
+        release(s.lock)
+else:
+  template withSelectLock[T](s: Selector[T], body: untyped) =
+    body
+
+template withData*[T](s: Selector[T], fd: SocketHandle, value,
+                      body: untyped) =
+  mixin withSelectLock
+  s.withSelectLock():
+    var value: ptr T
+    let fdi = int(fd)
+    var i = 0
+    while i < FD_SETSIZE:
+      if s.fds[i].ident == fdi:
+        value = addr(s.fds[i].key.data)
+        break
+      inc(i)
+    if i != FD_SETSIZE:
+      body
+
+template withData*[T](s: Selector[T], fd: SocketHandle, value,
+                      body1, body2: untyped) =
+  mixin withSelectLock
+  s.withSelectLock():
+    var value: ptr T
+    let fdi = int(fd)
+    var i = 0
+    while i < FD_SETSIZE:
+      if s.fds[i].ident == fdi:
+        value = addr(s.fds[i].key.data)
+        break
+      inc(i)
+    if i != FD_SETSIZE:
+      body1
+    else:
+      body2
diff --git a/lib/pure/json.nim b/lib/pure/json.nim
index b4eecdf88..19947fbc2 100644
--- a/lib/pure/json.nim
+++ b/lib/pure/json.nim
@@ -126,7 +126,7 @@ type
   TJsonParser: JsonParser, TTokKind: TokKind].}
 
 const
-  errorMessages: array [JsonError, string] = [
+  errorMessages: array[JsonError, string] = [
     "no error",
     "invalid token",
     "string expected",
@@ -139,7 +139,7 @@ const
     "EOF expected",
     "expression expected"
   ]
-  tokToStr: array [TokKind, string] = [
+  tokToStr: array[TokKind, string] = [
     "invalid token",
     "EOF",
     "string literal",
@@ -712,6 +712,7 @@ proc `%`*(b: bool): JsonNode =
 
 proc `%`*(keyVals: openArray[tuple[key: string, val: JsonNode]]): JsonNode =
   ## Generic constructor for JSON data. Creates a new `JObject JsonNode`
+  if keyvals.len == 0: return newJArray()
   result = newJObject()
   for key, val in items(keyVals): result.fields[key] = val
 
@@ -1126,7 +1127,7 @@ proc parseJson(p: var JsonParser): JsonNode =
     discard getTok(p)
     while p.tok != tkCurlyRi:
       if p.tok != tkString:
-        raiseParseErr(p, "string literal as key expected")
+        raiseParseErr(p, "string literal as key")
       var key = p.a
       discard getTok(p)
       eat(p, tkColon)
diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim
index 8a0d2fedd..6a27e6af8 100644
--- a/lib/pure/logging.nim
+++ b/lib/pure/logging.nim
@@ -61,7 +61,7 @@ type
     lvlNone       ## no levels active
 
 const
-  LevelNames*: array [Level, string] = [
+  LevelNames*: array[Level, string] = [
     "DEBUG", "DEBUG", "INFO", "NOTICE", "WARN", "ERROR", "FATAL", "NONE"
   ]
 
diff --git a/lib/pure/nativesockets.nim b/lib/pure/nativesockets.nim
index 15828ff35..4526afa49 100644
--- a/lib/pure/nativesockets.nim
+++ b/lib/pure/nativesockets.nim
@@ -326,8 +326,13 @@ proc getHostByAddr*(ip: string): Hostent {.tags: [ReadIOEffect].} =
                                   cint(AF_INET))
     if s == nil: raiseOSError(osLastError())
   else:
-    var s = posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).Socklen,
-                                cint(posix.AF_INET))
+    var s =
+      when defined(android4):
+        posix.gethostbyaddr(cast[cstring](addr(myaddr)), sizeof(myaddr).cint,
+                            cint(posix.AF_INET))
+      else:
+        posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).Socklen,
+                            cint(posix.AF_INET))
     if s == nil:
       raiseOSError(osLastError(), $hstrerror(h_errno))
 
diff --git a/lib/pure/nimprof.nim b/lib/pure/nimprof.nim
index 5a7deaab0..956c35b2f 100644
--- a/lib/pure/nimprof.nim
+++ b/lib/pure/nimprof.nim
@@ -27,7 +27,7 @@ const
   tickCountCorrection = 50_000
 
 when not declared(system.StackTrace):
-  type StackTrace = array [0..20, cstring]
+  type StackTrace = array[0..20, cstring]
   {.deprecated: [TStackTrace: StackTrace].}
 
 # We use a simple hash table of bounded size to keep track of the stack traces:
@@ -35,7 +35,7 @@ type
   ProfileEntry = object
     total: int
     st: StackTrace
-  ProfileData = array [0..64*1024-1, ptr ProfileEntry]
+  ProfileData = array[0..64*1024-1, ptr ProfileEntry]
 {.deprecated: [TProfileEntry: ProfileEntry, TProfileData: ProfileData].}
 
 proc `==`(a, b: StackTrace): bool =
diff --git a/lib/pure/os.nim b/lib/pure/os.nim
index 52ba110a9..5611b8f0c 100644
--- a/lib/pure/os.nim
+++ b/lib/pure/os.nim
@@ -66,13 +66,13 @@ proc osErrorMsg*(): string {.rtl, extern: "nos$1", deprecated.} =
     if err != 0'i32:
       when useWinUnicode:
         var msgbuf: WideCString
-        if formatMessageW(0x00000100 or 0x00001000 or 0x00000200,
+        if formatMessageW(0x00000100 or 0x00001000 or 0x00000200 or 0x000000FF,
                           nil, err, 0, addr(msgbuf), 0, nil) != 0'i32:
           result = $msgbuf
           if msgbuf != nil: localFree(cast[pointer](msgbuf))
       else:
         var msgbuf: cstring
-        if formatMessageA(0x00000100 or 0x00001000 or 0x00000200,
+        if formatMessageA(0x00000100 or 0x00001000 or 0x00000200 or 0x000000FF,
                           nil, err, 0, addr(msgbuf), 0, nil) != 0'i32:
           result = $msgbuf
           if msgbuf != nil: localFree(msgbuf)
@@ -1122,7 +1122,7 @@ proc parseCmdLine*(c: string): seq[string] {.
   while true:
     setLen(a, 0)
     # eat all delimiting whitespace
-    while c[i] == ' ' or c[i] == '\t' or c [i] == '\l' or c [i] == '\r' : inc(i)
+    while c[i] == ' ' or c[i] == '\t' or c[i] == '\l' or c[i] == '\r' : inc(i)
     when defined(windows):
       # parse a single argument according to the above rules:
       if c[i] == '\0': break
diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim
index 9fc816f2f..65588529e 100644
--- a/lib/pure/ospaths.nim
+++ b/lib/pure/ospaths.nim
@@ -10,7 +10,7 @@
 # Included by the ``os`` module but a module in its own right for NimScript
 # support.
 
-when isMainModule:
+when not declared(os):
   {.pragma: rtl.}
   import strutils
 
diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim
index 1ec4d0d8d..03b77572a 100644
--- a/lib/pure/osproc.nim
+++ b/lib/pure/osproc.nim
@@ -721,7 +721,7 @@ elif not defined(useNimRtl):
                  env: StringTableRef = nil,
                  options: set[ProcessOption] = {poStdErrToStdOut}): Process =
     var
-      pStdin, pStdout, pStderr: array [0..1, cint]
+      pStdin, pStdout, pStderr: array[0..1, cint]
     new(result)
     result.options = options
     result.exitCode = -3 # for ``waitForExit``
diff --git a/lib/pure/parsexml.nim b/lib/pure/parsexml.nim
index 06daa3782..d16a55302 100644
--- a/lib/pure/parsexml.nim
+++ b/lib/pure/parsexml.nim
@@ -142,6 +142,9 @@ proc kind*(my: XmlParser): XmlEventKind {.inline.} =
 template charData*(my: XmlParser): string =
   ## returns the character data for the events: ``xmlCharData``,
   ## ``xmlWhitespace``, ``xmlComment``, ``xmlCData``, ``xmlSpecial``
+  ## Raises an assertion in debug mode if ``my.kind`` is not one 
+  ## of those events. In release mode, this will not trigger an error
+  ## but the value returned will not be valid. 
   assert(my.kind in {xmlCharData, xmlWhitespace, xmlComment, xmlCData,
                      xmlSpecial})
   my.a
@@ -149,31 +152,49 @@ template charData*(my: XmlParser): string =
 template elementName*(my: XmlParser): string =
   ## returns the element name for the events: ``xmlElementStart``,
   ## ``xmlElementEnd``, ``xmlElementOpen``
+  ## Raises an assertion in debug mode if ``my.kind`` is not one 
+  ## of those events. In release mode, this will not trigger an error
+  ## but the value returned will not be valid. 
   assert(my.kind in {xmlElementStart, xmlElementEnd, xmlElementOpen})
   my.a
 
 template entityName*(my: XmlParser): string =
   ## returns the entity name for the event: ``xmlEntity``
+  ## Raises an assertion in debug mode if ``my.kind`` is not 
+  ## ``xmlEntity``. In release mode, this will not trigger an error
+  ## but the value returned will not be valid. 
   assert(my.kind == xmlEntity)
   my.a
 
 template attrKey*(my: XmlParser): string =
   ## returns the attribute key for the event ``xmlAttribute``
+  ## Raises an assertion in debug mode if ``my.kind`` is not 
+  ## ``xmlAttribute``. In release mode, this will not trigger an error
+  ## but the value returned will not be valid. 
   assert(my.kind == xmlAttribute)
   my.a
 
 template attrValue*(my: XmlParser): string =
   ## returns the attribute value for the event ``xmlAttribute``
+  ## Raises an assertion in debug mode if ``my.kind`` is not 
+  ## ``xmlAttribute``. In release mode, this will not trigger an error
+  ## but the value returned will not be valid. 
   assert(my.kind == xmlAttribute)
   my.b
 
 template piName*(my: XmlParser): string =
   ## returns the processing instruction name for the event ``xmlPI``
+  ## Raises an assertion in debug mode if ``my.kind`` is not 
+  ## ``xmlPI``. In release mode, this will not trigger an error
+  ## but the value returned will not be valid. 
   assert(my.kind == xmlPI)
   my.a
 
 template piRest*(my: XmlParser): string =
   ## returns the rest of the processing instruction for the event ``xmlPI``
+  ## Raises an assertion in debug mode if ``my.kind`` is not 
+  ## ``xmlPI``. In release mode, this will not trigger an error
+  ## but the value returned will not be valid. 
   assert(my.kind == xmlPI)
   my.b
 
diff --git a/lib/pure/pegs.nim b/lib/pure/pegs.nim
index 7e1f50266..5c978a2f8 100644
--- a/lib/pure/pegs.nim
+++ b/lib/pure/pegs.nim
@@ -1841,8 +1841,8 @@ when isMainModule:
       result.add ", "
 
     result.add case n:
-      of 2: c[0].toLower & ": '" & c[1] & "'"
-      of 1: c[0].toLower & ": ''"
+      of 2: toLowerAscii(c[0]) & ": '" & c[1] & "'"
+      of 1: toLowerAscii(c[0]) & ": ''"
       else: ""
 
   assert("Var1=key1;var2=Key2;   VAR3".
diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim
index b6edb834c..9b6cf45c5 100644
--- a/lib/pure/strutils.nim
+++ b/lib/pure/strutils.nim
@@ -26,6 +26,12 @@ include "system/inclrtl"
 
 {.pop.}
 
+# Support old split with set[char]
+when defined(nimOldSplit):
+  {.pragma: deprecatedSplit, deprecated.}
+else:
+  {.pragma: deprecatedSplit.}
+
 type
   CharSet* {.deprecated.} = set[char] # for compatibility with Nim
 {.deprecated: [TCharSet: CharSet].}
@@ -64,8 +70,8 @@ const
     ##   doAssert "01234".find(invalid) == -1
     ##   doAssert "01A34".find(invalid) == 2
 
-proc isAlpha*(c: char): bool {.noSideEffect, procvar,
-  rtl, extern: "nsuIsAlphaChar".}=
+proc isAlphaAscii*(c: char): bool {.noSideEffect, procvar,
+  rtl, extern: "nsuIsAlphaAsciiChar".}=
   ## Checks whether or not `c` is alphabetical.
   ##
   ## This checks a-z, A-Z ASCII characters only.
@@ -85,27 +91,27 @@ proc isDigit*(c: char): bool {.noSideEffect, procvar,
   ## This checks 0-9 ASCII characters only.
   return c in Digits
 
-proc isSpace*(c: char): bool {.noSideEffect, procvar,
-  rtl, extern: "nsuIsSpaceChar".}=
+proc isSpaceAscii*(c: char): bool {.noSideEffect, procvar,
+  rtl, extern: "nsuIsSpaceAsciiChar".}=
   ## Checks whether or not `c` is a whitespace character.
   return c in Whitespace
 
-proc isLower*(c: char): bool {.noSideEffect, procvar,
-  rtl, extern: "nsuIsLowerChar".}=
+proc isLowerAscii*(c: char): bool {.noSideEffect, procvar,
+  rtl, extern: "nsuIsLowerAsciiChar".}=
   ## Checks whether or not `c` is a lower case character.
   ##
   ## This checks ASCII characters only.
   return c in {'a'..'z'}
 
-proc isUpper*(c: char): bool {.noSideEffect, procvar,
-  rtl, extern: "nsuIsUpperChar".}=
+proc isUpperAscii*(c: char): bool {.noSideEffect, procvar,
+  rtl, extern: "nsuIsUpperAsciiChar".}=
   ## Checks whether or not `c` is an upper case character.
   ##
   ## This checks ASCII characters only.
   return c in {'A'..'Z'}
 
-proc isAlpha*(s: string): bool {.noSideEffect, procvar,
-  rtl, extern: "nsuIsAlphaStr".}=
+proc isAlphaAscii*(s: string): bool {.noSideEffect, procvar,
+  rtl, extern: "nsuIsAlphaAsciiStr".}=
   ## Checks whether or not `s` is alphabetical.
   ##
   ## This checks a-z, A-Z ASCII characters only.
@@ -117,7 +123,7 @@ proc isAlpha*(s: string): bool {.noSideEffect, procvar,
 
   result = true
   for c in s:
-    result = c.isAlpha() and result
+    result = c.isAlphaAscii() and result
 
 proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar,
   rtl, extern: "nsuIsAlphaNumericStr".}=
@@ -149,8 +155,8 @@ proc isDigit*(s: string): bool {.noSideEffect, procvar,
   for c in s:
     result = c.isDigit() and result
 
-proc isSpace*(s: string): bool {.noSideEffect, procvar,
-  rtl, extern: "nsuIsSpaceStr".}=
+proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar,
+  rtl, extern: "nsuIsSpaceAsciiStr".}=
   ## Checks whether or not `s` is completely whitespace.
   ##
   ## Returns true if all characters in `s` are whitespace
@@ -160,10 +166,11 @@ proc isSpace*(s: string): bool {.noSideEffect, procvar,
 
   result = true
   for c in s:
-    result = c.isSpace() and result
+    if not c.isSpaceAscii():
+      return false
 
-proc isLower*(s: string): bool {.noSideEffect, procvar,
-  rtl, extern: "nsuIsLowerStr".}=
+proc isLowerAscii*(s: string): bool {.noSideEffect, procvar,
+  rtl, extern: "nsuIsLowerAsciiStr".}=
   ## Checks whether or not `s` contains all lower case characters.
   ##
   ## This checks ASCII characters only.
@@ -174,10 +181,10 @@ proc isLower*(s: string): bool {.noSideEffect, procvar,
 
   result = true
   for c in s:
-    result = c.isLower() and result
+    result = c.isLowerAscii() and result
 
-proc isUpper*(s: string): bool {.noSideEffect, procvar,
-  rtl, extern: "nsuIsUpperStr".}=
+proc isUpperAscii*(s: string): bool {.noSideEffect, procvar,
+  rtl, extern: "nsuIsUpperAsciiStr".}=
   ## Checks whether or not `s` contains all upper case characters.
   ##
   ## This checks ASCII characters only.
@@ -188,10 +195,10 @@ proc isUpper*(s: string): bool {.noSideEffect, procvar,
 
   result = true
   for c in s:
-    result = c.isUpper() and result
+    result = c.isUpperAscii() and result
 
-proc toLower*(c: char): char {.noSideEffect, procvar,
-  rtl, extern: "nsuToLowerChar".} =
+proc toLowerAscii*(c: char): char {.noSideEffect, procvar,
+  rtl, extern: "nsuToLowerAsciiChar".} =
   ## Converts `c` into lower case.
   ##
   ## This works only for the letters ``A-Z``. See `unicode.toLower
@@ -202,8 +209,8 @@ proc toLower*(c: char): char {.noSideEffect, procvar,
   else:
     result = c
 
-proc toLower*(s: string): string {.noSideEffect, procvar,
-  rtl, extern: "nsuToLowerStr".} =
+proc toLowerAscii*(s: string): string {.noSideEffect, procvar,
+  rtl, extern: "nsuToLowerAsciiStr".} =
   ## Converts `s` into lower case.
   ##
   ## This works only for the letters ``A-Z``. See `unicode.toLower
@@ -211,10 +218,10 @@ proc toLower*(s: string): string {.noSideEffect, procvar,
   ## character.
   result = newString(len(s))
   for i in 0..len(s) - 1:
-    result[i] = toLower(s[i])
+    result[i] = toLowerAscii(s[i])
 
-proc toUpper*(c: char): char {.noSideEffect, procvar,
-  rtl, extern: "nsuToUpperChar".} =
+proc toUpperAscii*(c: char): char {.noSideEffect, procvar,
+  rtl, extern: "nsuToUpperAsciiChar".} =
   ## Converts `c` into upper case.
   ##
   ## This works only for the letters ``A-Z``.  See `unicode.toUpper
@@ -225,8 +232,8 @@ proc toUpper*(c: char): char {.noSideEffect, procvar,
   else:
     result = c
 
-proc toUpper*(s: string): string {.noSideEffect, procvar,
-  rtl, extern: "nsuToUpperStr".} =
+proc toUpperAscii*(s: string): string {.noSideEffect, procvar,
+  rtl, extern: "nsuToUpperAsciiStr".} =
   ## Converts `s` into upper case.
   ##
   ## This works only for the letters ``A-Z``.  See `unicode.toUpper
@@ -234,14 +241,145 @@ proc toUpper*(s: string): string {.noSideEffect, procvar,
   ## character.
   result = newString(len(s))
   for i in 0..len(s) - 1:
-    result[i] = toUpper(s[i])
+    result[i] = toUpperAscii(s[i])
+
+proc capitalizeAscii*(s: string): string {.noSideEffect, procvar,
+  rtl, extern: "nsuCapitalizeAscii".} =
+  ## Converts the first character of `s` into upper case.
+  ##
+  ## This works only for the letters ``A-Z``.
+  result = toUpperAscii(s[0]) & substr(s, 1)
+
+proc isSpace*(c: char): bool {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuIsSpaceChar".}=
+  ## Checks whether or not `c` is a whitespace character.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``isSpaceAscii`` instead.
+  isSpaceAscii(c)
+
+proc isLower*(c: char): bool {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuIsLowerChar".}=
+  ## Checks whether or not `c` is a lower case character.
+  ##
+  ## This checks ASCII characters only.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``isLowerAscii`` instead.
+  isLowerAscii(c)
+
+proc isUpper*(c: char): bool {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuIsUpperChar".}=
+  ## Checks whether or not `c` is an upper case character.
+  ##
+  ## This checks ASCII characters only.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``isUpperAscii`` instead.
+  isUpperAscii(c)
+
+proc isAlpha*(c: char): bool {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuIsAlphaChar".}=
+  ## Checks whether or not `c` is alphabetical.
+  ##
+  ## This checks a-z, A-Z ASCII characters only.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``isAlphaAscii`` instead.
+  isAlphaAscii(c)
+
+proc isAlpha*(s: string): bool {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuIsAlphaStr".}=
+  ## Checks whether or not `s` is alphabetical.
+  ##
+  ## This checks a-z, A-Z ASCII characters only.
+  ## Returns true if all characters in `s` are
+  ## alphabetic and there is at least one character
+  ## in `s`.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``isAlphaAscii`` instead.
+  isAlphaAscii(s)
+
+proc isSpace*(s: string): bool {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuIsSpaceStr".}=
+  ## Checks whether or not `s` is completely whitespace.
+  ##
+  ## Returns true if all characters in `s` are whitespace
+  ## characters and there is at least one character in `s`.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``isSpaceAscii`` instead.
+  isSpaceAscii(s)
+
+proc isLower*(s: string): bool {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuIsLowerStr".}=
+  ## Checks whether or not `s` contains all lower case characters.
+  ##
+  ## This checks ASCII characters only.
+  ## Returns true if all characters in `s` are lower case
+  ## and there is at least one character  in `s`.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``isLowerAscii`` instead.
+  isLowerAscii(s)
+
+proc isUpper*(s: string): bool {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuIsUpperStr".}=
+  ## Checks whether or not `s` contains all upper case characters.
+  ##
+  ## This checks ASCII characters only.
+  ## Returns true if all characters in `s` are upper case
+  ## and there is at least one character in `s`.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``isUpperAscii`` instead.
+  isUpperAscii(s)
+
+proc toLower*(c: char): char {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuToLowerChar".} =
+  ## Converts `c` into lower case.
+  ##
+  ## This works only for the letters ``A-Z``. See `unicode.toLower
+  ## <unicode.html#toLower>`_ for a version that works for any Unicode
+  ## character.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``toLowerAscii`` instead.
+  toLowerAscii(c)
+
+proc toLower*(s: string): string {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuToLowerStr".} =
+  ## Converts `s` into lower case.
+  ##
+  ## This works only for the letters ``A-Z``. See `unicode.toLower
+  ## <unicode.html#toLower>`_ for a version that works for any Unicode
+  ## character.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``toLowerAscii`` instead.
+  toLowerAscii(s)
+
+proc toUpper*(c: char): char {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuToUpperChar".} =
+  ## Converts `c` into upper case.
+  ##
+  ## This works only for the letters ``A-Z``.  See `unicode.toUpper
+  ## <unicode.html#toUpper>`_ for a version that works for any Unicode
+  ## character.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``toUpperAscii`` instead.
+  toUpperAscii(c)
+
+proc toUpper*(s: string): string {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuToUpperStr".} =
+  ## Converts `s` into upper case.
+  ##
+  ## This works only for the letters ``A-Z``.  See `unicode.toUpper
+  ## <unicode.html#toUpper>`_ for a version that works for any Unicode
+  ## character.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``toUpperAscii`` instead.
+  toUpperAscii(s)
 
 proc capitalize*(s: string): string {.noSideEffect, procvar,
-  rtl, extern: "nsuCapitalize".} =
+  rtl, deprecated, extern: "nsuCapitalize".} =
   ## Converts the first character of `s` into upper case.
   ##
   ## This works only for the letters ``A-Z``.
-  result = toUpper(s[0]) & substr(s, 1)
+  ##
+  ## **Deprecated since version 0.15.0**: use ``capitalizeAscii`` instead.
+  capitalizeAscii(s)
 
 proc normalize*(s: string): string {.noSideEffect, procvar,
   rtl, extern: "nsuNormalize".} =
@@ -270,7 +408,7 @@ proc cmpIgnoreCase*(a, b: string): int {.noSideEffect,
   var i = 0
   var m = min(a.len, b.len)
   while i < m:
-    result = ord(toLower(a[i])) - ord(toLower(b[i]))
+    result = ord(toLowerAscii(a[i])) - ord(toLowerAscii(b[i]))
     if result != 0: return
     inc(i)
   result = a.len - b.len
@@ -291,8 +429,8 @@ proc cmpIgnoreStyle*(a, b: string): int {.noSideEffect,
   while true:
     while a[i] == '_': inc(i)
     while b[j] == '_': inc(j) # BUGFIX: typo
-    var aa = toLower(a[i])
-    var bb = toLower(b[j])
+    var aa = toLowerAscii(a[i])
+    var bb = toLowerAscii(b[j])
     result = ord(aa) - ord(bb)
     if result != 0 or aa == '\0': break
     inc(i)
@@ -326,17 +464,77 @@ proc toOctal*(c: char): string {.noSideEffect, rtl, extern: "nsuToOctal".} =
     result[i] = chr(val mod 8 + ord('0'))
     val = val div 8
 
+proc isNilOrEmpty*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsNilOrEmpty".} =
+  ## Checks if `s` is nil or empty.
+  result = len(s) == 0
+
+proc isNilOrWhitespace*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsNilOrWhitespace".} =
+  ## Checks if `s` is nil or consists entirely of whitespace characters.
+  if len(s) == 0:
+    return true
+
+  result = true
+  for c in s:
+    if not c.isSpace():
+      return false
+
+proc substrEq(s: string, pos: int, substr: string): bool =
+  var i = 0
+  var length = substr.len
+  while i < length and s[pos+i] == substr[i]:
+    inc i
+
+  return i == length
+
+# --------- Private templates for different split separators -----------
+
+template stringHasSep(s: string, index: int, seps: set[char]): bool =
+  s[index] in seps
+
+template stringHasSep(s: string, index: int, sep: char): bool =
+  s[index] == sep
+
+template stringHasSep(s: string, index: int, sep: string): bool =
+  s.substrEq(index, sep)
+
+template splitCommon(s, sep, maxsplit, sepLen) =
+  ## Common code for split procedures
+  var last = 0
+  var splits = maxsplit
+
+  if len(s) > 0:
+    while last <= len(s):
+      var first = last
+      while last < len(s) and not stringHasSep(s, last, sep):
+        inc(last)
+      if splits == 0: last = len(s)
+      yield substr(s, first, last-1)
+      if splits == 0: break
+      dec(splits)
+      inc(last, sepLen)
+
+template oldSplit(s, seps, maxsplit) =
+  var last = 0
+  var splits = maxsplit
+  assert(not ('\0' in seps))
+  while last < len(s):
+    while s[last] in seps: inc(last)
+    var first = last
+    while last < len(s) and s[last] notin seps: inc(last)
+    if first <= last-1:
+      if splits == 0: last = len(s)
+      yield substr(s, first, last-1)
+      if splits == 0: break
+      dec(splits)
+
 iterator split*(s: string, seps: set[char] = Whitespace,
                 maxsplit: int = -1): string =
   ## Splits the string `s` into substrings using a group of separators.
   ##
-  ## Substrings are separated by a substring containing only `seps`. Note
-  ## that whole sequences of characters found in ``seps`` will be counted as
-  ## a single split point and leading/trailing separators will be ignored.
-  ## The following example:
+  ## Substrings are separated by a substring containing only `seps`.
   ##
   ## .. code-block:: nim
-  ##   for word in split("  this is an  example  "):
+  ##   for word in split("this\lis an\texample"):
   ##     writeLine(stdout, word)
   ##
   ## ...generates this output:
@@ -350,7 +548,7 @@ iterator split*(s: string, seps: set[char] = Whitespace,
   ## And the following code:
   ##
   ## .. code-block:: nim
-  ##   for word in split(";;this;is;an;;example;;;", {';'}):
+  ##   for word in split("this:is;an$example", {';', ':', '$'}):
   ##     writeLine(stdout, word)
   ##
   ## ...produces the same output as the first example. The code:
@@ -371,26 +569,26 @@ iterator split*(s: string, seps: set[char] = Whitespace,
   ##   "08"
   ##   "08.398990"
   ##
-  var last = 0
-  var splits = maxsplit
-  assert(not ('\0' in seps))
-  while last < len(s):
-    while s[last] in seps: inc(last)
-    var first = last
-    while last < len(s) and s[last] notin seps: inc(last) # BUGFIX!
-    if first <= last-1:
-      if splits == 0: last = len(s)
-      yield substr(s, first, last-1)
-      if splits == 0: break
-      dec(splits)
+  when defined(nimOldSplit):
+    oldSplit(s, seps, maxsplit)
+  else:
+    splitCommon(s, seps, maxsplit, 1)
+
+iterator splitWhitespace*(s: string): string =
+  ## Splits at whitespace.
+  oldSplit(s, Whitespace, -1)
+
+proc splitWhitespace*(s: string): seq[string] {.noSideEffect,
+  rtl, extern: "nsuSplitWhitespace".} =
+  ## The same as the `splitWhitespace <#splitWhitespace.i,string>`_
+  ## iterator, but is a proc that returns a sequence of substrings.
+  accumulateResult(splitWhitespace(s))
 
 iterator split*(s: string, sep: char, maxsplit: int = -1): string =
   ## Splits the string `s` into substrings using a single separator.
   ##
   ## Substrings are separated by the character `sep`.
-  ## Unlike the version of the iterator which accepts a set of separator
-  ## characters, this proc will not coalesce groups of the
-  ## separator, returning a string for each found character. The code:
+  ## The code:
   ##
   ## .. code-block:: nim
   ##   for word in split(";;this;is;an;;example;;;", ';'):
@@ -410,56 +608,27 @@ iterator split*(s: string, sep: char, maxsplit: int = -1): string =
   ##   ""
   ##   ""
   ##
-  var last = 0
-  var splits = maxsplit
-  assert('\0' != sep)
-  if len(s) > 0:
-    # `<=` is correct here for the edge cases!
-    while last <= len(s):
-      var first = last
-      while last < len(s) and s[last] != sep: inc(last)
-      if splits == 0: last = len(s)
-      yield substr(s, first, last-1)
-      if splits == 0: break
-      dec(splits)
-      inc(last)
-
-proc substrEq(s: string, pos: int, substr: string): bool =
-  var i = 0
-  var length = substr.len
-  while i < length and s[pos+i] == substr[i]:
-    inc i
-
-  return i == length
+  splitCommon(s, sep, maxsplit, 1)
 
 iterator split*(s: string, sep: string, maxsplit: int = -1): string =
   ## Splits the string `s` into substrings using a string separator.
   ##
   ## Substrings are separated by the string `sep`.
-  var last = 0
-  var splits = maxsplit
-
-  if len(s) > 0:
-    while last <= len(s):
-      var first = last
-      while last < len(s) and not s.substrEq(last, sep):
-        inc(last)
-      if splits == 0: last = len(s)
-      yield substr(s, first, last-1)
-      if splits == 0: break
-      dec(splits)
-      inc(last, sep.len)
-
-# --------- Private templates for different rsplit separators -----------
-
-template stringHasSep(s: string, index: int, seps: set[char]): bool =
-  s[index] in seps
-
-template stringHasSep(s: string, index: int, sep: char): bool =
-  s[index] == sep
+  ## The code:
+  ##
+  ## .. code-block:: nim
+  ##   for word in split("thisDATAisDATAcorrupted", "DATA"):
+  ##     writeLine(stdout, word)
+  ##
+  ## Results in:
+  ##
+  ## .. code-block::
+  ##   "this"
+  ##   "is"
+  ##   "corrupted"
+  ##
 
-template stringHasSep(s: string, index: int, sep: string): bool =
-  s.substrEq(index, sep)
+  splitCommon(s, sep, maxsplit, sep.len)
 
 template rsplitCommon(s, sep, maxsplit, sepLen) =
   ## Common code for rsplit functions
@@ -2123,13 +2292,13 @@ when isMainModule:
 
   doAssert "  foo\n  bar".indent(4, "Q") == "QQQQ  foo\nQQQQ  bar"
 
-  doAssert isAlpha('r')
-  doAssert isAlpha('A')
-  doAssert(not isAlpha('$'))
+  doAssert isAlphaAscii('r')
+  doAssert isAlphaAscii('A')
+  doAssert(not isAlphaAscii('$'))
 
-  doAssert isAlpha("Rasp")
-  doAssert isAlpha("Args")
-  doAssert(not isAlpha("$Tomato"))
+  doAssert isAlphaAscii("Rasp")
+  doAssert isAlphaAscii("Args")
+  doAssert(not isAlphaAscii("$Tomato"))
 
   doAssert isAlphaNumeric('3')
   doAssert isAlphaNumeric('R')
@@ -2148,32 +2317,43 @@ when isMainModule:
   doAssert(not isDigit("12.33"))
   doAssert(not isDigit("A45b"))
 
-  doAssert isSpace('\t')
-  doAssert isSpace('\l')
-  doAssert(not isSpace('A'))
+  doAssert isSpaceAscii('\t')
+  doAssert isSpaceAscii('\l')
+  doAssert(not isSpaceAscii('A'))
 
-  doAssert isSpace("\t\l \v\r\f")
-  doAssert isSpace("       ")
-  doAssert(not isSpace("ABc   \td"))
+  doAssert isSpaceAscii("\t\l \v\r\f")
+  doAssert isSpaceAscii("       ")
+  doAssert(not isSpaceAscii("ABc   \td"))
 
-  doAssert isLower('a')
-  doAssert isLower('z')
-  doAssert(not isLower('A'))
-  doAssert(not isLower('5'))
-  doAssert(not isLower('&'))
+  doAssert(isNilOrEmpty(""))
+  doAssert(isNilOrEmpty(nil))
+  doAssert(not isNilOrEmpty("test"))
+  doAssert(not isNilOrEmpty(" "))
 
-  doAssert isLower("abcd")
-  doAssert(not isLower("abCD"))
-  doAssert(not isLower("33aa"))
+  doAssert(isNilOrWhitespace(""))
+  doAssert(isNilOrWhitespace(nil))
+  doAssert(isNilOrWhitespace("       "))
+  doAssert(isNilOrWhitespace("\t\l \v\r\f"))
+  doAssert(not isNilOrWhitespace("ABc   \td"))
 
-  doAssert isUpper('A')
-  doAssert(not isUpper('b'))
-  doAssert(not isUpper('5'))
-  doAssert(not isUpper('%'))
+  doAssert isLowerAscii('a')
+  doAssert isLowerAscii('z')
+  doAssert(not isLowerAscii('A'))
+  doAssert(not isLowerAscii('5'))
+  doAssert(not isLowerAscii('&'))
 
-  doAssert isUpper("ABC")
-  doAssert(not isUpper("AAcc"))
-  doAssert(not isUpper("A#$"))
+  doAssert isLowerAscii("abcd")
+  doAssert(not isLowerAscii("abCD"))
+  doAssert(not isLowerAscii("33aa"))
+
+  doAssert isUpperAscii('A')
+  doAssert(not isUpperAscii('b'))
+  doAssert(not isUpperAscii('5'))
+  doAssert(not isUpperAscii('%'))
+
+  doAssert isUpperAscii("ABC")
+  doAssert(not isUpperAscii("AAcc"))
+  doAssert(not isUpperAscii("A#$"))
 
   doAssert rsplit("foo bar", seps=Whitespace) == @["foo", "bar"]
   doAssert rsplit(" foo bar", seps=Whitespace, maxsplit=1) == @[" foo", "bar"]
@@ -2218,11 +2398,14 @@ bar
     bar
   """.unindent() == "foo\nfoo\nbar\n"
 
-  let s = " this   is     an example   "
-  doAssert s.split() == @["this", "is", "an", "example"]
-  doAssert s.split(maxsplit=4) == @["this", "is", "an", "example"]
-  doAssert s.split(' ', maxsplit=4) == @["", "this", "", "", "is     an example   "]
-  doAssert s.split(" ", maxsplit=4) == @["", "this", "", "", "is     an example   "]
+  let s = " this is an example  "
+  let s2 = ":this;is;an:example;;"
+
+  doAssert s.split() == @["", "this", "is", "an", "example", "", ""]
+  doAssert s2.split(seps={':', ';'}) == @["", "this", "is", "an", "example", "", ""]
+  doAssert s.split(maxsplit=4) == @["", "this", "is", "an", "example  "]
+  doAssert s.split(' ', maxsplit=1) == @["", "this is an example  "]
+  doAssert s.split(" ", maxsplit=4) == @["", "this", "is", "an", "example  "]
 
   block: # formatEng tests
     doAssert formatEng(0, 2, trim=false) == "0.00"
diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim
index 60f064e7c..1f34ec07e 100644
--- a/lib/pure/terminal.nim
+++ b/lib/pure/terminal.nim
@@ -384,7 +384,7 @@ proc setForegroundColor*(f: File, fg: ForegroundColor, bright=false) =
     var old = getAttributes(h) and not 0x0007
     if bright:
       old = old or FOREGROUND_INTENSITY
-    const lookup: array [ForegroundColor, int] = [
+    const lookup: array[ForegroundColor, int] = [
       0,
       (FOREGROUND_RED),
       (FOREGROUND_GREEN),
@@ -406,7 +406,7 @@ proc setBackgroundColor*(f: File, bg: BackgroundColor, bright=false) =
     var old = getAttributes(h) and not 0x0070
     if bright:
       old = old or BACKGROUND_INTENSITY
-    const lookup: array [BackgroundColor, int] = [
+    const lookup: array[BackgroundColor, int] = [
       0,
       (BACKGROUND_RED),
       (BACKGROUND_GREEN),
@@ -493,17 +493,21 @@ template styledEcho*(args: varargs[expr]): expr =
   ## Echoes styles arguments to stdout using ``styledWriteLine``.
   callStyledEcho(args)
 
-when defined(nimdoc):
-  proc getch*(): char =
-    ## Read a single character from the terminal, blocking until it is entered.
-    ## The character is not printed to the terminal. This is not available for
-    ## Windows.
-    discard
-elif not defined(windows):
-  proc getch*(): char =
-    ## Read a single character from the terminal, blocking until it is entered.
-    ## The character is not printed to the terminal. This is not available for
-    ## Windows.
+proc getch*(): char =
+  ## Read a single character from the terminal, blocking until it is entered.
+  ## The character is not printed to the terminal.
+  when defined(windows):
+    let fd = getStdHandle(STD_INPUT_HANDLE)
+    var keyEvent = KEY_EVENT_RECORD()
+    var numRead: cint 
+    while true:
+      # Block until character is entered
+      doAssert(waitForSingleObject(fd, INFINITE) == WAIT_OBJECT_0)
+      doAssert(readConsoleInput(fd, addr(keyEvent), 1, addr(numRead)) != 0)
+      if numRead == 0 or keyEvent.eventType != 1 or keyEvent.bKeyDown == 0:
+        continue
+      return char(keyEvent.uChar)
+  else:
     let fd = getFileHandle(stdin)
     var oldMode: Termios
     discard fd.tcgetattr(addr oldMode)
diff --git a/lib/pure/times.nim b/lib/pure/times.nim
index c0a121518..d6cb5c9da 100644
--- a/lib/pure/times.nim
+++ b/lib/pure/times.nim
@@ -477,7 +477,7 @@ when not defined(JS):
   # our own procs on top of that:
   proc tmToTimeInfo(tm: StructTM, local: bool): TimeInfo =
     const
-      weekDays: array [0..6, WeekDay] = [
+      weekDays: array[0..6, WeekDay] = [
         dSun, dMon, dTue, dWed, dThu, dFri, dSat]
     when defined(freebsd) or defined(netbsd) or defined(openbsd):
       TimeInfo(second: int(tm.second),
@@ -523,7 +523,7 @@ when not defined(JS):
 
   proc timeInfoToTM(t: TimeInfo): StructTM =
     const
-      weekDays: array [WeekDay, int8] = [1'i8,2'i8,3'i8,4'i8,5'i8,6'i8,0'i8]
+      weekDays: array[WeekDay, int8] = [1'i8,2'i8,3'i8,4'i8,5'i8,6'i8,0'i8]
     result.second = t.second
     result.minute = t.minute
     result.hour = t.hour
@@ -652,7 +652,7 @@ elif defined(JS):
     return newDate()
 
   const
-    weekDays: array [0..6, WeekDay] = [
+    weekDays: array[0..6, WeekDay] = [
       dSun, dMon, dTue, dWed, dThu, dFri, dSat]
 
   proc getLocalTime(t: Time): TimeInfo =
diff --git a/lib/pure/unicode.nim b/lib/pure/unicode.nim
index ac25dccef..b5383c5d3 100644
--- a/lib/pure/unicode.nim
+++ b/lib/pure/unicode.nim
@@ -1369,6 +1369,64 @@ proc isCombining*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} =
     (c >= 0x20d0 and c <= 0x20ff) or
     (c >= 0xfe20 and c <= 0xfe2f))
 
+template runeCheck(s, runeProc) =
+  ## Common code for rune.isLower, rune.isUpper, etc
+  result = if len(s) == 0: false else: true
+
+  var
+    i = 0
+    rune: Rune
+
+  while i < len(s) and result:
+    fastRuneAt(s, i, rune, doInc=true)
+    result = runeProc(rune) and result
+
+proc isUpper*(s: string): bool {.noSideEffect, procvar,
+  rtl, extern: "nuc$1Str".} =
+  ## Returns true iff `s` contains all upper case unicode characters.
+  runeCheck(s, isUpper)
+
+proc isLower*(s: string): bool {.noSideEffect, procvar,
+  rtl, extern: "nuc$1Str".} =
+  ## Returns true iff `s` contains all lower case unicode characters.
+  runeCheck(s, isLower)
+
+proc isAlpha*(s: string): bool {.noSideEffect, procvar,
+  rtl, extern: "nuc$1Str".} =
+  ## Returns true iff `s` contains all alphabetic unicode characters.
+  runeCheck(s, isAlpha)
+
+proc isSpace*(s: string): bool {.noSideEffect, procvar,
+  rtl, extern: "nuc$1Str".} =
+  ## Returns true iff `s` contains all whitespace unicode characters.
+  runeCheck(s, isWhiteSpace)
+
+template convertRune(s, runeProc) =
+  ## Convert runes in `s` using `runeProc` as the converter.
+  result = newString(len(s))
+
+  var
+    i = 0
+    lastIndex = 0
+    rune: Rune
+
+  while i < len(s):
+    lastIndex = i
+    fastRuneAt(s, i, rune, doInc=true)
+    rune = runeProc(rune)
+
+    rune.fastToUTF8Copy(result, lastIndex)
+
+proc toUpper*(s: string): string {.noSideEffect, procvar,
+  rtl, extern: "nuc$1Str".} =
+  ## Converts `s` into upper-case unicode characters.
+  convertRune(s, toUpper)
+
+proc toLower*(s: string): string {.noSideEffect, procvar,
+  rtl, extern: "nuc$1Str".} =
+  ## Converts `s` into lower-case unicode characters.
+  convertRune(s, toLower)
+
 proc swapCase*(s: string): string {.noSideEffect, procvar,
   rtl, extern: "nuc$1".} =
   ## Swaps the case of unicode characters in `s`
@@ -1395,6 +1453,20 @@ proc swapCase*(s: string): string {.noSideEffect, procvar,
 
     rune.fastToUTF8Copy(result, lastIndex)
 
+proc capitalize*(s: string): string {.noSideEffect, procvar,
+  rtl, extern: "nuc$1".} =
+  ## Converts the first character of `s` into an upper-case unicode character.
+  if len(s) == 0:
+    return s
+
+  var
+    rune: Rune
+    i = 0
+
+  fastRuneAt(s, i, rune, doInc=true)
+
+  result = $toUpper(rune) & substr(s, i)
+
 proc translate*(s: string, replacements: proc(key: string): string): string {.
   rtl, extern: "nuc$1".} =
   ## Translates words in a string using the `replacements` proc to substitute
@@ -1618,6 +1690,10 @@ when isMainModule:
   doAssert title("αlpha βeta γamma") == "Αlpha Βeta Γamma"
   doAssert title("") == ""
 
+  doAssert capitalize("βeta") == "Βeta"
+  doAssert capitalize("foo") == "Foo"
+  doAssert capitalize("") == ""
+
   doAssert isTitle("Foo")
   doAssert(not isTitle("Foo bar"))
   doAssert(not isTitle("αlpha Βeta"))
@@ -1630,6 +1706,64 @@ when isMainModule:
   doAssert swapCase("a✓B") == "A✓b"
   doAssert swapCase("") == ""
 
+  doAssert isAlpha("r")
+  doAssert isAlpha("α")
+  doAssert(not isAlpha("$"))
+  doAssert(not isAlpha(""))
+
+  doAssert isAlpha("Βeta")
+  doAssert isAlpha("Args")
+  doAssert(not isAlpha("$Foo✓"))
+
+  doAssert isSpace("\t")
+  doAssert isSpace("\l")
+  doAssert(not isSpace("Β"))
+  doAssert(not isSpace("Βeta"))
+
+  doAssert isSpace("\t\l \v\r\f")
+  doAssert isSpace("       ")
+  doAssert(not isSpace(""))
+  doAssert(not isSpace("ΑΓc   \td"))
+
+  doAssert isLower("a")
+  doAssert isLower("γ")
+  doAssert(not isLower("Γ"))
+  doAssert(not isLower("4"))
+  doAssert(not isLower(""))
+
+  doAssert isLower("abcdγ")
+  doAssert(not isLower("abCDΓ"))
+  doAssert(not isLower("33aaΓ"))
+
+  doAssert isUpper("Γ")
+  doAssert(not isUpper("b"))
+  doAssert(not isUpper("α"))
+  doAssert(not isUpper("✓"))
+  doAssert(not isUpper(""))
+
+  doAssert isUpper("ΑΒΓ")
+  doAssert(not isUpper("AAccβ"))
+  doAssert(not isUpper("A#$β"))
+
+  doAssert toUpper("Γ") == "Γ"
+  doAssert toUpper("b") == "B"
+  doAssert toUpper("α") == "Α"
+  doAssert toUpper("✓") == "✓"
+  doAssert toUpper("") == ""
+
+  doAssert toUpper("ΑΒΓ") == "ΑΒΓ"
+  doAssert toUpper("AAccβ") == "AACCΒ"
+  doAssert toUpper("A✓$β") == "A✓$Β"
+
+  doAssert toLower("a") == "a"
+  doAssert toLower("γ") == "γ"
+  doAssert toLower("Γ") == "γ"
+  doAssert toLower("4") == "4"
+  doAssert toLower("") == ""
+
+  doAssert toLower("abcdγ") == "abcdγ"
+  doAssert toLower("abCDΓ") == "abcdγ"
+  doAssert toLower("33aaΓ") == "33aaγ"
 
   doAssert reversed("Reverse this!") == "!siht esreveR"
   doAssert reversed("先秦兩漢") == "漢兩秦先"