Merge branch 'devel' into async-improvements

This commit is contained in:
Dominik Picheta 2018-01-17 16:28:00 +00:00
commit 47d05b3f2e
201 changed files with 6604 additions and 2628 deletions

View file

@ -168,18 +168,20 @@ type
timers*: HeapQueue[tuple[finishAt: float, fut: Future[void]]]
callbacks*: Deque[proc ()]
proc processTimers(p: PDispatcherBase) {.inline.} =
proc processTimers(p: PDispatcherBase; didSomeWork: var bool) {.inline.} =
#Process just part if timers at a step
var count = p.timers.len
let t = epochTime()
while count > 0 and t >= p.timers[0].finishAt:
p.timers.pop().fut.complete()
dec count
didSomeWork = true
proc processPendingCallbacks(p: PDispatcherBase) =
proc processPendingCallbacks(p: PDispatcherBase; didSomeWork: var bool) =
while p.callbacks.len > 0:
var cb = p.callbacks.popFirst()
cb()
didSomeWork = true
proc adjustedTimeout(p: PDispatcherBase, timeout: int): int {.inline.} =
# If dispatcher has active timers this proc returns the timeout
@ -298,14 +300,13 @@ when defined(windows) or defined(nimdoc):
let p = getGlobalDispatcher()
p.handles.len != 0 or p.timers.len != 0 or p.callbacks.len != 0
proc poll*(timeout = 500) =
## Waits for completion events and processes them. Raises ``ValueError``
## if there are no pending operations.
proc runOnce(timeout = 500): bool =
let p = getGlobalDispatcher()
if p.handles.len == 0 and p.timers.len == 0 and p.callbacks.len == 0:
raise newException(ValueError,
"No handles or timers registered in dispatcher.")
result = false
if p.handles.len != 0:
let at = p.adjustedTimeout(timeout)
var llTimeout =
@ -318,6 +319,7 @@ when defined(windows) or defined(nimdoc):
let res = getQueuedCompletionStatus(p.ioPort,
addr lpNumberOfBytesTransferred, addr lpCompletionKey,
cast[ptr POVERLAPPED](addr customOverlapped), llTimeout).bool
result = true
# http://stackoverflow.com/a/12277264/492186
# TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html
@ -347,13 +349,14 @@ when defined(windows) or defined(nimdoc):
else:
if errCode.int32 == WAIT_TIMEOUT:
# Timed out
discard
result = false
else: raiseOSError(errCode)
# Timer processing.
processTimers(p)
processTimers(p, result)
# Callback queue processing
processPendingCallbacks(p)
processPendingCallbacks(p, result)
var acceptEx: WSAPROC_ACCEPTEX
var connectEx: WSAPROC_CONNECTEX
@ -1229,7 +1232,7 @@ else:
# descriptor was unregistered in callback via `unregister()`.
discard
proc poll*(timeout = 500) =
proc runOnce(timeout = 500): bool =
let p = getGlobalDispatcher()
when ioselSupportedPlatform:
let customSet = {Event.Timer, Event.Signal, Event.Process,
@ -1239,6 +1242,7 @@ else:
raise newException(ValueError,
"No handles or timers registered in dispatcher.")
result = false
if not p.selector.isEmpty():
var keys: array[64, ReadyKey]
var count = p.selector.selectInto(p.adjustedTimeout(timeout), keys)
@ -1251,20 +1255,24 @@ else:
if Event.Read in events or events == {Event.Error}:
processBasicCallbacks(fd, readList)
result = true
if Event.Write in events or events == {Event.Error}:
processBasicCallbacks(fd, writeList)
result = true
if Event.User in events or events == {Event.Error}:
if Event.User in events:
processBasicCallbacks(fd, readList)
custom = true
if rLength == 0:
p.selector.unregister(fd)
result = true
when ioselSupportedPlatform:
if (customSet * events) != {}:
custom = true
processCustomCallbacks(fd)
result = true
# because state `data` can be modified in callback we need to update
# descriptor events with currently registered callbacks.
@ -1276,9 +1284,9 @@ else:
p.selector.updateHandle(SocketHandle(fd), newEvents)
# Timer processing.
processTimers(p)
processTimers(p, result)
# Callback queue processing
processPendingCallbacks(p)
processPendingCallbacks(p, result)
proc recv*(socket: AsyncFD, size: int,
flags = {SocketFlag.SafeDisconn}): Future[string] =
@ -1501,6 +1509,19 @@ else:
data.readList.add(cb)
p.selector.registerEvent(SelectEvent(ev), data)
proc drain*(timeout = 500) =
## Waits for completion events and processes them. Raises ``ValueError``
## if there are no pending operations. In contrast to ``poll`` this
## processes as many events as are available.
if runOnce(timeout):
while hasPendingOperations() and runOnce(0): discard
proc poll*(timeout = 500) =
## Waits for completion events and processes them. Raises ``ValueError``
## if there are no pending operations. This runs the underlying OS
## `epoll`:idx: or `kqueue`:idx: primitive only once.
discard runOnce(timeout)
# Common procedures between current and upcoming asyncdispatch
include includes.asynccommon

View file

@ -1,4 +1,4 @@
import os, tables, strutils, times, heapqueue, options, deques
import os, tables, strutils, times, heapqueue, options, deques, cstrutils
# TODO: This shouldn't need to be included, but should ideally be exported.
type
@ -217,17 +217,78 @@ proc `callback=`*[T](future: Future[T],
## If future has already completed then ``cb`` will be called immediately.
future.callback = proc () = cb(future)
proc injectStacktrace[T](future: Future[T]) =
# TODO: Come up with something better.
when not defined(release):
var msg = ""
msg.add("\n " & future.fromProc & "'s lead up to read of failed Future:")
proc getHint(entry: StackTraceEntry): string =
## We try to provide some hints about stack trace entries that the user
## may not be familiar with, in particular calls inside the stdlib.
result = ""
if entry.procname == "processPendingCallbacks":
if cmpIgnoreStyle(entry.filename, "asyncdispatch.nim") == 0:
return "Executes pending callbacks"
elif entry.procname == "poll":
if cmpIgnoreStyle(entry.filename, "asyncdispatch.nim") == 0:
return "Processes asynchronous completion events"
if not future.errorStackTrace.isNil and future.errorStackTrace != "":
msg.add("\n" & indent(future.errorStackTrace.strip(), 4))
else:
msg.add("\n Empty or nil stack trace.")
future.error.msg.add(msg)
if entry.procname.endsWith("_continue"):
if cmpIgnoreStyle(entry.filename, "asyncmacro.nim") == 0:
return "Resumes an async procedure"
proc `$`*(entries: seq[StackTraceEntry]): string =
result = ""
# Find longest filename & line number combo for alignment purposes.
var longestLeft = 0
for entry in entries:
if entry.procName.isNil: continue
let left = $entry.filename & $entry.line
if left.len > longestLeft:
longestLeft = left.len
var indent = 2
# Format the entries.
for entry in entries:
if entry.procName.isNil:
if entry.line == -10:
result.add(spaces(indent) & "#[\n")
indent.inc(2)
else:
indent.dec(2)
result.add(spaces(indent)& "]#\n")
continue
let left = "$#($#)" % [$entry.filename, $entry.line]
result.add((spaces(indent) & "$#$# $#\n") % [
left,
spaces(longestLeft - left.len + 2),
$entry.procName
])
let hint = getHint(entry)
if hint.len > 0:
result.add(spaces(indent+2) & "## " & hint & "\n")
proc injectStacktrace[T](future: Future[T]) =
when not defined(release):
const header = "\nAsync traceback:\n"
var exceptionMsg = future.error.msg
if header in exceptionMsg:
# This is messy: extract the original exception message from the msg
# containing the async traceback.
let start = exceptionMsg.find(header)
exceptionMsg = exceptionMsg[0..<start]
var newMsg = exceptionMsg & header
let entries = getStackTraceEntries(future.error)
newMsg.add($entries)
newMsg.add("Exception message: " & exceptionMsg & "\n")
newMsg.add("Exception type:")
# # For debugging purposes
# for entry in getStackTraceEntries(future.error):
# newMsg.add "\n" & $entry
future.error.msg = newMsg
proc read*[T](future: Future[T] | FutureVar[T]): T =
## Retrieves the value of ``future``. Future must be finished otherwise
@ -263,12 +324,12 @@ proc mget*[T](future: FutureVar[T]): var T =
## Future has not been finished.
result = Future[T](future).value
proc finished*[T](future: Future[T] | FutureVar[T]): bool =
proc finished*(future: FutureBase | FutureVar): bool =
## Determines whether ``future`` has completed.
##
## ``True`` may indicate an error or a value. Use ``failed`` to distinguish.
when future is FutureVar[T]:
result = (Future[T](future)).finished
when future is FutureVar:
result = (FutureBase(future)).finished
else:
result = future.finished

View file

@ -25,22 +25,28 @@ proc skipStmtList(node: NimNode): NimNode {.compileTime.} =
result = node[0]
template createCb(retFutureSym, iteratorNameSym,
name, futureVarCompletions: untyped) =
strName, identName, futureVarCompletions: untyped) =
var nameIterVar = iteratorNameSym
#{.push stackTrace: off.}
proc cb0 {.closure.} =
proc identName {.closure.} =
try:
if not nameIterVar.finished:
var next = nameIterVar()
# Continue while the yielded future is already finished.
while (not next.isNil) and next.finished:
next = nameIterVar()
if nameIterVar.finished:
break
if next == nil:
if not retFutureSym.finished:
let msg = "Async procedure ($1) yielded `nil`, are you await'ing a " &
"`nil` Future?"
raise newException(AssertionError, msg % name)
raise newException(AssertionError, msg % strName)
else:
{.gcsafe.}:
{.push hint[ConvFromXtoItselfNotNeeded]: off.}
next.callback = (proc() {.closure, gcsafe.})(cb0)
next.callback = (proc() {.closure, gcsafe.})(identName)
{.pop.}
except:
futureVarCompletions
@ -52,7 +58,7 @@ template createCb(retFutureSym, iteratorNameSym,
else:
retFutureSym.fail(getCurrentException())
cb0()
identName()
#{.pop.}
proc generateExceptionCheck(futSym,
tryStmt, rootReceiver, fromNode: NimNode): NimNode {.compileTime.} =
@ -389,9 +395,12 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
outerProcBody.add(closureIterator)
# -> createCb(retFuture)
#var cbName = newIdentNode("cb")
# NOTE: The "_continue" suffix is checked for in asyncfutures.nim to produce
# friendlier stack traces:
var cbName = genSym(nskProc, prcName & "_continue")
var procCb = getAst createCb(retFutureSym, iteratorNameSym,
newStrLitNode(prcName),
cbName,
createFutureVarCompletions(futureVarIdents, nil))
outerProcBody.add procCb

View file

@ -286,6 +286,7 @@ template readInto(buf: pointer, size: int, socket: AsyncSocket,
flags: set[SocketFlag]): int =
## Reads **up to** ``size`` bytes from ``socket`` into ``buf``. Note that
## this is a template and not a proc.
assert(not socket.closed, "Cannot `recv` on a closed socket")
var res = 0
if socket.isSsl:
when defineSsl:
@ -412,6 +413,7 @@ proc send*(socket: AsyncSocket, buf: pointer, size: int,
## Sends ``size`` bytes from ``buf`` to ``socket``. The returned future will complete once all
## data has been sent.
assert socket != nil
assert(not socket.closed, "Cannot `send` on a closed socket")
if socket.isSsl:
when defineSsl:
sslLoop(socket, flags,

View file

@ -141,8 +141,8 @@ proc excl*[T](c: var CritBitTree[T], key: string) =
proc missingOrExcl*[T](c: var CritBitTree[T], key: string): bool =
## Returns true iff `c` does not contain the given `key`. If the key
## does exist, c.excl(key) is performed.
let oldCount = c.count
## does exist, c.excl(key) is performed.
let oldCount = c.count
var n = exclImpl(c, key)
result = c.count == oldCount
@ -326,7 +326,7 @@ proc `$`*[T](c: CritBitTree[T]): string =
result.add($key)
when T isnot void:
result.add(": ")
result.add($val)
result.addQuoted(val)
result.add("}")
when isMainModule:

View file

@ -185,7 +185,7 @@ proc `$`*[T](deq: Deque[T]): string =
result = "["
for x in deq:
if result.len > 1: result.add(", ")
result.add($x)
result.addQuoted(x)
result.add("]")
when isMainModule:

View file

@ -135,7 +135,7 @@ proc `$`*[T](L: SomeLinkedCollection[T]): string =
result = "["
for x in nodes(L):
if result.len > 1: result.add(", ")
result.add($x.value)
result.addQuoted(x.value)
result.add("]")
proc find*[T](L: SomeLinkedCollection[T], value: T): SomeLinkedNode[T] =

View file

@ -406,7 +406,7 @@ template dollarImpl() {.dirty.} =
result = "{"
for key in items(s):
if result.len > 1: result.add(", ")
result.add($key)
result.addQuoted(key)
result.add("}")
proc `$`*[A](s: HashSet[A]): string =

View file

@ -73,10 +73,10 @@ proc add*[A](x: var SharedList[A]; y: A) =
node.d[node.dataLen] = y
inc(node.dataLen)
proc initSharedList*[A](): SharedList[A] =
initLock result.lock
result.head = nil
result.tail = nil
proc init*[A](t: var SharedList[A]) =
initLock t.lock
t.head = nil
t.tail = nil
proc clear*[A](t: var SharedList[A]) =
withLock(t):
@ -92,4 +92,11 @@ proc deinitSharedList*[A](t: var SharedList[A]) =
clear(t)
deinitLock t.lock
proc initSharedList*[A](): SharedList[A] {.deprecated.} =
## Deprecated. Use `init` instead.
## This is not posix compliant, may introduce undefined behavior.
initLock result.lock
result.head = nil
result.tail = nil
{.pop.}

View file

@ -183,6 +183,7 @@ proc `[]=`*[A, B](t: var SharedTable[A, B], key: A, val: B) =
proc add*[A, B](t: var SharedTable[A, B], key: A, val: B) =
## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists.
## This can introduce duplicate keys into the table!
withLock t:
addImpl(enlarge)
@ -191,19 +192,29 @@ proc del*[A, B](t: var SharedTable[A, B], key: A) =
withLock t:
delImpl()
proc initSharedTable*[A, B](initialSize=64): SharedTable[A, B] =
proc init*[A, B](t: var SharedTable[A, B], initialSize=64) =
## creates a new hash table that is empty.
##
## `initialSize` needs to be a power of two. If you need to accept runtime
## values for this you could use the ``nextPowerOfTwo`` proc from the
## `math <math.html>`_ module or the ``rightSize`` proc from this module.
assert isPowerOfTwo(initialSize)
result.counter = 0
result.dataLen = initialSize
result.data = cast[KeyValuePairSeq[A, B]](allocShared0(
t.counter = 0
t.dataLen = initialSize
t.data = cast[KeyValuePairSeq[A, B]](allocShared0(
sizeof(KeyValuePair[A, B]) * initialSize))
initLock result.lock
initLock t.lock
proc deinitSharedTable*[A, B](t: var SharedTable[A, B]) =
deallocShared(t.data)
deinitLock t.lock
proc initSharedTable*[A, B](initialSize=64): SharedTable[A, B] {.deprecated.} =
## Deprecated. Use `init` instead.
## This is not posix compliant, may introduce undefined behavior.
assert isPowerOfTwo(initialSize)
result.counter = 0
result.dataLen = initialSize
result.data = cast[KeyValuePairSeq[A, B]](allocShared0(
sizeof(KeyValuePair[A, B]) * initialSize))
initLock result.lock

View file

@ -308,6 +308,7 @@ proc `[]=`*[A, B](t: var Table[A, B], key: A, val: B) =
proc add*[A, B](t: var Table[A, B], key: A, val: B) =
## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists.
## This can introduce duplicate keys into the table!
addImpl(enlarge)
proc len*[A, B](t: TableRef[A, B]): int =
@ -337,9 +338,9 @@ template dollarImpl(): untyped {.dirty.} =
result = "{"
for key, val in pairs(t):
if result.len > 1: result.add(", ")
result.add($key)
result.addQuoted(key)
result.add(": ")
result.add($val)
result.addQuoted(val)
result.add("}")
proc `$`*[A, B](t: Table[A, B]): string =
@ -430,6 +431,7 @@ proc `[]=`*[A, B](t: TableRef[A, B], key: A, val: B) =
proc add*[A, B](t: TableRef[A, B], key: A, val: B) =
## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists.
## This can introduce duplicate keys into the table!
t[].add(key, val)
proc del*[A, B](t: TableRef[A, B], key: A) =
@ -604,6 +606,7 @@ proc `[]=`*[A, B](t: var OrderedTable[A, B], key: A, val: B) =
proc add*[A, B](t: var OrderedTable[A, B], key: A, val: B) =
## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists.
## This can introduce duplicate keys into the table!
addImpl(enlarge)
proc mgetOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): var B =
@ -770,6 +773,7 @@ proc `[]=`*[A, B](t: OrderedTableRef[A, B], key: A, val: B) =
proc add*[A, B](t: OrderedTableRef[A, B], key: A, val: B) =
## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists.
## This can introduce duplicate keys into the table!
t[].add(key, val)
proc newOrderedTable*[A, B](initialSize=64): OrderedTableRef[A, B] =
@ -962,9 +966,10 @@ proc initCountTable*[A](initialSize=64): CountTable[A] =
newSeq(result.data, initialSize)
proc toCountTable*[A](keys: openArray[A]): CountTable[A] =
## creates a new count table with every key in `keys` having a count of 1.
## creates a new count table with every key in `keys` having a count
## of how many times it occurs in `keys`.
result = initCountTable[A](rightSize(keys.len))
for key in items(keys): result[key] = 1
for key in items(keys): result.inc key
proc `$`*[A](t: CountTable[A]): string =
## The `$` operator for count tables.
@ -989,9 +994,10 @@ proc inc*[A](t: var CountTable[A], key: A, val = 1) =
proc smallest*[A](t: CountTable[A]): tuple[key: A, val: int] =
## returns the (key,val)-pair with the smallest `val`. Efficiency: O(n)
assert t.len > 0
var minIdx = 0
for h in 1..high(t.data):
if t.data[h].val > 0 and t.data[minIdx].val > t.data[h].val: minIdx = h
var minIdx = -1
for h in 0..high(t.data):
if t.data[h].val > 0 and (minIdx == -1 or t.data[minIdx].val > t.data[h].val):
minIdx = h
result.key = t.data[minIdx].key
result.val = t.data[minIdx].val
@ -1325,3 +1331,7 @@ when isMainModule:
assert((a == b) == true)
assert((b == a) == true)
block: # CountTable.smallest
var t = initCountTable[int]()
for v in items([0, 0, 5, 5, 5]): t.inc(v)
doAssert t.smallest == (0, 2)

View file

@ -51,7 +51,7 @@ proc setCookie*(key, value: string, domain = "", path = "",
if secure: result.add("; Secure")
if httpOnly: result.add("; HttpOnly")
proc setCookie*(key, value: string, expires: TimeInfo,
proc setCookie*(key, value: string, expires: DateTime,
domain = "", path = "", noName = false,
secure = false, httpOnly = false): string =
## Creates a command in the format of
@ -63,9 +63,9 @@ proc setCookie*(key, value: string, expires: TimeInfo,
noname, secure, httpOnly)
when isMainModule:
var tim = Time(int(getTime()) + 76 * (60 * 60 * 24))
var tim = fromUnix(getTime().toUnix + 76 * (60 * 60 * 24))
let cookie = setCookie("test", "value", tim.getGMTime())
let cookie = setCookie("test", "value", tim.utc)
when not defined(testing):
echo cookie
let start = "Set-Cookie: test=value; Expires="

79
lib/pure/cstrutils.nim Normal file
View file

@ -0,0 +1,79 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2017 Nim contributors
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module supports helper routines for working with ``cstring``
## without having to convert ``cstring`` to ``string`` in order to
## save allocations.
include "system/inclrtl"
proc toLowerAscii(c: char): char {.inline.} =
if c in {'A'..'Z'}:
result = chr(ord(c) + (ord('a') - ord('A')))
else:
result = c
proc startsWith*(s, prefix: cstring): bool {.noSideEffect,
rtl, extern: "csuStartsWith".} =
## Returns true iff ``s`` starts with ``prefix``.
##
## If ``prefix == ""`` true is returned.
var i = 0
while true:
if prefix[i] == '\0': return true
if s[i] != prefix[i]: return false
inc(i)
proc endsWith*(s, suffix: cstring): bool {.noSideEffect,
rtl, extern: "csuEndsWith".} =
## Returns true iff ``s`` ends with ``suffix``.
##
## If ``suffix == ""`` true is returned.
let slen = s.len
var i = 0
var j = slen - len(suffix)
while i+j <% slen:
if s[i+j] != suffix[i]: return false
inc(i)
if suffix[i] == '\0': return true
proc cmpIgnoreStyle*(a, b: cstring): int {.noSideEffect,
rtl, extern: "csuCmpIgnoreStyle".} =
## Compares two strings normalized (i.e. case and
## underscores do not matter). Returns:
##
## | 0 iff a == b
## | < 0 iff a < b
## | > 0 iff a > b
var i = 0
var j = 0
while true:
while a[i] == '_': inc(i)
while b[j] == '_': inc(j) # BUGFIX: typo
var aa = toLowerAscii(a[i])
var bb = toLowerAscii(b[j])
result = ord(aa) - ord(bb)
if result != 0 or aa == '\0': break
inc(i)
inc(j)
proc cmpIgnoreCase*(a, b: cstring): int {.noSideEffect,
rtl, extern: "csuCmpIgnoreCase".} =
## Compares two strings in a case insensitive manner. Returns:
##
## | 0 iff a == b
## | < 0 iff a < b
## | > 0 iff a > b
var i = 0
while true:
var aa = toLowerAscii(a[i])
var bb = toLowerAscii(b[i])
result = ord(aa) - ord(bb)
if result != 0 or aa == '\0': break
inc(i)

View file

@ -923,8 +923,14 @@ proc parseChunks(client: HttpClient | AsyncHttpClient): Future[void]
if chunkSize <= 0:
discard await recvFull(client, 2, client.timeout, false) # Skip \c\L
break
discard await recvFull(client, chunkSize, client.timeout, true)
discard await recvFull(client, 2, client.timeout, false) # Skip \c\L
var bytesRead = await recvFull(client, chunkSize, client.timeout, true)
if bytesRead != chunkSize:
httpError("Server terminated connection prematurely")
bytesRead = await recvFull(client, 2, client.timeout, false) # Skip \c\L
if bytesRead != 2:
httpError("Server terminated connection prematurely")
# Trailer headers will only be sent if the request specifies that we want
# them: http://tools.ietf.org/html/rfc2616#section-3.6.1
@ -965,7 +971,7 @@ proc parseBody(client: HttpClient | AsyncHttpClient,
if headers.getOrDefault"Connection" == "close" or httpVersion == "1.0":
while true:
let recvLen = await client.recvFull(4000, client.timeout, true)
if recvLen == 0:
if recvLen != 4000:
client.close()
break

View file

@ -277,15 +277,16 @@ proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool,
var events = {Event.Timer}
var epv = EpollEvent(events: EPOLLIN or EPOLLRDHUP)
epv.data.u64 = fdi.uint
if oneshot:
new_ts.it_interval.tv_sec = 0.Time
new_ts.it_interval.tv_sec = posix.Time(0)
new_ts.it_interval.tv_nsec = 0
new_ts.it_value.tv_sec = (timeout div 1_000).Time
new_ts.it_value.tv_sec = posix.Time(timeout div 1_000)
new_ts.it_value.tv_nsec = (timeout %% 1_000) * 1_000_000
incl(events, Event.Oneshot)
epv.events = epv.events or EPOLLONESHOT
else:
new_ts.it_interval.tv_sec = (timeout div 1000).Time
new_ts.it_interval.tv_sec = posix.Time(timeout div 1000)
new_ts.it_interval.tv_nsec = (timeout %% 1_000) * 1_000_000
new_ts.it_value.tv_sec = new_ts.it_interval.tv_sec
new_ts.it_value.tv_nsec = new_ts.it_interval.tv_nsec

View file

@ -452,10 +452,10 @@ proc selectInto*[T](s: Selector[T], timeout: int,
if timeout != -1:
if timeout >= 1000:
tv.tv_sec = (timeout div 1_000).Time
tv.tv_sec = posix.Time(timeout div 1_000)
tv.tv_nsec = (timeout %% 1_000) * 1_000_000
else:
tv.tv_sec = 0.Time
tv.tv_sec = posix.Time(0)
tv.tv_nsec = timeout * 1_000_000
else:
ptv = nil

View file

@ -107,9 +107,14 @@ var
proc substituteLog*(frmt: string, level: Level, args: varargs[string, `$`]): string =
## Format a log message using the ``frmt`` format string, ``level`` and varargs.
## See the module documentation for the format string syntax.
const nilString = "nil"
var msgLen = 0
for arg in args:
msgLen += arg.len
if arg.isNil:
msgLen += nilString.len
else:
msgLen += arg.len
result = newStringOfCap(frmt.len + msgLen + 20)
var i = 0
while i < frmt.len:
@ -136,7 +141,10 @@ proc substituteLog*(frmt: string, level: Level, args: varargs[string, `$`]): str
of "levelname": result.add(LevelNames[level])
else: discard
for arg in args:
result.add(arg)
if arg.isNil:
result.add(nilString)
else:
result.add(arg)
method log*(logger: Logger, level: Level, args: varargs[string, `$`]) {.
raises: [Exception], gcsafe,
@ -361,3 +369,6 @@ when not defined(testing) and isMainModule:
addHandler(L)
for i in 0 .. 25:
info("hello", i)
var nilString: string
info "hello ", nilString

View file

@ -291,6 +291,8 @@ when not defined(JS):
## echo fmod(-2.5, 0.3) ## -0.1
else:
proc trunc*(x: float32): float32 {.importc: "Math.trunc", nodecl.}
proc trunc*(x: float64): float64 {.importc: "Math.trunc", nodecl.}
proc floor*(x: float32): float32 {.importc: "Math.floor", nodecl.}
proc floor*(x: float64): float64 {.importc: "Math.floor", nodecl.}
proc ceil*(x: float32): float32 {.importc: "Math.ceil", nodecl.}
@ -349,15 +351,19 @@ proc round*[T: float32|float64](x: T, places: int = 0): T =
result = round0(x*mult)/mult
when not defined(JS):
proc frexp*(x: float32, exponent: var int): float32 {.
proc c_frexp*(x: float32, exponent: var int32): float32 {.
importc: "frexp", header: "<math.h>".}
proc frexp*(x: float64, exponent: var int): float64 {.
proc c_frexp*(x: float64, exponent: var int32): float64 {.
importc: "frexp", header: "<math.h>".}
proc frexp*[T, U](x: T, exponent: var U): T =
## Split a number into mantissa and exponent.
## `frexp` calculates the mantissa m (a float greater than or equal to 0.5
## and less than 1) and the integer value n such that `x` (the original
## float value) equals m * 2**n. frexp stores n in `exponent` and returns
## m.
var exp: int32
result = c_frexp(x, exp)
exponent = exp
else:
proc frexp*[T: float32|float64](x: T, exponent: var int): T =
if x == 0.0:
@ -366,9 +372,14 @@ else:
elif x < 0.0:
result = -frexp(-x, exponent)
else:
var ex = floor(log2(x))
exponent = round(ex)
var ex = trunc(log2(x))
exponent = int(ex)
result = x / pow(2.0, ex)
if abs(result) >= 1:
inc(exponent)
result = result / 2
if exponent == 1024 and result == 0.0:
result = 0.99999999999999988898
proc splitDecimal*[T: float32|float64](x: T): tuple[intpart: T, floatpart: T] =
## Breaks `x` into an integral and a fractional part.

View file

@ -257,10 +257,13 @@ proc close*(f: var MemFile) =
when defined(windows):
if f.wasOpened:
error = unmapViewOfFile(f.mem) == 0
lastErr = osLastError()
error = (closeHandle(f.mapHandle) == 0) or error
if f.fHandle != INVALID_HANDLE_VALUE:
error = (closeHandle(f.fHandle) == 0) or error
if not error:
error = closeHandle(f.mapHandle) == 0
if not error and f.fHandle != INVALID_HANDLE_VALUE:
discard closeHandle(f.fHandle)
f.fHandle = INVALID_HANDLE_VALUE
if error:
lastErr = osLastError()
else:
error = munmap(f.mem, f.size) != 0
lastErr = osLastError()

View file

@ -413,7 +413,7 @@ proc isIpAddress*(address_str: string): bool {.tags: [].} =
when defineSsl:
CRYPTO_malloc_init()
SslLibraryInit()
doAssert SslLibraryInit() == 1
SslLoadErrorStrings()
ErrLoadBioStrings()
OpenSSL_add_all_algorithms()
@ -864,6 +864,7 @@ proc close*(socket: Socket) =
socket.sslHandle = nil
socket.fd.close()
socket.fd = osInvalidSocket
when defined(posix):
from posix import TCP_NODELAY
@ -1001,15 +1002,25 @@ proc select(readfd: Socket, timeout = 500): int =
var fds = @[readfd.fd]
result = select(fds, timeout)
proc isClosed(socket: Socket): bool =
socket.fd == osInvalidSocket
proc uniRecv(socket: Socket, buffer: pointer, size, flags: cint): int =
## Handles SSL and non-ssl recv in a nice package.
##
## In particular handles the case where socket has been closed properly
## for both SSL and non-ssl.
result = 0
assert(not socket.isClosed, "Cannot `recv` on a closed socket")
when defineSsl:
if socket.isSsl:
return SSLRead(socket.sslHandle, buffer, size)
return recv(socket.fd, buffer, size, flags)
proc readIntoBuf(socket: Socket, flags: int32): int =
result = 0
when defineSsl:
if socket.isSSL:
result = SSLRead(socket.sslHandle, addr(socket.buffer), int(socket.buffer.high))
else:
result = recv(socket.fd, addr(socket.buffer), cint(socket.buffer.high), flags)
else:
result = recv(socket.fd, addr(socket.buffer), cint(socket.buffer.high), flags)
result = uniRecv(socket, addr(socket.buffer), socket.buffer.high, flags)
if result < 0:
# Save it in case it gets reset (the Nim codegen occasionally may call
# Win API functions which reset it).
@ -1055,16 +1066,16 @@ proc recv*(socket: Socket, data: pointer, size: int): int {.tags: [ReadIOEffect]
else:
when defineSsl:
if socket.isSSL:
if socket.sslHasPeekChar:
if socket.sslHasPeekChar: # TODO: Merge this peek char mess into uniRecv
copyMem(data, addr(socket.sslPeekChar), 1)
socket.sslHasPeekChar = false
if size-1 > 0:
var d = cast[cstring](data)
result = SSLRead(socket.sslHandle, addr(d[1]), size-1) + 1
result = uniRecv(socket, addr(d[1]), cint(size-1), 0'i32) + 1
else:
result = 1
else:
result = SSLRead(socket.sslHandle, data, size)
result = uniRecv(socket, data, size.cint, 0'i32)
else:
result = recv(socket.fd, data, size.cint, 0'i32)
else:
@ -1131,17 +1142,21 @@ proc recv*(socket: Socket, data: var string, size: int, timeout = -1,
##
## When 0 is returned the socket's connection has been closed.
##
## This function will throw an EOS exception when an error occurs. A value
## This function will throw an OSError exception when an error occurs. A value
## lower than 0 is never returned.
##
## A timeout may be specified in milliseconds, if enough data is not received
## within the time specified an ETimeout exception will be raised.
## within the time specified an TimeoutError exception will be raised.
##
## **Note**: ``data`` must be initialised.
##
## **Warning**: Only the ``SafeDisconn`` flag is currently supported.
data.setLen(size)
result = recv(socket, cstring(data), size, timeout)
result =
if timeout == -1:
recv(socket, cstring(data), size)
else:
recv(socket, cstring(data), size, timeout)
if result < 0:
data.setLen(0)
let lastError = getSocketError(socket)
@ -1178,7 +1193,7 @@ proc peekChar(socket: Socket, c: var char): int {.tags: [ReadIOEffect].} =
when defineSsl:
if socket.isSSL:
if not socket.sslHasPeekChar:
result = SSLRead(socket.sslHandle, addr(socket.sslPeekChar), 1)
result = uniRecv(socket, addr(socket.sslPeekChar), 1, 0'i32)
socket.sslHasPeekChar = true
c = socket.sslPeekChar
@ -1312,6 +1327,7 @@ proc send*(socket: Socket, data: pointer, size: int): int {.
##
## **Note**: This is a low-level version of ``send``. You likely should use
## the version below.
assert(not socket.isClosed, "Cannot `send` on a closed socket")
when defineSsl:
if socket.isSSL:
return SSLWrite(socket.sslHandle, cast[cstring](data), size)
@ -1356,6 +1372,7 @@ proc sendTo*(socket: Socket, address: string, port: Port, data: pointer,
## which is defined below.
##
## **Note:** This proc is not available for SSL sockets.
assert(not socket.isClosed, "Cannot `sendTo` on a closed socket")
var aiList = getAddrInfo(address, port, af)
# try all possibilities:

View file

@ -88,7 +88,7 @@ proc generatedTime*(oid: Oid): Time =
var tmp: int32
var dummy = oid.time
bigEndian32(addr(tmp), addr(dummy))
result = Time(tmp)
result = fromUnix(tmp)
when not defined(testing) and isMainModule:
let xo = genOid()

View file

@ -173,33 +173,33 @@ proc findExe*(exe: string, followSymlinks: bool = true;
return x
result = ""
proc getLastModificationTime*(file: string): Time {.rtl, extern: "nos$1".} =
proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1".} =
## Returns the `file`'s last modification time.
when defined(posix):
var res: Stat
if stat(file, res) < 0'i32: raiseOSError(osLastError())
return res.st_mtime
return fromUnix(res.st_mtime.int64)
else:
var f: WIN32_FIND_DATA
var h = findFirstFile(file, f)
if h == -1'i32: raiseOSError(osLastError())
result = winTimeToUnixTime(rdFileTime(f.ftLastWriteTime))
result = fromUnix(winTimeToUnixTime(rdFileTime(f.ftLastWriteTime)).int64)
findClose(h)
proc getLastAccessTime*(file: string): Time {.rtl, extern: "nos$1".} =
proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1".} =
## Returns the `file`'s last read or write access time.
when defined(posix):
var res: Stat
if stat(file, res) < 0'i32: raiseOSError(osLastError())
return res.st_atime
return fromUnix(res.st_atime.int64)
else:
var f: WIN32_FIND_DATA
var h = findFirstFile(file, f)
if h == -1'i32: raiseOSError(osLastError())
result = winTimeToUnixTime(rdFileTime(f.ftLastAccessTime))
result = fromUnix(winTimeToUnixTime(rdFileTime(f.ftLastAccessTime)).int64)
findClose(h)
proc getCreationTime*(file: string): Time {.rtl, extern: "nos$1".} =
proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1".} =
## Returns the `file`'s creation time.
##
## **Note:** Under POSIX OS's, the returned time may actually be the time at
@ -208,12 +208,12 @@ proc getCreationTime*(file: string): Time {.rtl, extern: "nos$1".} =
when defined(posix):
var res: Stat
if stat(file, res) < 0'i32: raiseOSError(osLastError())
return res.st_ctime
return fromUnix(res.st_ctime.int64)
else:
var f: WIN32_FIND_DATA
var h = findFirstFile(file, f)
if h == -1'i32: raiseOSError(osLastError())
result = winTimeToUnixTime(rdFileTime(f.ftCreationTime))
result = fromUnix(winTimeToUnixTime(rdFileTime(f.ftCreationTime)).int64)
findClose(h)
proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1".} =
@ -672,7 +672,10 @@ template walkCommon(pattern: string, filter) =
if dotPos < 0 or idx >= ff.len or ff[idx] == '.' or
pattern[dotPos+1] == '*':
yield splitFile(pattern).dir / extractFilename(ff)
if findNextFile(res, f) == 0'i32: break
if findNextFile(res, f) == 0'i32:
let errCode = getLastError()
if errCode == ERROR_NO_MORE_FILES: break
else: raiseOSError(errCode.OSErrorCode)
else: # here we use glob
var
f: Glob
@ -782,7 +785,10 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path:
let xx = if relative: extractFilename(getFilename(f))
else: dir / extractFilename(getFilename(f))
yield (k, xx)
if findNextFile(h, f) == 0'i32: break
if findNextFile(h, f) == 0'i32:
let errCode = getLastError()
if errCode == ERROR_NO_MORE_FILES: break
else: raiseOSError(errCode.OSErrorCode)
else:
var d = opendir(dir)
if d != nil:
@ -816,32 +822,40 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path:
k = getSymlinkFileKind(y)
yield (k, y)
iterator walkDirRec*(dir: string, filter={pcFile, pcDir}): string {.
tags: [ReadDirEffect].} =
## Recursively walks over the directory `dir` and yields for each file in `dir`.
## The full path for each file is returned. Directories are not returned.
iterator walkDirRec*(dir: string, yieldFilter = {pcFile},
followFilter = {pcDir}): string {.tags: [ReadDirEffect].} =
## Recursively walks over the directory `dir` and yields for each file
## or directory in `dir`.
## The full path for each file or directory is returned.
## **Warning**:
## Modifying the directory structure while the iterator
## is traversing may result in undefined behavior!
##
## Walking is recursive. `filter` controls the behaviour of the iterator:
## Walking is recursive. `filters` controls the behaviour of the iterator:
##
## --------------------- ---------------------------------------------
## filter meaning
## yieldFilter meaning
## --------------------- ---------------------------------------------
## ``pcFile`` yield real files
## ``pcLinkToFile`` yield symbolic links to files
## ``pcDir`` yield real directories
## ``pcLinkToDir`` yield symbolic links to directories
## --------------------- ---------------------------------------------
##
## --------------------- ---------------------------------------------
## followFilter meaning
## --------------------- ---------------------------------------------
## ``pcDir`` follow real directories
## ``pcLinkToDir`` follow symbolic links to directories
## --------------------- ---------------------------------------------
##
var stack = @[dir]
while stack.len > 0:
for k,p in walkDir(stack.pop()):
if k in filter:
case k
of pcFile, pcLinkToFile: yield p
of pcDir, pcLinkToDir: stack.add(p)
for k, p in walkDir(stack.pop()):
if k in {pcDir, pcLinkToDir} and k in followFilter:
stack.add(p)
if k in yieldFilter:
yield p
proc rawRemoveDir(dir: string) =
when defined(windows):
@ -1443,7 +1457,7 @@ proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect].} =
winlean.sleep(int32(milsecs))
else:
var a, b: Timespec
a.tv_sec = Time(milsecs div 1000)
a.tv_sec = posix.Time(milsecs div 1000)
a.tv_nsec = (milsecs mod 1000) * 1000 * 1000
discard posix.nanosleep(a, b)
@ -1481,16 +1495,17 @@ type
size*: BiggestInt # Size of file.
permissions*: set[FilePermission] # File permissions
linkCount*: BiggestInt # Number of hard links the file object has.
lastAccessTime*: Time # Time file was last accessed.
lastWriteTime*: Time # Time file was last modified/written to.
creationTime*: Time # Time file was created. Not supported on all systems!
lastAccessTime*: times.Time # Time file was last accessed.
lastWriteTime*: times.Time # Time file was last modified/written to.
creationTime*: times.Time # Time file was created. Not supported on all systems!
template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped =
## Transforms the native file info structure into the one nim uses.
## 'rawInfo' is either a 'TBY_HANDLE_FILE_INFORMATION' structure on Windows,
## or a 'Stat' structure on posix
when defined(Windows):
template toTime(e: FILETIME): untyped {.gensym.} = winTimeToUnixTime(rdFileTime(e)) # local templates default to bind semantics
template toTime(e: FILETIME): untyped {.gensym.} =
fromUnix(winTimeToUnixTime(rdFileTime(e)).int64) # local templates default to bind semantics
template merge(a, b): untyped = a or (b shl 32)
formalInfo.id.device = rawInfo.dwVolumeSerialNumber
formalInfo.id.file = merge(rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh)
@ -1522,9 +1537,9 @@ template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped =
formalInfo.id = (rawInfo.st_dev, rawInfo.st_ino)
formalInfo.size = rawInfo.st_size
formalInfo.linkCount = rawInfo.st_Nlink.BiggestInt
formalInfo.lastAccessTime = rawInfo.st_atime
formalInfo.lastWriteTime = rawInfo.st_mtime
formalInfo.creationTime = rawInfo.st_ctime
formalInfo.lastAccessTime = fromUnix(rawInfo.st_atime.int64)
formalInfo.lastWriteTime = fromUnix(rawInfo.st_mtime.int64)
formalInfo.creationTime = fromUnix(rawInfo.st_ctime.int64)
result.permissions = {}
checkAndIncludeMode(S_IRUSR, fpUserRead)

View file

@ -47,6 +47,7 @@ type
ProcessObj = object of RootObj
when defined(windows):
fProcessHandle: Handle
fThreadHandle: Handle
inHandle, outHandle, errHandle: FileHandle
id: Handle
else:
@ -54,6 +55,7 @@ type
inStream, outStream, errStream: Stream
id: Pid
exitStatus: cint
exitFlag: bool
options: set[ProcessOption]
Process* = ref ProcessObj ## represents an operating system process
@ -237,11 +239,13 @@ proc execProcesses*(cmds: openArray[string],
if n > 1:
var i = 0
var q = newSeq[Process](n)
var m = min(n, cmds.len)
when defined(windows):
var w: WOHandleArray
var m = min(min(n, MAXIMUM_WAIT_OBJECTS), cmds.len)
var wcount = m
else:
var m = min(n, cmds.len)
while i < m:
if beforeRunEvent != nil:
@ -253,6 +257,7 @@ proc execProcesses*(cmds: openArray[string],
var ecount = len(cmds)
while ecount > 0:
var rexit = -1
when defined(windows):
# waiting for all children, get result if any child exits
var ret = waitForMultipleObjects(int32(wcount), addr(w), 0'i32,
@ -262,22 +267,37 @@ proc execProcesses*(cmds: openArray[string],
discard
elif ret == WAIT_FAILED:
raiseOSError(osLastError())
else:
var status: int32
for r in 0..m-1:
if not isNil(q[r]) and q[r].fProcessHandle == w[ret]:
discard getExitCodeProcess(q[r].fProcessHandle, status)
q[r].exitFlag = true
q[r].exitStatus = status
rexit = r
break
else:
var status : cint = 1
var status: cint = 1
# waiting for all children, get result if any child exits
let res = waitpid(-1, status, 0)
if res > 0:
for r in 0..m-1:
if not isNil(q[r]) and q[r].id == res:
# we updating `exitStatus` manually, so `running()` can work.
if WIFEXITED(status) or WIFSIGNALED(status):
q[r].exitFlag = true
q[r].exitStatus = status
rexit = r
break
else:
let err = osLastError()
if err == OSErrorCode(ECHILD):
# some child exits, we need to check our childs exit codes
discard
for r in 0..m-1:
if (not isNil(q[r])) and (not running(q[r])):
q[r].exitFlag = true
q[r].exitStatus = status
rexit = r
break
elif err == OSErrorCode(EINTR):
# signal interrupted our syscall, lets repeat it
continue
@ -285,26 +305,27 @@ proc execProcesses*(cmds: openArray[string],
# all other errors are exceptions
raiseOSError(err)
for r in 0..m-1:
if not isNil(q[r]):
if not running(q[r]):
result = max(result, q[r].peekExitCode())
if afterRunEvent != nil: afterRunEvent(r, q[r])
close(q[r])
if i < len(cmds):
if beforeRunEvent != nil: beforeRunEvent(i)
q[r] = startProcess(cmds[i],
if rexit >= 0:
result = max(result, q[rexit].peekExitCode())
if afterRunEvent != nil: afterRunEvent(rexit, q[rexit])
close(q[rexit])
if i < len(cmds):
if beforeRunEvent != nil: beforeRunEvent(i)
q[rexit] = startProcess(cmds[i],
options = options + {poEvalCommand})
when defined(windows):
w[r] = q[r].fProcessHandle
inc(i)
else:
q[r] = nil
when defined(windows):
for c in r..MAXIMUM_WAIT_OBJECTS - 2:
w[c] = w[c + 1]
when defined(windows):
w[rexit] = q[rexit].fProcessHandle
inc(i)
else:
when defined(windows):
for k in 0..wcount - 1:
if w[k] == q[rexit].fProcessHandle:
w[k] = w[wcount - 1]
w[wcount - 1] = 0
dec(wcount)
dec(ecount)
break
q[rexit] = nil
dec(ecount)
else:
for i in 0..high(cmds):
if beforeRunEvent != nil:
@ -491,6 +512,7 @@ when defined(Windows) and not defined(useNimRtl):
hi, ho, he: Handle
new(result)
result.options = options
result.exitFlag = true
si.cb = sizeof(si).cint
if poParentStreams notin options:
si.dwFlags = STARTF_USESTDHANDLES # STARTF_USESHOWWINDOW or
@ -559,28 +581,31 @@ when defined(Windows) and not defined(useNimRtl):
"Requested command not found: '$1'. OS error:" % command)
else:
raiseOSError(lastError, command)
# Close the handle now so anyone waiting is woken:
discard closeHandle(procInfo.hThread)
result.fProcessHandle = procInfo.hProcess
result.fThreadHandle = procInfo.hThread
result.id = procInfo.dwProcessId
result.exitFlag = false
proc close(p: Process) =
if poInteractive in p.options:
# somehow this is not always required on Windows:
if poParentStreams notin p.options:
discard closeHandle(p.inHandle)
discard closeHandle(p.outHandle)
discard closeHandle(p.errHandle)
#discard closeHandle(p.FProcessHandle)
discard closeHandle(p.fThreadHandle)
discard closeHandle(p.fProcessHandle)
proc suspend(p: Process) =
discard suspendThread(p.fProcessHandle)
discard suspendThread(p.fThreadHandle)
proc resume(p: Process) =
discard resumeThread(p.fProcessHandle)
discard resumeThread(p.fThreadHandle)
proc running(p: Process): bool =
var x = waitForSingleObject(p.fProcessHandle, 50)
return x == WAIT_TIMEOUT
if p.exitFlag:
return false
else:
var x = waitForSingleObject(p.fProcessHandle, 0)
return x == WAIT_TIMEOUT
proc terminate(p: Process) =
if running(p):
@ -590,22 +615,37 @@ when defined(Windows) and not defined(useNimRtl):
terminate(p)
proc waitForExit(p: Process, timeout: int = -1): int =
discard waitForSingleObject(p.fProcessHandle, timeout.int32)
if p.exitFlag:
return p.exitStatus
var res: int32
discard getExitCodeProcess(p.fProcessHandle, res)
result = res
p.exitStatus = res
discard closeHandle(p.fProcessHandle)
let res = waitForSingleObject(p.fProcessHandle, timeout.int32)
if res == WAIT_TIMEOUT:
terminate(p)
var status: int32
discard getExitCodeProcess(p.fProcessHandle, status)
if status != STILL_ACTIVE:
p.exitFlag = true
p.exitStatus = status
discard closeHandle(p.fThreadHandle)
discard closeHandle(p.fProcessHandle)
result = status
else:
result = -1
proc peekExitCode(p: Process): int =
var b = waitForSingleObject(p.fProcessHandle, 50) == WAIT_TIMEOUT
if b: result = -1
else:
var res: int32
discard getExitCodeProcess(p.fProcessHandle, res)
if res == 0: return p.exitStatus
return res
if p.exitFlag:
return p.exitStatus
result = -1
var b = waitForSingleObject(p.fProcessHandle, 0) == WAIT_TIMEOUT
if not b:
var status: int32
discard getExitCodeProcess(p.fProcessHandle, status)
p.exitFlag = true
p.exitStatus = status
discard closeHandle(p.fThreadHandle)
discard closeHandle(p.fProcessHandle)
result = status
proc inputStream(p: Process): Stream =
streamAccess(p)
@ -737,7 +777,8 @@ elif not defined(useNimRtl):
pStdin, pStdout, pStderr: array[0..1, cint]
new(result)
result.options = options
result.exitStatus = -3 # for ``waitForExit``
result.exitFlag = true
if poParentStreams notin options:
if pipe(pStdin) != 0'i32 or pipe(pStdout) != 0'i32 or
pipe(pStderr) != 0'i32:
@ -792,6 +833,7 @@ elif not defined(useNimRtl):
if poEchoCmd in options:
echo(command, " ", join(args, " "))
result.id = pid
result.exitFlag = false
if poParentStreams in options:
# does not make much sense, but better than nothing:
@ -968,14 +1010,14 @@ elif not defined(useNimRtl):
if kill(p.id, SIGCONT) != 0'i32: raiseOsError(osLastError())
proc running(p: Process): bool =
if p.exitStatus != -3:
if p.exitFlag:
return false
else:
var ret : int
var status : cint = 1
ret = waitpid(p.id, status, WNOHANG)
var status: cint = 1
let ret = waitpid(p.id, status, WNOHANG)
if ret == int(p.id):
if isExitStatus(status):
p.exitFlag = true
p.exitStatus = status
return false
else:
@ -998,13 +1040,14 @@ elif not defined(useNimRtl):
import kqueue, times
proc waitForExit(p: Process, timeout: int = -1): int =
if p.exitStatus != -3:
if p.exitFlag:
return exitStatus(p.exitStatus)
if timeout == -1:
var status : cint = 1
var status: cint = 1
if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError())
p.exitFlag = true
p.exitStatus = status
else:
var kqFD = kqueue()
@ -1017,15 +1060,15 @@ elif not defined(useNimRtl):
var tmspec: Timespec
if timeout >= 1000:
tmspec.tv_sec = (timeout div 1_000).Time
tmspec.tv_sec = posix.Time(timeout div 1_000)
tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000
else:
tmspec.tv_sec = 0.Time
tmspec.tv_sec = posix.Time(0)
tmspec.tv_nsec = (timeout * 1_000_000)
try:
while true:
var status : cint = 1
var status: cint = 1
var count = kevent(kqFD, addr(kevIn), 1, addr(kevOut), 1,
addr(tmspec))
if count < 0:
@ -1038,12 +1081,14 @@ elif not defined(useNimRtl):
raiseOSError(osLastError())
if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError())
p.exitFlag = true
p.exitStatus = status
break
else:
if kevOut.ident == p.id.uint and kevOut.filter == EVFILT_PROC:
if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError())
p.exitFlag = true
p.exitStatus = status
break
else:
@ -1064,36 +1109,33 @@ elif not defined(useNimRtl):
var b: Timespec
b.tv_sec = e.tv_sec
b.tv_nsec = e.tv_nsec
e.tv_sec = (e.tv_sec - s.tv_sec).Time
e.tv_sec = e.tv_sec - s.tv_sec
if e.tv_nsec >= s.tv_nsec:
e.tv_nsec -= s.tv_nsec
else:
if e.tv_sec == 0.Time:
if e.tv_sec == posix.Time(0):
raise newException(ValueError, "System time was modified")
else:
diff = s.tv_nsec - e.tv_nsec
e.tv_nsec = 1_000_000_000 - diff
t.tv_sec = (t.tv_sec - e.tv_sec).Time
t.tv_sec = t.tv_sec - e.tv_sec
if t.tv_nsec >= e.tv_nsec:
t.tv_nsec -= e.tv_nsec
else:
t.tv_sec = (int(t.tv_sec) - 1).Time
t.tv_sec = t.tv_sec - posix.Time(1)
diff = e.tv_nsec - t.tv_nsec
t.tv_nsec = 1_000_000_000 - diff
s.tv_sec = b.tv_sec
s.tv_nsec = b.tv_nsec
#if waitPid(p.id, p.exitStatus, 0) == int(p.id):
# ``waitPid`` fails if the process is not running anymore. But then
# ``running`` probably set ``p.exitStatus`` for us. Since ``p.exitStatus`` is
# initialized with -3, wrong success exit codes are prevented.
if p.exitStatus != -3:
if p.exitFlag:
return exitStatus(p.exitStatus)
if timeout == -1:
var status : cint = 1
var status: cint = 1
if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError())
p.exitFlag = true
p.exitStatus = status
else:
var nmask, omask: Sigset
@ -1112,10 +1154,10 @@ elif not defined(useNimRtl):
raiseOSError(osLastError())
if timeout >= 1000:
tmspec.tv_sec = (timeout div 1_000).Time
tmspec.tv_sec = posix.Time(timeout div 1_000)
tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000
else:
tmspec.tv_sec = 0.Time
tmspec.tv_sec = posix.Time(0)
tmspec.tv_nsec = (timeout * 1_000_000)
try:
@ -1125,9 +1167,10 @@ elif not defined(useNimRtl):
let res = sigtimedwait(nmask, sinfo, tmspec)
if res == SIGCHLD:
if sinfo.si_pid == p.id:
var status : cint = 1
var status: cint = 1
if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError())
p.exitFlag = true
p.exitStatus = status
break
else:
@ -1148,9 +1191,10 @@ elif not defined(useNimRtl):
# timeout expired, so we trying to kill process
if posix.kill(p.id, SIGKILL) == -1:
raiseOSError(osLastError())
var status : cint = 1
var status: cint = 1
if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError())
p.exitFlag = true
p.exitStatus = status
break
else:
@ -1168,12 +1212,13 @@ elif not defined(useNimRtl):
proc peekExitCode(p: Process): int =
var status = cint(0)
result = -1
if p.exitStatus != -3:
if p.exitFlag:
return exitStatus(p.exitStatus)
var ret = waitpid(p.id, status, WNOHANG)
if ret > 0:
if isExitStatus(status):
p.exitFlag = true
p.exitStatus = status
result = exitStatus(status)

View file

@ -32,7 +32,7 @@
## import parsecsv
## import os
## # Prepare a file
## var csv_content = """One,Two,Three,Four
## var content = """One,Two,Three,Four
## 1,2,3,4
## 10,20,30,40
## 100,200,300,400

View file

@ -55,6 +55,13 @@ const
";", ":", ",", "(", ")", "[", "]", "."
]
reservedKeywords = @[
# statements
"select", "from", "where", "group", "limit", "having",
# functions
"count",
]
proc open(L: var SqlLexer, input: Stream, filename: string) =
lexbase.open(L, input)
L.filename = filename
@ -274,16 +281,16 @@ proc getSymbol(c: var SqlLexer, tok: var Token) =
c.bufpos = pos
tok.kind = tkIdentifier
proc getQuotedIdentifier(c: var SqlLexer, tok: var Token) =
proc getQuotedIdentifier(c: var SqlLexer, tok: var Token, quote='\"') =
var pos = c.bufpos + 1
var buf = c.buf
tok.kind = tkQuotedIdentifier
while true:
var ch = buf[pos]
if ch == '\"':
if buf[pos+1] == '\"':
if ch == quote:
if buf[pos+1] == quote:
inc(pos, 2)
add(tok.literal, '\"')
add(tok.literal, quote)
else:
inc(pos)
break
@ -442,7 +449,8 @@ proc getTok(c: var SqlLexer, tok: var Token) =
add(tok.literal, '.')
of '0'..'9': getNumeric(c, tok)
of '\'': getString(c, tok, tkStringConstant)
of '"': getQuotedIdentifier(c, tok)
of '"': getQuotedIdentifier(c, tok, '"')
of '`': getQuotedIdentifier(c, tok, '`')
of lexbase.EndOfFile:
tok.kind = tkEof
tok.literal = "[EOF]"
@ -450,7 +458,7 @@ proc getTok(c: var SqlLexer, tok: var Token) =
'\128'..'\255':
getSymbol(c, tok)
of '+', '-', '*', '/', '<', '>', '=', '~', '!', '@', '#', '%',
'^', '&', '|', '`', '?':
'^', '&', '|', '?':
getOperator(c, tok)
else:
add(tok.literal, c.buf[c.bufpos])
@ -462,27 +470,27 @@ proc errorStr(L: SqlLexer, msg: string): string =
# ----------------------------- parser ----------------------------------------
# Operator/Element Associativity Description
# . left table/column name separator
# :: left PostgreSQL-style typecast
# [ ] left array element selection
# - right unary minus
# ^ left exponentiation
# * / % left multiplication, division, modulo
# + - left addition, subtraction
# IS IS TRUE, IS FALSE, IS UNKNOWN, IS NULL
# ISNULL test for null
# NOTNULL test for not null
# (any other) left all other native and user-defined oprs
# IN set membership
# BETWEEN range containment
# OVERLAPS time interval overlap
# LIKE ILIKE SIMILAR string pattern matching
# < > less than, greater than
# = right equality, assignment
# NOT right logical negation
# AND left logical conjunction
# OR left logical disjunction
# Operator/Element Associativity Description
# . left table/column name separator
# :: left PostgreSQL-style typecast
# [ ] left array element selection
# - right unary minus
# ^ left exponentiation
# * / % left multiplication, division, modulo
# + - left addition, subtraction
# IS IS TRUE, IS FALSE, IS UNKNOWN, IS NULL
# ISNULL test for null
# NOTNULL test for not null
# (any other) left all other native and user-defined oprs
# IN set membership
# BETWEEN range containment
# OVERLAPS time interval overlap
# LIKE ILIKE SIMILAR string pattern matching
# < > less than, greater than
# = right equality, assignment
# NOT right logical negation
# AND left logical conjunction
# OR left logical disjunction
type
SqlNodeKind* = enum ## kind of SQL abstract syntax tree
@ -504,6 +512,7 @@ type
nkPrefix,
nkInfix,
nkCall,
nkPrGroup,
nkColumnReference,
nkReferences,
nkDefault,
@ -518,11 +527,15 @@ type
nkSelect,
nkSelectDistinct,
nkSelectColumns,
nkSelectPair,
nkAsgn,
nkFrom,
nkFromItemPair,
nkGroup,
nkLimit,
nkHaving,
nkOrder,
nkJoin,
nkDesc,
nkUnion,
nkIntersect,
@ -658,10 +671,12 @@ proc getPrecedence(p: SqlParser): int =
elif isOpr(p, "=") or isOpr(p, "<") or isOpr(p, ">") or isOpr(p, ">=") or
isOpr(p, "<=") or isOpr(p, "<>") or isOpr(p, "!=") or isKeyw(p, "is") or
isKeyw(p, "like"):
result = 3
result = 4
elif isKeyw(p, "and"):
result = 2
result = 3
elif isKeyw(p, "or"):
result = 2
elif isKeyw(p, "between"):
result = 1
elif p.tok.kind == tkOperator:
# user-defined operator:
@ -670,6 +685,7 @@ proc getPrecedence(p: SqlParser): int =
result = - 1
proc parseExpr(p: var SqlParser): SqlNode
proc parseSelect(p: var SqlParser): SqlNode
proc identOrLiteral(p: var SqlParser): SqlNode =
case p.tok.kind
@ -693,7 +709,8 @@ proc identOrLiteral(p: var SqlParser): SqlNode =
getTok(p)
of tkParLe:
getTok(p)
result = parseExpr(p)
result = newNode(nkPrGroup)
result.add(parseExpr(p))
eat(p, tkParRi)
else:
sqlError(p, "expression expected")
@ -745,7 +762,7 @@ proc lowestExprAux(p: var SqlParser, v: var SqlNode, limit: int): int =
result = opPred
while opPred > limit:
node = newNode(nkInfix)
opNode = newNode(nkIdent, p.tok.literal)
opNode = newNode(nkIdent, p.tok.literal.toLower())
getTok(p)
result = lowestExprAux(p, v2, opPred)
node.add(opNode)
@ -921,6 +938,19 @@ proc parseWhere(p: var SqlParser): SqlNode =
result = newNode(nkWhere)
result.add(parseExpr(p))
proc parseFromItem(p: var SqlParser): SqlNode =
result = newNode(nkFromItemPair)
if p.tok.kind == tkParLe:
getTok(p)
var select = parseSelect(p)
result.add(select)
eat(p, tkParRi)
else:
result.add(parseExpr(p))
if isKeyw(p, "as"):
getTok(p)
result.add(parseExpr(p))
proc parseIndexDef(p: var SqlParser): SqlNode =
result = parseIfNotExists(p, nkCreateIndex)
if isKeyw(p, "primary"):
@ -997,6 +1027,8 @@ proc parseUpdate(p: var SqlParser): SqlNode =
proc parseDelete(p: var SqlParser): SqlNode =
getTok(p)
if isOpr(p, "*"):
getTok(p)
result = newNode(nkDelete)
eat(p, "from")
result.add(primary(p))
@ -1019,7 +1051,12 @@ proc parseSelect(p: var SqlParser): SqlNode =
a.add(newNode(nkIdent, "*"))
getTok(p)
else:
a.add(parseExpr(p))
var pair = newNode(nkSelectPair)
pair.add(parseExpr(p))
a.add(pair)
if isKeyw(p, "as"):
getTok(p)
pair.add(parseExpr(p))
if p.tok.kind != tkComma: break
getTok(p)
result.add(a)
@ -1027,7 +1064,7 @@ proc parseSelect(p: var SqlParser): SqlNode =
var f = newNode(nkFrom)
while true:
getTok(p)
f.add(parseExpr(p))
f.add(parseFromItem(p))
if p.tok.kind != tkComma: break
result.add(f)
if isKeyw(p, "where"):
@ -1041,6 +1078,11 @@ proc parseSelect(p: var SqlParser): SqlNode =
if p.tok.kind != tkComma: break
getTok(p)
result.add(g)
if isKeyw(p, "limit"):
getTok(p)
var l = newNode(nkLimit)
l.add(parseExpr(p))
result.add(l)
if isKeyw(p, "having"):
var h = newNode(nkHaving)
while true:
@ -1073,6 +1115,19 @@ proc parseSelect(p: var SqlParser): SqlNode =
if p.tok.kind != tkComma: break
getTok(p)
result.add(n)
if isKeyw(p, "join") or isKeyw(p, "inner") or isKeyw(p, "outer") or isKeyw(p, "cross"):
var join = newNode(nkJoin)
result.add(join)
if isKeyw(p, "join"):
join.add(newNode(nkIdent, ""))
getTok(p)
else:
join.add(newNode(nkIdent, p.tok.literal.toLower()))
getTok(p)
eat(p, "join")
join.add(parseFromItem(p))
eat(p, "on")
join.add(parseExpr(p))
proc parseStmt(p: var SqlParser; parent: SqlNode) =
if isKeyw(p, "create"):
@ -1104,7 +1159,7 @@ proc parseStmt(p: var SqlParser; parent: SqlNode) =
elif isKeyw(p, "begin"):
getTok(p)
else:
sqlError(p, "CREATE expected")
sqlError(p, "SELECT, CREATE, UPDATE or DELETE expected")
proc open(p: var SqlParser, input: Stream, filename: string) =
## opens the parser `p` and assigns the input stream `input` to it.
@ -1116,13 +1171,13 @@ proc open(p: var SqlParser, input: Stream, filename: string) =
proc parse(p: var SqlParser): SqlNode =
## parses the content of `p`'s input stream and returns the SQL AST.
## Syntax errors raise an `EInvalidSql` exception.
## Syntax errors raise an `SqlParseError` exception.
result = newNode(nkStmtList)
while p.tok.kind != tkEof:
parseStmt(p, result)
if p.tok.kind == tkEof:
break
eat(p, tkSemicolon)
if result.len == 1:
result = result.sons[0]
proc close(p: var SqlParser) =
## closes the parser `p`. The associated input stream is closed too.
@ -1131,7 +1186,7 @@ proc close(p: var SqlParser) =
proc parseSQL*(input: Stream, filename: string): SqlNode =
## parses the SQL from `input` into an AST and returns the AST.
## `filename` is only used for error messages.
## Syntax errors raise an `EInvalidSql` exception.
## Syntax errors raise an `SqlParseError` exception.
var p: SqlParser
open(p, input, filename)
try:
@ -1139,24 +1194,69 @@ proc parseSQL*(input: Stream, filename: string): SqlNode =
finally:
close(p)
proc ra(n: SqlNode, s: var string, indent: int)
proc parseSQL*(input: string, filename=""): SqlNode =
## parses the SQL from `input` into an AST and returns the AST.
## `filename` is only used for error messages.
## Syntax errors raise an `SqlParseError` exception.
parseSQL(newStringStream(input), "")
proc rs(n: SqlNode, s: var string, indent: int,
prefix = "(", suffix = ")",
sep = ", ") =
type
SqlWriter = object
indent: int
upperCase: bool
buffer: string
proc add(s: var SqlWriter, thing: char) =
s.buffer.add(thing)
proc add(s: var SqlWriter, thing: string) =
if s.buffer.len > 0 and s.buffer[^1] notin {' ', '\L', '(', '.'}:
s.buffer.add(" ")
s.buffer.add(thing)
proc addKeyw(s: var SqlWriter, thing: string) =
var keyw = thing
if s.upperCase:
keyw = keyw.toUpper()
s.add(keyw)
proc addIden(s: var SqlWriter, thing: string) =
var iden = thing
if iden.toLower() in reservedKeywords:
iden = '"' & iden & '"'
s.add(iden)
proc ra(n: SqlNode, s: var SqlWriter)
proc rs(n: SqlNode, s: var SqlWriter, prefix = "(", suffix = ")", sep = ", ") =
if n.len > 0:
s.add(prefix)
for i in 0 .. n.len-1:
if i > 0: s.add(sep)
ra(n.sons[i], s, indent)
ra(n.sons[i], s)
s.add(suffix)
proc ra(n: SqlNode, s: var string, indent: int) =
proc addMulti(s: var SqlWriter, n: SqlNode, sep = ',') =
if n.len > 0:
for i in 0 .. n.len-1:
if i > 0: s.add(sep)
ra(n.sons[i], s)
proc addMulti(s: var SqlWriter, n: SqlNode, sep = ',', prefix, suffix: char) =
if n.len > 0:
s.add(prefix)
for i in 0 .. n.len-1:
if i > 0: s.add(sep)
ra(n.sons[i], s)
s.add(suffix)
proc ra(n: SqlNode, s: var SqlWriter) =
if n == nil: return
case n.kind
of nkNone: discard
of nkIdent:
if allCharsInSet(n.strVal, {'\33'..'\127'}):
if allCharsInSet(n.strVal, {'\33'..'\127'}) and n.strVal.toLower() notin reservedKeywords:
s.add(n.strVal)
else:
s.add("\"" & replace(n.strVal, "\"", "\"\"") & "\"")
@ -1169,217 +1269,206 @@ proc ra(n: SqlNode, s: var string, indent: int) =
of nkIntegerLit, nkNumericLit:
s.add(n.strVal)
of nkPrimaryKey:
s.add(" primary key")
rs(n, s, indent)
s.addKeyw("primary key")
rs(n, s)
of nkForeignKey:
s.add(" foreign key")
rs(n, s, indent)
s.addKeyw("foreign key")
rs(n, s)
of nkNotNull:
s.add(" not null")
s.addKeyw("not null")
of nkNull:
s.add(" null")
s.addKeyw("null")
of nkDot:
ra(n.sons[0], s, indent)
s.add(".")
ra(n.sons[1], s, indent)
ra(n.sons[0], s)
s.add('.')
ra(n.sons[1], s)
of nkDotDot:
ra(n.sons[0], s, indent)
ra(n.sons[0], s)
s.add(". .")
ra(n.sons[1], s, indent)
ra(n.sons[1], s)
of nkPrefix:
s.add('(')
ra(n.sons[0], s, indent)
ra(n.sons[0], s)
s.add(' ')
ra(n.sons[1], s, indent)
s.add(')')
ra(n.sons[1], s)
of nkInfix:
s.add('(')
ra(n.sons[1], s, indent)
ra(n.sons[1], s)
s.add(' ')
ra(n.sons[0], s, indent)
ra(n.sons[0], s)
s.add(' ')
ra(n.sons[2], s, indent)
s.add(')')
ra(n.sons[2], s)
of nkCall, nkColumnReference:
ra(n.sons[0], s, indent)
ra(n.sons[0], s)
s.add('(')
for i in 1..n.len-1:
if i > 1: s.add(", ")
ra(n.sons[i], s, indent)
if i > 1: s.add(',')
ra(n.sons[i], s)
s.add(')')
of nkPrGroup:
s.add('(')
s.addMulti(n)
s.add(')')
of nkReferences:
s.add(" references ")
ra(n.sons[0], s, indent)
s.addKeyw("references")
ra(n.sons[0], s)
of nkDefault:
s.add(" default ")
ra(n.sons[0], s, indent)
s.addKeyw("default")
ra(n.sons[0], s)
of nkCheck:
s.add(" check ")
ra(n.sons[0], s, indent)
s.addKeyw("check")
ra(n.sons[0], s)
of nkConstraint:
s.add(" constraint ")
ra(n.sons[0], s, indent)
s.add(" check ")
ra(n.sons[1], s, indent)
s.addKeyw("constraint")
ra(n.sons[0], s)
s.addKeyw("check")
ra(n.sons[1], s)
of nkUnique:
s.add(" unique")
rs(n, s, indent)
s.addKeyw("unique")
rs(n, s)
of nkIdentity:
s.add(" identity")
s.addKeyw("identity")
of nkColumnDef:
s.add("\n ")
rs(n, s, indent, "", "", " ")
rs(n, s, "", "", " ")
of nkStmtList:
for i in 0..n.len-1:
ra(n.sons[i], s, indent)
s.add("\n")
ra(n.sons[i], s)
s.add(';')
of nkInsert:
assert n.len == 3
s.add("insert into ")
ra(n.sons[0], s, indent)
ra(n.sons[1], s, indent)
s.addKeyw("insert into")
ra(n.sons[0], s)
s.add(' ')
ra(n.sons[1], s)
if n.sons[2].kind == nkDefault:
s.add("default values")
s.addKeyw("default values")
else:
s.add("\n")
ra(n.sons[2], s, indent)
s.add(';')
ra(n.sons[2], s)
of nkUpdate:
s.add("update ")
ra(n.sons[0], s, indent)
s.add(" set ")
s.addKeyw("update")
ra(n.sons[0], s)
s.addKeyw("set")
var L = n.len
for i in 1 .. L-2:
if i > 1: s.add(", ")
var it = n.sons[i]
assert it.kind == nkAsgn
ra(it, s, indent)
ra(n.sons[L-1], s, indent)
s.add(';')
ra(it, s)
ra(n.sons[L-1], s)
of nkDelete:
s.add("delete from ")
ra(n.sons[0], s, indent)
ra(n.sons[1], s, indent)
s.add(';')
s.addKeyw("delete from")
ra(n.sons[0], s)
ra(n.sons[1], s)
of nkSelect, nkSelectDistinct:
s.add("select ")
s.addKeyw("select")
if n.kind == nkSelectDistinct:
s.add("distinct ")
rs(n.sons[0], s, indent, "", "", ", ")
for i in 1 .. n.len-1: ra(n.sons[i], s, indent)
s.add(';')
s.addKeyw("distinct")
s.addMulti(n.sons[0])
for i in 1 .. n.len-1:
ra(n.sons[i], s)
of nkSelectColumns:
assert(false)
of nkSelectPair:
ra(n.sons[0], s)
if n.sons.len == 2:
s.addKeyw("as")
ra(n.sons[1], s)
of nkFromItemPair:
if n.sons[0].kind == nkIdent:
ra(n.sons[0], s)
else:
assert n.sons[0].kind == nkSelect
s.add('(')
ra(n.sons[0], s)
s.add(')')
if n.sons.len == 2:
s.addKeyw("as")
ra(n.sons[1], s)
of nkAsgn:
ra(n.sons[0], s, indent)
ra(n.sons[0], s)
s.add(" = ")
ra(n.sons[1], s, indent)
ra(n.sons[1], s)
of nkFrom:
s.add("\nfrom ")
rs(n, s, indent, "", "", ", ")
s.addKeyw("from")
s.addMulti(n)
of nkGroup:
s.add("\ngroup by")
rs(n, s, indent, "", "", ", ")
s.addKeyw("group by")
s.addMulti(n)
of nkLimit:
s.addKeyw("limit")
s.addMulti(n)
of nkHaving:
s.add("\nhaving")
rs(n, s, indent, "", "", ", ")
s.addKeyw("having")
s.addMulti(n)
of nkOrder:
s.add("\norder by ")
rs(n, s, indent, "", "", ", ")
s.addKeyw("order by")
s.addMulti(n)
of nkJoin:
var joinType = n.sons[0].strVal
if joinType == "":
joinType = "join"
else:
joinType &= " " & "join"
s.addKeyw(joinType)
ra(n.sons[1], s)
s.addKeyw("on")
ra(n.sons[2], s)
of nkDesc:
ra(n.sons[0], s, indent)
s.add(" desc")
ra(n.sons[0], s)
s.addKeyw("desc")
of nkUnion:
s.add(" union")
s.addKeyw("union")
of nkIntersect:
s.add(" intersect")
s.addKeyw("intersect")
of nkExcept:
s.add(" except")
s.addKeyw("except")
of nkColumnList:
rs(n, s, indent)
rs(n, s)
of nkValueList:
s.add("values ")
rs(n, s, indent)
s.addKeyw("values")
rs(n, s)
of nkWhere:
s.add("\nwhere ")
ra(n.sons[0], s, indent)
s.addKeyw("where")
ra(n.sons[0], s)
of nkCreateTable, nkCreateTableIfNotExists:
s.add("create table ")
s.addKeyw("create table")
if n.kind == nkCreateTableIfNotExists:
s.add("if not exists ")
ra(n.sons[0], s, indent)
s.addKeyw("if not exists")
ra(n.sons[0], s)
s.add('(')
for i in 1..n.len-1:
if i > 1: s.add(", ")
ra(n.sons[i], s, indent)
if i > 1: s.add(',')
ra(n.sons[i], s)
s.add(");")
of nkCreateType, nkCreateTypeIfNotExists:
s.add("create type ")
s.addKeyw("create type")
if n.kind == nkCreateTypeIfNotExists:
s.add("if not exists ")
ra(n.sons[0], s, indent)
s.add(" as ")
ra(n.sons[1], s, indent)
s.add(';')
s.addKeyw("if not exists")
ra(n.sons[0], s)
s.addKeyw("as")
ra(n.sons[1], s)
of nkCreateIndex, nkCreateIndexIfNotExists:
s.add("create index ")
s.addKeyw("create index")
if n.kind == nkCreateIndexIfNotExists:
s.add("if not exists ")
ra(n.sons[0], s, indent)
s.add(" on ")
ra(n.sons[1], s, indent)
s.addKeyw("if not exists")
ra(n.sons[0], s)
s.addKeyw("on")
ra(n.sons[1], s)
s.add('(')
for i in 2..n.len-1:
if i > 2: s.add(", ")
ra(n.sons[i], s, indent)
ra(n.sons[i], s)
s.add(");")
of nkEnumDef:
s.add("enum ")
rs(n, s, indent)
s.addKeyw("enum")
rs(n, s)
# What I want:
#
#select(columns = [T1.all, T2.name],
# fromm = [T1, T2],
# where = T1.name ==. T2.name,
# orderby = [name]):
#
#for row in dbQuery(db, """select x, y, z
# from a, b
# where a.name = b.name"""):
#
#select x, y, z:
# fromm: Table1, Table2
# where: x.name == y.name
#db.select(fromm = [t1, t2], where = t1.name == t2.name):
#for x, y, z in db.select(fromm = a, b where = a.name == b.name):
# writeLine x, y, z
proc renderSQL*(n: SqlNode): string =
proc renderSQL*(n: SqlNode, upperCase=false): string =
## Converts an SQL abstract syntax tree to its string representation.
result = ""
ra(n, result, 0)
var s: SqlWriter
s.buffer = ""
s.upperCase = upperCase
ra(n, s)
return s.buffer
proc `$`*(n: SqlNode): string =
## an alias for `renderSQL`.
renderSQL(n)
when not defined(testing) and isMainModule:
echo(renderSQL(parseSQL(newStringStream("""
CREATE TYPE happiness AS ENUM ('happy', 'very happy', 'ecstatic');
CREATE TABLE holidays (
num_weeks int,
happiness happiness
);
CREATE INDEX table1_attr1 ON table1(attr1);
SELECT * FROM myTab WHERE col1 = 'happy';
"""), "stdin")))
# CREATE TYPE happiness AS ENUM ('happy', 'very happy', 'ecstatic');
# CREATE TABLE holidays (
# num_weeks int,
# happiness happiness
# );
# CREATE INDEX table1_attr1 ON table1(attr1)

View file

@ -87,6 +87,23 @@ proc parseOct*(s: string, number: var int, start = 0): int {.
inc(i)
if foundDigit: result = i-start
proc parseBin*(s: string, number: var int, start = 0): int {.
rtl, extern: "npuParseBin", noSideEffect.} =
## parses an binary number and stores its value in ``number``. Returns
## the number of the parsed characters or 0 in case of an error.
var i = start
var foundDigit = false
if s[i] == '0' and (s[i+1] == 'b' or s[i+1] == 'B'): inc(i, 2)
while true:
case s[i]
of '_': discard
of '0'..'1':
number = number shl 1 or (ord(s[i]) - ord('0'))
foundDigit = true
else: break
inc(i)
if foundDigit: result = i-start
proc parseIdent*(s: string, ident: var string, start = 0): int =
## parses an identifier and stores it in ``ident``. Returns
## the number of the parsed characters or 0 in case of an error.

View file

@ -7,16 +7,16 @@
# distribution, for details about the copyright.
#
## Nim's standard random number generator. Based on the ``xoroshiro128+`` (xor/rotate/shift/rotate) library.
## Nim's standard random number generator. Based on
## the ``xoroshiro128+`` (xor/rotate/shift/rotate) library.
## * More information: http://xoroshiro.di.unimi.it/
## * C implementation: http://xoroshiro.di.unimi.it/xoroshiro128plus.c
##
## Do not use this module for cryptographic use!
## **Do not use this module for cryptographic purposes!**
include "system/inclrtl"
{.push debugger:off.}
# XXX Expose RandomGenState
when defined(JS):
type ui = uint32
@ -27,31 +27,34 @@ else:
const randMax = 18_446_744_073_709_551_615u64
type
RandomGenState = object
Rand* = object ## State of the random number generator.
## The procs that use the default state
## are **not** thread-safe!
a0, a1: ui
when defined(JS):
var state = RandomGenState(
var state = Rand(
a0: 0x69B4C98Cu32,
a1: 0xFED1DD30u32) # global for backwards compatibility
else:
# racy for multi-threading but good enough for now:
var state = RandomGenState(
var state = Rand(
a0: 0x69B4C98CB8530805u64,
a1: 0xFED1DD3004688D67CAu64) # global for backwards compatibility
proc rotl(x, k: ui): ui =
result = (x shl k) or (x shr (ui(64) - k))
proc next(s: var RandomGenState): uint64 =
let s0 = s.a0
var s1 = s.a1
proc next*(r: var Rand): uint64 =
## Uses the state to compute a new ``uint64`` random number.
let s0 = r.a0
var s1 = r.a1
result = s0 + s1
s1 = s1 xor s0
s.a0 = rotl(s0, 55) xor s1 xor (s1 shl 14) # a, b
s.a1 = rotl(s1, 36) # c
r.a0 = rotl(s0, 55) xor s1 xor (s1 shl 14) # a, b
r.a1 = rotl(s1, 36) # c
proc skipRandomNumbers(s: var RandomGenState) =
proc skipRandomNumbers*(s: var Rand) =
## This is the jump function for the generator. It is equivalent
## to 2^64 calls to next(); it can be used to generate 2^64
## non-overlapping subsequences for parallel computations.
@ -71,21 +74,23 @@ proc skipRandomNumbers(s: var RandomGenState) =
s.a0 = s0
s.a1 = s1
proc random*(max: int): int {.benign.} =
proc random*(max: int): int {.benign, deprecated.} =
## Returns a random number in the range 0..max-1. The sequence of
## random number is always the same, unless `randomize` is called
## which initializes the random number generator with a "random"
## number, i.e. a tickcount.
## number, i.e. a tickcount. **Deprecated since version 0.18.0**.
## Use ``rand`` instead.
while true:
let x = next(state)
if x < randMax - (randMax mod ui(max)):
return int(x mod uint64(max))
proc random*(max: float): float {.benign.} =
proc random*(max: float): float {.benign, deprecated.} =
## Returns a random number in the range 0..<max. The sequence of
## random number is always the same, unless `randomize` is called
## which initializes the random number generator with a "random"
## number, i.e. a tickcount.
## number, i.e. a tickcount. **Deprecated since version 0.18.0**.
## Use ``rand`` instead.
let x = next(state)
when defined(JS):
result = (float(x) / float(high(uint32))) * max
@ -93,25 +98,91 @@ proc random*(max: float): float {.benign.} =
let u = (0x3FFu64 shl 52u64) or (x shr 12u64)
result = (cast[float](u) - 1.0) * max
proc random*[T](x: HSlice[T, T]): T =
proc random*[T](x: HSlice[T, T]): T {.deprecated.} =
## For a slice `a .. b` returns a value in the range `a .. b-1`.
## **Deprecated since version 0.18.0**.
## Use ``rand`` instead.
result = T(random(x.b - x.a)) + x.a
proc random*[T](a: openArray[T]): T =
proc random*[T](a: openArray[T]): T {.deprecated.} =
## returns a random element from the openarray `a`.
## **Deprecated since version 0.18.0**.
## Use ``rand`` instead.
result = a[random(a.low..a.len)]
proc rand*(r: var Rand; max: int): int {.benign.} =
## Returns a random number in the range 0..max. The sequence of
## random number is always the same, unless `randomize` is called
## which initializes the random number generator with a "random"
## number, i.e. a tickcount.
while true:
let x = next(r)
if x <= randMax - (randMax mod ui(max)):
return int(x mod (uint64(max)+1u64))
proc rand*(max: int): int {.benign.} =
## Returns a random number in the range 0..max. The sequence of
## random number is always the same, unless `randomize` is called
## which initializes the random number generator with a "random"
## number, i.e. a tickcount.
rand(state, max)
proc rand*(r: var Rand; max: float): float {.benign.} =
## Returns a random number in the range 0..max. The sequence of
## random number is always the same, unless `randomize` is called
## which initializes the random number generator with a "random"
## number, i.e. a tickcount.
let x = next(r)
when defined(JS):
result = (float(x) / float(high(uint32))) * max
else:
let u = (0x3FFu64 shl 52u64) or (x shr 12u64)
result = (cast[float](u) - 1.0) * max
proc rand*(max: float): float {.benign.} =
## Returns a random number in the range 0..max. The sequence of
## random number is always the same, unless `randomize` is called
## which initializes the random number generator with a "random"
## number, i.e. a tickcount.
rand(state, max)
proc rand*[T](r: var Rand; x: HSlice[T, T]): T =
## For a slice `a .. b` returns a value in the range `a .. b`.
result = T(rand(r, x.b - x.a)) + x.a
proc rand*[T](x: HSlice[T, T]): T =
## For a slice `a .. b` returns a value in the range `a .. b`.
result = rand(state, x)
proc rand*[T](r: var Rand; a: openArray[T]): T =
## returns a random element from the openarray `a`.
result = a[rand(r, a.low..a.high)]
proc rand*[T](a: openArray[T]): T =
## returns a random element from the openarray `a`.
result = a[rand(a.low..a.high)]
proc initRand*(seed: int64): Rand =
## Creates a new ``Rand`` state from ``seed``.
result.a0 = ui(seed shr 16)
result.a1 = ui(seed and 0xffff)
discard next(result)
proc randomize*(seed: int64) {.benign.} =
## Initializes the random number generator with a specific seed.
state.a0 = ui(seed shr 16)
state.a1 = ui(seed and 0xffff)
discard next(state)
## Initializes the default random number generator
## with a specific seed.
state = initRand(seed)
proc shuffle*[T](r: var Rand; x: var openArray[T]) =
## Swaps the positions of elements in a sequence randomly.
for i in countdown(x.high, 1):
let j = r.rand(i)
swap(x[i], x[j])
proc shuffle*[T](x: var openArray[T]) =
## Will randomly swap the positions of elements in a sequence.
for i in countdown(x.high, 1):
let j = random(i + 1)
swap(x[i], x[j])
## Swaps the positions of elements in a sequence randomly.
shuffle(state, x)
when not defined(nimscript):
import times
@ -119,12 +190,8 @@ when not defined(nimscript):
proc randomize*() {.benign.} =
## Initializes the random number generator with a "random"
## number, i.e. a tickcount. Note: Does not work for NimScript.
when defined(JS):
proc getMil(t: Time): int {.importcpp: "getTime", nodecl.}
randomize(getMil times.getTime())
else:
let time = int64(times.epochTime() * 1_000_000_000)
randomize(time)
let time = int64(times.epochTime() * 1_000_000_000)
randomize(time)
{.pop.}
@ -134,12 +201,12 @@ when isMainModule:
var x = 8234
for i in 0..100_000:
x = random(len(occur)) # myrand(x)
x = rand(high(occur))
inc occur[x]
for i, oc in occur:
if oc < 69:
doAssert false, "too few occurrences of " & $i
elif oc > 130:
elif oc > 150:
doAssert false, "too many occurrences of " & $i
var a = [0, 1]

View file

@ -39,7 +39,7 @@ proc toRational*[T:SomeInteger](x: T): Rational[T] =
result.num = x
result.den = 1
proc toRational*(x: float, n: int = high(int32)): Rational[int] =
proc toRational*(x: float, n: int = high(int) shr (sizeof(int) div 2 * 8)): Rational[int] =
## Calculates the best rational numerator and denominator
## that approximates to `x`, where the denominator is
## smaller than `n` (default is the largest possible
@ -323,8 +323,13 @@ when isMainModule:
assert abs(toFloat(y) - 0.4814814814814815) < 1.0e-7
assert toInt(z) == 0
assert toRational(0.98765432) == 2111111029 // 2137499919
assert toRational(PI) == 817696623 // 260280919
when sizeof(int) == 8:
assert toRational(0.98765432) == 2111111029 // 2137499919
assert toRational(PI) == 817696623 // 260280919
when sizeof(int) == 4:
assert toRational(0.98765432) == 80 // 81
assert toRational(PI) == 355 // 113
assert toRational(0.1) == 1 // 10
assert toRational(0.9) == 9 // 10

638
lib/pure/strformat.nim Normal file
View file

@ -0,0 +1,638 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2017 Nim contributors
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
##[
String `interpolation`:idx: / `format`:idx: inspired by
Python's ``f``-strings.
Examples:
.. code-block:: nim
doAssert &"""{"abc":>4}""" == " abc"
doAssert &"""{"abc":<4}""" == "abc "
doAssert &"{-12345:08}" == "-0012345"
doAssert &"{-1:3}" == " -1"
doAssert &"{-1:03}" == "-01"
doAssert &"{16:#X}" == "0x10"
doAssert &"{123.456}" == "123.456"
doAssert &"{123.456:>9.3f}" == " 123.456"
doAssert &"{123.456:9.3f}" == " 123.456"
doAssert &"{123.456:9.4f}" == " 123.4560"
doAssert &"{123.456:>9.0f}" == " 123."
doAssert &"{123.456:<9.4f}" == "123.4560 "
doAssert &"{123.456:e}" == "1.234560e+02"
doAssert &"{123.456:>13e}" == " 1.234560e+02"
doAssert &"{123.456:13e}" == " 1.234560e+02"
An expression like ``&"{key} is {value:arg} {{z}}"`` is transformed into:
.. code-block:: nim
var temp = newStringOfCap(educatedCapGuess)
format(key, temp)
format(" is ", temp)
format(value, arg, temp)
format(" {z}", temp)
temp
Parts of the string that are enclosed in the curly braces are interpreted
as Nim code, to escape an ``{`` or ``}`` double it.
``&`` delegates most of the work to an open overloaded set
of ``format`` procs. The required signature for a type ``T`` that supports
formatting is usually ``proc format(x: T; result: var string)`` for efficiency
but can also be ``proc format(x: T): string``. ``add`` and ``$`` procs are
used as the fallback implementation.
This is the concrete lookup algorithm that ``&`` uses:
.. code-block:: nim
when compiles(format(arg, res)):
format(arg, res)
elif compiles(format(arg)):
res.add format(arg)
elif compiles(add(res, arg)):
res.add(arg)
else:
res.add($arg)
The subexpression after the colon
(``arg`` in ``&"{key} is {value:arg} {{z}}"``) is an optional argument
passed to ``format``.
If an optional argument is present the following lookup algorithm is used:
.. code-block:: nim
when compiles(format(arg, option, res)):
format(arg, option, res)
else:
res.add format(arg, option)
For strings and numeric types the optional argument is a so-called
"standard format specifier".
Standard format specifier
=========================
The general form of a standard format specifier is::
[[fill]align][sign][#][0][minimumwidth][.precision][type]
The square brackets ``[]`` indicate an optional element.
The optional align flag can be one of the following:
'<'
Forces the field to be left-aligned within the available
space. (This is the default for strings.)
'>'
Forces the field to be right-aligned within the available space.
(This is the default for numbers.)
'^'
Forces the field to be centered within the available space.
Note that unless a minimum field width is defined, the field width
will always be the same size as the data to fill it, so that the alignment
option has no meaning in this case.
The optional 'fill' character defines the character to be used to pad
the field to the minimum width. The fill character, if present, must be
followed by an alignment flag.
The 'sign' option is only valid for numeric types, and can be one of the following:
================= ====================================================
Sign Meaning
================= ====================================================
``+`` Indicates that a sign should be used for both
positive as well as negative numbers.
``-`` Indicates that a sign should be used only for
negative numbers (this is the default behavior).
(space) Indicates that a leading space should be used on
positive numbers.
================= ====================================================
If the '#' character is present, integers use the 'alternate form' for formatting.
This means that binary, octal, and hexadecimal output will be prefixed
with '0b', '0o', and '0x', respectively.
'width' is a decimal integer defining the minimum field width. If not specified,
then the field width will be determined by the content.
If the width field is preceded by a zero ('0') character, this enables
zero-padding.
The 'precision' is a decimal number indicating how many digits should be displayed
after the decimal point in a floating point conversion. For non-numeric types the
field indicates the maximum field size - in other words, how many characters will
be used from the field content. The precision is ignored for integer conversions.
Finally, the 'type' determines how the data should be presented.
The available integer presentation types are:
================= ====================================================
Type Result
================= ====================================================
``b`` Binary. Outputs the number in base 2.
``d`` Decimal Integer. Outputs the number in base 10.
``o`` Octal format. Outputs the number in base 8.
``x`` Hex format. Outputs the number in base 16, using
lower-case letters for the digits above 9.
``X`` Hex format. Outputs the number in base 16, using
uppercase letters for the digits above 9.
(None) the same as 'd'
================= ====================================================
The available floating point presentation types are:
================= ====================================================
Type Result
================= ====================================================
``e`` Exponent notation. Prints the number in scientific
notation using the letter 'e' to indicate the
exponent.
``E`` Exponent notation. Same as 'e' except it converts
the number to uppercase.
``f`` Fixed point. Displays the number as a fixed-point
number.
``F`` Fixed point. Same as 'f' except it converts the
number to uppercase.
``g`` General format. This prints the number as a
fixed-point number, unless the number is too
large, in which case it switches to 'e'
exponent notation.
``G`` General format. Same as 'g' except switches to 'E'
if the number gets to large.
(None) similar to 'g', except that it prints at least one
digit after the decimal point.
================= ====================================================
Future directions
=================
A curly expression with commas in it like ``{x, argA, argB}`` could be
transformed to ``format(x, argA, argB, res)`` in order to support
formatters that do not need to parse a custom language within a custom
language but instead prefer to use Nim's existing syntax. This also
helps in readability since there is only so much you can cram into
single letter DSLs.
]##
import macros, parseutils, unicode
import strutils
template callFormat(res, arg) {.dirty.} =
when arg is string:
# workaround in order to circumvent 'strutils.format' which matches
# too but doesn't adhere to our protocol.
res.add arg
elif compiles(format(arg, res)):
format(arg, res)
elif compiles(format(arg)):
res.add format(arg)
elif compiles(add(res, arg)):
res.add(arg)
else:
res.add($arg)
template callFormatOption(res, arg, option) {.dirty.} =
when compiles(format(arg, option, res)):
format(arg, option, res)
elif compiles(format(arg, option)):
res.add format(arg, option)
else:
format($arg, option, res)
macro `&`*(pattern: string): untyped =
## For a specification of the ``&`` macro, see the module level documentation.
runnableExamples:
template check(actual, expected: string) =
doAssert actual == expected
from strutils import toUpperAscii, repeat
# Basic tests
let s = "string"
check &"{0} {s}", "0 string"
check &"{s[0..2].toUpperAscii}", "STR"
check &"{-10:04}", "-010"
check &"{-10:<04}", "-010"
check &"{-10:>04}", "-010"
check &"0x{10:02X}", "0x0A"
check &"{10:#04X}", "0x0A"
check &"""{"test":#>5}""", "#test"
check &"""{"test":>5}""", " test"
check &"""{"test":#^7}""", "#test##"
check &"""{"test": <5}""", "test "
check &"""{"test":<5}""", "test "
check &"{1f:.3f}", "1.000"
check &"Hello, {s}!", "Hello, string!"
# Tests for identifers without parenthesis
check &"{s} works{s}", "string worksstring"
check &"{s:>7}", " string"
doAssert(not compiles(&"{s_works}")) # parsed as identifier `s_works`
# Misc general tests
check &"{{}}", "{}"
check &"{0}%", "0%"
check &"{0}%asdf", "0%asdf"
check &("\n{\"\\n\"}\n"), "\n\n\n"
check &"""{"abc"}s""", "abcs"
# String tests
check &"""{"abc"}""", "abc"
check &"""{"abc":>4}""", " abc"
check &"""{"abc":<4}""", "abc "
check &"""{"":>4}""", " "
check &"""{"":<4}""", " "
# Int tests
check &"{12345}", "12345"
check &"{ - 12345}", "-12345"
check &"{12345:6}", " 12345"
check &"{12345:>6}", " 12345"
check &"{12345:4}", "12345"
check &"{12345:08}", "00012345"
check &"{-12345:08}", "-0012345"
check &"{0:0}", "0"
check &"{0:02}", "00"
check &"{-1:3}", " -1"
check &"{-1:03}", "-01"
check &"{10}", "10"
check &"{16:#X}", "0x10"
check &"{16:^#7X}", " 0x10 "
check &"{16:^+#7X}", " +0x10 "
# Hex tests
check &"{0:x}", "0"
check &"{-0:x}", "0"
check &"{255:x}", "ff"
check &"{255:X}", "FF"
check &"{-255:x}", "-ff"
check &"{-255:X}", "-FF"
check &"{255:x} uNaffeCteD CaSe", "ff uNaffeCteD CaSe"
check &"{255:X} uNaffeCteD CaSe", "FF uNaffeCteD CaSe"
check &"{255:4x}", " ff"
check &"{255:04x}", "00ff"
check &"{-255:4x}", " -ff"
check &"{-255:04x}", "-0ff"
# Float tests
check &"{123.456}", "123.456"
check &"{-123.456}", "-123.456"
check &"{123.456:.3f}", "123.456"
check &"{123.456:+.3f}", "+123.456"
check &"{-123.456:+.3f}", "-123.456"
check &"{-123.456:.3f}", "-123.456"
check &"{123.456:1g}", "123.456"
check &"{123.456:.1f}", "123.5"
check &"{123.456:.0f}", "123."
#check &"{123.456:.0f}", "123."
check &"{123.456:>9.3f}", " 123.456"
check &"{123.456:9.3f}", " 123.456"
check &"{123.456:>9.4f}", " 123.4560"
check &"{123.456:>9.0f}", " 123."
check &"{123.456:<9.4f}", "123.4560 "
# Float (scientific) tests
check &"{123.456:e}", "1.234560e+02"
check &"{123.456:>13e}", " 1.234560e+02"
check &"{123.456:<13e}", "1.234560e+02 "
check &"{123.456:.1e}", "1.2e+02"
check &"{123.456:.2e}", "1.23e+02"
check &"{123.456:.3e}", "1.235e+02"
# Note: times.format adheres to the format protocol. Test that this
# works:
import times
var nullTime: DateTime
check &"{nullTime:yyyy-mm-dd}", "0000-00-00"
# Unicode string tests
check &"""{"αβγ"}""", "αβγ"
check &"""{"αβγ":>5}""", " αβγ"
check &"""{"αβγ":<5}""", "αβγ "
check &"""a{"a"}α{"α"}€{"€"}𐍈{"𐍈"}""", "aaαα€€𐍈𐍈"
check &"""a{"a":2}α{"α":2}€{"€":2}𐍈{"𐍈":2}""", "aa αα €€ 𐍈𐍈 "
# Invalid unicode sequences should be handled as plain strings.
# Invalid examples taken from: https://stackoverflow.com/a/3886015/1804173
let invalidUtf8 = [
"\xc3\x28", "\xa0\xa1",
"\xe2\x28\xa1", "\xe2\x82\x28",
"\xf0\x28\x8c\xbc", "\xf0\x90\x28\xbc", "\xf0\x28\x8c\x28"
]
for s in invalidUtf8:
check &"{s:>5}", repeat(" ", 5-s.len) & s
if pattern.kind notin {nnkStrLit..nnkTripleStrLit}:
error "& only works with string literals", pattern
let f = pattern.strVal
var i = 0
let res = genSym(nskVar, "fmtRes")
result = newNimNode(nnkStmtListExpr, lineInfoFrom=pattern)
result.add newVarStmt(res, newCall(bindSym"newStringOfCap", newLit(f.len + count(f, '{')*10)))
var strlit = ""
while i < f.len:
if f[i] == '{':
inc i
if f[i] == '{':
inc i
strlit.add '{'
else:
if strlit.len > 0:
result.add newCall(bindSym"add", res, newLit(strlit))
strlit = ""
var subexpr = ""
while i < f.len and f[i] != '}' and f[i] != ':':
subexpr.add f[i]
inc i
let x = parseExpr(subexpr)
if f[i] == ':':
inc i
var options = ""
while i < f.len and f[i] != '}':
options.add f[i]
inc i
result.add getAst(callFormatOption(res, x, newLit(options)))
else:
result.add getAst(callFormat(res, x))
if f[i] == '}':
inc i
else:
doAssert false, "invalid format string: missing '}'"
elif f[i] == '}':
if f[i+1] == '}':
strlit.add '}'
inc i, 2
else:
doAssert false, "invalid format string: '}' instead of '}}'"
inc i
else:
strlit.add f[i]
inc i
if strlit.len > 0:
result.add newCall(bindSym"add", res, newLit(strlit))
result.add res
when defined(debugFmtDsl):
echo repr result
template fmt*(pattern: string): untyped =
## An alias for ``&``.
## **Examples:**
##
## .. code-block:: nim
## import json
## import strformat except `&`
##
## let example = "oh, look no conflicts anymore"
## echo fmt"{example}"
bind `&`
&pattern
proc mkDigit(v: int, typ: char): string {.inline.} =
assert(v < 26)
if v < 10:
result = $chr(ord('0') + v)
else:
result = $chr(ord(if typ == 'x': 'a' else: 'A') + v - 10)
proc alignString*(s: string, minimumWidth: int; align = '\0'; fill = ' '): string =
## Aligns ``s`` using ``fill`` char.
## This is only of interest if you want to write a custom ``format`` proc that
## should support the standard format specifiers.
if minimumWidth == 0:
result = s
else:
let sRuneLen = if s.validateUtf8 == -1: s.runeLen else: s.len
let toFill = minimumWidth - sRuneLen
if toFill <= 0:
result = s
elif align == '<' or align == '\0':
result = s & repeat(fill, toFill)
elif align == '^':
let half = toFill div 2
result = repeat(fill, half) & s & repeat(fill, toFill - half)
else:
result = repeat(fill, toFill) & s
type
StandardFormatSpecifier* = object ## Type that describes "standard format specifiers".
fill*, align*: char ## Desired fill and alignment.
sign*: char ## Desired sign.
alternateForm*: bool ## Whether to prefix binary, octal and hex numbers
## with ``0b``, ``0o``, ``0x``.
padWithZero*: bool ## Whether to pad with zeros rather than spaces.
minimumWidth*, precision*: int ## Desired minium width and precision.
typ*: char ## Type like 'f', 'g' or 'd'.
endPosition*: int ## End position in the format specifier after
## ``parseStandardFormatSpecifier`` returned.
proc formatInt(n: SomeNumber; radix: int; spec: StandardFormatSpecifier): string =
## Converts ``n`` to string. If ``n`` is `SomeReal`, it casts to `int64`.
## Conversion is done using ``radix``. If result's length is lesser than
## ``minimumWidth``, it aligns result to the right or left (depending on ``a``)
## with ``fill`` char.
when n is SomeUnsignedInt:
var v = n.uint64
let negative = false
else:
var v = n.int64
let negative = v.int64 < 0
if negative:
# FIXME: overflow error for low(int64)
v = v * -1
var xx = ""
if spec.alternateForm:
case spec.typ
of 'X': xx = "0x"
of 'x': xx = "0x"
of 'b': xx = "0b"
of 'o': xx = "0o"
else: discard
if v == 0:
result = "0"
else:
result = ""
while v > type(v)(0):
let d = v mod type(v)(radix)
v = v div type(v)(radix)
result.add(mkDigit(d.int, spec.typ))
for idx in 0..<(result.len div 2):
swap result[idx], result[result.len - idx - 1]
if spec.padWithZero:
let sign = negative or spec.sign != '-'
let toFill = spec.minimumWidth - result.len - xx.len - ord(sign)
if toFill > 0:
result = repeat('0', toFill) & result
if negative:
result = "-" & xx & result
elif spec.sign != '-':
result = spec.sign & xx & result
else:
result = xx & result
if spec.align == '<':
for i in result.len..<spec.minimumWidth:
result.add(spec.fill)
else:
let toFill = spec.minimumWidth - result.len
if spec.align == '^':
let half = toFill div 2
result = repeat(spec.fill, half) & result & repeat(spec.fill, toFill - half)
else:
if toFill > 0:
result = repeat(spec.fill, toFill) & result
proc parseStandardFormatSpecifier*(s: string; start = 0;
ignoreUnknownSuffix = false): StandardFormatSpecifier =
## An exported helper proc that parses the "standard format specifiers",
## as specified by the grammar::
##
## [[fill]align][sign][#][0][minimumwidth][.precision][type]
##
## This is only of interest if you want to write a custom ``format`` proc that
## should support the standard format specifiers. If ``ignoreUnknownSuffix`` is true,
## an unknown suffix after the ``type`` field is not an error.
const alignChars = {'<', '>', '^'}
result.fill = ' '
result.align = '\0'
result.sign = '-'
var i = start
if i + 1 < s.len and s[i+1] in alignChars:
result.fill = s[i]
result.align = s[i+1]
inc i, 2
elif i < s.len and s[i] in alignChars:
result.align = s[i]
inc i
if i < s.len and s[i] in {'-', '+', ' '}:
result.sign = s[i]
inc i
if i < s.len and s[i] == '#':
result.alternateForm = true
inc i
if i+1 < s.len and s[i] == '0' and s[i+1] in {'0'..'9'}:
result.padWithZero = true
inc i
let parsedLength = parseSaturatedNatural(s, result.minimumWidth, i)
inc i, parsedLength
if i < s.len and s[i] == '.':
inc i
let parsedLengthB = parseSaturatedNatural(s, result.precision, i)
inc i, parsedLengthB
else:
result.precision = -1
if i < s.len and s[i] in {'A'..'Z', 'a'..'z'}:
result.typ = s[i]
inc i
result.endPosition = i
if i != s.len and not ignoreUnknownSuffix:
raise newException(ValueError,
"invalid format string, cannot parse: " & s[i..^1])
proc format*(value: SomeInteger; specifier: string; res: var string) =
## Standard format implementation for ``SomeInteger``. It makes little
## sense to call this directly, but it is required to exist
## by the ``&`` macro.
let spec = parseStandardFormatSpecifier(specifier)
var radix = 10
case spec.typ
of 'x', 'X': radix = 16
of 'd', '\0': discard
of 'b': radix = 2
of 'o': radix = 8
else:
raise newException(ValueError,
"invalid type in format string for number, expected one " &
" of 'x', 'X', 'b', 'd', 'o' but got: " & spec.typ)
res.add formatInt(value, radix, spec)
proc format*(value: SomeReal; specifier: string; res: var string) =
## Standard format implementation for ``SomeReal``. It makes little
## sense to call this directly, but it is required to exist
## by the ``&`` macro.
let spec = parseStandardFormatSpecifier(specifier)
var fmode = ffDefault
case spec.typ
of 'e', 'E':
fmode = ffScientific
of 'f', 'F':
fmode = ffDecimal
of 'g', 'G':
fmode = ffDefault
of '\0': discard
else:
raise newException(ValueError,
"invalid type in format string for number, expected one " &
" of 'e', 'E', 'f', 'F', 'g', 'G' but got: " & spec.typ)
var f = formatBiggestFloat(value, fmode, spec.precision)
if value >= 0.0 and spec.sign != '-':
f = spec.sign & f
# the default for numbers is right-alignment:
let align = if spec.align == '\0': '>' else: spec.align
let result = alignString(f, spec.minimumWidth,
align, spec.fill)
if spec.typ in {'A'..'Z'}:
res.add toUpperAscii(result)
else:
res.add result
proc format*(value: string; specifier: string; res: var string) =
## Standard format implementation for ``string``. It makes little
## sense to call this directly, but it is required to exist
## by the ``&`` macro.
let spec = parseStandardFormatSpecifier(specifier)
case spec.typ
of 's', '\0': discard
else:
raise newException(ValueError,
"invalid type in format string for string, expected 's', but got " &
spec.typ)
res.add alignString(value, spec.minimumWidth, spec.align, spec.fill)
when isMainModule:
import json
doAssert fmt"{'a'} {'b'}" == "a b"

View file

@ -31,7 +31,10 @@ As can be seen from the examples, strings are matched verbatim except for
substrings starting with ``$``. These constructions are available:
================= ========================================================
``$i`` Matches an integer. This uses ``parseutils.parseInt``.
``$b`` Matches a binary integer. This uses ``parseutils.parseBin``.
``$o`` Matches an octal integer. This uses ``parseutils.parseOct``.
``$i`` Matches a decimal integer. This uses ``parseutils.parseInt``.
``$h`` Matches a hex integer. This uses ``parseutils.parseHex``.
``$f`` Matches a floating pointer number. Uses ``parseFloat``.
``$w`` Matches an ASCII identifier: ``[A-Z-a-z_][A-Za-z_0-9]*``.
``$s`` Skips optional whitespace.
@ -330,19 +333,37 @@ macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): b
conds.add resLen.notZero
conds.add resLen
of 'w':
if i < results.len or getType(results[i]).typeKind != ntyString:
if i < results.len and getType(results[i]).typeKind == ntyString:
matchBind "parseIdent"
else:
error("no string var given for $w")
inc i
of 'b':
if i < results.len and getType(results[i]).typeKind == ntyInt:
matchBind "parseBin"
else:
error("no int var given for $b")
inc i
of 'o':
if i < results.len and getType(results[i]).typeKind == ntyInt:
matchBind "parseOct"
else:
error("no int var given for $o")
inc i
of 'i':
if i < results.len or getType(results[i]).typeKind != ntyInt:
if i < results.len and getType(results[i]).typeKind == ntyInt:
matchBind "parseInt"
else:
error("no int var given for $d")
error("no int var given for $i")
inc i
of 'h':
if i < results.len and getType(results[i]).typeKind == ntyInt:
matchBind "parseHex"
else:
error("no int var given for $h")
inc i
of 'f':
if i < results.len or getType(results[i]).typeKind != ntyFloat:
if i < results.len and getType(results[i]).typeKind == ntyFloat:
matchBind "parseFloat"
else:
error("no float var given for $f")
@ -357,7 +378,7 @@ macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): b
else:
error("invalid format string")
of '*', '+':
if i < results.len or getType(results[i]).typeKind != ntyString:
if i < results.len and getType(results[i]).typeKind == ntyString:
var min = ord(pattern[p] == '+')
var q=p+1
var token = ""
@ -441,7 +462,7 @@ template success*(x: int): bool = x != 0
template nxt*(input: string; idx, step: int = 1) = inc(idx, step)
macro scanp*(input, idx: typed; pattern: varargs[untyped]): bool =
## See top level documentation of his module of how ``scanp`` works.
## ``scanp`` is currently undocumented.
type StmtTriple = tuple[init, cond, action: NimNode]
template interf(x): untyped = bindSym(x, brForceOpen)
@ -645,6 +666,14 @@ when isMainModule:
doAssert intval == 89
doAssert floatVal == 33.25
var binval: int
var octval: int
var hexval: int
doAssert scanf("0b0101 0o1234 0xabcd", "$b$s$o$s$h", binval, octval, hexval)
doAssert binval == 0b0101
doAssert octval == 0o1234
doAssert hexval == 0xabcd
let xx = scanf("$abc", "$$$i", intval)
doAssert xx == false

View file

@ -1761,29 +1761,15 @@ proc insertSep*(s: string, sep = '_', digits = 3): string {.noSideEffect,
proc escape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect,
rtl, extern: "nsuEscape".} =
## Escapes a string `s`.
## Escapes a string `s`. See `system.addEscapedChar <system.html#addEscapedChar>`_
## for the escaping scheme.
##
## This does these operations (at the same time):
## * replaces any ``\`` by ``\\``
## * replaces any ``'`` by ``\'``
## * replaces any ``"`` by ``\"``
## * replaces any other character in the set ``{'\0'..'\31', '\127'..'\255'}``
## by ``\xHH`` where ``HH`` is its hexadecimal value.
## The procedure has been designed so that its output is usable for many
## different common syntaxes. The resulting string is prefixed with
## `prefix` and suffixed with `suffix`. Both may be empty strings.
## **Note**: This is not correct for producing Ansi C code!
## The resulting string is prefixed with `prefix` and suffixed with `suffix`.
## Both may be empty strings.
result = newStringOfCap(s.len + s.len shr 2)
result.add(prefix)
for c in items(s):
case c
of '\0'..'\31', '\127'..'\255':
add(result, "\\x")
add(result, toHex(ord(c), 2))
of '\\': add(result, "\\\\")
of '\'': add(result, "\\'")
of '\"': add(result, "\\\"")
else: add(result, c)
result.addEscapedChar(c)
add(result, suffix)
proc unescape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect,

View file

@ -391,12 +391,11 @@ proc eraseLine*(f: File) =
origin.X = 0'i16
if setConsoleCursorPosition(h, origin) == 0:
raiseOSError(osLastError())
var ht = scrbuf.dwSize.Y - origin.Y
var wt = scrbuf.dwSize.X - origin.X
if fillConsoleOutputCharacter(h, ' ', ht*wt,
var wt: DWORD = scrbuf.dwSize.X - origin.X
if fillConsoleOutputCharacter(h, ' ', wt,
origin, addr(numwrote)) == 0:
raiseOSError(osLastError())
if fillConsoleOutputAttribute(h, scrbuf.wAttributes, ht * wt,
if fillConsoleOutputAttribute(h, scrbuf.wAttributes, wt,
scrbuf.dwCursorPosition, addr(numwrote)) == 0:
raiseOSError(osLastError())
else:
@ -634,7 +633,10 @@ proc getch*(): char =
doAssert(readConsoleInput(fd, addr(keyEvent), 1, addr(numRead)) != 0)
if numRead == 0 or keyEvent.eventType != 1 or keyEvent.bKeyDown == 0:
continue
return char(keyEvent.uChar)
if keyEvent.uChar == 0:
return char(keyEvent.wVirtualKeyCode)
else:
return char(keyEvent.uChar)
else:
let fd = getFileHandle(stdin)
var oldMode: Termios
@ -650,10 +652,10 @@ template setCursorPos*(x, y: int) = setCursorPos(stdout, x, y)
template setCursorXPos*(x: int) = setCursorXPos(stdout, x)
when defined(windows):
template setCursorYPos(x: int) = setCursorYPos(stdout, x)
template cursorUp*(count=1) = cursorUp(stdout, f)
template cursorDown*(count=1) = cursorDown(stdout, f)
template cursorForward*(count=1) = cursorForward(stdout, f)
template cursorBackward*(count=1) = cursorBackward(stdout, f)
template cursorUp*(count=1) = cursorUp(stdout, count)
template cursorDown*(count=1) = cursorDown(stdout, count)
template cursorForward*(count=1) = cursorForward(stdout, count)
template cursorBackward*(count=1) = cursorBackward(stdout, count)
template eraseLine*() = eraseLine(stdout)
template eraseScreen*() = eraseScreen(stdout)
template setStyle*(style: set[Style]) =

File diff suppressed because it is too large Load diff

View file

@ -21,13 +21,41 @@
## ``nim c -r <testfile.nim>`` exits with 0 or 1
##
## Running a single test
## ---------------------
## =====================
##
## Simply specify the test name as a command line argument.
## Specify the test name as a command line argument.
##
## .. code::
##
## nim c -r test "my super awesome test name"
## nim c -r test "my test name" "another test"
##
## Multiple arguments can be used.
##
## Running a single test suite
## ===========================
##
## Specify the suite name delimited by ``"::"``.
##
## .. code::
##
## nim c -r test "my test name::"
##
## Selecting tests by pattern
## ==========================
##
## A single ``"*"`` can be used for globbing.
##
## Delimit the end of a suite name with ``"::"``.
##
## Tests matching **any** of the arguments are executed.
##
## .. code::
##
## nim c -r test fast_suite::mytest1 fast_suite::mytest2
## nim c -r test "fast_suite::mytest*"
## nim c -r test "auth*::" "crypto::hashing*"
## # Run suites starting with 'bug #' and standalone tests starting with '#'
## nim c -r test 'bug #*::' '::#*'
##
## Example
## -------
@ -121,7 +149,7 @@ var
checkpoints {.threadvar.}: seq[string]
formatters {.threadvar.}: seq[OutputFormatter]
testsToRun {.threadvar.}: HashSet[string]
testsFilters {.threadvar.}: HashSet[string]
when declared(stdout):
abortOnError = existsEnv("NIMTEST_ABORT_ON_ERROR")
@ -300,22 +328,63 @@ method testEnded*(formatter: JUnitOutputFormatter, testResult: TestResult) =
method suiteEnded*(formatter: JUnitOutputFormatter) =
formatter.stream.writeLine("\t</testsuite>")
proc shouldRun(testName: string): bool =
if testsToRun.len == 0:
proc glob(matcher, filter: string): bool =
## Globbing using a single `*`. Empty `filter` matches everything.
if filter.len == 0:
return true
result = testName in testsToRun
if not filter.contains('*'):
return matcher == filter
let beforeAndAfter = filter.split('*', maxsplit=1)
if beforeAndAfter.len == 1:
# "foo*"
return matcher.startswith(beforeAndAfter[0])
if matcher.len < filter.len - 1:
return false # "12345" should not match "123*345"
return matcher.startsWith(beforeAndAfter[0]) and matcher.endsWith(beforeAndAfter[1])
proc matchFilter(suiteName, testName, filter: string): bool =
if filter == "":
return true
if testName == filter:
# corner case for tests containing "::" in their name
return true
let suiteAndTestFilters = filter.split("::", maxsplit=1)
if suiteAndTestFilters.len == 1:
# no suite specified
let test_f = suiteAndTestFilters[0]
return glob(testName, test_f)
return glob(suiteName, suiteAndTestFilters[0]) and glob(testName, suiteAndTestFilters[1])
when defined(testing): export matchFilter
proc shouldRun(currentSuiteName, testName: string): bool =
## Check if a test should be run by matching suiteName and testName against
## test filters.
if testsFilters.len == 0:
return true
for f in testsFilters:
if matchFilter(currentSuiteName, testName, f):
return true
return false
proc ensureInitialized() =
if formatters == nil:
formatters = @[OutputFormatter(defaultConsoleFormatter())]
if not testsToRun.isValid:
testsToRun.init()
if not testsFilters.isValid:
testsFilters.init()
when declared(paramCount):
# Read tests to run from the command line.
for i in 1 .. paramCount():
testsToRun.incl(paramStr(i))
testsFilters.incl(paramStr(i))
# These two procs are added as workarounds for
# https://github.com/nim-lang/Nim/issues/5549
@ -395,7 +464,7 @@ template test*(name, body) {.dirty.} =
ensureInitialized()
if shouldRun(name):
if shouldRun(when declared(testSuiteName): testSuiteName else: "", name):
checkpoints = @[]
var testStatusIMPL {.inject.} = OK