summary refs log tree commit diff stats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/pure/asyncdispatch.nim79
-rw-r--r--lib/pure/asynchttpserver.nim25
-rw-r--r--lib/pure/asyncnet.nim34
-rw-r--r--lib/pure/strutils.nim18
-rw-r--r--lib/pure/times.nim61
-rw-r--r--lib/system.nim46
6 files changed, 205 insertions, 58 deletions
diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim
index f49388b17..d91507a85 100644
--- a/lib/pure/asyncdispatch.nim
+++ b/lib/pure/asyncdispatch.nim
@@ -126,6 +126,7 @@ export Port, SocketFlag
 ## * Can't await in a ``except`` body
 ## * Forward declarations for async procs are broken,
 ##   link includes workaround: https://github.com/nim-lang/Nim/issues/3182.
+## * FutureVar[T] needs to be completed manually.
 
 # TODO: Check if yielded future is nil and throw a more meaningful exception
 
@@ -145,10 +146,15 @@ type
   Future*[T] = ref object of FutureBase ## Typed future.
     value: T ## Stored value
 
-{.deprecated: [PFutureBase: FutureBase, PFuture: Future].}
+  FutureVar*[T] = distinct Future[T]
+
+  FutureError* = object of Exception
+    cause*: FutureBase
 
+{.deprecated: [PFutureBase: FutureBase, PFuture: Future].}
 
-var currentID = 0
+when not defined(release):
+  var currentID = 0
 proc newFuture*[T](fromProc: string = "unspecified"): Future[T] =
   ## Creates a new future.
   ##
@@ -162,18 +168,39 @@ proc newFuture*[T](fromProc: string = "unspecified"): Future[T] =
     result.fromProc = fromProc
     currentID.inc()
 
+proc newFutureVar*[T](fromProc = "unspecified"): FutureVar[T] =
+  ## Create a new ``FutureVar``. This Future type is ideally suited for
+  ## situations where you want to avoid unnecessary allocations of Futures.
+  ##
+  ## Specifying ``fromProc``, which is a string specifying the name of the proc
+  ## that this future belongs to, is a good habit as it helps with debugging.
+  result = FutureVar[T](newFuture[T](fromProc))
+
+proc clean*[T](future: FutureVar[T]) =
+  ## Resets the ``finished`` status of ``future``.
+  Future[T](future).finished = false
+  Future[T](future).error = nil
+
 proc checkFinished[T](future: Future[T]) =
+  ## Checks whether `future` is finished. If it is then raises a
+  ## ``FutureError``.
   when not defined(release):
     if future.finished:
-      echo("<-----> ", future.id, " ", future.fromProc)
-      echo(future.stackTrace)
-      echo("-----")
+      var msg = ""
+      msg.add("An attempt was made to complete a Future more than once. ")
+      msg.add("Details:")
+      msg.add("\n  Future ID: " & $future.id)
+      msg.add("\n  Created in proc: " & future.fromProc)
+      msg.add("\n  Stack trace to moment of creation:")
+      msg.add("\n" & indent(future.stackTrace.strip(), 4))
       when T is string:
-        echo("Contents: ", future.value.repr)
-      echo("<----->")
-      echo("Future already finished, cannot finish twice.")
-      echo getStackTrace()
-      assert false
+        msg.add("\n  Contents (string): ")
+        msg.add("\n" & indent(future.value.repr, 4))
+      msg.add("\n  Stack trace to moment of secondary completion:")
+      msg.add("\n" & indent(getStackTrace().strip(), 4))
+      var err = newException(FutureError, msg)
+      err.cause = future
+      raise err
 
 proc complete*[T](future: Future[T], val: T) =
   ## Completes ``future`` with value ``val``.
@@ -194,6 +221,15 @@ proc complete*(future: Future[void]) =
   if future.cb != nil:
     future.cb()
 
+proc complete*[T](future: FutureVar[T]) =
+  ## Completes a ``FutureVar``.
+  template fut: expr = Future[T](future)
+  checkFinished(fut)
+  assert(fut.error == nil)
+  fut.finished = true
+  if fut.cb != nil:
+    fut.cb()
+
 proc fail*[T](future: Future[T], error: ref Exception) =
   ## Completes ``future`` with ``error``.
   #assert(not future.finished, "Future already finished, cannot finish twice.")
@@ -230,15 +266,17 @@ proc `callback=`*[T](future: Future[T],
   ## If future has already completed then ``cb`` will be called immediately.
   future.callback = proc () = cb(future)
 
-proc echoOriginalStackTrace[T](future: Future[T]) =
+proc injectStacktrace[T](future: Future[T]) =
   # TODO: Come up with something better.
   when not defined(release):
-    echo("Original stack trace in ", future.fromProc, ":")
+    var msg = ""
+    msg.add("\n  " & future.fromProc & "'s lead up to read of failed Future:")
+
     if not future.errorStackTrace.isNil and future.errorStackTrace != "":
-      echo(future.errorStackTrace)
+      msg.add("\n" & indent(future.errorStackTrace.strip(), 4))
     else:
-      echo("Empty or nil stack trace.")
-    echo("Continuing...")
+      msg.add("\n    Empty or nil stack trace.")
+    future.error.msg.add(msg)
 
 proc read*[T](future: Future[T]): T =
   ## Retrieves the value of ``future``. Future must be finished otherwise
@@ -247,7 +285,7 @@ proc read*[T](future: Future[T]): T =
   ## If the result of the future is an error then that error will be raised.
   if future.finished:
     if future.error != nil:
-      echoOriginalStackTrace(future)
+      injectStacktrace(future)
       raise future.error
     when T isnot void:
       return future.value
@@ -264,6 +302,13 @@ proc readError*[T](future: Future[T]): ref Exception =
   else:
     raise newException(ValueError, "No error in future.")
 
+proc mget*[T](future: FutureVar[T]): var T =
+  ## Returns a mutable value stored in ``future``.
+  ##
+  ## Unlike ``read``, this function will not raise an exception if the
+  ## Future has not been finished.
+  result = Future[T](future).value
+
 proc finished*[T](future: Future[T]): bool =
   ## Determines whether ``future`` has completed.
   ##
@@ -282,7 +327,7 @@ proc asyncCheck*[T](future: Future[T]) =
   future.callback =
     proc () =
       if future.failed:
-        echoOriginalStackTrace(future)
+        injectStacktrace(future)
         raise future.error
 
 proc `and`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim
index f9085e4bf..aa7d458b3 100644
--- a/lib/pure/asynchttpserver.nim
+++ b/lib/pure/asynchttpserver.nim
@@ -151,7 +151,8 @@ proc processClient(client: AsyncSocket, address: string,
   var request: Request
   request.url = initUri()
   request.headers = newStringTable(modeCaseInsensitive)
-  var line = newStringOfCap(80)
+  var lineFut = newFutureVar[string]("asynchttpserver.processClient")
+  lineFut.mget() = newStringOfCap(80)
   var key, value = ""
 
   while not client.isClosed:
@@ -165,14 +166,15 @@ proc processClient(client: AsyncSocket, address: string,
     request.client = client
 
     # First line - GET /path HTTP/1.1
-    line.setLen(0)
-    await client.recvLineInto(addr line) # TODO: Timeouts.
-    if line == "":
+    lineFut.mget().setLen(0)
+    lineFut.clean()
+    await client.recvLineInto(lineFut) # TODO: Timeouts.
+    if lineFut.mget == "":
       client.close()
       return
 
     var i = 0
-    for linePart in line.split(' '):
+    for linePart in lineFut.mget.split(' '):
       case i
       of 0: request.reqMethod.shallowCopy(linePart.normalize)
       of 1: parseUri(linePart, request.url)
@@ -184,20 +186,21 @@ proc processClient(client: AsyncSocket, address: string,
             "Invalid request protocol. Got: " & linePart)
           continue
       else:
-        await request.respond(Http400, "Invalid request. Got: " & line)
+        await request.respond(Http400, "Invalid request. Got: " & lineFut.mget)
         continue
       inc i
 
     # Headers
     while true:
       i = 0
-      line.setLen(0)
-      await client.recvLineInto(addr line)
+      lineFut.mget.setLen(0)
+      lineFut.clean()
+      await client.recvLineInto(lineFut)
 
-      if line == "":
+      if lineFut.mget == "":
         client.close(); return
-      if line == "\c\L": break
-      let (key, value) = parseHeader(line)
+      if lineFut.mget == "\c\L": break
+      let (key, value) = parseHeader(lineFut.mget)
       request.headers[key] = value
 
     if request.reqMethod == "post":
diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim
index 9139200f3..ba314af10 100644
--- a/lib/pure/asyncnet.nim
+++ b/lib/pure/asyncnet.nim
@@ -316,7 +316,7 @@ proc accept*(socket: AsyncSocket,
         retFut.complete(future.read.client)
   return retFut
 
-proc recvLineInto*(socket: AsyncSocket, resString: ptr string,
+proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string],
     flags = {SocketFlag.SafeDisconn}) {.async.} =
   ## Reads a line of data from ``socket`` into ``resString``.
   ##
@@ -338,16 +338,23 @@ proc recvLineInto*(socket: AsyncSocket, resString: ptr string,
   ## **Warning**: ``recvLineInto`` currently uses a raw pointer to a string for
   ## performance reasons. This will likely change soon to use FutureVars.
   assert SocketFlag.Peek notin flags ## TODO:
+  assert(not resString.mget.isNil(),
+         "String inside resString future needs to be initialised")
   result = newFuture[void]("asyncnet.recvLineInto")
 
+  # TODO: Make the async transformation check for FutureVar params and complete
+  # them when the result future is completed.
+  # Can we replace the result future with the FutureVar?
+
   template addNLIfEmpty(): stmt =
-    if resString[].len == 0:
-      resString[].add("\c\L")
+    if resString.mget.len == 0:
+      resString.mget.add("\c\L")
 
   if socket.isBuffered:
     if socket.bufLen == 0:
       let res = socket.readIntoBuf(flags)
       if res == 0:
+        resString.complete()
         return
 
     var lastR = false
@@ -355,7 +362,8 @@ proc recvLineInto*(socket: AsyncSocket, resString: ptr string,
       if socket.currPos >= socket.bufLen:
         let res = socket.readIntoBuf(flags)
         if res == 0:
-          resString[].setLen(0)
+          resString.mget.setLen(0)
+          resString.complete()
           return
 
       case socket.buffer[socket.currPos]
@@ -365,13 +373,15 @@ proc recvLineInto*(socket: AsyncSocket, resString: ptr string,
       of '\L':
         addNLIfEmpty()
         socket.currPos.inc()
+        resString.complete()
         return
       else:
         if lastR:
           socket.currPos.inc()
+          resString.complete()
           return
         else:
-          resString[].add socket.buffer[socket.currPos]
+          resString.mget.add socket.buffer[socket.currPos]
       socket.currPos.inc()
   else:
     var c = ""
@@ -379,18 +389,22 @@ proc recvLineInto*(socket: AsyncSocket, resString: ptr string,
       let recvFut = recv(socket, 1, flags)
       c = recvFut.read()
       if c.len == 0:
-        resString[].setLen(0)
+        resString.mget.setLen(0)
+        resString.complete()
         return
       if c == "\r":
         let recvFut = recv(socket, 1, flags) # Skip \L
         c = recvFut.read()
         assert c == "\L"
         addNLIfEmpty()
+        resString.complete()
         return
       elif c == "\L":
         addNLIfEmpty()
+        resString.complete()
         return
-      resString[].add c
+      resString.mget.add c
+  resString.complete()
 
 proc recvLine*(socket: AsyncSocket,
     flags = {SocketFlag.SafeDisconn}): Future[string] {.async.} =
@@ -416,8 +430,10 @@ proc recvLine*(socket: AsyncSocket,
       result.add("\c\L")
   assert SocketFlag.Peek notin flags ## TODO:
 
-  result = ""
-  await socket.recvLineInto(addr result, flags)
+  # TODO: Optimise this
+  var resString = newFutureVar[string]("asyncnet.recvLine")
+  await socket.recvLineInto(resString, flags)
+  result = resString.mget()
 
 proc listen*(socket: AsyncSocket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} =
   ## Marks ``socket`` as accepting connections.
diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim
index ae3bd7f63..d1c09f43d 100644
--- a/lib/pure/strutils.nim
+++ b/lib/pure/strutils.nim
@@ -630,6 +630,22 @@ proc wordWrap*(s: string, maxLineWidth = 80,
       result.add(lastSep & word)
       lastSep.setLen(0)
 
+proc indent*(s: string, count: Natural, padding: string = " "): string
+    {.noSideEffect, rtl, extern: "nsuIndent".} =
+  ## Indents each line in ``s`` by ``count`` amount of ``padding``.
+  ##
+  ## **Note:** This currently does not preserve the specific new line characters
+  ## used.
+  result = ""
+  var i = 0
+  for line in s.splitLines():
+    if i != 0:
+      result.add("\n")
+    for j in 1..count:
+      result.add(padding)
+    result.add(line)
+    i.inc
+
 proc unindent*(s: string, eatAllIndent = false): string {.
                noSideEffect, rtl, extern: "nsuUnindent".} =
   ## Unindents `s`.
@@ -1502,3 +1518,5 @@ when isMainModule:
                  chars = {'s', 't', 'r', 'i', 'p', 'm', 'e'}) == " but don't strip this "
   doAssert strip("sfoofoofoos", leading = false, chars = {'s'}) == "sfoofoofoo"
   doAssert strip("sfoofoofoos", trailing = false, chars = {'s'}) == "foofoofoos"
+
+  doAssert "  foo\n  bar".indent(4, "Q") == "QQQQ  foo\nQQQQ  bar"
diff --git a/lib/pure/times.nim b/lib/pure/times.nim
index aa4ae5ace..3142952e7 100644
--- a/lib/pure/times.nim
+++ b/lib/pure/times.nim
@@ -11,6 +11,26 @@
 ## This module contains routines and types for dealing with time.
 ## This module is available for the `JavaScript target
 ## <backends.html#the-javascript-target>`_.
+##
+## Examples:
+##
+## .. code-block:: nim
+##
+##  import times, os
+##  var
+##    t = cpuTime()
+##
+##  sleep(100)   # replace this with something to be timed
+##  echo "Time taken: ",cpuTime() - t
+##
+##  echo "My formatted time: ", format(getLocalTime(getTime()), "d MMMM yyyy HH:mm")
+##  echo "Using predefined formats: ", getClockStr(), " ", getDateStr()
+##
+##  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 (UTC) now: ", getGmTime(getTime()) + initInterval(0,0,0,1)
 
 {.push debugger:off.} # the user does not want to trace a part
                       # of the standard library!
@@ -288,10 +308,10 @@ proc `+`*(a: TimeInfo, interval: TimeInterval): TimeInfo =
   ## 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))
+  #if a.tzname == "UTC":
+  #  result = getGMTime(fromSeconds(t + secs))
+  #else:
+  result = getLocalTime(fromSeconds(t + secs))
 
 proc `-`*(a: TimeInfo, interval: TimeInterval): TimeInfo =
   ## subtracts ``interval`` time.
@@ -300,10 +320,10 @@ proc `-`*(a: TimeInfo, interval: TimeInterval): TimeInfo =
   ## 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))
+  #if a.tzname == "UTC":
+  #  result = getGMTime(fromSeconds(t - secs))
+  #else:
+  result = getLocalTime(fromSeconds(t - secs))
 
 when not defined(JS):
   proc epochTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].}
@@ -1269,3 +1289,28 @@ when isMainModule:
   assert getDayOfWeekJulian(21, 9, 1970) == dMon
   assert getDayOfWeekJulian(1, 1, 2000) == dSat
   assert getDayOfWeekJulian(1, 1, 2021) == dFri
+
+  # toSeconds tests with GM and Local timezones
+  #var t4 = getGMTime(fromSeconds(876124714)) # Mon  6 Oct 08:58:34 BST 1997
+  var t4L = getLocalTime(fromSeconds(876124714))
+  assert toSeconds(timeInfoToTime(t4L)) == 876124714    # fromSeconds is effectively "localTime"
+  assert toSeconds(timeInfoToTime(t4L)) + t4L.timezone.float == toSeconds(timeInfoToTime(t4))
+
+  assert toSeconds(t4, initInterval(seconds=0)) == 0.0
+  assert toSeconds(t4L, initInterval(milliseconds=1)) == toSeconds(t4, initInterval(milliseconds=1))
+  assert toSeconds(t4L, initInterval(seconds=1)) == toSeconds(t4, initInterval(seconds=1))
+  assert toSeconds(t4L, initInterval(minutes=1)) == toSeconds(t4, initInterval(minutes=1))
+  assert toSeconds(t4L, initInterval(hours=1)) == toSeconds(t4, initInterval(hours=1))
+  assert toSeconds(t4L, initInterval(days=1)) == toSeconds(t4, initInterval(days=1))
+  assert toSeconds(t4L, initInterval(months=1)) == toSeconds(t4, initInterval(months=1))
+  assert toSeconds(t4L, initInterval(years=1)) == toSeconds(t4, initInterval(years=1))
+
+  # adding intervals
+  var
+    a1L = toSeconds(timeInfoToTime(t4L + initInterval(hours = 1))) + t4L.timezone.float
+    a1G = toSeconds(timeInfoToTime(t4)) + 60.0 * 60.0
+  assert a1L == a1G
+  # subtracting intervals
+  a1L = toSeconds(timeInfoToTime(t4L - initInterval(hours = 1))) + t4L.timezone.float
+  a1G = toSeconds(timeInfoToTime(t4)) - (60.0 * 60.0)
+  assert a1L == a1G
diff --git a/lib/system.nim b/lib/system.nim
index 3d7d4bd28..1890ce5be 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -1175,9 +1175,12 @@ const
 
   seqShallowFlag = low(int)
 
-let nimvm* {.magic: "Nimvm".}: bool = false
+when defined(nimKnowsNimvm):
+  let nimvm* {.magic: "Nimvm".}: bool = false
     ## may be used only in "when" expression.
     ## It is true in Nim VM context and false otherwise
+else:
+  const nimvm*: bool = false
 
 proc compileOption*(option: string): bool {.
   magic: "CompileOption", noSideEffect.}
@@ -1293,25 +1296,42 @@ proc shallowCopy*[T](x: var T, y: T) {.noSideEffect, magic: "ShallowCopy".}
 proc del*[T](x: var seq[T], i: Natural) {.noSideEffect.} =
   ## deletes the item at index `i` by putting ``x[high(x)]`` into position `i`.
   ## This is an O(1) operation.
-  let xl = x.len
-  shallowCopy(x[i], x[xl-1])
-  setLen(x, xl-1)
+  let xl = x.len - 1
+  shallowCopy(x[i], x[xl])
+  setLen(x, xl)
 
 proc delete*[T](x: var seq[T], i: Natural) {.noSideEffect.} =
   ## deletes the item at index `i` by moving ``x[i+1..]`` by one position.
   ## This is an O(n) operation.
-  let xl = x.len
-  for j in i..xl-2: shallowCopy(x[j], x[j+1])
-  setLen(x, xl-1)
+  template defaultImpl =
+    let xl = x.len
+    for j in i..xl-2: shallowCopy(x[j], x[j+1])
+    setLen(x, xl-1)
+
+  when nimvm:
+    defaultImpl()
+  else:
+    when defined(js):
+      {.emit: "`x`[`x`_Idx].splice(`i`, 1);".}
+    else:
+      defaultImpl()
 
 proc insert*[T](x: var seq[T], item: T, i = 0.Natural) {.noSideEffect.} =
   ## inserts `item` into `x` at position `i`.
-  let xl = x.len
-  setLen(x, xl+1)
-  var j = xl-1
-  while j >= i:
-    shallowCopy(x[j+1], x[j])
-    dec(j)
+  template defaultImpl =
+    let xl = x.len
+    setLen(x, xl+1)
+    var j = xl-1
+    while j >= i:
+      shallowCopy(x[j+1], x[j])
+      dec(j)
+  when nimvm:
+    defaultImpl()
+  else:
+    when defined(js):
+      {.emit: "`x`[`x`_Idx].splice(`i`, 0, null);".}
+    else:
+      defaultImpl()
   x[i] = item
 
 proc repr*[T](x: T): string {.magic: "Repr", noSideEffect.}