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/asyncftpclient.nim4
-rw-r--r--lib/pure/db_common.nim103
-rw-r--r--lib/pure/events.nim2
-rw-r--r--lib/pure/httpclient.nim10
-rw-r--r--lib/pure/lexbase.nim25
-rw-r--r--lib/pure/math.nim62
-rw-r--r--lib/pure/nativesockets.nim5
-rw-r--r--lib/pure/nimprof.nim38
-rw-r--r--lib/pure/osproc.nim4
-rw-r--r--lib/pure/parseutils.nim7
-rw-r--r--lib/pure/redis.nim1096
-rw-r--r--lib/pure/stats.nim348
-rw-r--r--lib/pure/strutils.nim10
-rw-r--r--lib/pure/times.nim184
14 files changed, 674 insertions, 1224 deletions
diff --git a/lib/pure/asyncftpclient.nim b/lib/pure/asyncftpclient.nim
index b806f4235..fd899e080 100644
--- a/lib/pure/asyncftpclient.nim
+++ b/lib/pure/asyncftpclient.nim
@@ -288,7 +288,7 @@ proc defaultOnProgressChanged*(total, progress: BiggestInt,
   result.complete()
 
 proc retrFile*(ftp: AsyncFtpClient, file, dest: string,
-               onProgressChanged = defaultOnProgressChanged) {.async.} =
+               onProgressChanged: ProgressChangedProc = defaultOnProgressChanged) {.async.} =
   ## Downloads ``file`` and saves it to ``dest``.
   ## The ``EvRetr`` event is passed to the specified ``handleEvent`` function
   ## when the download is finished. The event's ``filename`` field will be equal
@@ -339,7 +339,7 @@ proc doUpload(ftp: AsyncFtpClient, file: File,
     await countdownFut or sendFut
 
 proc store*(ftp: AsyncFtpClient, file, dest: string,
-            onProgressChanged = defaultOnProgressChanged) {.async.} =
+            onProgressChanged: ProgressChangedProc = defaultOnProgressChanged) {.async.} =
   ## Uploads ``file`` to ``dest`` on the remote FTP server. Usage of this
   ## function asynchronously is recommended to view the progress of
   ## the download.
diff --git a/lib/pure/db_common.nim b/lib/pure/db_common.nim
new file mode 100644
index 000000000..957389605
--- /dev/null
+++ b/lib/pure/db_common.nim
@@ -0,0 +1,103 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2015 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## Common datatypes and definitions for all ``db_*.nim`` (
+## `db_mysql <db_mysql.html>`_, `db_postgres <db_postgres.html>`_,
+## and `db_sqlite <db_sqlite.html>`_) modules.
+
+type
+  DbError* = object of IOError ## exception that is raised if a database error occurs
+
+  SqlQuery* = distinct string ## an SQL query string
+
+
+  DbEffect* = object of IOEffect ## effect that denotes a database operation
+  ReadDbEffect* = object of DbEffect   ## effect that denotes a read operation
+  WriteDbEffect* = object of DbEffect  ## effect that denotes a write operation
+
+  DbTypeKind* = enum  ## a superset of datatypes that might be supported.
+    dbUnknown,        ## unknown datatype
+    dbSerial,         ## datatype used for primary auto-increment keys
+    dbNull,           ## datatype used for the NULL value
+    dbBit,            ## bit datatype
+    dbBool,           ## boolean datatype
+    dbBlob,           ## blob datatype
+    dbFixedChar,      ## string of fixed length
+    dbVarchar,        ## string datatype
+    dbJson,           ## JSON datatype
+    dbXml,            ## XML datatype
+    dbInt,            ## some integer type
+    dbUInt,           ## some unsigned integer type
+    dbDecimal,        ## decimal numbers (fixed-point number)
+    dbFloat,          ## some floating point type
+    dbDate,           ## a year-month-day description
+    dbTime,           ## HH:MM:SS information
+    dbDatetime,       ## year-month-day and HH:MM:SS information,
+                      ## plus optional time or timezone information
+    dbTimestamp,      ## Timestamp values are stored as the number of seconds
+                      ## since the epoch ('1970-01-01 00:00:00' UTC).
+    dbTimeInterval,   ## an interval [a,b] of times
+    dbEnum,           ## some enum
+    dbSet,            ## set of enum values
+    dbArray,          ## an array of values
+    dbComposite,      ## composite type (record, struct, etc)
+    dbUrl,            ## a URL
+    dbUuid,           ## a UUID
+    dbInet,           ## an IP address
+    dbMacAddress,     ## a MAC address
+    dbGeometry,       ## some geometric type
+    dbPoint,          ## Point on a plane   (x,y)
+    dbLine,           ## Infinite line ((x1,y1),(x2,y2))
+    dbLseg,           ## Finite line segment   ((x1,y1),(x2,y2))
+    dbBox,            ## Rectangular box   ((x1,y1),(x2,y2))
+    dbPath,           ## Closed or open path (similar to polygon) ((x1,y1),...)
+    dbPolygon,        ## Polygon (similar to closed path)   ((x1,y1),...)
+    dbCircle,         ## Circle   <(x,y),r> (center point and radius)
+    dbUser1,          ## user definable datatype 1 (for unknown extensions)
+    dbUser2,          ## user definable datatype 2 (for unknown extensions)
+    dbUser3,          ## user definable datatype 3 (for unknown extensions)
+    dbUser4,          ## user definable datatype 4 (for unknown extensions)
+    dbUser5           ## user definable datatype 5 (for unknown extensions)
+
+  DbType* = object    ## describes a database type
+    kind*: DbTypeKind ## the kind of the described type
+    notNull*: bool    ## does the type contain NULL?
+    name*: string     ## the name of the type
+    size*: Natural    ## the size of the datatype; 0 if of variable size
+    maxReprLen*: Natural ## maximal length required for the representation
+    precision*, scale*: Natural ## precision and scale of the number
+    min*, max*: BiggestInt ## the minimum and maximum of allowed values
+    validValues*: seq[string] ## valid values of an enum or a set
+
+  DbColumn* = object   ## information about a database column
+    name*: string      ## name of the column
+    tableName*: string ## name of the table the column belongs to (optional)
+    typ*: DbType       ## type of the column
+    primaryKey*: bool  ## is this a primary key?
+    foreignKey*: bool  ## is this a foreign key?
+  DbColumns* = seq[DbColumn]
+
+{.deprecated: [EDb: DbError, TSqlQuery: SqlQuery, FDb: DbEffect,
+              FReadDb: ReadDbEffect, FWriteDb: WriteDbEffect].}
+
+template sql*(query: string): SqlQuery =
+  ## constructs a SqlQuery from the string `query`. This is supposed to be
+  ## used as a raw-string-literal modifier:
+  ## ``sql"update user set counter = counter + 1"``
+  ##
+  ## If assertions are turned off, it does nothing. If assertions are turned
+  ## on, later versions will check the string for valid syntax.
+  SqlQuery(query)
+
+proc dbError*(msg: string) {.noreturn, noinline.} =
+  ## raises an DbError exception with message `msg`.
+  var e: ref DbError
+  new(e)
+  e.msg = msg
+  raise e
diff --git a/lib/pure/events.nim b/lib/pure/events.nim
index 62800c5c8..23a8a2c58 100644
--- a/lib/pure/events.nim
+++ b/lib/pure/events.nim
@@ -57,7 +57,7 @@ proc addHandler*(handler: var EventHandler, fn: proc(e: EventArgs) {.closure.})
 
 proc removeHandler*(handler: var EventHandler, fn: proc(e: EventArgs) {.closure.}) =
   ## Removes the callback from the specified event handler.
-  for i in countup(0, len(handler.handlers) -1):
+  for i in countup(0, len(handler.handlers)-1):
     if fn == handler.handlers[i]:
       handler.handlers.del(i)
       break
diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim
index 8e182e274..1b91132db 100644
--- a/lib/pure/httpclient.nim
+++ b/lib/pure/httpclient.nim
@@ -110,7 +110,7 @@ type
   EInvalidProtocol: ProtocolError, EHttpRequestErr: HttpRequestError
 ].}
 
-const defUserAgent* = "Nim httpclient/0.1"
+const defUserAgent* = "Nim httpclient/" & NimVersion
 
 proc httpError(msg: string) =
   var e: ref ProtocolError
@@ -389,6 +389,7 @@ proc request*(url: string, httpMethod: string, extraHeaders = "",
   ## | An optional timeout can be specified in milliseconds, if reading from the
   ## server takes longer than specified an ETimeout exception will be raised.
   var r = if proxy == nil: parseUri(url) else: proxy.url
+  var hostUrl = if proxy == nil: r else: parseUri(url)
   var headers = substr(httpMethod, len("http"))
   # TODO: Use generateHeaders further down once it supports proxies.
   if proxy == nil:
@@ -402,10 +403,10 @@ proc request*(url: string, httpMethod: string, extraHeaders = "",
 
   headers.add(" HTTP/1.1\c\L")
 
-  if r.port == "":
-    add(headers, "Host: " & r.hostname & "\c\L")
+  if hostUrl.port == "":
+    add(headers, "Host: " & hostUrl.hostname & "\c\L")
   else:
-    add(headers, "Host: " & r.hostname & ":" & r.port & "\c\L")
+    add(headers, "Host: " & hostUrl.hostname & ":" & hostUrl.port & "\c\L")
 
   if userAgent != "":
     add(headers, "User-Agent: " & userAgent & "\c\L")
@@ -414,7 +415,6 @@ proc request*(url: string, httpMethod: string, extraHeaders = "",
     add(headers, "Proxy-Authorization: basic " & auth & "\c\L")
   add(headers, extraHeaders)
   add(headers, "\c\L")
-
   var s = newSocket()
   if s == nil: raiseOSError(osLastError())
   var port = net.Port(80)
diff --git a/lib/pure/lexbase.nim b/lib/pure/lexbase.nim
index bfecf6a58..cf2e8bb89 100644
--- a/lib/pure/lexbase.nim
+++ b/lib/pure/lexbase.nim
@@ -28,7 +28,10 @@ type
   BaseLexer* = object of RootObj ## the base lexer. Inherit your lexer from
                                  ## this object.
     bufpos*: int              ## the current position within the buffer
-    buf*: cstring             ## the buffer itself
+    when defined(js):         ## the buffer itself
+      buf*: string
+    else:
+      buf*: cstring
     bufLen*: int              ## length of buffer in characters
     input: Stream            ## the input stream
     lineNumber*: int          ## the current line number
@@ -43,7 +46,8 @@ const
 
 proc close*(L: var BaseLexer) =
   ## closes the base lexer. This closes `L`'s associated stream too.
-  dealloc(L.buf)
+  when not defined(js):
+    dealloc(L.buf)
   close(L.input)
 
 proc fillBuffer(L: var BaseLexer) =
@@ -58,8 +62,11 @@ proc fillBuffer(L: var BaseLexer) =
   toCopy = L.bufLen - L.sentinel - 1
   assert(toCopy >= 0)
   if toCopy > 0:
-    moveMem(L.buf, addr(L.buf[L.sentinel + 1]), toCopy * chrSize)
-    # "moveMem" handles overlapping regions
+    when defined(js):
+      for i in 0 ..< toCopy: L.buf[i] = L.buf[L.sentinel + 1 + i]
+    else:
+      # "moveMem" handles overlapping regions
+      moveMem(L.buf, addr L.buf[L.sentinel + 1], toCopy * chrSize)
   charsRead = readData(L.input, addr(L.buf[toCopy]),
                        (L.sentinel + 1) * chrSize) div chrSize
   s = toCopy + charsRead
@@ -81,7 +88,10 @@ proc fillBuffer(L: var BaseLexer) =
         # double the buffer's size and try again:
         oldBufLen = L.bufLen
         L.bufLen = L.bufLen * 2
-        L.buf = cast[cstring](realloc(L.buf, L.bufLen * chrSize))
+        when defined(js):
+          L.buf.setLen(L.bufLen)
+        else:
+          L.buf = cast[cstring](realloc(L.buf, L.bufLen * chrSize))
         assert(L.bufLen - oldBufLen == oldBufLen)
         charsRead = readData(L.input, addr(L.buf[oldBufLen]),
                              oldBufLen * chrSize) div chrSize
@@ -139,7 +149,10 @@ proc open*(L: var BaseLexer, input: Stream, bufLen: int = 8192;
   L.bufpos = 0
   L.bufLen = bufLen
   L.refillChars = refillChars
-  L.buf = cast[cstring](alloc(bufLen * chrSize))
+  when defined(js):
+    L.buf = newString(bufLen)
+  else:
+    L.buf = cast[cstring](alloc(bufLen * chrSize))
   L.sentinel = bufLen - 1
   L.lineStart = 0
   L.lineNumber = 1            # lines start at 1
diff --git a/lib/pure/math.nim b/lib/pure/math.nim
index 391a880ae..b0104336e 100644
--- a/lib/pure/math.nim
+++ b/lib/pure/math.nim
@@ -118,26 +118,6 @@ proc sum*[T](x: openArray[T]): T {.noSideEffect.} =
   ## If `x` is empty, 0 is returned.
   for i in items(x): result = result + i
 
-template toFloat(f: float): float = f
-
-proc mean*[T](x: openArray[T]): float {.noSideEffect.} =
-  ## Computes the mean of the elements in `x`, which are first converted to floats.
-  ## If `x` is empty, NaN is returned.
-  ## ``toFloat(x: T): float`` must be defined.
-  for i in items(x): result = result + toFloat(i)
-  result = result / toFloat(len(x))
-
-proc variance*[T](x: openArray[T]): float {.noSideEffect.} =
-  ## Computes the variance of the elements in `x`.
-  ## If `x` is empty, NaN is returned.
-  ## ``toFloat(x: T): float`` must be defined.
-  result = 0.0
-  var m = mean(x)
-  for i in items(x):
-    var diff = toFloat(i) - m
-    result = result + diff*diff
-  result = result / toFloat(len(x))
-
 proc random*(max: int): int {.benign.}
   ## Returns a random number in the range 0..max-1. The sequence of
   ## random number is always the same, unless `randomize` is called
@@ -376,48 +356,6 @@ proc random*[T](a: openArray[T]): T =
   ## returns a random element from the openarray `a`.
   result = a[random(a.low..a.len)]
 
-type
-  RunningStat* = object                 ## an accumulator for statistical data
-    n*: int                             ## number of pushed data
-    sum*, min*, max*, mean*: float      ## self-explaining
-    oldM, oldS, newS: float
-
-{.deprecated: [TFloatClass: FloatClass, TRunningStat: RunningStat].}
-
-proc push*(s: var RunningStat, x: float) =
-  ## pushes a value `x` for processing
-  inc(s.n)
-  # See Knuth TAOCP vol 2, 3rd edition, page 232
-  if s.n == 1:
-    s.min = x
-    s.max = x
-    s.oldM = x
-    s.mean = x
-    s.oldS = 0.0
-  else:
-    if s.min > x: s.min = x
-    if s.max < x: s.max = x
-    s.mean = s.oldM + (x - s.oldM)/toFloat(s.n)
-    s.newS = s.oldS + (x - s.oldM)*(x - s.mean)
-
-    # set up for next iteration:
-    s.oldM = s.mean
-    s.oldS = s.newS
-  s.sum = s.sum + x
-
-proc push*(s: var RunningStat, x: int) =
-  ## pushes a value `x` for processing. `x` is simply converted to ``float``
-  ## and the other push operation is called.
-  push(s, toFloat(x))
-
-proc variance*(s: RunningStat): float =
-  ## computes the current variance of `s`
-  if s.n > 1: result = s.newS / (toFloat(s.n - 1))
-
-proc standardDeviation*(s: RunningStat): float =
-  ## computes the current standard deviation of `s`
-  result = sqrt(variance(s))
-
 {.pop.}
 {.pop.}
 
diff --git a/lib/pure/nativesockets.nim b/lib/pure/nativesockets.nim
index 9e6ff21ef..b5a8d5777 100644
--- a/lib/pure/nativesockets.nim
+++ b/lib/pure/nativesockets.nim
@@ -206,8 +206,9 @@ proc getAddrInfo*(address: string, port: Port, domain: Domain = AF_INET,
   # OpenBSD doesn't support AI_V4MAPPED and doesn't define the macro AI_V4MAPPED.
   # FreeBSD doesn't support AI_V4MAPPED but defines the macro.
   # https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198092
-  when not defined(freebsd) or defined(openbsd):
-    hints.ai_flags = AI_V4MAPPED
+  when not defined(freebsd) and not defined(openbsd) and not defined(netbsd):
+    if domain == AF_INET6:
+      hints.ai_flags = AI_V4MAPPED
   var gaiResult = getaddrinfo(address, $port, addr(hints), result)
   if gaiResult != 0'i32:
     when useWinVersion:
diff --git a/lib/pure/nimprof.nim b/lib/pure/nimprof.nim
index cfe6bc40d..e2397b91c 100644
--- a/lib/pure/nimprof.nim
+++ b/lib/pure/nimprof.nim
@@ -1,7 +1,7 @@
 #
 #
 #            Nim's Runtime Library
-#        (c) Copyright 2012 Andreas Rumpf
+#        (c) Copyright 2015 Andreas Rumpf
 #
 #    See the file "copying.txt", included in this
 #    distribution, for details about the copyright.
@@ -117,24 +117,38 @@ when defined(memProfiler):
   var
     gTicker {.threadvar.}: int
 
-  proc hook(st: StackTrace, size: int) {.nimcall.} =
+  proc requestedHook(): bool {.nimcall.} =
     if gTicker == 0:
-      gTicker = -1
-      when defined(ignoreAllocationSize):
-        hookAux(st, 1)
-      else:
-        hookAux(st, size)
       gTicker = SamplingInterval
+      result = true
     dec gTicker
 
+  proc hook(st: StackTrace, size: int) {.nimcall.} =
+    when defined(ignoreAllocationSize):
+      hookAux(st, 1)
+    else:
+      hookAux(st, size)
+
 else:
   var
     t0 {.threadvar.}: Ticks
+    gTicker: int # we use an additional counter to
+                 # avoid calling 'getTicks' too frequently
+
+  proc requestedHook(): bool {.nimcall.} =
+    if interval == 0: result = true
+    elif gTicker == 0:
+      gTicker = 500
+      if getTicks() - t0 > interval:
+        result = true
+    else:
+      dec gTicker
 
   proc hook(st: StackTrace) {.nimcall.} =
+    #echo "profiling! ", interval
     if interval == 0:
       hookAux(st, 1)
-    elif int64(t0) == 0 or getTicks() - t0 > interval:
+    else:
       hookAux(st, 1)
       t0 = getTicks()
 
@@ -145,9 +159,10 @@ proc cmpEntries(a, b: ptr ProfileEntry): int =
   result = b.getTotal - a.getTotal
 
 proc `//`(a, b: int): string =
-  result = format("$1/$2 = $3%", a, b, formatFloat(a / b * 100.0, ffDefault, 2))
+  result = format("$1/$2 = $3%", a, b, formatFloat(a / b * 100.0, ffDecimal, 2))
 
 proc writeProfile() {.noconv.} =
+  system.profilingRequestedHook = nil
   when declared(system.StackTrace):
     system.profilerHook = nil
   const filename = "profile_results.txt"
@@ -193,14 +208,15 @@ var
 proc disableProfiling*() =
   when declared(system.StackTrace):
     atomicDec disabled
-    system.profilerHook = nil
+    system.profilingRequestedHook = nil
 
 proc enableProfiling*() =
   when declared(system.StackTrace):
     if atomicInc(disabled) >= 0:
-      system.profilerHook = hook
+      system.profilingRequestedHook = requestedHook
 
 when declared(system.StackTrace):
+  system.profilingRequestedHook = requestedHook
   system.profilerHook = hook
   addQuitProc(writeProfile)
 
diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim
index 7a1e14a57..8560c3ee4 100644
--- a/lib/pure/osproc.nim
+++ b/lib/pure/osproc.nim
@@ -886,7 +886,7 @@ elif not defined(useNimRtl):
     discard write(data.pErrorPipe[writeIdx], addr error, sizeof(error))
     exitnow(1)
 
-  when defined(macosx) or defined(freebsd):
+  when defined(macosx) or defined(freebsd) or defined(netbsd) or defined(android):
     var environ {.importc.}: cstringArray
 
   proc startProcessAfterFork(data: ptr StartProcessData) =
@@ -916,7 +916,7 @@ elif not defined(useNimRtl):
     discard fcntl(data.pErrorPipe[writeIdx], F_SETFD, FD_CLOEXEC)
 
     if data.optionPoUsePath:
-      when defined(macosx) or defined(freebsd):
+      when defined(macosx) or defined(freebsd) or defined(netbsd) or defined(android):
         # MacOSX doesn't have execvpe, so we need workaround.
         # On MacOSX we can arrive here only from fork, so this is safe:
         environ = data.sysEnv
diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim
index b3708838a..698bde42a 100644
--- a/lib/pure/parseutils.nim
+++ b/lib/pure/parseutils.nim
@@ -25,7 +25,7 @@ const
 proc toLower(c: char): char {.inline.} =
   result = if c in {'A'..'Z'}: chr(ord(c)-ord('A')+ord('a')) else: c
 
-proc parseHex*(s: string, number: var int, start = 0): int {.
+proc parseHex*(s: string, number: var int, start = 0; maxLen = 0): int {.
   rtl, extern: "npuParseHex", noSideEffect.}  =
   ## Parses a hexadecimal number and stores its value in ``number``.
   ##
@@ -45,11 +45,14 @@ proc parseHex*(s: string, number: var int, start = 0): int {.
   ##   discard parseHex("0x38", value)
   ##   assert value == -200
   ##
+  ## If 'maxLen==0' the length of the hexadecimal number has no
+  ## upper bound. Not more than ```maxLen`` characters are parsed.
   var i = start
   var foundDigit = false
   if s[i] == '0' and (s[i+1] == 'x' or s[i+1] == 'X'): inc(i, 2)
   elif s[i] == '#': inc(i)
-  while true:
+  let last = if maxLen == 0: s.len else: i+maxLen
+  while i < last:
     case s[i]
     of '_': discard
     of '0'..'9':
diff --git a/lib/pure/redis.nim b/lib/pure/redis.nim
deleted file mode 100644
index e3f18a496..000000000
--- a/lib/pure/redis.nim
+++ /dev/null
@@ -1,1096 +0,0 @@
-#
-#
-#            Nim's Runtime Library
-#        (c) Copyright 2012 Dominik Picheta
-#
-#    See the file "copying.txt", included in this
-#    distribution, for details about the copyright.
-#
-
-## This module implements a redis client. It allows you to connect to a
-## redis-server instance, send commands and receive replies.
-##
-## **Beware**: Most (if not all) functions that return a ``RedisString`` may
-## return ``redisNil``, and functions which return a ``RedisList``
-## may return ``nil``.
-
-import sockets, os, strutils, parseutils
-
-const
-  redisNil* = "\0\0"
-
-type
-  Pipeline = ref object
-    enabled: bool
-    buffer: string
-    expected: int ## number of replies expected if pipelined
-
-type
-  SendMode = enum
-    normal, pipelined, multiple
-
-type
-  Redis* = object
-    socket: Socket
-    connected: bool
-    pipeline: Pipeline
-
-  RedisStatus* = string
-  RedisInteger* = BiggestInt
-  RedisString* = string ## Bulk reply
-  RedisList* = seq[RedisString] ## Multi-bulk reply
-
-  ReplyError* = object of IOError ## Invalid reply from redis
-  RedisError* = object of IOError        ## Error in redis
-
-{.deprecated: [TSendMode: SendMode, TRedis: Redis, TRedisStatus: RedisStatus,
-     TRedisInteger: RedisInteger, TRedisString: RedisString,
-     TRedisList: RedisList, EInvalidReply: ReplyError, ERedis: RedisError].}
-
-proc newPipeline(): Pipeline =
-  new(result)
-  result.buffer = ""
-  result.enabled = false
-  result.expected = 0
-
-proc open*(host = "localhost", port = 6379.Port): Redis =
-  ## Opens a connection to the redis server.
-  result.socket = socket(buffered = false)
-  if result.socket == invalidSocket:
-    raiseOSError(osLastError())
-  result.socket.connect(host, port)
-  result.pipeline = newPipeline()
-
-proc raiseInvalidReply(expected, got: char) =
-  raise newException(ReplyError,
-          "Expected '$1' at the beginning of a status reply got '$2'" %
-          [$expected, $got])
-
-proc raiseNoOK(status: string, pipelineEnabled: bool) =
-  if pipelineEnabled and not (status == "QUEUED" or status == "PIPELINED"):
-    raise newException(ReplyError, "Expected \"QUEUED\" or \"PIPELINED\" got \"$1\"" % status)
-  elif not pipelineEnabled and status != "OK":
-    raise newException(ReplyError, "Expected \"OK\" got \"$1\"" % status)
-
-template readSocket(r: Redis, dummyVal:expr): stmt =
-  var line {.inject.}: TaintedString = ""
-  if r.pipeline.enabled:
-    return dummyVal
-  else:
-    readLine(r.socket, line)
-
-proc parseStatus(r: Redis, line: string = ""): RedisStatus =
-  if r.pipeline.enabled:
-    return "PIPELINED"
-
-  if line == "":
-    raise newException(RedisError, "Server closed connection prematurely")
-
-  if line[0] == '-':
-    raise newException(RedisError, strip(line))
-  if line[0] != '+':
-    raiseInvalidReply('+', line[0])
-
-  return line.substr(1) # Strip '+'
-
-proc readStatus(r:Redis): RedisStatus =
-  r.readSocket("PIPELINED")
-  return r.parseStatus(line)
-
-proc parseInteger(r: Redis, line: string = ""): RedisInteger =
-  if r.pipeline.enabled: return -1
-
-  #if line == "+QUEUED":  # inside of multi
-  #  return -1
-
-  if line == "":
-    raise newException(RedisError, "Server closed connection prematurely")
-
-  if line[0] == '-':
-    raise newException(RedisError, strip(line))
-  if line[0] != ':':
-    raiseInvalidReply(':', line[0])
-
-  # Strip ':'
-  if parseBiggestInt(line, result, 1) == 0:
-    raise newException(ReplyError, "Unable to parse integer.")
-
-proc readInteger(r: Redis): RedisInteger =
-  r.readSocket(-1)
-  return r.parseInteger(line)
-
-proc recv(sock: Socket, size: int): TaintedString =
-  result = newString(size).TaintedString
-  if sock.recv(cstring(result), size) != size:
-    raise newException(ReplyError, "recv failed")
-
-proc parseSingleString(r: Redis, line:string, allowMBNil = false): RedisString =
-  if r.pipeline.enabled: return ""
-
-  # Error.
-  if line[0] == '-':
-    raise newException(RedisError, strip(line))
-
-  # Some commands return a /bulk/ value or a /multi-bulk/ nil. Odd.
-  if allowMBNil:
-    if line == "*-1":
-       return redisNil
-
-  if line[0] != '$':
-    raiseInvalidReply('$', line[0])
-
-  var numBytes = parseInt(line.substr(1))
-  if numBytes == -1:
-    return redisNil
-
-  var s = r.socket.recv(numBytes+2)
-  result = strip(s.string)
-
-proc readSingleString(r: Redis): RedisString =
-  r.readSocket("")
-  return r.parseSingleString(line)
-
-proc readNext(r: Redis): RedisList
-
-proc parseArrayLines(r: Redis, countLine:string): RedisList =
-  if countLine.string[0] != '*':
-    raiseInvalidReply('*', countLine.string[0])
-
-  var numElems = parseInt(countLine.string.substr(1))
-  if numElems == -1: return nil
-  result = @[]
-
-  for i in 1..numElems:
-    var parsed = r.readNext()
-    if not isNil(parsed):
-      for item in parsed:
-        result.add(item)
-
-proc readArrayLines(r: Redis): RedisList =
-  r.readSocket(nil)
-  return r.parseArrayLines(line)
-
-proc parseBulkString(r: Redis, allowMBNil = false, line:string = ""): RedisString =
-  if r.pipeline.enabled: return ""
-
-  return r.parseSingleString(line, allowMBNil)
-
-proc readBulkString(r: Redis, allowMBNil = false): RedisString =
-  r.readSocket("")
-  return r.parseBulkString(allowMBNil, line)
-
-proc readArray(r: Redis): RedisList =
-  r.readSocket(@[])
-  return r.parseArrayLines(line)
-
-proc readNext(r: Redis): RedisList =
-  r.readSocket(@[])
-
-  var res = case line[0]
-    of '+', '-': @[r.parseStatus(line)]
-    of ':': @[$(r.parseInteger(line))]
-    of '$': @[r.parseBulkString(true,line)]
-    of '*': r.parseArrayLines(line)
-    else:
-      raise newException(ReplyError, "readNext failed on line: " & line)
-      nil
-  r.pipeline.expected -= 1
-  return res
-
-proc flushPipeline*(r: Redis, wasMulti = false): RedisList =
-  ## Send buffered commands, clear buffer, return results
-  if r.pipeline.buffer.len > 0:
-    r.socket.send(r.pipeline.buffer)
-  r.pipeline.buffer = ""
-
-  r.pipeline.enabled = false
-  result = @[]
-
-  var tot = r.pipeline.expected
-
-  for i in 0..tot-1:
-    var ret = r.readNext()
-    for item in ret:
-     if not (item.contains("OK") or item.contains("QUEUED")):
-       result.add(item)
-
-  r.pipeline.expected = 0
-
-proc startPipelining*(r: Redis) =
-  ## Enable command pipelining (reduces network roundtrips).
-  ## Note that when enabled, you must call flushPipeline to actually send commands, except
-  ## for multi/exec() which enable and flush the pipeline automatically.
-  ## Commands return immediately with dummy values; actual results returned from
-  ## flushPipeline() or exec()
-  r.pipeline.expected = 0
-  r.pipeline.enabled = true
-
-proc sendCommand(r: Redis, cmd: string, args: varargs[string]) =
-  var request = "*" & $(1 + args.len()) & "\c\L"
-  request.add("$" & $cmd.len() & "\c\L")
-  request.add(cmd & "\c\L")
-  for i in items(args):
-    request.add("$" & $i.len() & "\c\L")
-    request.add(i & "\c\L")
-
-  if r.pipeline.enabled:
-    r.pipeline.buffer.add(request)
-    r.pipeline.expected += 1
-  else:
-    r.socket.send(request)
-
-proc sendCommand(r: Redis, cmd: string, arg1: string,
-                 args: varargs[string]) =
-  var request = "*" & $(2 + args.len()) & "\c\L"
-  request.add("$" & $cmd.len() & "\c\L")
-  request.add(cmd & "\c\L")
-  request.add("$" & $arg1.len() & "\c\L")
-  request.add(arg1 & "\c\L")
-  for i in items(args):
-    request.add("$" & $i.len() & "\c\L")
-    request.add(i & "\c\L")
-
-  if r.pipeline.enabled:
-    r.pipeline.expected += 1
-    r.pipeline.buffer.add(request)
-  else:
-    r.socket.send(request)
-
-# Keys
-
-proc del*(r: Redis, keys: varargs[string]): RedisInteger =
-  ## Delete a key or multiple keys
-  r.sendCommand("DEL", keys)
-  return r.readInteger()
-
-proc exists*(r: Redis, key: string): bool =
-  ## Determine if a key exists
-  r.sendCommand("EXISTS", key)
-  return r.readInteger() == 1
-
-proc expire*(r: Redis, key: string, seconds: int): bool =
-  ## Set a key's time to live in seconds. Returns `false` if the key could
-  ## not be found or the timeout could not be set.
-  r.sendCommand("EXPIRE", key, $seconds)
-  return r.readInteger() == 1
-
-proc expireAt*(r: Redis, key: string, timestamp: int): bool =
-  ## Set the expiration for a key as a UNIX timestamp. Returns `false`
-  ## if the key could not be found or the timeout could not be set.
-  r.sendCommand("EXPIREAT", key, $timestamp)
-  return r.readInteger() == 1
-
-proc keys*(r: Redis, pattern: string): RedisList =
-  ## Find all keys matching the given pattern
-  r.sendCommand("KEYS", pattern)
-  return r.readArray()
-
-proc scan*(r: Redis, cursor: var BiggestInt): RedisList =
-  ## Find all keys matching the given pattern and yield it to client in portions
-  ## using default Redis values for MATCH and COUNT parameters
-  r.sendCommand("SCAN", $cursor)
-  let reply = r.readArray()
-  cursor = strutils.parseBiggestInt(reply[0])
-  return reply[1..high(reply)]
-
-proc scan*(r: Redis, cursor: var BiggestInt, pattern: string): RedisList =
-  ## Find all keys matching the given pattern and yield it to client in portions
-  ## using cursor as a client query identifier. Using default Redis value for COUNT argument
-  r.sendCommand("SCAN", $cursor, ["MATCH", pattern])
-  let reply = r.readArray()
-  cursor = strutils.parseBiggestInt(reply[0])
-  return reply[1..high(reply)]
-
-proc scan*(r: Redis, cursor: var BiggestInt, pattern: string, count: int): RedisList =
-  ## Find all keys matching the given pattern and yield it to client in portions
-  ## using cursor as a client query identifier.
-  r.sendCommand("SCAN", $cursor, ["MATCH", pattern, "COUNT", $count])
-  let reply = r.readArray()
-  cursor = strutils.parseBiggestInt(reply[0])
-  return reply[1..high(reply)]
-
-proc move*(r: Redis, key: string, db: int): bool =
-  ## Move a key to another database. Returns `true` on a successful move.
-  r.sendCommand("MOVE", key, $db)
-  return r.readInteger() == 1
-
-proc persist*(r: Redis, key: string): bool =
-  ## Remove the expiration from a key.
-  ## Returns `true` when the timeout was removed.
-  r.sendCommand("PERSIST", key)
-  return r.readInteger() == 1
-
-proc randomKey*(r: Redis): RedisString =
-  ## Return a random key from the keyspace
-  r.sendCommand("RANDOMKEY")
-  return r.readBulkString()
-
-proc rename*(r: Redis, key, newkey: string): RedisStatus =
-  ## Rename a key.
-  ##
-  ## **WARNING:** Overwrites `newkey` if it exists!
-  r.sendCommand("RENAME", key, newkey)
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-proc renameNX*(r: Redis, key, newkey: string): bool =
-  ## Same as ``rename`` but doesn't continue if `newkey` exists.
-  ## Returns `true` if key was renamed.
-  r.sendCommand("RENAMENX", key, newkey)
-  return r.readInteger() == 1
-
-proc ttl*(r: Redis, key: string): RedisInteger =
-  ## Get the time to live for a key
-  r.sendCommand("TTL", key)
-  return r.readInteger()
-
-proc keyType*(r: Redis, key: string): RedisStatus =
-  ## Determine the type stored at key
-  r.sendCommand("TYPE", key)
-  return r.readStatus()
-
-
-# Strings
-
-proc append*(r: Redis, key, value: string): RedisInteger =
-  ## Append a value to a key
-  r.sendCommand("APPEND", key, value)
-  return r.readInteger()
-
-proc decr*(r: Redis, key: string): RedisInteger =
-  ## Decrement the integer value of a key by one
-  r.sendCommand("DECR", key)
-  return r.readInteger()
-
-proc decrBy*(r: Redis, key: string, decrement: int): RedisInteger =
-  ## Decrement the integer value of a key by the given number
-  r.sendCommand("DECRBY", key, $decrement)
-  return r.readInteger()
-
-proc get*(r: Redis, key: string): RedisString =
-  ## Get the value of a key. Returns `redisNil` when `key` doesn't exist.
-  r.sendCommand("GET", key)
-  return r.readBulkString()
-
-proc getBit*(r: Redis, key: string, offset: int): RedisInteger =
-  ## Returns the bit value at offset in the string value stored at key
-  r.sendCommand("GETBIT", key, $offset)
-  return r.readInteger()
-
-proc getRange*(r: Redis, key: string, start, stop: int): RedisString =
-  ## Get a substring of the string stored at a key
-  r.sendCommand("GETRANGE", key, $start, $stop)
-  return r.readBulkString()
-
-proc getSet*(r: Redis, key: string, value: string): RedisString =
-  ## Set the string value of a key and return its old value. Returns `redisNil`
-  ## when key doesn't exist.
-  r.sendCommand("GETSET", key, value)
-  return r.readBulkString()
-
-proc incr*(r: Redis, key: string): RedisInteger =
-  ## Increment the integer value of a key by one.
-  r.sendCommand("INCR", key)
-  return r.readInteger()
-
-proc incrBy*(r: Redis, key: string, increment: int): RedisInteger =
-  ## Increment the integer value of a key by the given number
-  r.sendCommand("INCRBY", key, $increment)
-  return r.readInteger()
-
-proc setk*(r: Redis, key, value: string) =
-  ## Set the string value of a key.
-  ##
-  ## NOTE: This function had to be renamed due to a clash with the `set` type.
-  r.sendCommand("SET", key, value)
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-proc setNX*(r: Redis, key, value: string): bool =
-  ## Set the value of a key, only if the key does not exist. Returns `true`
-  ## if the key was set.
-  r.sendCommand("SETNX", key, value)
-  return r.readInteger() == 1
-
-proc setBit*(r: Redis, key: string, offset: int,
-             value: string): RedisInteger =
-  ## Sets or clears the bit at offset in the string value stored at key
-  r.sendCommand("SETBIT", key, $offset, value)
-  return r.readInteger()
-
-proc setEx*(r: Redis, key: string, seconds: int, value: string): RedisStatus =
-  ## Set the value and expiration of a key
-  r.sendCommand("SETEX", key, $seconds, value)
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-proc setRange*(r: Redis, key: string, offset: int,
-               value: string): RedisInteger =
-  ## Overwrite part of a string at key starting at the specified offset
-  r.sendCommand("SETRANGE", key, $offset, value)
-  return r.readInteger()
-
-proc strlen*(r: Redis, key: string): RedisInteger =
-  ## Get the length of the value stored in a key. Returns 0 when key doesn't
-  ## exist.
-  r.sendCommand("STRLEN", key)
-  return r.readInteger()
-
-# Hashes
-proc hDel*(r: Redis, key, field: string): bool =
-  ## Delete a hash field at `key`. Returns `true` if the field was removed.
-  r.sendCommand("HDEL", key, field)
-  return r.readInteger() == 1
-
-proc hExists*(r: Redis, key, field: string): bool =
-  ## Determine if a hash field exists.
-  r.sendCommand("HEXISTS", key, field)
-  return r.readInteger() == 1
-
-proc hGet*(r: Redis, key, field: string): RedisString =
-  ## Get the value of a hash field
-  r.sendCommand("HGET", key, field)
-  return r.readBulkString()
-
-proc hGetAll*(r: Redis, key: string): RedisList =
-  ## Get all the fields and values in a hash
-  r.sendCommand("HGETALL", key)
-  return r.readArray()
-
-proc hIncrBy*(r: Redis, key, field: string, incr: int): RedisInteger =
-  ## Increment the integer value of a hash field by the given number
-  r.sendCommand("HINCRBY", key, field, $incr)
-  return r.readInteger()
-
-proc hKeys*(r: Redis, key: string): RedisList =
-  ## Get all the fields in a hash
-  r.sendCommand("HKEYS", key)
-  return r.readArray()
-
-proc hLen*(r: Redis, key: string): RedisInteger =
-  ## Get the number of fields in a hash
-  r.sendCommand("HLEN", key)
-  return r.readInteger()
-
-proc hMGet*(r: Redis, key: string, fields: varargs[string]): RedisList =
-  ## Get the values of all the given hash fields
-  r.sendCommand("HMGET", key, fields)
-  return r.readArray()
-
-proc hMSet*(r: Redis, key: string,
-            fieldValues: openArray[tuple[field, value: string]]) =
-  ## Set multiple hash fields to multiple values
-  var args = @[key]
-  for field, value in items(fieldValues):
-    args.add(field)
-    args.add(value)
-  r.sendCommand("HMSET", args)
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-proc hSet*(r: Redis, key, field, value: string): RedisInteger =
-  ## Set the string value of a hash field
-  r.sendCommand("HSET", key, field, value)
-  return r.readInteger()
-
-proc hSetNX*(r: Redis, key, field, value: string): RedisInteger =
-  ## Set the value of a hash field, only if the field does **not** exist
-  r.sendCommand("HSETNX", key, field, value)
-  return r.readInteger()
-
-proc hVals*(r: Redis, key: string): RedisList =
-  ## Get all the values in a hash
-  r.sendCommand("HVALS", key)
-  return r.readArray()
-
-# Lists
-
-proc bLPop*(r: Redis, keys: varargs[string], timeout: int): RedisList =
-  ## Remove and get the *first* element in a list, or block until
-  ## one is available
-  var args: seq[string] = @[]
-  for i in items(keys): args.add(i)
-  args.add($timeout)
-  r.sendCommand("BLPOP", args)
-  return r.readArray()
-
-proc bRPop*(r: Redis, keys: varargs[string], timeout: int): RedisList =
-  ## Remove and get the *last* element in a list, or block until one
-  ## is available.
-  var args: seq[string] = @[]
-  for i in items(keys): args.add(i)
-  args.add($timeout)
-  r.sendCommand("BRPOP", args)
-  return r.readArray()
-
-proc bRPopLPush*(r: Redis, source, destination: string,
-                 timeout: int): RedisString =
-  ## Pop a value from a list, push it to another list and return it; or
-  ## block until one is available.
-  ##
-  ## http://redis.io/commands/brpoplpush
-  r.sendCommand("BRPOPLPUSH", source, destination, $timeout)
-  return r.readBulkString(true) # Multi-Bulk nil allowed.
-
-proc lIndex*(r: Redis, key: string, index: int): RedisString =
-  ## Get an element from a list by its index
-  r.sendCommand("LINDEX", key, $index)
-  return r.readBulkString()
-
-proc lInsert*(r: Redis, key: string, before: bool, pivot, value: string):
-              RedisInteger =
-  ## Insert an element before or after another element in a list
-  var pos = if before: "BEFORE" else: "AFTER"
-  r.sendCommand("LINSERT", key, pos, pivot, value)
-  return r.readInteger()
-
-proc lLen*(r: Redis, key: string): RedisInteger =
-  ## Get the length of a list
-  r.sendCommand("LLEN", key)
-  return r.readInteger()
-
-proc lPop*(r: Redis, key: string): RedisString =
-  ## Remove and get the first element in a list
-  r.sendCommand("LPOP", key)
-  return r.readBulkString()
-
-proc lPush*(r: Redis, key, value: string, create: bool = true): RedisInteger =
-  ## Prepend a value to a list. Returns the length of the list after the push.
-  ## The ``create`` param specifies whether a list should be created if it
-  ## doesn't exist at ``key``. More specifically if ``create`` is true, `LPUSH`
-  ## will be used, otherwise `LPUSHX`.
-  if create:
-    r.sendCommand("LPUSH", key, value)
-  else:
-    r.sendCommand("LPUSHX", key, value)
-  return r.readInteger()
-
-proc lRange*(r: Redis, key: string, start, stop: int): RedisList =
-  ## Get a range of elements from a list. Returns `nil` when `key`
-  ## doesn't exist.
-  r.sendCommand("LRANGE", key, $start, $stop)
-  return r.readArray()
-
-proc lRem*(r: Redis, key: string, value: string, count: int = 0): RedisInteger =
-  ## Remove elements from a list. Returns the number of elements that have been
-  ## removed.
-  r.sendCommand("LREM", key, $count, value)
-  return r.readInteger()
-
-proc lSet*(r: Redis, key: string, index: int, value: string) =
-  ## Set the value of an element in a list by its index
-  r.sendCommand("LSET", key, $index, value)
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-proc lTrim*(r: Redis, key: string, start, stop: int)  =
-  ## Trim a list to the specified range
-  r.sendCommand("LTRIM", key, $start, $stop)
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-proc rPop*(r: Redis, key: string): RedisString =
-  ## Remove and get the last element in a list
-  r.sendCommand("RPOP", key)
-  return r.readBulkString()
-
-proc rPopLPush*(r: Redis, source, destination: string): RedisString =
-  ## Remove the last element in a list, append it to another list and return it
-  r.sendCommand("RPOPLPUSH", source, destination)
-  return r.readBulkString()
-
-proc rPush*(r: Redis, key, value: string, create: bool = true): RedisInteger =
-  ## Append a value to a list. Returns the length of the list after the push.
-  ## The ``create`` param specifies whether a list should be created if it
-  ## doesn't exist at ``key``. More specifically if ``create`` is true, `RPUSH`
-  ## will be used, otherwise `RPUSHX`.
-  if create:
-    r.sendCommand("RPUSH", key, value)
-  else:
-    r.sendCommand("RPUSHX", key, value)
-  return r.readInteger()
-
-# Sets
-
-proc sadd*(r: Redis, key: string, member: string): RedisInteger =
-  ## Add a member to a set
-  r.sendCommand("SADD", key, member)
-  return r.readInteger()
-
-proc scard*(r: Redis, key: string): RedisInteger =
-  ## Get the number of members in a set
-  r.sendCommand("SCARD", key)
-  return r.readInteger()
-
-proc sdiff*(r: Redis, keys: varargs[string]): RedisList =
-  ## Subtract multiple sets
-  r.sendCommand("SDIFF", keys)
-  return r.readArray()
-
-proc sdiffstore*(r: Redis, destination: string,
-                keys: varargs[string]): RedisInteger =
-  ## Subtract multiple sets and store the resulting set in a key
-  r.sendCommand("SDIFFSTORE", destination, keys)
-  return r.readInteger()
-
-proc sinter*(r: Redis, keys: varargs[string]): RedisList =
-  ## Intersect multiple sets
-  r.sendCommand("SINTER", keys)
-  return r.readArray()
-
-proc sinterstore*(r: Redis, destination: string,
-                 keys: varargs[string]): RedisInteger =
-  ## Intersect multiple sets and store the resulting set in a key
-  r.sendCommand("SINTERSTORE", destination, keys)
-  return r.readInteger()
-
-proc sismember*(r: Redis, key: string, member: string): RedisInteger =
-  ## Determine if a given value is a member of a set
-  r.sendCommand("SISMEMBER", key, member)
-  return r.readInteger()
-
-proc smembers*(r: Redis, key: string): RedisList =
-  ## Get all the members in a set
-  r.sendCommand("SMEMBERS", key)
-  return r.readArray()
-
-proc smove*(r: Redis, source: string, destination: string,
-           member: string): RedisInteger =
-  ## Move a member from one set to another
-  r.sendCommand("SMOVE", source, destination, member)
-  return r.readInteger()
-
-proc spop*(r: Redis, key: string): RedisString =
-  ## Remove and return a random member from a set
-  r.sendCommand("SPOP", key)
-  return r.readBulkString()
-
-proc srandmember*(r: Redis, key: string): RedisString =
-  ## Get a random member from a set
-  r.sendCommand("SRANDMEMBER", key)
-  return r.readBulkString()
-
-proc srem*(r: Redis, key: string, member: string): RedisInteger =
-  ## Remove a member from a set
-  r.sendCommand("SREM", key, member)
-  return r.readInteger()
-
-proc sunion*(r: Redis, keys: varargs[string]): RedisList =
-  ## Add multiple sets
-  r.sendCommand("SUNION", keys)
-  return r.readArray()
-
-proc sunionstore*(r: Redis, destination: string,
-                 key: varargs[string]): RedisInteger =
-  ## Add multiple sets and store the resulting set in a key
-  r.sendCommand("SUNIONSTORE", destination, key)
-  return r.readInteger()
-
-# Sorted sets
-
-proc zadd*(r: Redis, key: string, score: int, member: string): RedisInteger =
-  ## Add a member to a sorted set, or update its score if it already exists
-  r.sendCommand("ZADD", key, $score, member)
-  return r.readInteger()
-
-proc zcard*(r: Redis, key: string): RedisInteger =
-  ## Get the number of members in a sorted set
-  r.sendCommand("ZCARD", key)
-  return r.readInteger()
-
-proc zcount*(r: Redis, key: string, min: string, max: string): RedisInteger =
-  ## Count the members in a sorted set with scores within the given values
-  r.sendCommand("ZCOUNT", key, min, max)
-  return r.readInteger()
-
-proc zincrby*(r: Redis, key: string, increment: string,
-             member: string): RedisString =
-  ## Increment the score of a member in a sorted set
-  r.sendCommand("ZINCRBY", key, increment, member)
-  return r.readBulkString()
-
-proc zinterstore*(r: Redis, destination: string, numkeys: string,
-                 keys: openArray[string], weights: openArray[string] = [],
-                 aggregate: string = ""): RedisInteger =
-  ## Intersect multiple sorted sets and store the resulting sorted set in
-  ## a new key
-  var args = @[destination, numkeys]
-  for i in items(keys): args.add(i)
-
-  if weights.len != 0:
-    args.add("WITHSCORE")
-    for i in items(weights): args.add(i)
-  if aggregate.len != 0:
-    args.add("AGGREGATE")
-    args.add(aggregate)
-
-  r.sendCommand("ZINTERSTORE", args)
-
-  return r.readInteger()
-
-proc zrange*(r: Redis, key: string, start: string, stop: string,
-            withScores: bool): RedisList =
-  ## Return a range of members in a sorted set, by index
-  if not withScores:
-    r.sendCommand("ZRANGE", key, start, stop)
-  else:
-    r.sendCommand("ZRANGE", "WITHSCORES", key, start, stop)
-  return r.readArray()
-
-proc zrangebyscore*(r: Redis, key: string, min: string, max: string,
-                   withScore: bool = false, limit: bool = false,
-                   limitOffset: int = 0, limitCount: int = 0): RedisList =
-  ## Return a range of members in a sorted set, by score
-  var args = @[key, min, max]
-
-  if withScore: args.add("WITHSCORE")
-  if limit:
-    args.add("LIMIT")
-    args.add($limitOffset)
-    args.add($limitCount)
-
-  r.sendCommand("ZRANGEBYSCORE", args)
-  return r.readArray()
-
-proc zrank*(r: Redis, key: string, member: string): RedisString =
-  ## Determine the index of a member in a sorted set
-  r.sendCommand("ZRANK", key, member)
-  return r.readBulkString()
-
-proc zrem*(r: Redis, key: string, member: string): RedisInteger =
-  ## Remove a member from a sorted set
-  r.sendCommand("ZREM", key, member)
-  return r.readInteger()
-
-proc zremrangebyrank*(r: Redis, key: string, start: string,
-                     stop: string): RedisInteger =
-  ## Remove all members in a sorted set within the given indexes
-  r.sendCommand("ZREMRANGEBYRANK", key, start, stop)
-  return r.readInteger()
-
-proc zremrangebyscore*(r: Redis, key: string, min: string,
-                      max: string): RedisInteger =
-  ## Remove all members in a sorted set within the given scores
-  r.sendCommand("ZREMRANGEBYSCORE", key, min, max)
-  return r.readInteger()
-
-proc zrevrange*(r: Redis, key: string, start: string, stop: string,
-               withScore: bool): RedisList =
-  ## Return a range of members in a sorted set, by index,
-  ## with scores ordered from high to low
-  if withScore:
-    r.sendCommand("ZREVRANGE", "WITHSCORE", key, start, stop)
-  else: r.sendCommand("ZREVRANGE", key, start, stop)
-  return r.readArray()
-
-proc zrevrangebyscore*(r: Redis, key: string, min: string, max: string,
-                   withScore: bool = false, limit: bool = false,
-                   limitOffset: int = 0, limitCount: int = 0): RedisList =
-  ## Return a range of members in a sorted set, by score, with
-  ## scores ordered from high to low
-  var args = @[key, min, max]
-
-  if withScore: args.add("WITHSCORE")
-  if limit:
-    args.add("LIMIT")
-    args.add($limitOffset)
-    args.add($limitCount)
-
-  r.sendCommand("ZREVRANGEBYSCORE", args)
-  return r.readArray()
-
-proc zrevrank*(r: Redis, key: string, member: string): RedisString =
-  ## Determine the index of a member in a sorted set, with
-  ## scores ordered from high to low
-  r.sendCommand("ZREVRANK", key, member)
-  return r.readBulkString()
-
-proc zscore*(r: Redis, key: string, member: string): RedisString =
-  ## Get the score associated with the given member in a sorted set
-  r.sendCommand("ZSCORE", key, member)
-  return r.readBulkString()
-
-proc zunionstore*(r: Redis, destination: string, numkeys: string,
-                 keys: openArray[string], weights: openArray[string] = [],
-                 aggregate: string = ""): RedisInteger =
-  ## Add multiple sorted sets and store the resulting sorted set in a new key
-  var args = @[destination, numkeys]
-  for i in items(keys): args.add(i)
-
-  if weights.len != 0:
-    args.add("WEIGHTS")
-    for i in items(weights): args.add(i)
-  if aggregate.len != 0:
-    args.add("AGGREGATE")
-    args.add(aggregate)
-
-  r.sendCommand("ZUNIONSTORE", args)
-
-  return r.readInteger()
-
-# HyperLogLog
-
-proc pfadd*(r: Redis, key: string, elements: varargs[string]): RedisInteger =
-  ## Add variable number of elements into special 'HyperLogLog' set type
-  r.sendCommand("PFADD", key, elements)
-  return r.readInteger()
-
-proc pfcount*(r: Redis, key: string): RedisInteger =
-  ## Count approximate number of elements in 'HyperLogLog'
-  r.sendCommand("PFCOUNT", key)
-  return r.readInteger()
-
-proc pfmerge*(r: Redis, destination: string, sources: varargs[string]) =
-  ## Merge several source HyperLogLog's into one specified by destKey
-  r.sendCommand("PFMERGE", destination, sources)
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-# Pub/Sub
-
-# TODO: pub/sub -- I don't think this will work synchronously.
-discard """
-proc psubscribe*(r: Redis, pattern: openarray[string]): ???? =
-  ## Listen for messages published to channels matching the given patterns
-  r.socket.send("PSUBSCRIBE $#\c\L" % pattern)
-  return ???
-
-proc publish*(r: Redis, channel: string, message: string): RedisInteger =
-  ## Post a message to a channel
-  r.socket.send("PUBLISH $# $#\c\L" % [channel, message])
-  return r.readInteger()
-
-proc punsubscribe*(r: Redis, [pattern: openarray[string], : string): ???? =
-  ## Stop listening for messages posted to channels matching the given patterns
-  r.socket.send("PUNSUBSCRIBE $# $#\c\L" % [[pattern.join(), ])
-  return ???
-
-proc subscribe*(r: Redis, channel: openarray[string]): ???? =
-  ## Listen for messages published to the given channels
-  r.socket.send("SUBSCRIBE $#\c\L" % channel.join)
-  return ???
-
-proc unsubscribe*(r: Redis, [channel: openarray[string], : string): ???? =
-  ## Stop listening for messages posted to the given channels
-  r.socket.send("UNSUBSCRIBE $# $#\c\L" % [[channel.join(), ])
-  return ???
-
-"""
-
-# Transactions
-
-proc discardMulti*(r: Redis) =
-  ## Discard all commands issued after MULTI
-  r.sendCommand("DISCARD")
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-proc exec*(r: Redis): RedisList =
-  ## Execute all commands issued after MULTI
-  r.sendCommand("EXEC")
-  r.pipeline.enabled = false
-  # Will reply with +OK for MULTI/EXEC and +QUEUED for every command
-  # between, then with the results
-  return r.flushPipeline(true)
-
-
-proc multi*(r: Redis) =
-  ## Mark the start of a transaction block
-  r.startPipelining()
-  r.sendCommand("MULTI")
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-proc unwatch*(r: Redis) =
-  ## Forget about all watched keys
-  r.sendCommand("UNWATCH")
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-proc watch*(r: Redis, key: varargs[string]) =
-  ## Watch the given keys to determine execution of the MULTI/EXEC block
-  r.sendCommand("WATCH", key)
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-# Connection
-
-proc auth*(r: Redis, password: string) =
-  ## Authenticate to the server
-  r.sendCommand("AUTH", password)
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-proc echoServ*(r: Redis, message: string): RedisString =
-  ## Echo the given string
-  r.sendCommand("ECHO", message)
-  return r.readBulkString()
-
-proc ping*(r: Redis): RedisStatus =
-  ## Ping the server
-  r.sendCommand("PING")
-  return r.readStatus()
-
-proc quit*(r: Redis) =
-  ## Close the connection
-  r.sendCommand("QUIT")
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-proc select*(r: Redis, index: int): RedisStatus =
-  ## Change the selected database for the current connection
-  r.sendCommand("SELECT", $index)
-  return r.readStatus()
-
-# Server
-
-proc bgrewriteaof*(r: Redis) =
-  ## Asynchronously rewrite the append-only file
-  r.sendCommand("BGREWRITEAOF")
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-proc bgsave*(r: Redis) =
-  ## Asynchronously save the dataset to disk
-  r.sendCommand("BGSAVE")
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-proc configGet*(r: Redis, parameter: string): RedisList =
-  ## Get the value of a configuration parameter
-  r.sendCommand("CONFIG", "GET", parameter)
-  return r.readArray()
-
-proc configSet*(r: Redis, parameter: string, value: string) =
-  ## Set a configuration parameter to the given value
-  r.sendCommand("CONFIG", "SET", parameter, value)
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-proc configResetStat*(r: Redis) =
-  ## Reset the stats returned by INFO
-  r.sendCommand("CONFIG", "RESETSTAT")
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-proc dbsize*(r: Redis): RedisInteger =
-  ## Return the number of keys in the selected database
-  r.sendCommand("DBSIZE")
-  return r.readInteger()
-
-proc debugObject*(r: Redis, key: string): RedisStatus =
-  ## Get debugging information about a key
-  r.sendCommand("DEBUG", "OBJECT", key)
-  return r.readStatus()
-
-proc debugSegfault*(r: Redis) =
-  ## Make the server crash
-  r.sendCommand("DEBUG", "SEGFAULT")
-
-proc flushall*(r: Redis): RedisStatus =
-  ## Remove all keys from all databases
-  r.sendCommand("FLUSHALL")
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-proc flushdb*(r: Redis): RedisStatus =
-  ## Remove all keys from the current database
-  r.sendCommand("FLUSHDB")
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-proc info*(r: Redis): RedisString =
-  ## Get information and statistics about the server
-  r.sendCommand("INFO")
-  return r.readBulkString()
-
-proc lastsave*(r: Redis): RedisInteger =
-  ## Get the UNIX time stamp of the last successful save to disk
-  r.sendCommand("LASTSAVE")
-  return r.readInteger()
-
-discard """
-proc monitor*(r: Redis) =
-  ## Listen for all requests received by the server in real time
-  r.socket.send("MONITOR\c\L")
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-"""
-
-proc save*(r: Redis) =
-  ## Synchronously save the dataset to disk
-  r.sendCommand("SAVE")
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-proc shutdown*(r: Redis) =
-  ## Synchronously save the dataset to disk and then shut down the server
-  r.sendCommand("SHUTDOWN")
-  var s = "".TaintedString
-  r.socket.readLine(s)
-  if s.string.len != 0: raise newException(RedisError, s.string)
-
-proc slaveof*(r: Redis, host: string, port: string) =
-  ## Make the server a slave of another instance, or promote it as master
-  r.sendCommand("SLAVEOF", host, port)
-  raiseNoOK(r.readStatus(), r.pipeline.enabled)
-
-iterator hPairs*(r: Redis, key: string): tuple[key, value: string] =
-  ## Iterator for keys and values in a hash.
-  var
-    contents = r.hGetAll(key)
-    k = ""
-  for i in items(contents):
-    if k == "":
-      k = i
-    else:
-      yield (k, i)
-      k = ""
-
-proc someTests(r: Redis, how: SendMode):seq[string] =
-  var list:seq[string] = @[]
-
-  if how == pipelined:
-    r.startPipelining()
-  elif how ==  multiple:
-    r.multi()
-
-  r.setk("nim:test", "Testing something.")
-  r.setk("nim:utf8", "こんにちは")
-  r.setk("nim:esc", "\\ths ągt\\")
-  r.setk("nim:int", "1")
-  list.add(r.get("nim:esc"))
-  list.add($(r.incr("nim:int")))
-  list.add(r.get("nim:int"))
-  list.add(r.get("nim:utf8"))
-  list.add($(r.hSet("test1", "name", "A Test")))
-  var res = r.hGetAll("test1")
-  for r in res:
-    list.add(r)
-  list.add(r.get("invalid_key"))
-  list.add($(r.lPush("mylist","itema")))
-  list.add($(r.lPush("mylist","itemb")))
-  r.lTrim("mylist",0,1)
-  var p = r.lRange("mylist", 0, -1)
-
-  for i in items(p):
-    if not isNil(i):
-      list.add(i)
-
-  list.add(r.debugObject("mylist"))
-
-  r.configSet("timeout", "299")
-  var g = r.configGet("timeout")
-  for i in items(g):
-    list.add(i)
-
-  list.add(r.echoServ("BLAH"))
-
-  case how
-  of normal:
-    return list
-  of pipelined:
-    return r.flushPipeline()
-  of multiple:
-    return r.exec()
-
-proc assertListsIdentical(listA, listB: seq[string]) =
-  assert(listA.len == listB.len)
-  var i = 0
-  for item in listA:
-    assert(item == listB[i])
-    i = i + 1
-
-when not defined(testing) and isMainModule:
-  when false:
-    var r = open()
-
-    # Test with no pipelining
-    var listNormal = r.someTests(normal)
-
-    # Test with pipelining enabled
-    var listPipelined = r.someTests(pipelined)
-    assertListsIdentical(listNormal, listPipelined)
-
-    # Test with multi/exec() (automatic pipelining)
-    var listMulti = r.someTests(multiple)
-    assertListsIdentical(listNormal, listMulti)
diff --git a/lib/pure/stats.nim b/lib/pure/stats.nim
new file mode 100644
index 000000000..ec4cd182b
--- /dev/null
+++ b/lib/pure/stats.nim
@@ -0,0 +1,348 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2015 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+## Statistical analysis framework for performing
+## basic statistical analysis of data.
+## The data is analysed in a single pass, when a data value
+## is pushed to the ``RunningStat`` or ``RunningRegress`` objects
+##
+## ``RunningStat`` calculates for a single data set
+## - n (data count)
+## - min  (smallest value)
+## - max  (largest value)
+## - sum
+## - mean
+## - variance
+## - varianceS (sample var)
+## - standardDeviation
+## - standardDeviationS  (sample stddev)
+## - skewness (the third statistical moment)
+## - kurtosis (the fourth statistical moment)
+##
+## ``RunningRegress`` calculates for two sets of data
+## - n
+## - slope
+## - intercept
+## - correlation
+##
+## Procs have been provided to calculate statistics on arrays and sequences.
+##
+## However, if more than a single statistical calculation is required, it is more
+## efficient to push the data once to the RunningStat object, and
+## call the numerous statistical procs for the RunningStat object.
+##
+## .. code-block:: Nim
+##
+##  var rs: RunningStat
+##  rs.push(MySeqOfData)
+##  rs.mean()
+##  rs.variance()
+##  rs.skewness()
+##  rs.kurtosis()
+
+from math import FloatClass, sqrt, pow, round
+
+{.push debugger:off .} # the user does not want to trace a part
+                       # of the standard library!
+{.push checks:off, line_dir:off, stack_trace:off.}
+
+type
+  RunningStat* = object             ## an accumulator for statistical data
+    n*: int                         ## number of pushed data
+    min*, max*, sum*: float         ## self-explaining
+    mom1, mom2, mom3, mom4: float   ## statistical moments, mom1 is mean
+
+
+  RunningRegress* = object  ## an accumulator for regression calculations
+    n*: int                 ## number of pushed data
+    x_stats*: RunningStat   ## stats for first set of data
+    y_stats*: RunningStat   ## stats for second set of data
+    s_xy: float             ## accumulated data for combined xy
+
+{.deprecated: [TFloatClass: FloatClass, TRunningStat: RunningStat].}
+
+# ----------- RunningStat --------------------------
+proc clear*(s: var RunningStat) =
+  ## reset `s`
+  s.n = 0
+  s.min = toBiggestFloat(int.high)
+  s.max = 0.0
+  s.sum = 0.0
+  s.mom1 = 0.0
+  s.mom2 = 0.0
+  s.mom3 = 0.0
+  s.mom4 = 0.0
+
+proc push*(s: var RunningStat, x: float) =
+  ## pushes a value `x` for processing
+  if s.n == 0: s.min = x
+  inc(s.n)
+  # See Knuth TAOCP vol 2, 3rd edition, page 232
+  if s.min > x: s.min = x
+  if s.max < x: s.max = x
+  s.sum += x
+  let n = toFloat(s.n)
+  let delta = x - s.mom1
+  let delta_n = delta / toFloat(s.n)
+  let delta_n2 = delta_n * delta_n
+  let term1 = delta * delta_n * toFloat(s.n - 1)
+  s.mom4 += term1 * delta_n2 * (n*n - 3*n + 3) +
+              6*delta_n2*s.mom2 - 4*delta_n*s.mom3
+  s.mom3 += term1 * delta_n * (n - 2) - 3*delta_n*s.mom2
+  s.mom2 += term1
+  s.mom1 += delta_n
+
+proc push*(s: var RunningStat, x: int) =
+  ## pushes a value `x` for processing.
+  ##
+  ## `x` is simply converted to ``float``
+  ## and the other push operation is called.
+  s.push(toFloat(x))
+
+proc push*(s: var RunningStat, x: openarray[float|int]) =
+  ## pushes all values of `x` for processing.
+  ##
+  ## Int values of `x` are simply converted to ``float`` and
+  ## the other push operation is called.
+  for val in x:
+    s.push(val)
+
+proc mean*(s: RunningStat): float =
+  ## computes the current mean of `s`
+  result = s.mom1
+
+proc variance*(s: RunningStat): float =
+  ## computes the current population variance of `s`
+  result = s.mom2 / toFloat(s.n)
+
+proc varianceS*(s: RunningStat): float =
+  ## computes the current sample variance of `s`
+  if s.n > 1: result = s.mom2 / toFloat(s.n - 1)
+
+proc standardDeviation*(s: RunningStat): float =
+  ## computes the current population standard deviation of `s`
+  result = sqrt(variance(s))
+
+proc standardDeviationS*(s: RunningStat): float =
+  ## computes the current sample standard deviation of `s`
+  result = sqrt(varianceS(s))
+
+proc skewness*(s: RunningStat): float =
+  ## computes the current population skewness of `s`
+  result = sqrt(toFloat(s.n)) * s.mom3 / pow(s.mom2, 1.5)
+
+proc skewnessS*(s: RunningStat): float =
+  ## computes the current sample skewness of `s`
+  let s2 = skewness(s)
+  result = sqrt(toFloat(s.n*(s.n-1)))*s2 / toFloat(s.n-2)
+
+proc kurtosis*(s: RunningStat): float =
+  ## computes the current population kurtosis of `s`
+  result = toFloat(s.n) * s.mom4 / (s.mom2 * s.mom2) - 3.0
+
+proc kurtosisS*(s: RunningStat): float =
+  ## computes the current sample kurtosis of `s`
+  result = toFloat(s.n-1) / toFloat((s.n-2)*(s.n-3)) *
+              (toFloat(s.n+1)*kurtosis(s) + 6)
+
+proc `+`*(a, b: RunningStat): RunningStat =
+  ## combine two RunningStats.
+  ##
+  ## Useful if performing parallel analysis of data series
+  ## and need to re-combine parallel result sets
+  result.clear()
+  result.n = a.n + b.n
+
+  let delta = b.mom1 - a.mom1
+  let delta2 = delta*delta
+  let delta3 = delta*delta2
+  let delta4 = delta2*delta2
+  let n = toFloat(result.n)
+
+  result.mom1 = (a.n.float*a.mom1 + b.n.float*b.mom1) / n
+  result.mom2 = a.mom2 + b.mom2 + delta2 * a.n.float * b.n.float / n
+  result.mom3 = a.mom3 + b.mom3 +
+                delta3 * a.n.float * b.n.float * (a.n.float - b.n.float)/(n*n);
+  result.mom3 += 3.0*delta * (a.n.float*b.mom2 - b.n.float*a.mom2) / n
+  result.mom4 = a.mom4 + b.mom4 +
+            delta4*a.n.float*b.n.float * toFloat(a.n*a.n - a.n*b.n + b.n*b.n) /
+                (n*n*n)
+  result.mom4 += 6.0*delta2 * (a.n.float*a.n.float*b.mom2 + b.n.float*b.n.float*a.mom2) /
+                (n*n) +
+                4.0*delta*(a.n.float*b.mom3 - b.n.float*a.mom3) / n
+  result.max = max(a.max, b.max)
+  result.min = max(a.min, b.min)
+
+proc `+=`*(a: var RunningStat, b: RunningStat) {.inline.} =
+  ## add a second RunningStats `b` to `a`
+  a = a + b
+# ---------------------- standalone array/seq stats ---------------------
+proc mean*[T](x: openArray[T]): float =
+  ## computes the mean of `x`
+  var rs: RunningStat
+  rs.push(x)
+  result = rs.mean()
+
+proc variance*[T](x: openArray[T]): float =
+  ## computes the population variance of `x`
+  var rs: RunningStat
+  rs.push(x)
+  result = rs.variance()
+
+proc varianceS*[T](x: openArray[T]): float =
+  ## computes the sample variance of `x`
+  var rs: RunningStat
+  rs.push(x)
+  result = rs.varianceS()
+
+proc standardDeviation*[T](x: openArray[T]): float =
+  ## computes the population standardDeviation of `x`
+  var rs: RunningStat
+  rs.push(x)
+  result = rs.standardDeviation()
+
+proc standardDeviationS*[T](x: openArray[T]): float =
+  ## computes the sanple standardDeviation of `x`
+  var rs: RunningStat
+  rs.push(x)
+  result = rs.standardDeviationS()
+
+proc skewness*[T](x: openArray[T]): float =
+  ## computes the population skewness of `x`
+  var rs: RunningStat
+  rs.push(x)
+  result = rs.skewness()
+
+proc skewnessS*[T](x: openArray[T]): float =
+  ## computes the sample skewness of `x`
+  var rs: RunningStat
+  rs.push(x)
+  result = rs.skewnessS()
+
+proc kurtosis*[T](x: openArray[T]): float =
+  ## computes the population kurtosis of `x`
+  var rs: RunningStat
+  rs.push(x)
+  result = rs.kurtosis()
+
+proc kurtosisS*[T](x: openArray[T]): float =
+  ## computes the sample kurtosis of `x`
+  var rs: RunningStat
+  rs.push(x)
+  result = rs.kurtosisS()
+
+# ---------------------- Running Regression -----------------------------
+
+proc clear*(r: var RunningRegress) =
+  ## reset `r`
+  r.x_stats.clear()
+  r.y_stats.clear()
+  r.s_xy = 0.0
+  r.n = 0
+
+proc push*(r: var RunningRegress, x, y: float) =
+  ## pushes two values `x` and `y` for processing
+  r.s_xy += (r.x_stats.mean() - x)*(r.y_stats.mean() - y)*
+                toFloat(r.n) / toFloat(r.n + 1)
+  r.x_stats.push(x)
+  r.y_stats.push(y)
+  inc(r.n)
+
+proc push*(r: var RunningRegress, x, y: int) {.inline.} =
+  ## pushes two values `x` and `y` for processing.
+  ##
+  ## `x` and `y` are converted to ``float``
+  ## and the other push operation is called.
+  r.push(toFloat(x), toFloat(y))
+
+proc push*(r: var RunningRegress, x, y: openarray[float|int]) =
+  ## pushes two sets of values `x` and `y` for processing.
+  assert(x.len == y.len)
+  for i in 0..<x.len:
+    r.push(x[i], y[i])
+
+proc slope*(r: RunningRegress): float =
+  ## computes the current slope of `r`
+  let s_xx = r.x_stats.varianceS()*toFloat(r.n - 1)
+  result = r.s_xy / s_xx
+
+proc intercept*(r: RunningRegress): float =
+  ## computes the current intercept of `r`
+  result = r.y_stats.mean() - r.slope()*r.x_stats.mean()
+
+proc correlation*(r: RunningRegress): float =
+  ## computes the current correlation of the two data
+  ## sets pushed into `r`
+  let t = r.x_stats.standardDeviation() * r.y_stats.standardDeviation()
+  result = r.s_xy / ( toFloat(r.n) * t )
+
+proc `+`*(a, b: RunningRegress):  RunningRegress =
+  ## combine two `RunningRegress` objects.
+  ##
+  ## Useful if performing parallel analysis of data series
+  ## and need to re-combine parallel result sets
+  result.clear()
+  result.x_stats = a.x_stats + b.x_stats
+  result.y_stats = a.y_stats + b.y_stats
+  result.n = a.n + b.n
+
+  let delta_x = b.x_stats.mean() - a.x_stats.mean()
+  let delta_y = b.y_stats.mean() - a.y_stats.mean()
+  result.s_xy = a.s_xy + b.s_xy +
+      toFloat(a.n*b.n)*delta_x*delta_y/toFloat(result.n)
+
+proc `+=`*(a: var RunningRegress, b: RunningRegress) =
+  ## add RunningRegress `b` to `a`
+  a = a + b
+
+{.pop.}
+{.pop.}
+
+when isMainModule:
+  proc clean(x: float): float =
+    result = round(1.0e8*x).float * 1.0e-8
+
+  var rs: RunningStat
+  rs.push(@[1.0, 2.0, 1.0, 4.0, 1.0, 4.0, 1.0, 2.0])
+  doAssert(rs.n == 8)
+  doAssert(clean(rs.mean) == 2.0)
+  doAssert(clean(rs.variance()) == 1.5)
+  doAssert(clean(rs.varianceS()) == 1.71428571)
+  doAssert(clean(rs.skewness()) == 0.81649658)
+  doAssert(clean(rs.skewnessS()) == 1.01835015)
+  doAssert(clean(rs.kurtosis()) == -1.0)
+  doAssert(clean(rs.kurtosisS()) == -0.7000000000000001)
+
+  var rs1, rs2: RunningStat
+  rs1.push(@[1.0, 2.0, 1.0, 4.0])
+  rs2.push(@[1.0, 4.0, 1.0, 2.0])
+  let rs3 = rs1 + rs2
+  doAssert(clean(rs3.mom2) == clean(rs.mom2))
+  doAssert(clean(rs3.mom3) == clean(rs.mom3))
+  doAssert(clean(rs3.mom4) == clean(rs.mom4))
+  rs1 += rs2
+  doAssert(clean(rs1.mom2) == clean(rs.mom2))
+  doAssert(clean(rs1.mom3) == clean(rs.mom3))
+  doAssert(clean(rs1.mom4) == clean(rs.mom4))
+  rs1.clear()
+  rs1.push(@[1.0, 2.2, 1.4, 4.9])
+  doAssert(rs1.sum == 9.5)
+  doAssert(rs1.mean() == 2.375)
+
+  var rr: RunningRegress
+  rr.push(@[0.0,1.0,2.8,3.0,4.0], @[0.0,1.0,2.3,3.0,4.0])
+  doAssert(rr.slope() == 0.9695585996955861)
+  doAssert(rr.intercept() == -0.03424657534246611)
+  doAssert(rr.correlation() == 0.9905100362239381)
+  var rr1, rr2: RunningRegress
+  rr1.push(@[0.0,1.0], @[0.0,1.0])
+  rr2.push(@[2.8,3.0,4.0], @[2.3,3.0,4.0])
+  let rr3 = rr1 + rr2
+  doAssert(rr3.correlation() == rr.correlation())
+  doAssert(clean(rr3.slope()) == clean(rr.slope()))
+  doAssert(clean(rr3.intercept()) == clean(rr.intercept()))
diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim
index aa29bb073..a446f85b4 100644
--- a/lib/pure/strutils.nim
+++ b/lib/pure/strutils.nim
@@ -1210,22 +1210,21 @@ proc unescape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect,
   ## If `s` does not begin with ``prefix`` and end with ``suffix`` a
   ## ValueError exception will be raised.
   result = newStringOfCap(s.len)
-  var i = 0
+  var i = prefix.len
   if not s.startsWith(prefix):
     raise newException(ValueError,
                        "String does not start with a prefix of: " & prefix)
-  inc(i)
   while true:
     if i == s.len-suffix.len: break
     case s[i]
     of '\\':
       case s[i+1]:
       of 'x':
-        inc i
+        inc i, 2
         var c: int
-        i += parseutils.parseHex(s, c, i)
+        i += parseutils.parseHex(s, c, i, maxLen=2)
         result.add(chr(c))
-        inc(i, 2)
+        dec i, 2
       of '\\':
         result.add('\\')
       of '\'':
@@ -1721,3 +1720,4 @@ when isMainModule:
   doAssert isUpper("ABC")
   doAssert(not isUpper("AAcc"))
   doAssert(not isUpper("A#$"))
+  doAssert(unescape(r"\x013", "", "") == "\x013")
diff --git a/lib/pure/times.nim b/lib/pure/times.nim
index c9854a650..03745d54e 100644
--- a/lib/pure/times.nim
+++ b/lib/pure/times.nim
@@ -29,7 +29,7 @@
 ##  echo "epochTime() float value: ", epochTime()
 ##  echo "getTime()   float value: ", toSeconds(getTime())
 ##  echo "cpuTime()   float value: ", cpuTime()
-##  echo "An hour from now      : ", getLocalTime(getTime()) + initInterval(0,0,0,1)
+##  echo "An hour from now      : ", getLocalTime(getTime()) + 1.hours
 ##  echo "An hour from (UTC) now: ", getGmTime(getTime()) + initInterval(0,0,0,1)
 
 {.push debugger:off.} # the user does not want to trace a part
@@ -171,11 +171,6 @@ type
 {.deprecated: [TMonth: Month, TWeekDay: WeekDay, TTime: Time,
     TTimeInterval: TimeInterval, TTimeInfo: TimeInfo].}
 
-proc miliseconds*(t: TimeInterval): int {.deprecated.} = t.milliseconds
-
-proc `miliseconds=`*(t:var TimeInterval, milliseconds: int) {.deprecated.} =
-  t.milliseconds = milliseconds
-
 proc getTime*(): Time {.tags: [TimeEffect], benign.}
   ## gets the current calendar time as a UNIX epoch value (number of seconds
   ## elapsed since 1970) with integer precission. Use epochTime for higher
@@ -245,13 +240,59 @@ proc getStartMilsecs*(): int {.deprecated, tags: [TimeEffect], benign.}
 proc initInterval*(milliseconds, seconds, minutes, hours, days, months,
                    years: int = 0): TimeInterval =
   ## creates a new ``TimeInterval``.
-  result.milliseconds = milliseconds
-  result.seconds = seconds
-  result.minutes = minutes
-  result.hours = hours
-  result.days = days
-  result.months = months
-  result.years = years
+  ##
+  ## You can also use the convenience procedures called ``milliseconds``,
+  ## ``seconds``, ``minutes``, ``hours``, ``days``, ``months``, and ``years``.
+  ##
+  ## Example:
+  ##
+  ## .. code-block:: nim
+  ##
+  ##     let day = initInterval(hours=24)
+  ##     let tomorrow = getTime() + day
+  ##     echo(tomorrow)
+  var carryO = 0
+  result.milliseconds = `mod`(milliseconds, 1000)
+  carryO = `div`(milliseconds, 1000)
+  result.seconds = `mod`(carryO + seconds, 60)
+  carryO = `div`(seconds, 60)
+  result.minutes = `mod`(carryO + minutes, 60)
+  carryO = `div`(minutes, 60)
+  result.hours = `mod`(carryO + hours, 24)
+  carryO = `div`(hours, 24)
+  result.days = carryO + days
+  carryO = 0
+  result.months = `mod`(months, 12)
+  carryO = `div`(months, 12)
+  result.years = carryO + years
+
+proc `+`*(ti1, ti2: TimeInterval): TimeInterval =
+  ## Adds two ``TimeInterval`` objects together.
+  var carryO = 0
+  result.milliseconds = `mod`(ti1.milliseconds + ti2.milliseconds, 1000)
+  carryO = `div`(ti1.milliseconds + ti2.milliseconds, 1000)
+  result.seconds = `mod`(carryO + ti1.seconds + ti2.seconds, 60)
+  carryO = `div`(ti1.seconds + ti2.seconds, 60)
+  result.minutes = `mod`(carryO + ti1.minutes + ti2.minutes, 60)
+  carryO = `div`(ti1.minutes + ti2.minutes, 60)
+  result.hours = `mod`(carryO + ti1.hours + ti2.hours, 24)
+  carryO = `div`(ti1.hours + ti2.hours, 24)
+  result.days = carryO + ti1.days + ti2.days
+  carryO = 0
+  result.months = `mod`(ti1.months + ti2.months, 12)
+  carryO = `div`(ti1.months + ti2.months, 12)
+  result.years = carryO + ti1.years + ti2.years
+
+proc `-`*(ti1, ti2: TimeInterval): TimeInterval =
+  ## Subtracts TimeInterval ``ti1`` from ``ti2``.
+  result = ti1
+  result.milliseconds -= ti2.milliseconds
+  result.seconds -= ti2.seconds
+  result.minutes -= ti2.minutes
+  result.hours -= ti2.hours
+  result.days -= ti2.days
+  result.months -= ti2.months
+  result.years -= ti2.years
 
 proc isLeapYear*(year: int): bool =
   ## returns true if ``year`` is a leap year
@@ -288,13 +329,22 @@ proc toSeconds(a: TimeInfo, interval: TimeInterval): float =
 
   newinterv.months += interval.years * 12
   var curMonth = anew.month
-  for mth in 1 .. newinterv.months:
-    result += float(getDaysInMonth(curMonth, anew.year) * 24 * 60 * 60)
-    if curMonth == mDec:
-      curMonth = mJan
-      anew.year.inc()
-    else:
-      curMonth.inc()
+  if newinterv.months < 0:   # subtracting
+    for mth in countDown(-1 * newinterv.months, 1):
+      result -= float(getDaysInMonth(curMonth, anew.year) * 24 * 60 * 60)
+      if curMonth == mJan:
+        curMonth = mDec
+        anew.year.dec()
+      else:
+        curMonth.dec()
+  else:  # adding
+    for mth in 1 .. newinterv.months:
+      result += float(getDaysInMonth(curMonth, anew.year) * 24 * 60 * 60)
+      if curMonth == mDec:
+        curMonth = mJan
+        anew.year.inc()
+      else:
+        curMonth.inc()
   result += float(newinterv.days * 24 * 60 * 60)
   result += float(newinterv.hours * 60 * 60)
   result += float(newinterv.minutes * 60)
@@ -302,28 +352,39 @@ proc toSeconds(a: TimeInfo, interval: TimeInterval): float =
   result += newinterv.milliseconds / 1000
 
 proc `+`*(a: TimeInfo, interval: TimeInterval): TimeInfo =
-  ## adds ``interval`` time.
+  ## adds ``interval`` time from TimeInfo ``a``.
   ##
   ## **Note:** This has been only briefly tested and it may not be
   ## very accurate.
   let t = toSeconds(timeInfoToTime(a))
   let secs = toSeconds(a, interval)
-  #if a.tzname == "UTC":
-  #  result = getGMTime(fromSeconds(t + secs))
-  #else:
   result = getLocalTime(fromSeconds(t + secs))
 
 proc `-`*(a: TimeInfo, interval: TimeInterval): TimeInfo =
-  ## subtracts ``interval`` time.
+  ## subtracts ``interval`` time from TimeInfo ``a``.
   ##
   ## **Note:** This has been only briefly tested, it is inaccurate especially
   ## when you subtract so much that you reach the Julian calendar.
   let t = toSeconds(timeInfoToTime(a))
-  let secs = toSeconds(a, interval)
-  #if a.tzname == "UTC":
-  #  result = getGMTime(fromSeconds(t - secs))
-  #else:
-  result = getLocalTime(fromSeconds(t - secs))
+  var intval: TimeInterval
+  intval.milliseconds = - interval.milliseconds
+  intval.seconds = - interval.seconds
+  intval.minutes = - interval.minutes
+  intval.hours = - interval.hours
+  intval.days = - interval.days
+  intval.months = - interval.months
+  intval.years = - interval.years
+  let secs = toSeconds(a, intval)
+  result = getLocalTime(fromSeconds(t + secs))
+
+proc miliseconds*(t: TimeInterval): int {.deprecated.} = t.milliseconds
+
+proc `miliseconds=`*(t: var TimeInterval, milliseconds: int) {.deprecated.} =
+  ## An alias for a misspelled field in ``TimeInterval``.
+  ##
+  ## **Warning:** This should not be used! It will be removed in the next
+  ## version.
+  t.milliseconds = milliseconds
 
 when not defined(JS):
   proc epochTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].}
@@ -603,6 +664,69 @@ proc `$`*(m: Month): string =
       "November", "December"]
   return lookup[m]
 
+proc milliseconds*(ms: int): TimeInterval {.inline.} =
+  ## TimeInterval of `ms` milliseconds
+  ##
+  ## Note: not all time functions have millisecond resolution
+  initInterval(`mod`(ms,1000), `div`(ms,1000))
+
+proc seconds*(s: int): TimeInterval {.inline.} =
+  ## TimeInterval of `s` seconds
+  ##
+  ## ``echo getTime() + 5.second``
+  initInterval(0,`mod`(s,60), `div`(s,60))
+
+proc minutes*(m: int): TimeInterval {.inline.} =
+  ## TimeInterval of `m` minutes
+  ##
+  ## ``echo getTime() + 5.minutes``
+  initInterval(0,0,`mod`(m,60), `div`(m,60))
+
+proc hours*(h: int): TimeInterval {.inline.} =
+  ## TimeInterval of `h` hours
+  ##
+  ## ``echo getTime() + 2.hours``
+  initInterval(0,0,0,`mod`(h,24),`div`(h,24))
+
+proc days*(d: int): TimeInterval {.inline.} =
+  ## TimeInterval of `d` days
+  ##
+  ## ``echo getTime() + 2.days``
+  initInterval(0,0,0,0,d)
+
+proc months*(m: int): TimeInterval {.inline.} =
+  ## TimeInterval of `m` months
+  ##
+  ## ``echo getTime() + 2.months``
+  initInterval(0,0,0,0,0,`mod`(m,12),`div`(m,12))
+
+proc years*(y: int): TimeInterval {.inline.} =
+  ## TimeInterval of `y` years
+  ##
+  ## ``echo getTime() + 2.years``
+  initInterval(0,0,0,0,0,0,y)
+
+proc `+=`*(t: var Time, ti: TimeInterval) =
+  ## modifies `t` by adding the interval `ti`
+  t = timeInfoToTime(getLocalTime(t) + ti)
+
+proc `+`*(t: Time, ti: TimeInterval): Time =
+  ## adds the interval `ti` to Time `t`
+  ## by converting to localTime, adding the interval, and converting back
+  ##
+  ## ``echo getTime() + 1.day``
+  result = timeInfoToTime(getLocalTime(t) + ti)
+
+proc `-=`*(t: var Time, ti: TimeInterval) =
+  ## modifies `t` by subtracting the interval `ti`
+  t = timeInfoToTime(getLocalTime(t) - ti)
+
+proc `-`*(t: Time, ti: TimeInterval): Time =
+  ## adds the interval `ti` to Time `t`
+  ##
+  ## ``echo getTime() - 1.day``
+  result = timeInfoToTime(getLocalTime(t) - ti)
+
 proc formatToken(info: TimeInfo, token: string, buf: var string) =
   ## Helper of the format proc to parse individual tokens.
   ##