".}
-
- # Verifies random seed initialization.
- let seed = gettime(nil)
- randomize(seed)
- const SIZE = 10
- var buf : array[0..SIZE, int]
- # Fill the buffer with random values
- for i in 0..SIZE-1:
- buf[i] = random(high(int))
- # Check that the second random calls are the same for each position.
- randomize(seed)
- for i in 0..SIZE-1:
- assert buf[i] == random(high(int)), "non deterministic random seeding"
-
- when not defined(testing):
- echo "random values equal after reseeding"
-
# Check for no side effect annotation
proc mySqrt(num: float): float {.noSideEffect.} =
return sqrt(num)
@@ -418,3 +447,65 @@ when isMainModule and not defined(JS):
assert(lgamma(1.0) == 0.0) # ln(1.0) == 0.0
assert(erf(6.0) > erf(5.0))
assert(erfc(6.0) < erfc(5.0))
+when isMainModule:
+ # Function for approximate comparison of floats
+ proc `==~`(x, y: float): bool = (abs(x-y) < 1e-9)
+
+ block: # round() tests
+ # Round to 0 decimal places
+ doAssert round(54.652) ==~ 55.0
+ doAssert round(54.352) ==~ 54.0
+ doAssert round(-54.652) ==~ -55.0
+ doAssert round(-54.352) ==~ -54.0
+ doAssert round(0.0) ==~ 0.0
+ # Round to positive decimal places
+ doAssert round(-547.652, 1) ==~ -547.7
+ doAssert round(547.652, 1) ==~ 547.7
+ doAssert round(-547.652, 2) ==~ -547.65
+ doAssert round(547.652, 2) ==~ 547.65
+ # Round to negative decimal places
+ doAssert round(547.652, -1) ==~ 550.0
+ doAssert round(547.652, -2) ==~ 500.0
+ doAssert round(547.652, -3) ==~ 1000.0
+ doAssert round(547.652, -4) ==~ 0.0
+ doAssert round(-547.652, -1) ==~ -550.0
+ doAssert round(-547.652, -2) ==~ -500.0
+ doAssert round(-547.652, -3) ==~ -1000.0
+ doAssert round(-547.652, -4) ==~ 0.0
+
+ block: # splitDecimal() tests
+ doAssert splitDecimal(54.674).intpart ==~ 54.0
+ doAssert splitDecimal(54.674).floatpart ==~ 0.674
+ doAssert splitDecimal(-693.4356).intpart ==~ -693.0
+ doAssert splitDecimal(-693.4356).floatpart ==~ -0.4356
+ doAssert splitDecimal(0.0).intpart ==~ 0.0
+ doAssert splitDecimal(0.0).floatpart ==~ 0.0
+
+ block: # trunc tests for vcc
+ doAssert(trunc(-1.1) == -1)
+ doAssert(trunc(1.1) == 1)
+ doAssert(trunc(-0.1) == -0)
+ doAssert(trunc(0.1) == 0)
+
+ #special case
+ doAssert(classify(trunc(1e1000000)) == fcInf)
+ doAssert(classify(trunc(-1e1000000)) == fcNegInf)
+ doAssert(classify(trunc(0.0/0.0)) == fcNan)
+ doAssert(classify(trunc(0.0)) == fcZero)
+
+ #trick the compiler to produce signed zero
+ let
+ f_neg_one = -1.0
+ f_zero = 0.0
+ f_nan = f_zero / f_zero
+
+ doAssert(classify(trunc(f_neg_one*f_zero)) == fcNegZero)
+
+ doAssert(trunc(-1.1'f32) == -1)
+ doAssert(trunc(1.1'f32) == 1)
+ doAssert(trunc(-0.1'f32) == -0)
+ doAssert(trunc(0.1'f32) == 0)
+ doAssert(classify(trunc(1e1000000'f32)) == fcInf)
+ doAssert(classify(trunc(-1e1000000'f32)) == fcNegInf)
+ doAssert(classify(trunc(f_nan.float32)) == fcNan)
+ doAssert(classify(trunc(0.0'f32)) == fcZero)
diff --git a/lib/pure/memfiles.nim b/lib/pure/memfiles.nim
index b9c574944c..aa32778c52 100644
--- a/lib/pure/memfiles.nim
+++ b/lib/pure/memfiles.nim
@@ -42,6 +42,10 @@ type
proc mapMem*(m: var MemFile, mode: FileMode = fmRead,
mappedSize = -1, offset = 0): pointer =
+ ## returns a pointer to a mapped portion of MemFile `m`
+ ##
+ ## ``mappedSize`` of ``-1`` maps to the whole file, and
+ ## ``offset`` must be multiples of the PAGE SIZE of your OS
var readonly = mode == fmRead
when defined(windows):
result = mapViewOfFileEx(
@@ -68,7 +72,9 @@ proc mapMem*(m: var MemFile, mode: FileMode = fmRead,
proc unmapMem*(f: var MemFile, p: pointer, size: int) =
## unmaps the memory region ``(p, ".}
-
proc `$`*(ms: MemSlice): string {.inline.} =
## Return a Nim string built from a MemSlice.
var buf = newString(ms.size)
- c_memcpy(addr(buf[0]), ms.data, ms.size)
+ copyMem(addr(buf[0]), ms.data, ms.size)
buf[ms.size] = '\0'
result = buf
@@ -287,7 +299,9 @@ iterator memSlices*(mfile: MemFile, delim='\l', eat='\r'): MemSlice {.inline.} =
## iterate over line-like records in a file. However, returned (data,size)
## objects are not Nim strings, bounds checked Nim arrays, or even terminated
## C strings. So, care is required to access the data (e.g., think C mem*
- ## functions, not str* functions). Example:
+ ## functions, not str* functions).
+ ##
+ ## Example:
##
## .. code-block:: nim
## var count = 0
@@ -320,7 +334,9 @@ iterator lines*(mfile: MemFile, buf: var TaintedString, delim='\l', eat='\r'): T
## Replace contents of passed buffer with each new line, like
## `readLine(File) `_.
## `delim`, `eat`, and delimiting logic is exactly as for
- ## `memSlices <#memSlices>`_, but Nim strings are returned. Example:
+ ## `memSlices <#memSlices>`_, but Nim strings are returned.
+ ##
+ ## Example:
##
## .. code-block:: nim
## var buffer: TaintedString = ""
@@ -329,7 +345,7 @@ iterator lines*(mfile: MemFile, buf: var TaintedString, delim='\l', eat='\r'): T
for ms in memSlices(mfile, delim, eat):
buf.setLen(ms.size)
- c_memcpy(addr(buf[0]), ms.data, ms.size)
+ copyMem(addr(buf[0]), ms.data, ms.size)
buf[ms.size] = '\0'
yield buf
@@ -337,7 +353,9 @@ iterator lines*(mfile: MemFile, delim='\l', eat='\r'): TaintedString {.inline.}
## Return each line in a file as a Nim string, like
## `lines(File) `_.
## `delim`, `eat`, and delimiting logic is exactly as for
- ## `memSlices <#memSlices>`_, but Nim strings are returned. Example:
+ ## `memSlices <#memSlices>`_, but Nim strings are returned.
+ ##
+ ## Example:
##
## .. code-block:: nim
## for line in lines(memfiles.open("foo")):
diff --git a/lib/pure/mersenne.nim b/lib/pure/mersenne.nim
index ae0845714e..36b597767a 100644
--- a/lib/pure/mersenne.nim
+++ b/lib/pure/mersenne.nim
@@ -1,3 +1,12 @@
+#
+#
+# Nim's Runtime Library
+# (c) Copyright 2015 Nim Contributors
+#
+# See the file "copying.txt", included in this
+# distribution, for details about the copyright.
+#
+
type
MersenneTwister* = object
mt: array[0..623, uint32]
@@ -5,29 +14,31 @@ type
{.deprecated: [TMersenneTwister: MersenneTwister].}
-proc newMersenneTwister*(seed: int): MersenneTwister =
+proc newMersenneTwister*(seed: uint32): MersenneTwister =
result.index = 0
- result.mt[0]= uint32(seed)
+ result.mt[0] = seed
for i in 1..623'u32:
- result.mt[i]= (0x6c078965'u32 * (result.mt[i-1] xor (result.mt[i-1] shr 30'u32)) + i)
+ result.mt[i] = (0x6c078965'u32 * (result.mt[i-1] xor (result.mt[i-1] shr 30'u32)) + i)
proc generateNumbers(m: var MersenneTwister) =
for i in 0..623:
- var y = (m.mt[i] and 0x80000000'u32) + (m.mt[(i+1) mod 624] and 0x7fffffff'u32)
+ var y = (m.mt[i] and 0x80000000'u32) +
+ (m.mt[(i+1) mod 624] and 0x7fffffff'u32)
m.mt[i] = m.mt[(i+397) mod 624] xor uint32(y shr 1'u32)
if (y mod 2'u32) != 0:
- m.mt[i] = m.mt[i] xor 0x9908b0df'u32
+ m.mt[i] = m.mt[i] xor 0x9908b0df'u32
-proc getNum*(m: var MersenneTwister): int =
+proc getNum*(m: var MersenneTwister): uint32 =
+ ## Returns the next pseudo random number ranging from 0 to high(uint32)
if m.index == 0:
generateNumbers(m)
- var y = m.mt[m.index]
- y = y xor (y shr 11'u32)
- y = y xor ((7'u32 shl y) and 0x9d2c5680'u32)
- y = y xor ((15'u32 shl y) and 0xefc60000'u32)
- y = y xor (y shr 18'u32)
- m.index = (m.index+1) mod 624
- return int(y)
+ result = m.mt[m.index]
+ m.index = (m.index + 1) mod m.mt.len
+
+ result = result xor (result shr 11'u32)
+ result = result xor ((7'u32 shl result) and 0x9d2c5680'u32)
+ result = result xor ((15'u32 shl result) and 0xefc60000'u32)
+ result = result xor (result shr 18'u32)
# Test
when not defined(testing) and isMainModule:
diff --git a/lib/pure/nativesockets.nim b/lib/pure/nativesockets.nim
index 043d6d80a3..4526afa49e 100644
--- a/lib/pure/nativesockets.nim
+++ b/lib/pure/nativesockets.nim
@@ -27,7 +27,7 @@ else:
import posix
export fcntl, F_GETFL, O_NONBLOCK, F_SETFL, EAGAIN, EWOULDBLOCK, MSG_NOSIGNAL,
EINTR, EINPROGRESS, ECONNRESET, EPIPE, ENETRESET
- export Sockaddr_storage
+ export Sockaddr_storage, Sockaddr_un, Sockaddr_un_path_length
export SocketHandle, Sockaddr_in, Addrinfo, INADDR_ANY, SockAddr, SockLen,
Sockaddr_in6,
@@ -38,7 +38,7 @@ export
SOL_SOCKET,
SOMAXCONN,
SO_ACCEPTCONN, SO_BROADCAST, SO_DEBUG, SO_DONTROUTE,
- SO_KEEPALIVE, SO_OOBINLINE, SO_REUSEADDR,
+ SO_KEEPALIVE, SO_OOBINLINE, SO_REUSEADDR, SO_REUSEPORT,
MSG_PEEK
when defined(macosx) and not defined(nimdoc):
@@ -326,8 +326,13 @@ proc getHostByAddr*(ip: string): Hostent {.tags: [ReadIOEffect].} =
cint(AF_INET))
if s == nil: raiseOSError(osLastError())
else:
- var s = posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).Socklen,
- cint(posix.AF_INET))
+ var s =
+ when defined(android4):
+ posix.gethostbyaddr(cast[cstring](addr(myaddr)), sizeof(myaddr).cint,
+ cint(posix.AF_INET))
+ else:
+ posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).Socklen,
+ cint(posix.AF_INET))
if s == nil:
raiseOSError(osLastError(), $hstrerror(h_errno))
diff --git a/lib/pure/net.nim b/lib/pure/net.nim
index 330682ca90..bd208761b6 100644
--- a/lib/pure/net.nim
+++ b/lib/pure/net.nim
@@ -8,19 +8,77 @@
#
## This module implements a high-level cross-platform sockets interface.
+## The procedures implemented in this module are primarily for blocking sockets.
+## For asynchronous non-blocking sockets use the ``asyncnet`` module together
+## with the ``asyncdispatch`` module.
+##
+## The first thing you will always need to do in order to start using sockets,
+## is to create a new instance of the ``Socket`` type using the ``newSocket``
+## procedure.
+##
+## SSL
+## ====
+##
+## In order to use the SSL procedures defined in this module, you will need to
+## compile your application with the ``-d:ssl`` flag.
+##
+## Examples
+## ========
+##
+## Connecting to a server
+## ----------------------
+##
+## After you create a socket with the ``newSocket`` procedure, you can easily
+## connect it to a server running at a known hostname (or IP address) and port.
+## To do so over TCP, use the example below.
+##
+## .. code-block:: Nim
+## var socket = newSocket()
+## socket.connect("google.com", Port(80))
+##
+## UDP is a connectionless protocol, so UDP sockets don't have to explicitly
+## call the ``connect`` procedure. They can simply start sending data
+## immediately.
+##
+## .. code-block:: Nim
+## var socket = newSocket()
+## socket.sendTo("192.168.0.1", Port(27960), "status\n")
+##
+## Creating a server
+## -----------------
+##
+## After you create a socket with the ``newSocket`` procedure, you can create a
+## TCP server by calling the ``bindAddr`` and ``listen`` procedures.
+##
+## .. code-block:: Nim
+## var socket = newSocket()
+## socket.bindAddr(Port(1234))
+## socket.listen()
+##
+## You can then begin accepting connections using the ``accept`` procedure.
+##
+## .. code-block:: Nim
+## var client = newSocket()
+## var address = ""
+## while true:
+## socket.acceptAddr(client, address)
+## echo("Client connected from: ", address)
+##
{.deadCodeElim: on.}
-import nativesockets, os, strutils, parseutils, times
+import nativesockets, os, strutils, parseutils, times, sets
export Port, `$`, `==`
+export Domain, SockType, Protocol
const useWinVersion = defined(Windows) or defined(nimdoc)
+const defineSsl = defined(ssl) or defined(nimdoc)
-when defined(ssl):
+when defineSsl:
import openssl
# Note: The enumerations are mapped to Window's constants.
-when defined(ssl):
+when defineSsl:
type
SslError* = object of Exception
@@ -30,7 +88,10 @@ when defined(ssl):
SslProtVersion* = enum
protSSLv2, protSSLv3, protTLSv1, protSSLv23
- SslContext* = distinct SslCtx
+ SslContext* = ref object
+ context*: SslCtx
+ extraInternalIndex: int
+ referencedData: HashSet[int]
SslAcceptResult* = enum
AcceptNoClient = 0, AcceptNoHandshake, AcceptSuccess
@@ -38,6 +99,10 @@ when defined(ssl):
SslHandshakeType* = enum
handshakeAsClient, handshakeAsServer
+ SslClientGetPskFunc* = proc(hint: string): tuple[identity: string, psk: string]
+
+ SslServerGetPskFunc* = proc(identity: string): string
+
{.deprecated: [ESSL: SSLError, TSSLCVerifyMode: SSLCVerifyMode,
TSSLProtVersion: SSLProtVersion, PSSLContext: SSLContext,
TSSLAcceptResult: SSLAcceptResult].}
@@ -54,7 +119,7 @@ type
currPos: int # current index in buffer
bufLen: int # current length of buffer
of false: nil
- when defined(ssl):
+ when defineSsl:
case isSsl: bool
of true:
sslHandle: SSLPtr
@@ -72,7 +137,7 @@ type
SOBool* = enum ## Boolean socket options.
OptAcceptConn, OptBroadcast, OptDebug, OptDontRoute, OptKeepAlive,
- OptOOBInline, OptReuseAddr
+ OptOOBInline, OptReuseAddr, OptReusePort
ReadLineResult* = enum ## result for readLineAsync
ReadFullLine, ReadPartialLine, ReadDisconnected, ReadNone
@@ -140,6 +205,10 @@ proc newSocket*(fd: SocketHandle, domain: Domain = AF_INET,
if buffered:
result.currPos = 0
+ # Set SO_NOSIGPIPE on OS X.
+ when defined(macosx):
+ setSockOptInt(fd, SOL_SOCKET, SO_NOSIGPIPE, 1)
+
proc newSocket*(domain, sockType, protocol: cint, buffered = true): Socket =
## Creates a new socket.
##
@@ -160,13 +229,18 @@ proc newSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM,
raiseOSError(osLastError())
result = newSocket(fd, domain, sockType, protocol, buffered)
-when defined(ssl):
+when defineSsl:
CRYPTO_malloc_init()
SslLibraryInit()
SslLoadErrorStrings()
ErrLoadBioStrings()
OpenSSL_add_all_algorithms()
+ type
+ SslContextExtraInternal = ref object of RootRef
+ serverGetPskFunc: SslServerGetPskFunc
+ clientGetPskFunc: SslClientGetPskFunc
+
proc raiseSSLError*(s = "") =
## Raises a new SSL error.
if s != "":
@@ -179,6 +253,34 @@ when defined(ssl):
var errStr = ErrErrorString(err, nil)
raise newException(SSLError, $errStr)
+ proc getExtraDataIndex*(ctx: SSLContext): int =
+ ## Retrieves unique index for storing extra data in SSLContext.
+ result = SSL_CTX_get_ex_new_index(0, nil, nil, nil, nil).int
+ if result < 0:
+ raiseSSLError()
+
+ proc getExtraData*(ctx: SSLContext, index: int): RootRef =
+ ## Retrieves arbitrary data stored inside SSLContext.
+ if index notin ctx.referencedData:
+ raise newException(IndexError, "No data with that index.")
+ let res = ctx.context.SSL_CTX_get_ex_data(index.cint)
+ if cast[int](res) == 0:
+ raiseSSLError()
+ return cast[RootRef](res)
+
+ proc setExtraData*(ctx: SSLContext, index: int, data: RootRef) =
+ ## Stores arbitrary data inside SSLContext. The unique `index`
+ ## should be retrieved using getSslContextExtraDataIndex.
+ if index in ctx.referencedData:
+ GC_unref(getExtraData(ctx, index))
+
+ if ctx.context.SSL_CTX_set_ex_data(index.cint, cast[pointer](data)) == -1:
+ raiseSSLError()
+
+ if index notin ctx.referencedData:
+ ctx.referencedData.incl(index)
+ GC_ref(data)
+
# http://simplestcodings.blogspot.co.uk/2010/08/secure-server-client-using-openssl-in-c.html
proc loadCertificates(ctx: SSL_CTX, certFile, keyFile: string) =
if certFile != "" and not existsFile(certFile):
@@ -201,7 +303,7 @@ when defined(ssl):
raiseSSLError("Verification of private key file failed.")
proc newContext*(protVersion = protSSLv23, verifyMode = CVerifyPeer,
- certFile = "", keyFile = ""): SSLContext =
+ certFile = "", keyFile = "", cipherList = "ALL"): SSLContext =
## Creates an SSL context.
##
## Protocol version specifies the protocol to use. SSLv2, SSLv3, TLSv1
@@ -222,13 +324,13 @@ when defined(ssl):
of protSSLv23:
newCTX = SSL_CTX_new(SSLv23_method()) # SSlv2,3 and TLS1 support.
of protSSLv2:
- raiseSslError("SSLv2 is no longer secure and has been deprecated, use protSSLv3")
+ raiseSslError("SSLv2 is no longer secure and has been deprecated, use protSSLv23")
of protSSLv3:
- newCTX = SSL_CTX_new(SSLv3_method())
+ raiseSslError("SSLv3 is no longer secure and has been deprecated, use protSSLv23")
of protTLSv1:
newCTX = SSL_CTX_new(TLSv1_method())
- if newCTX.SSLCTXSetCipherList("ALL") != 1:
+ if newCTX.SSLCTXSetCipherList(cipherList) != 1:
raiseSSLError()
case verifyMode
of CVerifyPeer:
@@ -240,7 +342,87 @@ when defined(ssl):
discard newCTX.SSLCTXSetMode(SSL_MODE_AUTO_RETRY)
newCTX.loadCertificates(certFile, keyFile)
- return SSLContext(newCTX)
+
+ result = SSLContext(context: newCTX, extraInternalIndex: 0,
+ referencedData: initSet[int]())
+ result.extraInternalIndex = getExtraDataIndex(result)
+
+ let extraInternal = new(SslContextExtraInternal)
+ result.setExtraData(result.extraInternalIndex, extraInternal)
+
+ proc getExtraInternal(ctx: SSLContext): SslContextExtraInternal =
+ return SslContextExtraInternal(ctx.getExtraData(ctx.extraInternalIndex))
+
+ proc destroyContext*(ctx: SSLContext) =
+ ## Free memory referenced by SSLContext.
+
+ # We assume here that OpenSSL's internal indexes increase by 1 each time.
+ # That means we can assume that the next internal index is the length of
+ # extra data indexes.
+ for i in ctx.referencedData:
+ GC_unref(getExtraData(ctx, i).RootRef)
+ ctx.context.SSL_CTX_free()
+
+ proc `pskIdentityHint=`*(ctx: SSLContext, hint: string) =
+ ## Sets the identity hint passed to server.
+ ##
+ ## Only used in PSK ciphersuites.
+ if ctx.context.SSL_CTX_use_psk_identity_hint(hint) <= 0:
+ raiseSSLError()
+
+ proc clientGetPskFunc*(ctx: SSLContext): SslClientGetPskFunc =
+ return ctx.getExtraInternal().clientGetPskFunc
+
+ proc pskClientCallback(ssl: SslPtr; hint: cstring; identity: cstring; max_identity_len: cuint; psk: ptr cuchar;
+ max_psk_len: cuint): cuint {.cdecl.} =
+ let ctx = SSLContext(context: ssl.SSL_get_SSL_CTX, extraInternalIndex: 0)
+ let hintString = if hint == nil: nil else: $hint
+ let (identityString, pskString) = (ctx.clientGetPskFunc)(hintString)
+ if psk.len.cuint > max_psk_len:
+ return 0
+ if identityString.len.cuint >= max_identity_len:
+ return 0
+
+ copyMem(identity, identityString.cstring, pskString.len + 1) # with the last zero byte
+ copyMem(psk, pskString.cstring, pskString.len)
+
+ return pskString.len.cuint
+
+ proc `clientGetPskFunc=`*(ctx: SSLContext, fun: SslClientGetPskFunc) =
+ ## Sets function that returns the client identity and the PSK based on identity
+ ## hint from the server.
+ ##
+ ## Only used in PSK ciphersuites.
+ ctx.getExtraInternal().clientGetPskFunc = fun
+ assert ctx.extraInternalIndex == 0,
+ "The pskClientCallback assumes the extraInternalIndex is 0"
+ ctx.context.SSL_CTX_set_psk_client_callback(
+ if fun == nil: nil else: pskClientCallback)
+
+ proc serverGetPskFunc*(ctx: SSLContext): SslServerGetPskFunc =
+ return ctx.getExtraInternal().serverGetPskFunc
+
+ proc pskServerCallback(ssl: SslCtx; identity: cstring; psk: ptr cuchar; max_psk_len: cint): cuint {.cdecl.} =
+ let ctx = SSLContext(context: ssl.SSL_get_SSL_CTX, extraInternalIndex: 0)
+ let pskString = (ctx.serverGetPskFunc)($identity)
+ if psk.len.cint > max_psk_len:
+ return 0
+ copyMem(psk, pskString.cstring, pskString.len)
+
+ return pskString.len.cuint
+
+ proc `serverGetPskFunc=`*(ctx: SSLContext, fun: SslServerGetPskFunc) =
+ ## Sets function that returns PSK based on the client identity.
+ ##
+ ## Only used in PSK ciphersuites.
+ ctx.getExtraInternal().serverGetPskFunc = fun
+ ctx.context.SSL_CTX_set_psk_server_callback(if fun == nil: nil
+ else: pskServerCallback)
+
+ proc getPskIdentity*(socket: Socket): string =
+ ## Gets the PSK identity provided by the client.
+ assert socket.isSSL
+ return $(socket.sslHandle.SSL_get_psk_identity)
proc wrapSocket*(ctx: SSLContext, socket: Socket) =
## Wraps a socket in an SSL context. This function effectively turns
@@ -255,7 +437,7 @@ when defined(ssl):
assert (not socket.isSSL)
socket.isSSL = true
socket.sslContext = ctx
- socket.sslHandle = SSLNew(SSLCTX(socket.sslContext))
+ socket.sslHandle = SSLNew(socket.sslContext.context)
socket.sslNoHandshake = false
socket.sslHasPeekChar = false
if socket.sslHandle == nil:
@@ -301,7 +483,7 @@ proc socketError*(socket: Socket, err: int = -1, async = false,
## error was caused by no data being available to be read.
##
## If ``err`` is not lower than 0 no exception will be raised.
- when defined(ssl):
+ when defineSsl:
if socket.isSSL:
if err <= 0:
var ret = SSLGetError(socket.sslHandle, err.cint)
@@ -334,7 +516,7 @@ proc socketError*(socket: Socket, err: int = -1, async = false,
raiseSSLError()
else: raiseSSLError("Unknown Error")
- if err == -1 and not (when defined(ssl): socket.isSSL else: false):
+ if err == -1 and not (when defineSsl: socket.isSSL else: false):
var lastE = if lastError.int == -1: getSocketError(socket) else: lastError
if async:
when useWinVersion:
@@ -414,7 +596,7 @@ proc acceptAddr*(server: Socket, client: var Socket, address: var string,
client.isBuffered = server.isBuffered
# Handle SSL.
- when defined(ssl):
+ when defineSsl:
if server.isSSL:
# We must wrap the client sock in a ssl context.
@@ -425,7 +607,7 @@ proc acceptAddr*(server: Socket, client: var Socket, address: var string,
# Client socket is set above.
address = $inet_ntoa(sockAddress.sin_addr)
-when false: #defined(ssl):
+when false: #defineSsl:
proc acceptAddrSSL*(server: Socket, client: var Socket,
address: var string): SSLAcceptResult {.
tags: [ReadIOEffect].} =
@@ -444,7 +626,7 @@ when false: #defined(ssl):
## ``AcceptNoClient`` will be returned when no client is currently attempting
## to connect.
template doHandshake(): stmt =
- when defined(ssl):
+ when defineSsl:
if server.isSSL:
client.setBlocking(false)
# We must wrap the client sock in a ssl context.
@@ -495,7 +677,7 @@ proc accept*(server: Socket, client: var Socket,
proc close*(socket: Socket) =
## Closes a socket.
try:
- when defined(ssl):
+ when defineSsl:
if socket.isSSL:
ErrClearError()
# As we are closing the underlying socket immediately afterwards,
@@ -522,6 +704,7 @@ proc toCInt*(opt: SOBool): cint =
of OptKeepAlive: SO_KEEPALIVE
of OptOOBInline: SO_OOBINLINE
of OptReuseAddr: SO_REUSEADDR
+ of OptReusePort: SO_REUSEPORT
proc getSockOpt*(socket: Socket, opt: SOBool, level = SOL_SOCKET): bool {.
tags: [ReadIOEffect].} =
@@ -547,8 +730,35 @@ proc setSockOpt*(socket: Socket, opt: SOBool, value: bool, level = SOL_SOCKET) {
var valuei = cint(if value: 1 else: 0)
setSockOptInt(socket.fd, cint(level), toCInt(opt), valuei)
+when defined(posix) and not defined(nimdoc):
+ proc makeUnixAddr(path: string): Sockaddr_un =
+ result.sun_family = AF_UNIX.toInt
+ if path.len >= Sockaddr_un_path_length:
+ raise newException(ValueError, "socket path too long")
+ copyMem(addr result.sun_path, path.cstring, path.len + 1)
+
+when defined(posix):
+ proc connectUnix*(socket: Socket, path: string) =
+ ## Connects to Unix socket on `path`.
+ ## This only works on Unix-style systems: Mac OS X, BSD and Linux
+ when not defined(nimdoc):
+ var socketAddr = makeUnixAddr(path)
+ if socket.fd.connect(cast[ptr SockAddr](addr socketAddr),
+ sizeof(socketAddr).Socklen) != 0'i32:
+ raiseOSError(osLastError())
+
+ proc bindUnix*(socket: Socket, path: string) =
+ ## Binds Unix socket to `path`.
+ ## This only works on Unix-style systems: Mac OS X, BSD and Linux
+ when not defined(nimdoc):
+ var socketAddr = makeUnixAddr(path)
+ if socket.fd.bindAddr(cast[ptr SockAddr](addr socketAddr),
+ sizeof(socketAddr).Socklen) != 0'i32:
+ raiseOSError(osLastError())
+
when defined(ssl):
- proc handshake*(socket: Socket): bool {.tags: [ReadIOEffect, WriteIOEffect].} =
+ proc handshake*(socket: Socket): bool
+ {.tags: [ReadIOEffect, WriteIOEffect], deprecated.} =
## This proc needs to be called on a socket after it connects. This is
## only applicable when using ``connectAsync``.
## This proc performs the SSL handshake.
@@ -557,6 +767,8 @@ when defined(ssl):
## ``True`` whenever handshake completed successfully.
##
## A ESSL error is raised on any other errors.
+ ##
+ ## **Note:** This procedure is deprecated since version 0.14.0.
result = true
if socket.isSSL:
var ret = SSLConnect(socket.sslHandle)
@@ -594,7 +806,7 @@ proc hasDataBuffered*(s: Socket): bool =
if s.isBuffered:
result = s.bufLen > 0 and s.currPos != s.bufLen
- when defined(ssl):
+ when defineSsl:
if s.isSSL and not result:
result = s.sslHasPeekChar
@@ -608,7 +820,7 @@ proc select(readfd: Socket, timeout = 500): int =
proc readIntoBuf(socket: Socket, flags: int32): int =
result = 0
- when defined(ssl):
+ when defineSsl:
if socket.isSSL:
result = SSLRead(socket.sslHandle, addr(socket.buffer), int(socket.buffer.high))
else:
@@ -658,7 +870,7 @@ proc recv*(socket: Socket, data: pointer, size: int): int {.tags: [ReadIOEffect]
result = read
else:
- when defined(ssl):
+ when defineSsl:
if socket.isSSL:
if socket.sslHasPeekChar:
copyMem(data, addr(socket.sslPeekChar), 1)
@@ -696,7 +908,7 @@ proc waitFor(socket: Socket, waited: var float, timeout, size: int,
if timeout - int(waited * 1000.0) < 1:
raise newException(TimeoutError, "Call to '" & funcName & "' timed out.")
- when defined(ssl):
+ when defineSsl:
if socket.isSSL:
if socket.hasDataBuffered:
# sslPeekChar is present.
@@ -764,7 +976,7 @@ proc peekChar(socket: Socket, c: var char): int {.tags: [ReadIOEffect].} =
c = socket.buffer[socket.currPos]
else:
- when defined(ssl):
+ when defineSsl:
if socket.isSSL:
if not socket.sslHasPeekChar:
result = SSLRead(socket.sslHandle, addr(socket.sslPeekChar), 1)
@@ -792,11 +1004,11 @@ proc readLine*(socket: Socket, line: var TaintedString, timeout = -1,
##
## **Warning**: Only the ``SafeDisconn`` flag is currently supported.
- template addNLIfEmpty(): stmt =
+ template addNLIfEmpty() =
if line.len == 0:
line.string.add("\c\L")
- template raiseSockError(): stmt {.dirty, immediate.} =
+ template raiseSockError() {.dirty.} =
let lastError = getSocketError(socket)
if flags.isDisconnectionError(lastError): setLen(line.string, 0); return
socket.socketError(n, lastError = lastError)
@@ -872,7 +1084,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.
- when defined(ssl):
+ when defineSsl:
if socket.isSSL:
return SSLWrite(socket.sslHandle, cast[cstring](data), size)
@@ -943,7 +1155,7 @@ proc sendTo*(socket: Socket, address: string, port: Port,
proc isSsl*(socket: Socket): bool =
## Determines whether ``socket`` is a SSL socket.
- when defined(ssl):
+ when defineSsl:
result = socket.isSSL
else:
result = false
@@ -1253,7 +1465,7 @@ proc connect*(socket: Socket, address: string,
dealloc(aiList)
if not success: raiseOSError(lastError)
- when defined(ssl):
+ when defineSsl:
if socket.isSSL:
# RFC3546 for SNI specifies that IP addresses are not allowed.
if not isIpAddress(address):
@@ -1314,8 +1526,10 @@ proc connect*(socket: Socket, address: string, port = Port(0),
if selectWrite(s, timeout) != 1:
raise newException(TimeoutError, "Call to 'connect' timed out.")
else:
- when defined(ssl):
+ when defineSsl and not defined(nimdoc):
if socket.isSSL:
socket.fd.setBlocking(true)
+ {.warning[Deprecated]: off.}
doAssert socket.handshake()
+ {.warning[Deprecated]: on.}
socket.fd.setBlocking(true)
diff --git a/lib/pure/nimprof.nim b/lib/pure/nimprof.nim
index 5a7deaab0e..4289eb049c 100644
--- a/lib/pure/nimprof.nim
+++ b/lib/pure/nimprof.nim
@@ -27,19 +27,22 @@ const
tickCountCorrection = 50_000
when not declared(system.StackTrace):
- type StackTrace = array [0..20, cstring]
+ type StackTrace = object
+ lines: array[0..20, cstring]
+ files: array[0..20, cstring]
{.deprecated: [TStackTrace: StackTrace].}
+ proc `[]`*(st: StackTrace, i: int): cstring = st.lines[i]
# We use a simple hash table of bounded size to keep track of the stack traces:
type
ProfileEntry = object
total: int
st: StackTrace
- ProfileData = array [0..64*1024-1, ptr ProfileEntry]
+ ProfileData = array[0..64*1024-1, ptr ProfileEntry]
{.deprecated: [TProfileEntry: ProfileEntry, TProfileData: ProfileData].}
proc `==`(a, b: StackTrace): bool =
- for i in 0 .. high(a):
+ for i in 0 .. high(a.lines):
if a[i] != b[i]: return false
result = true
@@ -72,7 +75,7 @@ proc hookAux(st: StackTrace, costs: int) =
# this is quite performance sensitive!
when withThreads: acquire profilingLock
inc totalCalls
- var last = high(st)
+ var last = high(st.lines)
while last > 0 and isNil(st[last]): dec last
var h = hash(pointer(st[last])) and high(profileData)
@@ -178,7 +181,7 @@ proc writeProfile() {.noconv.} =
var perProc = initCountTable[string]()
for i in 0..entries-1:
var dups = initSet[string]()
- for ii in 0..high(StackTrace):
+ for ii in 0..high(StackTrace.lines):
let procname = profileData[i].st[ii]
if isNil(procname): break
let p = $procname
@@ -193,10 +196,11 @@ proc writeProfile() {.noconv.} =
writeLine(f, "Entry: ", i+1, "/", entries, " Calls: ",
profileData[i].total // totalCalls, " [sum: ", sum, "; ",
sum // totalCalls, "]")
- for ii in 0..high(StackTrace):
+ for ii in 0..high(StackTrace.lines):
let procname = profileData[i].st[ii]
+ let filename = profileData[i].st.files[ii]
if isNil(procname): break
- writeLine(f, " ", procname, " ", perProc[$procname] // totalCalls)
+ writeLine(f, " ", $filename & ": " & $procname, " ", perProc[$procname] // totalCalls)
close(f)
echo "... done"
else:
diff --git a/lib/pure/oids.nim b/lib/pure/oids.nim
index fca10dab61..e4c97b2609 100644
--- a/lib/pure/oids.nim
+++ b/lib/pure/oids.nim
@@ -74,8 +74,7 @@ proc genOid*(): Oid =
var t = gettime(nil)
- var i = int32(incr)
- atomicInc(incr)
+ var i = int32(atomicInc(incr))
if fuzz == 0:
# racy, but fine semantically:
diff --git a/lib/pure/os.nim b/lib/pure/os.nim
index 470559e173..1e474f4d4a 100644
--- a/lib/pure/os.nim
+++ b/lib/pure/os.nim
@@ -26,7 +26,6 @@ elif defined(posix):
else:
{.error: "OS module not ported to your operating system!".}
-include "system/ansi_c"
include ospaths
when defined(posix):
@@ -37,6 +36,23 @@ when defined(posix):
var
pathMax {.importc: "PATH_MAX", header: "".}: cint
+proc c_remove(filename: cstring): cint {.
+ importc: "remove", header: "".}
+proc c_rename(oldname, newname: cstring): cint {.
+ importc: "rename", header: "".}
+proc c_system(cmd: cstring): cint {.
+ importc: "system", header: "".}
+proc c_strerror(errnum: cint): cstring {.
+ importc: "strerror", header: "".}
+proc c_strlen(a: cstring): cint {.
+ importc: "strlen", header: "", noSideEffect.}
+proc c_getenv(env: cstring): cstring {.
+ importc: "getenv", header: "".}
+proc c_putenv(env: cstring): cint {.
+ importc: "putenv", header: "".}
+
+var errno {.importc, header: "".}: cint
+
proc osErrorMsg*(): string {.rtl, extern: "nos$1", deprecated.} =
## Retrieves the operating system's error flag, ``errno``.
## On Windows ``GetLastError`` is checked before ``errno``.
@@ -50,18 +66,18 @@ proc osErrorMsg*(): string {.rtl, extern: "nos$1", deprecated.} =
if err != 0'i32:
when useWinUnicode:
var msgbuf: WideCString
- if formatMessageW(0x00000100 or 0x00001000 or 0x00000200,
+ if formatMessageW(0x00000100 or 0x00001000 or 0x00000200 or 0x000000FF,
nil, err, 0, addr(msgbuf), 0, nil) != 0'i32:
result = $msgbuf
if msgbuf != nil: localFree(cast[pointer](msgbuf))
else:
var msgbuf: cstring
- if formatMessageA(0x00000100 or 0x00001000 or 0x00000200,
+ if formatMessageA(0x00000100 or 0x00001000 or 0x00000200 or 0x000000FF,
nil, err, 0, addr(msgbuf), 0, nil) != 0'i32:
result = $msgbuf
if msgbuf != nil: localFree(msgbuf)
if errno != 0'i32:
- result = $os.strerror(errno)
+ result = $os.c_strerror(errno)
{.push warning[deprecated]: off.}
proc raiseOSError*(msg: string = "") {.noinline, rtl, extern: "nos$1",
@@ -114,7 +130,7 @@ proc osErrorMsg*(errorCode: OSErrorCode): string =
if msgbuf != nil: localFree(msgbuf)
else:
if errorCode != OSErrorCode(0'i32):
- result = $os.strerror(errorCode.int32)
+ result = $os.c_strerror(errorCode.int32)
proc raiseOSError*(errorCode: OSErrorCode; additionalInfo = "") {.noinline.} =
## Raises an ``OSError`` exception. The ``errorCode`` will determine the
@@ -129,7 +145,7 @@ proc raiseOSError*(errorCode: OSErrorCode; additionalInfo = "") {.noinline.} =
if additionalInfo.len == 0:
e.msg = osErrorMsg(errorCode)
else:
- e.msg = additionalInfo & " " & osErrorMsg(errorCode)
+ e.msg = osErrorMsg(errorCode) & "\nAdditional info: " & additionalInfo
if e.msg == "":
e.msg = "unknown OS error"
raise e
@@ -157,24 +173,24 @@ proc osLastError*(): OSErrorCode =
when defined(windows):
when useWinUnicode:
- template wrapUnary(varname, winApiProc, arg: expr) {.immediate.} =
+ template wrapUnary(varname, winApiProc, arg: untyped) =
var varname = winApiProc(newWideCString(arg))
- template wrapBinary(varname, winApiProc, arg, arg2: expr) {.immediate.} =
+ template wrapBinary(varname, winApiProc, arg, arg2: untyped) =
var varname = winApiProc(newWideCString(arg), arg2)
proc findFirstFile(a: string, b: var WIN32_FIND_DATA): Handle =
result = findFirstFileW(newWideCString(a), b)
- template findNextFile(a, b: expr): expr = findNextFileW(a, b)
- template getCommandLine(): expr = getCommandLineW()
+ template findNextFile(a, b: untyped): untyped = findNextFileW(a, b)
+ template getCommandLine(): untyped = getCommandLineW()
- template getFilename(f: expr): expr =
+ template getFilename(f: untyped): untyped =
$cast[WideCString](addr(f.cFilename[0]))
else:
- template findFirstFile(a, b: expr): expr = findFirstFileA(a, b)
- template findNextFile(a, b: expr): expr = findNextFileA(a, b)
- template getCommandLine(): expr = getCommandLineA()
+ template findFirstFile(a, b: untyped): untyped = findFirstFileA(a, b)
+ template findNextFile(a, b: untyped): untyped = findNextFileA(a, b)
+ template getCommandLine(): untyped = getCommandLineA()
- template getFilename(f: expr): expr = $f.cFilename
+ template getFilename(f: untyped): untyped = $f.cFilename
proc skipFindData(f: WIN32_FIND_DATA): bool {.inline.} =
# Note - takes advantage of null delimiter in the cstring
@@ -320,7 +336,7 @@ proc setCurrentDir*(newDir: string) {.inline, tags: [].} =
proc expandFilename*(filename: string): string {.rtl, extern: "nos$1",
tags: [ReadDirEffect].} =
- ## Returns the full path of `filename`, raises OSError in case of an error.
+ ## Returns the full (`absolute`:idx:) path of the file `filename`, raises OSError in case of an error.
when defined(windows):
const bufsize = 3072'i32
when useWinUnicode:
@@ -762,12 +778,26 @@ iterator envPairs*(): tuple[key, value: TaintedString] {.tags: [ReadEnvEffect].}
yield (TaintedString(substr(environment[i], 0, p-1)),
TaintedString(substr(environment[i], p+1)))
-iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect].} =
- ## Iterate over all the files that match the `pattern`. On POSIX this uses
- ## the `glob`:idx: call.
- ##
- ## `pattern` is OS dependent, but at least the "\*.ext"
- ## notation is supported.
+# Templates for filtering directories and files
+when defined(windows):
+ template isDir(f: WIN32_FIND_DATA): bool =
+ (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32
+ template isFile(f: WIN32_FIND_DATA): bool =
+ not isDir(f)
+else:
+ template isDir(f: string): bool =
+ dirExists(f)
+ template isFile(f: string): bool =
+ fileExists(f)
+
+template defaultWalkFilter(item): bool =
+ ## Walk filter used to return true on both
+ ## files and directories
+ true
+
+template walkCommon(pattern: string, filter) =
+ ## Common code for getting the files and directories with the
+ ## specified `pattern`
when defined(windows):
var
f: WIN32_FIND_DATA
@@ -776,8 +806,7 @@ iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect].} =
if res != -1:
defer: findClose(res)
while true:
- if not skipFindData(f) and
- (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) == 0'i32:
+ if not skipFindData(f) and filter(f):
# Windows bug/gotcha: 't*.nim' matches 'tfoo.nims' -.- so we check
# that the file extensions have the same length ...
let ff = getFilename(f)
@@ -799,7 +828,33 @@ iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect].} =
if res == 0:
for i in 0.. f.gl_pathc - 1:
assert(f.gl_pathv[i] != nil)
- yield $f.gl_pathv[i]
+ let path = $f.gl_pathv[i]
+ if filter(path):
+ yield path
+
+iterator walkPattern*(pattern: string): string {.tags: [ReadDirEffect].} =
+ ## Iterate over all the files and directories that match the `pattern`.
+ ## On POSIX this uses the `glob`:idx: call.
+ ##
+ ## `pattern` is OS dependent, but at least the "\*.ext"
+ ## notation is supported.
+ walkCommon(pattern, defaultWalkFilter)
+
+iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect].} =
+ ## Iterate over all the files that match the `pattern`. On POSIX this uses
+ ## the `glob`:idx: call.
+ ##
+ ## `pattern` is OS dependent, but at least the "\*.ext"
+ ## notation is supported.
+ walkCommon(pattern, isFile)
+
+iterator walkDirs*(pattern: string): string {.tags: [ReadDirEffect].} =
+ ## Iterate over all the directories that match the `pattern`.
+ ## On POSIX this uses the `glob`:idx: call.
+ ##
+ ## `pattern` is OS dependent, but at least the "\*.ext"
+ ## notation is supported.
+ walkCommon(pattern, isDir)
type
PathComponent* = enum ## Enumeration specifying a path component.
@@ -1067,7 +1122,7 @@ proc parseCmdLine*(c: string): seq[string] {.
while true:
setLen(a, 0)
# eat all delimiting whitespace
- while c[i] == ' ' or c[i] == '\t' or c [i] == '\l' or c [i] == '\r' : inc(i)
+ while c[i] == ' ' or c[i] == '\t' or c[i] == '\l' or c[i] == '\r' : inc(i)
when defined(windows):
# parse a single argument according to the above rules:
if c[i] == '\0': break
@@ -1447,13 +1502,13 @@ type
lastWriteTime*: Time # Time file was last modified/written to.
creationTime*: Time # Time file was created. Not supported on all systems!
-template rawToFormalFileInfo(rawInfo, formalInfo): expr =
+template rawToFormalFileInfo(rawInfo, 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): expr = winTimeToUnixTime(rdFileTime(e))
- template merge(a, b): expr = a or (b shl 32)
+ template toTime(e: FILETIME): untyped {.gensym.} = winTimeToUnixTime(rdFileTime(e)) # 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)
formalInfo.size = merge(rawInfo.nFileSizeLow, rawInfo.nFileSizeHigh)
@@ -1478,7 +1533,7 @@ template rawToFormalFileInfo(rawInfo, formalInfo): expr =
else:
- template checkAndIncludeMode(rawMode, formalMode: expr) =
+ template checkAndIncludeMode(rawMode, formalMode: untyped) =
if (rawInfo.st_mode and rawMode) != 0'i32:
formalInfo.permissions.incl(formalMode)
formalInfo.id = (rawInfo.st_dev, rawInfo.st_ino)
diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim
index 9fc816f2f5..56671ee624 100644
--- a/lib/pure/ospaths.nim
+++ b/lib/pure/ospaths.nim
@@ -10,7 +10,7 @@
# Included by the ``os`` module but a module in its own right for NimScript
# support.
-when isMainModule:
+when not declared(os):
{.pragma: rtl.}
import strutils
@@ -556,12 +556,20 @@ when declared(getEnv) or defined(nimscript):
yield substr(s, first, last-1)
inc(last)
- proc findExe*(exe: string): string {.
+ when not defined(windows) and declared(os):
+ proc checkSymlink(path: string): bool =
+ var rawInfo: Stat
+ if lstat(path, rawInfo) < 0'i32: result = false
+ else: result = S_ISLNK(rawInfo.st_mode)
+
+ proc findExe*(exe: string, followSymlinks: bool = true): string {.
tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect].} =
## Searches for `exe` in the current working directory and then
## in directories listed in the ``PATH`` environment variable.
## Returns "" if the `exe` cannot be found. On DOS-like platforms, `exe`
## is added the `ExeExt <#ExeExt>`_ file extension if it has none.
+ ## If the system supports symlinks it also resolves them until it
+ ## meets the actual file. This behavior can be disabled if desired.
result = addFileExt(exe, ExeExt)
if existsFile(result): return
var path = string(getEnv("PATH"))
@@ -572,7 +580,25 @@ when declared(getEnv) or defined(nimscript):
result
else:
var x = expandTilde(candidate) / result
- if existsFile(x): return x
+ if existsFile(x):
+ when not defined(windows) and declared(os):
+ while followSymlinks: # doubles as if here
+ if x.checkSymlink:
+ var r = newString(256)
+ var len = readlink(x, r, 256)
+ if len < 0:
+ raiseOSError(osLastError())
+ if len > 256:
+ r = newString(len+1)
+ len = readlink(x, r, len)
+ setLen(r, len)
+ if isAbsolute(r):
+ x = r
+ else:
+ x = parentDir(x) / r
+ else:
+ break
+ return x
result = ""
when defined(nimscript) or (defined(nimdoc) and not declared(os)):
diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim
index 38b0ed4a30..7378520e3c 100644
--- a/lib/pure/osproc.nim
+++ b/lib/pure/osproc.nim
@@ -89,7 +89,7 @@ proc quoteShellWindows*(s: string): string {.noSideEffect, rtl, extern: "nosp$1"
result.add("\"")
proc quoteShellPosix*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
- ## Quote s, so it can be safely passed to POSIX shell.
+ ## Quote ``s``, so it can be safely passed to POSIX shell.
## Based on Python's pipes.quote
const safeUnixChars = {'%', '+', '-', '.', '/', '_', ':', '=', '@',
'0'..'9', 'A'..'Z', 'a'..'z'}
@@ -104,7 +104,7 @@ proc quoteShellPosix*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".}
return "'" & s.replace("'", "'\"'\"'") & "'"
proc quoteShell*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
- ## Quote s, so it can be safely passed to shell.
+ ## Quote ``s``, so it can be safely passed to shell.
when defined(Windows):
return quoteShellWindows(s)
elif defined(posix):
@@ -175,7 +175,11 @@ proc startCmd*(command: string, options: set[ProcessOption] = {
result = startProcess(command=command, options=options + {poEvalCommand})
proc close*(p: Process) {.rtl, extern: "nosp$1", tags: [].}
- ## When the process has finished executing, cleanup related handles
+ ## When the process has finished executing, cleanup related handles.
+ ##
+ ## **Warning:** If the process has not finished executing, this will forcibly
+ ## terminate the process. Doing so may result in zombie processes and
+ ## `pty leaks `_.
proc suspend*(p: Process) {.rtl, extern: "nosp$1", tags: [].}
## Suspends the process `p`.
@@ -400,15 +404,16 @@ when defined(Windows) and not defined(useNimRtl):
result = cast[cstring](alloc0(res.len+1))
copyMem(result, cstring(res), res.len)
- proc buildEnv(env: StringTableRef): cstring =
+ proc buildEnv(env: StringTableRef): tuple[str: cstring, len: int] =
var L = 0
for key, val in pairs(env): inc(L, key.len + val.len + 2)
- result = cast[cstring](alloc0(L+2))
+ var str = cast[cstring](alloc0(L+2))
L = 0
for key, val in pairs(env):
var x = key & "=" & val
- copyMem(addr(result[L]), cstring(x), x.len+1) # copy \0
+ copyMem(addr(str[L]), cstring(x), x.len+1) # copy \0
inc(L, x.len+1)
+ (str, L)
#proc open_osfhandle(osh: Handle, mode: int): int {.
# importc: "_open_osfhandle", header: "".}
@@ -526,13 +531,15 @@ when defined(Windows) and not defined(useNimRtl):
else:
cmdl = buildCommandLine(command, args)
var wd: cstring = nil
- var e: cstring = nil
+ var e = (str: nil.cstring, len: -1)
if len(workingDir) > 0: wd = workingDir
if env != nil: e = buildEnv(env)
if poEchoCmd in options: echo($cmdl)
when useWinUnicode:
var tmp = newWideCString(cmdl)
- var ee = newWideCString(e)
+ var ee =
+ if e.str.isNil: nil
+ else: newWideCString(e.str, e.len)
var wwd = newWideCString(wd)
var flags = NORMAL_PRIORITY_CLASS or CREATE_UNICODE_ENVIRONMENT
if poDemon in options: flags = flags or CREATE_NO_WINDOW
@@ -549,7 +556,7 @@ when defined(Windows) and not defined(useNimRtl):
if poStdErrToStdOut notin options:
fileClose(si.hStdError)
- if e != nil: dealloc(e)
+ if e.str != nil: dealloc(e.str)
if success == 0:
if poInteractive in result.options: close(result)
const errInvalidParameter = 87.int
@@ -721,7 +728,7 @@ elif not defined(useNimRtl):
env: StringTableRef = nil,
options: set[ProcessOption] = {poStdErrToStdOut}): Process =
var
- pStdin, pStdout, pStderr: array [0..1, cint]
+ pStdin, pStdout, pStderr: array[0..1, cint]
new(result)
result.options = options
result.exitCode = -3 # for ``waitForExit``
@@ -875,8 +882,9 @@ elif not defined(useNimRtl):
var error: cint
let sizeRead = read(data.pErrorPipe[readIdx], addr error, sizeof(error))
if sizeRead == sizeof(error):
- raiseOSError("Could not find command: '$1'. OS error: $2" %
- [$data.sysCommand, $strerror(error)])
+ raiseOSError(osLastError(),
+ "Could not find command: '$1'. OS error: $2" %
+ [$data.sysCommand, $strerror(error)])
return pid
@@ -967,16 +975,168 @@ elif not defined(useNimRtl):
if kill(p.id, SIGKILL) != 0'i32:
raiseOsError(osLastError())
- proc waitForExit(p: Process, timeout: int = -1): int =
- #if waitPid(p.id, p.exitCode, 0) == int(p.id):
- # ``waitPid`` fails if the process is not running anymore. But then
- # ``running`` probably set ``p.exitCode`` for us. Since ``p.exitCode`` is
- # initialized with -3, wrong success exit codes are prevented.
- if p.exitCode != -3: return p.exitCode
- if waitpid(p.id, p.exitCode, 0) < 0:
- p.exitCode = -3
- raiseOSError(osLastError())
- result = int(p.exitCode) shr 8
+ when defined(macosx) or defined(freebsd) or defined(netbsd) or
+ defined(openbsd):
+ import kqueue, times
+
+ proc waitForExit(p: Process, timeout: int = -1): int =
+ if p.exitCode != -3: return p.exitCode
+ if timeout == -1:
+ if waitpid(p.id, p.exitCode, 0) < 0:
+ p.exitCode = -3
+ raiseOSError(osLastError())
+ else:
+ var kqFD = kqueue()
+ if kqFD == -1:
+ raiseOSError(osLastError())
+
+ var kevIn = KEvent(ident: p.id.uint, filter: EVFILT_PROC,
+ flags: EV_ADD, fflags: NOTE_EXIT)
+ var kevOut: KEvent
+ var tmspec: Timespec
+
+ if timeout >= 1000:
+ tmspec.tv_sec = (timeout div 1_000).Time
+ tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000
+ else:
+ tmspec.tv_sec = 0.Time
+ tmspec.tv_nsec = (timeout * 1_000_000)
+
+ try:
+ while true:
+ var count = kevent(kqFD, addr(kevIn), 1, addr(kevOut), 1,
+ addr(tmspec))
+ if count < 0:
+ let err = osLastError()
+ if err.cint != EINTR:
+ raiseOSError(osLastError())
+ elif count == 0:
+ # timeout expired, so we trying to kill process
+ if posix.kill(p.id, SIGKILL) == -1:
+ raiseOSError(osLastError())
+ if waitpid(p.id, p.exitCode, 0) < 0:
+ p.exitCode = -3
+ raiseOSError(osLastError())
+ break
+ else:
+ if kevOut.ident == p.id.uint and kevOut.filter == EVFILT_PROC:
+ if waitpid(p.id, p.exitCode, 0) < 0:
+ p.exitCode = -3
+ raiseOSError(osLastError())
+ break
+ else:
+ raiseOSError(osLastError())
+ finally:
+ discard posix.close(kqFD)
+
+ result = int(p.exitCode) shr 8
+ else:
+ import times
+
+ const
+ hasThreadSupport = compileOption("threads") and not defined(nimscript)
+
+ proc waitForExit(p: Process, timeout: int = -1): int =
+ template adjustTimeout(t, s, e: Timespec) =
+ var diff: int
+ 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
+ if e.tv_nsec >= s.tv_nsec:
+ e.tv_nsec -= s.tv_nsec
+ else:
+ if e.tv_sec == 0.Time:
+ 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
+ if t.tv_nsec >= e.tv_nsec:
+ t.tv_nsec -= e.tv_nsec
+ else:
+ t.tv_sec = (int(t.tv_sec) - 1).Time
+ 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.exitCode, 0) == int(p.id):
+ # ``waitPid`` fails if the process is not running anymore. But then
+ # ``running`` probably set ``p.exitCode`` for us. Since ``p.exitCode`` is
+ # initialized with -3, wrong success exit codes are prevented.
+ if p.exitCode != -3: return p.exitCode
+ if timeout == -1:
+ if waitpid(p.id, p.exitCode, 0) < 0:
+ p.exitCode = -3
+ raiseOSError(osLastError())
+ else:
+ var nmask, omask: Sigset
+ var sinfo: SigInfo
+ var stspec, enspec, tmspec: Timespec
+
+ discard sigemptyset(nmask)
+ discard sigemptyset(omask)
+ discard sigaddset(nmask, SIGCHLD)
+
+ when hasThreadSupport:
+ if pthread_sigmask(SIG_BLOCK, nmask, omask) == -1:
+ raiseOSError(osLastError())
+ else:
+ if sigprocmask(SIG_BLOCK, nmask, omask) == -1:
+ raiseOSError(osLastError())
+
+ if timeout >= 1000:
+ tmspec.tv_sec = (timeout div 1_000).Time
+ tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000
+ else:
+ tmspec.tv_sec = 0.Time
+ tmspec.tv_nsec = (timeout * 1_000_000)
+
+ try:
+ if clock_gettime(CLOCK_REALTIME, stspec) == -1:
+ raiseOSError(osLastError())
+ while true:
+ let res = sigtimedwait(nmask, sinfo, tmspec)
+ if res == SIGCHLD:
+ if sinfo.si_pid == p.id:
+ if waitpid(p.id, p.exitCode, 0) < 0:
+ p.exitCode = -3
+ raiseOSError(osLastError())
+ break
+ else:
+ # we have SIGCHLD, but not for process we are waiting,
+ # so we need to adjust timeout value and continue
+ if clock_gettime(CLOCK_REALTIME, enspec) == -1:
+ raiseOSError(osLastError())
+ adjustTimeout(tmspec, stspec, enspec)
+ elif res < 0:
+ let err = osLastError()
+ if err.cint == EINTR:
+ # we have received another signal, so we need to
+ # adjust timeout and continue
+ if clock_gettime(CLOCK_REALTIME, enspec) == -1:
+ raiseOSError(osLastError())
+ adjustTimeout(tmspec, stspec, enspec)
+ elif err.cint == EAGAIN:
+ # timeout expired, so we trying to kill process
+ if posix.kill(p.id, SIGKILL) == -1:
+ raiseOSError(osLastError())
+ if waitpid(p.id, p.exitCode, 0) < 0:
+ p.exitCode = -3
+ raiseOSError(osLastError())
+ break
+ else:
+ raiseOSError(err)
+ finally:
+ when hasThreadSupport:
+ if pthread_sigmask(SIG_UNBLOCK, nmask, omask) == -1:
+ raiseOSError(osLastError())
+ else:
+ if sigprocmask(SIG_UNBLOCK, nmask, omask) == -1:
+ raiseOSError(osLastError())
+
+ result = int(p.exitCode) shr 8
proc peekExitCode(p: Process): int =
if p.exitCode != -3: return p.exitCode
diff --git a/lib/pure/oswalkdir.nim b/lib/pure/oswalkdir.nim
index 000fe25a39..23ca0566a9 100644
--- a/lib/pure/oswalkdir.nim
+++ b/lib/pure/oswalkdir.nim
@@ -1,3 +1,11 @@
+#
+#
+# Nim's Runtime Library
+# (c) Copyright 2015 Andreas Rumpf
+#
+# See the file "copying.txt", included in this
+# distribution, for details about the copyright.
+#
## Compile-time only version for walkDir if you need it at compile-time
## for JavaScript.
diff --git a/lib/pure/parsecfg.nim b/lib/pure/parsecfg.nim
index 9bcac0a50f..c648b07038 100644
--- a/lib/pure/parsecfg.nim
+++ b/lib/pure/parsecfg.nim
@@ -15,17 +15,78 @@
## This is an example of how a configuration file may look like:
##
-## .. include:: doc/mytest.cfg
+## .. include:: ../../doc/mytest.cfg
## :literal:
## The file ``examples/parsecfgex.nim`` demonstrates how to use the
## configuration file parser:
##
## .. code-block:: nim
-## :file: examples/parsecfgex.nim
-
+## :file: ../../examples/parsecfgex.nim
+##
+## Examples
+## --------
+##
+## This is an example of a configuration file.
+##
+## ::
+##
+## charset = "utf-8"
+## [Package]
+## name = "hello"
+## --threads:on
+## [Author]
+## name = "lihf8515"
+## qq = "10214028"
+## email = "lihaifeng@wxm.com"
+##
+## Creating a configuration file.
+## ==============================
+## .. code-block:: nim
+##
+## import parsecfg
+## var dict=newConfig()
+## dict.setSectionKey("","charset","utf-8")
+## dict.setSectionKey("Package","name","hello")
+## dict.setSectionKey("Package","--threads","on")
+## dict.setSectionKey("Author","name","lihf8515")
+## dict.setSectionKey("Author","qq","10214028")
+## dict.setSectionKey("Author","email","lihaifeng@wxm.com")
+## dict.writeConfig("config.ini")
+##
+## Reading a configuration file.
+## =============================
+## .. code-block:: nim
+##
+## import parsecfg
+## var dict = loadConfig("config.ini")
+## var charset = dict.getSectionValue("","charset")
+## var threads = dict.getSectionValue("Package","--threads")
+## var pname = dict.getSectionValue("Package","name")
+## var name = dict.getSectionValue("Author","name")
+## var qq = dict.getSectionValue("Author","qq")
+## var email = dict.getSectionValue("Author","email")
+## echo pname & "\n" & name & "\n" & qq & "\n" & email
+##
+## Modifying a configuration file.
+## ===============================
+## .. code-block:: nim
+##
+## import parsecfg
+## var dict = loadConfig("config.ini")
+## dict.setSectionKey("Author","name","lhf")
+## dict.writeConfig("config.ini")
+##
+## Deleting a section key in a configuration file.
+## ===============================================
+## .. code-block:: nim
+##
+## import parsecfg
+## var dict = loadConfig("config.ini")
+## dict.delSectionKey("Author","email")
+## dict.writeConfig("config.ini")
import
- hashes, strutils, lexbase, streams
+ hashes, strutils, lexbase, streams, tables
include "system/inclrtl"
@@ -70,7 +131,7 @@ type
# implementation
const
- SymChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF', '.', '/', '\\'}
+ SymChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF', '.', '/', '\\', '-'}
proc rawGetTok(c: var CfgParser, tok: var Token) {.gcsafe.}
@@ -359,3 +420,138 @@ proc next*(c: var CfgParser): CfgEvent {.rtl, extern: "npc$1".} =
result.kind = cfgError
result.msg = errorStr(c, "invalid token: " & c.tok.literal)
rawGetTok(c, c.tok)
+
+# ---------------- Configuration file related operations ----------------
+type
+ Config* = OrderedTableRef[string, OrderedTableRef[string, string]]
+
+proc newConfig*(): Config =
+ ## Create a new configuration table.
+ ## Useful when wanting to create a configuration file.
+ result = newOrderedTable[string, OrderedTableRef[string, string]]()
+
+proc loadConfig*(filename: string): Config =
+ ## Load the specified configuration file into a new Config instance.
+ var dict = newOrderedTable[string, OrderedTableRef[string, string]]()
+ var curSection = "" ## Current section,
+ ## the default value of the current section is "",
+ ## which means that the current section is a common
+ var p: CfgParser
+ var fileStream = newFileStream(filename, fmRead)
+ if fileStream != nil:
+ open(p, fileStream, filename)
+ while true:
+ var e = next(p)
+ case e.kind
+ of cfgEof:
+ break
+ of cfgSectionStart: # Only look for the first time the Section
+ curSection = e.section
+ of cfgKeyValuePair:
+ var t = newOrderedTable[string, string]()
+ if dict.hasKey(curSection):
+ t = dict[curSection]
+ t[e.key] = e.value
+ dict[curSection] = t
+ of cfgOption:
+ var c = newOrderedTable[string, string]()
+ if dict.hasKey(curSection):
+ c = dict[curSection]
+ c["--" & e.key] = e.value
+ dict[curSection] = c
+ of cfgError:
+ break
+ close(p)
+ result = dict
+
+proc replace(s: string): string =
+ var d = ""
+ var i = 0
+ while i < s.len():
+ if s[i] == '\\':
+ d.add(r"\\")
+ elif s[i] == '\c' and s[i+1] == '\L':
+ d.add(r"\n")
+ inc(i)
+ elif s[i] == '\c':
+ d.add(r"\n")
+ elif s[i] == '\L':
+ d.add(r"\n")
+ else:
+ d.add(s[i])
+ inc(i)
+ result = d
+
+proc writeConfig*(dict: Config, filename: string) =
+ ## Writes the contents of the table to the specified configuration file.
+ ## Note: Comment statement will be ignored.
+ var file: File
+ if file.open(filename, fmWrite):
+ try:
+ var section, key, value, kv, segmentChar:string
+ for pair in dict.pairs():
+ section = pair[0]
+ if section != "": ## Not general section
+ if not allCharsInSet(section, SymChars): ## Non system character
+ file.writeLine("[\"" & section & "\"]")
+ else:
+ file.writeLine("[" & section & "]")
+ for pair2 in pair[1].pairs():
+ key = pair2[0]
+ value = pair2[1]
+ if key[0] == '-' and key[1] == '-': ## If it is a command key
+ segmentChar = ":"
+ if not allCharsInSet(key[2..key.len()-1], SymChars):
+ kv.add("--\"")
+ kv.add(key[2..key.len()-1])
+ kv.add("\"")
+ else:
+ kv = key
+ else:
+ segmentChar = "="
+ kv = key
+ if value != "": ## If the key is not empty
+ if not allCharsInSet(value, SymChars):
+ kv.add(segmentChar)
+ kv.add("\"")
+ kv.add(replace(value))
+ kv.add("\"")
+ else:
+ kv.add(segmentChar)
+ kv.add(value)
+ file.writeLine(kv)
+ except:
+ raise
+ finally:
+ file.close()
+
+proc getSectionValue*(dict: Config, section, key: string): string =
+ ## Gets the Key value of the specified Section.
+ if dict.haskey(section):
+ if dict[section].hasKey(key):
+ result = dict[section][key]
+ else:
+ result = ""
+ else:
+ result = ""
+
+proc setSectionKey*(dict: var Config, section, key, value: string) =
+ ## Sets the Key value of the specified Section.
+ var t = newOrderedTable[string, string]()
+ if dict.hasKey(section):
+ t = dict[section]
+ t[key] = value
+ dict[section] = t
+
+proc delSection*(dict: var Config, section: string) =
+ ## Deletes the specified section and all of its sub keys.
+ dict.del(section)
+
+proc delSectionKey*(dict: var Config, section, key: string) =
+ ## Delete the key of the specified section.
+ if dict.haskey(section):
+ if dict[section].hasKey(key):
+ if dict[section].len() == 1:
+ dict.del(section)
+ else:
+ dict[section].del(key)
diff --git a/lib/pure/parsecsv.nim b/lib/pure/parsecsv.nim
index af51e12012..77b145a739 100644
--- a/lib/pure/parsecsv.nim
+++ b/lib/pure/parsecsv.nim
@@ -25,6 +25,28 @@
## echo "##", val, "##"
## close(x)
##
+## For CSV files with a header row, the header can be read and then used as a
+## reference for item access with `rowEntry <#rowEntry.CsvParser.string>`_:
+##
+## .. code-block:: nim
+## import parsecsv
+## import os
+## # Prepare a file
+## var csv_content = """One,Two,Three,Four
+## 1,2,3,4
+## 10,20,30,40
+## 100,200,300,400
+## """
+## writeFile("temp.csv", content)
+##
+## var p: CsvParser
+## p.open("temp.csv")
+## p.readHeaderRow()
+## while p.readRow():
+## echo "new row: "
+## for col in items(p.headers):
+## echo "##", col, ":", p.rowEntry(col), "##"
+## p.close()
import
lexbase, streams
@@ -37,6 +59,9 @@ type
sep, quote, esc: char
skipWhite: bool
currRow: int
+ headers*: seq[string] ## The columns that are defined in the csv file
+ ## (read using `readHeaderRow <#readHeaderRow.CsvParser>`_).
+ ## Used with `rowEntry <#rowEntry.CsvParser.string>`_).
CsvError* = object of IOError ## exception that is raised if
## a parsing error occurs
@@ -77,6 +102,15 @@ proc open*(my: var CsvParser, input: Stream, filename: string,
my.row = @[]
my.currRow = 0
+proc open*(my: var CsvParser, filename: string,
+ separator = ',', quote = '"', escape = '\0',
+ skipInitialSpace = false) =
+ ## same as the other `open` but creates the file stream for you.
+ var s = newFileStream(filename, fmRead)
+ if s == nil: my.error(0, "cannot open: " & filename)
+ open(my, s, filename, separator,
+ quote, escape, skipInitialSpace)
+
proc parseField(my: var CsvParser, a: var string) =
var pos = my.bufpos
var buf = my.buf
@@ -131,6 +165,8 @@ proc readRow*(my: var CsvParser, columns = 0): bool =
## reads the next row; if `columns` > 0, it expects the row to have
## exactly this many columns. Returns false if the end of the file
## has been encountered else true.
+ ##
+ ## Blank lines are skipped.
var col = 0 # current column
var oldpos = my.bufpos
while my.buf[my.bufpos] != '\0':
@@ -166,6 +202,22 @@ proc close*(my: var CsvParser) {.inline.} =
## closes the parser `my` and its associated input stream.
lexbase.close(my)
+proc readHeaderRow*(my: var CsvParser) =
+ ## Reads the first row and creates a look-up table for column numbers
+ ## See also `rowEntry <#rowEntry.CsvParser.string>`_.
+ var present = my.readRow()
+ if present:
+ my.headers = my.row
+
+proc rowEntry*(my: var CsvParser, entry: string): string =
+ ## Reads a specified `entry` from the current row.
+ ##
+ ## Assumes that `readHeaderRow <#readHeaderRow.CsvParser>`_ has already been
+ ## called.
+ var index = my.headers.find(entry)
+ if index >= 0:
+ result = my.row[index]
+
when not defined(testing) and isMainModule:
import os
var s = newFileStream(paramStr(1), fmRead)
@@ -178,3 +230,35 @@ when not defined(testing) and isMainModule:
echo "##", val, "##"
close(x)
+when isMainModule:
+ import os
+ import strutils
+ block: # Tests for reading the header row
+ var content = "One,Two,Three,Four\n1,2,3,4\n10,20,30,40,\n100,200,300,400\n"
+ writeFile("temp.csv", content)
+
+ var p: CsvParser
+ p.open("temp.csv")
+ p.readHeaderRow()
+ while p.readRow():
+ var zeros = repeat('0', p.currRow-2)
+ doAssert p.rowEntry("One") == "1" & zeros
+ doAssert p.rowEntry("Two") == "2" & zeros
+ doAssert p.rowEntry("Three") == "3" & zeros
+ doAssert p.rowEntry("Four") == "4" & zeros
+ p.close()
+
+ when not defined(testing):
+ var parser: CsvParser
+ parser.open("temp.csv")
+ parser.readHeaderRow()
+ while parser.readRow():
+ echo "new row: "
+ for col in items(parser.headers):
+ echo "##", col, ":", parser.rowEntry(col), "##"
+ parser.close()
+ removeFile("temp.csv")
+
+ # Tidy up
+ removeFile("temp.csv")
+
diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim
index 698bde42a5..fb7d72182d 100644
--- a/lib/pure/parseutils.nim
+++ b/lib/pure/parseutils.nim
@@ -173,6 +173,22 @@ proc parseUntil*(s: string, token: var string, until: char,
result = i-start
token = substr(s, start, i-1)
+proc parseUntil*(s: string, token: var string, until: string,
+ start = 0): int {.inline.} =
+ ## parses a token and stores it in ``token``. Returns
+ ## the number of the parsed characters or 0 in case of an error. A token
+ ## consists of any character that comes before the `until` token.
+ var i = start
+ while i < s.len:
+ if s[i] == until[0]:
+ var u = 1
+ while i+u < s.len and u < until.len and s[i+u] == until[u]:
+ inc u
+ if u >= until.len: break
+ inc(i)
+ result = i-start
+ token = substr(s, start, i-1)
+
proc parseWhile*(s: string, token: var string, validChars: set[char],
start = 0): int {.inline.} =
## parses a token and stores it in ``token``. Returns
@@ -234,6 +250,51 @@ proc parseInt*(s: string, number: var int, start = 0): int {.
elif result != 0:
number = int(res)
+# overflowChecks doesn't work with uint64
+proc rawParseUInt(s: string, b: var uint64, start = 0): int =
+ var
+ res = 0'u64
+ prev = 0'u64
+ i = start
+ if s[i] == '+': inc(i) # Allow
+ if s[i] in {'0'..'9'}:
+ b = 0
+ while s[i] in {'0'..'9'}:
+ prev = res
+ res = res * 10 + (ord(s[i]) - ord('0')).uint64
+ if prev > res:
+ return 0 # overflowChecks emulation
+ inc(i)
+ while s[i] == '_': inc(i) # underscores are allowed and ignored
+ b = res
+ result = i - start
+
+proc parseBiggestUInt*(s: string, number: var uint64, start = 0): int {.
+ rtl, extern: "npuParseBiggestUInt", noSideEffect.} =
+ ## parses an unsigned integer starting at `start` and stores the value
+ ## into `number`.
+ ## Result is the number of processed chars or 0 if there is no integer
+ ## or overflow detected.
+ var res: uint64
+ # use 'res' for exception safety (don't write to 'number' in case of an
+ # overflow exception):
+ result = rawParseUInt(s, res, start)
+ number = res
+
+proc parseUInt*(s: string, number: var uint, start = 0): int {.
+ rtl, extern: "npuParseUInt", noSideEffect.} =
+ ## parses an unsigned integer starting at `start` and stores the value
+ ## into `number`.
+ ## Result is the number of processed chars or 0 if there is no integer or
+ ## overflow detected.
+ var res: uint64
+ result = parseBiggestUInt(s, res, start)
+ if (sizeof(uint) <= 4) and
+ (res > 0xFFFF_FFFF'u64):
+ raise newException(OverflowError, "overflow")
+ elif result != 0:
+ number = uint(res)
+
proc parseBiggestFloat*(s: string, number: var BiggestFloat, start = 0): int {.
magic: "ParseBiggestFloat", importc: "nimParseBiggestFloat", noSideEffect.}
## parses a float starting at `start` and stores the value into `number`.
diff --git a/lib/pure/parsexml.nim b/lib/pure/parsexml.nim
index f8b2c3d8da..d16a553023 100644
--- a/lib/pure/parsexml.nim
+++ b/lib/pure/parsexml.nim
@@ -34,7 +34,7 @@
## document.
##
## .. code-block:: nim
-## :file: examples/htmltitle.nim
+## :file: ../../examples/htmltitle.nim
##
##
## Example 2: Retrieve all HTML links
@@ -45,7 +45,7 @@
## an HTML document contains.
##
## .. code-block:: nim
-## :file: examples/htmlrefs.nim
+## :file: ../../examples/htmlrefs.nim
##
import
@@ -142,6 +142,9 @@ proc kind*(my: XmlParser): XmlEventKind {.inline.} =
template charData*(my: XmlParser): string =
## returns the character data for the events: ``xmlCharData``,
## ``xmlWhitespace``, ``xmlComment``, ``xmlCData``, ``xmlSpecial``
+ ## Raises an assertion in debug mode if ``my.kind`` is not one
+ ## of those events. In release mode, this will not trigger an error
+ ## but the value returned will not be valid.
assert(my.kind in {xmlCharData, xmlWhitespace, xmlComment, xmlCData,
xmlSpecial})
my.a
@@ -149,31 +152,49 @@ template charData*(my: XmlParser): string =
template elementName*(my: XmlParser): string =
## returns the element name for the events: ``xmlElementStart``,
## ``xmlElementEnd``, ``xmlElementOpen``
+ ## Raises an assertion in debug mode if ``my.kind`` is not one
+ ## of those events. In release mode, this will not trigger an error
+ ## but the value returned will not be valid.
assert(my.kind in {xmlElementStart, xmlElementEnd, xmlElementOpen})
my.a
template entityName*(my: XmlParser): string =
## returns the entity name for the event: ``xmlEntity``
+ ## Raises an assertion in debug mode if ``my.kind`` is not
+ ## ``xmlEntity``. In release mode, this will not trigger an error
+ ## but the value returned will not be valid.
assert(my.kind == xmlEntity)
my.a
template attrKey*(my: XmlParser): string =
## returns the attribute key for the event ``xmlAttribute``
+ ## Raises an assertion in debug mode if ``my.kind`` is not
+ ## ``xmlAttribute``. In release mode, this will not trigger an error
+ ## but the value returned will not be valid.
assert(my.kind == xmlAttribute)
my.a
template attrValue*(my: XmlParser): string =
## returns the attribute value for the event ``xmlAttribute``
+ ## Raises an assertion in debug mode if ``my.kind`` is not
+ ## ``xmlAttribute``. In release mode, this will not trigger an error
+ ## but the value returned will not be valid.
assert(my.kind == xmlAttribute)
my.b
template piName*(my: XmlParser): string =
## returns the processing instruction name for the event ``xmlPI``
+ ## Raises an assertion in debug mode if ``my.kind`` is not
+ ## ``xmlPI``. In release mode, this will not trigger an error
+ ## but the value returned will not be valid.
assert(my.kind == xmlPI)
my.a
template piRest*(my: XmlParser): string =
## returns the rest of the processing instruction for the event ``xmlPI``
+ ## Raises an assertion in debug mode if ``my.kind`` is not
+ ## ``xmlPI``. In release mode, this will not trigger an error
+ ## but the value returned will not be valid.
assert(my.kind == xmlPI)
my.b
diff --git a/lib/pure/pegs.nim b/lib/pure/pegs.nim
index fead66de26..5c978a2f8c 100644
--- a/lib/pure/pegs.nim
+++ b/lib/pure/pegs.nim
@@ -12,7 +12,7 @@
## Matching performance is hopefully competitive with optimized regular
## expression engines.
##
-## .. include:: ../doc/pegdocs.txt
+## .. include:: ../../doc/pegdocs.txt
##
include "system/inclrtl"
@@ -659,7 +659,7 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
of pkSearch:
var oldMl = c.ml
result = 0
- while start+result < s.len:
+ while start+result <= s.len:
var x = rawMatch(s, p.sons[0], start+result, c)
if x >= 0:
inc(result, x)
@@ -671,7 +671,7 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
var idx = c.ml # reserve a slot for the subpattern
inc(c.ml)
result = 0
- while start+result < s.len:
+ while start+result <= s.len:
var x = rawMatch(s, p.sons[0], start+result, c)
if x >= 0:
if idx < MaxSubpatterns:
@@ -962,6 +962,50 @@ proc parallelReplace*(s: string, subs: varargs[
# copy the rest:
add(result, substr(s, i))
+proc replace*(s: string, sub: Peg, cb: proc(
+ match: int, cnt: int, caps: openArray[string]): string): string {.
+ rtl, extern: "npegs$1cb".}=
+ ## Replaces `sub` in `s` by the resulting strings from the callback.
+ ## The callback proc receives the index of the current match (starting with 0),
+ ## the count of captures and an open array with the captures of each match. Examples:
+ ##
+ ## .. code-block:: nim
+ ##
+ ## proc handleMatches*(m: int, n: int, c: openArray[string]): string =
+ ## result = ""
+ ## if m > 0:
+ ## result.add ", "
+ ## result.add case n:
+ ## of 2: c[0].toLower & ": '" & c[1] & "'"
+ ## of 1: c[0].toLower & ": ''"
+ ## else: ""
+ ##
+ ## let s = "Var1=key1;var2=Key2; VAR3"
+ ## echo s.replace(peg"{\ident}('='{\ident})* ';'* \s*", handleMatches)
+ ##
+ ## Results in:
+ ##
+ ## .. code-block:: nim
+ ##
+ ## "var1: 'key1', var2: 'Key2', var3: ''"
+ result = ""
+ var i = 0
+ var caps: array[0..MaxSubpatterns-1, string]
+ var c: Captures
+ var m = 0
+ while i < s.len:
+ c.ml = 0
+ var x = rawMatch(s, sub, i, c)
+ if x <= 0:
+ add(result, s[i])
+ inc(i)
+ else:
+ fillMatches(s, caps, c)
+ add(result, cb(m, c.ml, caps))
+ inc(i, x)
+ inc(m)
+ add(result, substr(s, i))
+
proc transformFile*(infile, outfile: string,
subs: varargs[tuple[pattern: Peg, repl: string]]) {.
rtl, extern: "npegs$1".} =
@@ -1789,3 +1833,22 @@ when isMainModule:
assert(str.find(empty_test) == 0)
assert(str.match(empty_test))
+
+ proc handleMatches*(m: int, n: int, c: openArray[string]): string =
+ result = ""
+
+ if m > 0:
+ result.add ", "
+
+ result.add case n:
+ of 2: toLowerAscii(c[0]) & ": '" & c[1] & "'"
+ of 1: toLowerAscii(c[0]) & ": ''"
+ else: ""
+
+ assert("Var1=key1;var2=Key2; VAR3".
+ replace(peg"{\ident}('='{\ident})* ';'* \s*",
+ handleMatches)=="var1: 'key1', var2: 'Key2', var3: ''")
+
+
+ doAssert "test1".match(peg"""{@}$""")
+ doAssert "test2".match(peg"""{(!$ .)*} $""")
diff --git a/lib/pure/punycode.nim b/lib/pure/punycode.nim
new file mode 100644
index 0000000000..ab6501ed18
--- /dev/null
+++ b/lib/pure/punycode.nim
@@ -0,0 +1,174 @@
+#
+#
+# Nim's Runtime Library
+# (c) Copyright 2016 Andreas Rumpf
+#
+# See the file "copying.txt", included in this
+# distribution, for details about the copyright.
+#
+
+import strutils
+import unicode
+
+# issue #3045
+
+const
+ Base = 36
+ TMin = 1
+ TMax = 26
+ Skew = 38
+ Damp = 700
+ InitialBias = 72
+ InitialN = 128
+ Delimiter = '-'
+
+type
+ PunyError* = object of Exception
+
+proc decodeDigit(x: char): int {.raises: [PunyError].} =
+ if '0' <= x and x <= '9':
+ result = ord(x) - (ord('0') - 26)
+ elif 'A' <= x and x <= 'Z':
+ result = ord(x) - ord('A')
+ elif 'a' <= x and x <= 'z':
+ result = ord(x) - ord('a')
+ else:
+ raise newException(PunyError, "Bad input")
+
+proc encodeDigit(digit: int): Rune {.raises: [PunyError].} =
+ if 0 <= digit and digit < 26:
+ result = Rune(digit + ord('a'))
+ elif 26 <= digit and digit < 36:
+ result = Rune(digit + (ord('0') - 26))
+ else:
+ raise newException(PunyError, "internal error in punycode encoding")
+
+proc isBasic(c: char): bool = ord(c) < 0x80
+proc isBasic(r: Rune): bool = int(r) < 0x80
+
+proc adapt(delta, numPoints: int, first: bool): int =
+ var d = if first: delta div Damp else: delta div 2
+ d += d div numPoints
+ var k = 0
+ while d > ((Base-TMin)*TMax) div 2:
+ d = d div (Base - TMin)
+ k += Base
+ result = k + (Base - TMin + 1) * d div (d + Skew)
+
+proc encode*(prefix, s: string): string {.raises: [PunyError].} =
+ ## Encode a string that may contain Unicode.
+ ## Prepend `prefix` to the result
+ result = prefix
+ var (d, n, bias) = (0, InitialN, InitialBias)
+ var (b, remaining) = (0, 0)
+ for r in s.runes:
+ if r.isBasic:
+ # basic Ascii character
+ inc b
+ result.add($r)
+ else:
+ # special character
+ inc remaining
+
+ var h = b
+ if b > 0:
+ result.add(Delimiter) # we have some Ascii chars
+ while remaining != 0:
+ var m: int = high(int32)
+ for r in s.runes:
+ if m > int(r) and int(r) >= n:
+ m = int(r)
+ d += (m - n) * (h + 1)
+ if d < 0:
+ raise newException(PunyError, "invalid label " & s)
+ n = m
+ for r in s.runes:
+ if int(r) < n:
+ inc d
+ if d < 0:
+ raise newException(PunyError, "invalid label " & s)
+ continue
+ if int(r) > n:
+ continue
+ var q = d
+ var k = Base
+ while true:
+ var t = k - bias
+ if t < TMin:
+ t = TMin
+ elif t > TMax:
+ t = TMax
+ if q < t:
+ break
+ result.add($encodeDigit(t + (q - t) mod (Base - t)))
+ q = (q - t) div (Base - t)
+ k += Base
+ result.add($encodeDigit(q))
+ bias = adapt(d, h + 1, h == b)
+ d = 0
+ inc h
+ dec remaining
+ inc d
+ inc n
+
+proc encode*(s: string): string {.raises: [PunyError].} =
+ ## Encode a string that may contain Unicode. Prefix is empty.
+ result = encode("", s)
+
+proc decode*(encoded: string): string {.raises: [PunyError].} =
+ ## Decode a Punycode-encoded string
+ var
+ n = InitialN
+ i = 0
+ bias = InitialBias
+ var d = rfind(encoded, Delimiter)
+ result = ""
+
+ if d > 0:
+ # found Delimiter
+ for j in 0.. (high(int32) - i) div w:
+ raise newException(PunyError, "Too large a value: " & $digit)
+ i += digit * w
+ var t: int
+ if k <= bias:
+ t = TMin
+ elif k >= bias + TMax:
+ t = TMax
+ else:
+ t = k - bias
+ if digit < t:
+ break
+ w *= Base - t
+ k += Base
+ bias = adapt(i - oldi, runelen(result) + 1, oldi == 0)
+
+ if i div (runelen(result) + 1) > high(int32) - n:
+ raise newException(PunyError, "Value too large")
+
+ n += i div (runelen(result) + 1)
+ i = i mod (runelen(result) + 1)
+ insert(result, $Rune(n), i)
+ inc i
+
+when isMainModule:
+ assert(decode(encode("", "bücher")) == "bücher")
+ assert(decode(encode("münchen")) == "münchen")
+ assert encode("xn--", "münchen") == "xn--mnchen-3ya"
diff --git a/lib/pure/random.nim b/lib/pure/random.nim
new file mode 100644
index 0000000000..08da771dc4
--- /dev/null
+++ b/lib/pure/random.nim
@@ -0,0 +1,128 @@
+#
+#
+# Nim's Runtime Library
+# (c) Copyright 2016 Andreas Rumpf
+#
+# See the file "copying.txt", included in this
+# distribution, for details about the copyright.
+#
+
+## 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
+##
+
+include "system/inclrtl"
+{.push debugger:off.}
+
+# XXX Expose RandomGenState
+when defined(JS):
+ type ui = uint32
+else:
+ type ui = uint64
+
+type
+ RandomGenState = object
+ a0, a1: ui
+
+when defined(JS):
+ var state = RandomGenState(
+ a0: 0x69B4C98Cu32,
+ a1: 0xFED1DD30u32) # global for backwards compatibility
+else:
+ # racy for multi-threading but good enough for now:
+ var state = RandomGenState(
+ 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
+ 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
+
+proc skipRandomNumbers(s: var RandomGenState) =
+ ## 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.
+ when defined(JS):
+ const helper = [0xbeac0467u32, 0xd86b048bu32]
+ else:
+ const helper = [0xbeac0467eba5facbu64, 0xd86b048b86aa9922u64]
+ var
+ s0 = ui 0
+ s1 = ui 0
+ for i in 0..high(helper):
+ for b in 0..< 64:
+ if (helper[i] and (ui(1) shl ui(b))) != 0:
+ s0 = s0 xor s.a0
+ s1 = s1 xor s.a1
+ discard next(s)
+ s.a0 = s0
+ s.a1 = s1
+
+proc random*(max: int): int {.benign.} =
+ ## Returns a random number in the range 0..max-1. The sequence of
+ ## random number is always the same, unless `randomize` is called
+ ## which initializes the random number generator with a "random"
+ ## number, i.e. a tickcount.
+ result = int(next(state) mod uint64(max))
+
+proc random*(max: float): float {.benign.} =
+ ## Returns a random number in the range 0.. 130:
+ doAssert false, "too many occurances of " & $i
+ main()
diff --git a/lib/pure/rationals.nim b/lib/pure/rationals.nim
index 6fd05dc4bd..bf134f2ae0 100644
--- a/lib/pure/rationals.nim
+++ b/lib/pure/rationals.nim
@@ -41,26 +41,26 @@ proc toRational*[T:SomeInteger](x: T): Rational[T] =
proc toRationalSub(x: float, n: int): Rational[int] =
var
- a = 0
- b, c, d = 1
+ a = 0'i64
+ b, c, d = 1'i64
result = 0 // 1 # rational 0
while b <= n and d <= n:
let ac = (a+c)
let bd = (b+d)
# scale by 1000 so not overflow for high precision
- let mediant = (ac/1000) / (bd/1000)
+ let mediant = (ac.float/1000) / (bd.float/1000)
if x == mediant:
if bd <= n:
- result.num = ac
- result.den = bd
+ result.num = ac.int
+ result.den = bd.int
return result
elif d > b:
- result.num = c
- result.den = d
+ result.num = c.int
+ result.den = d.int
return result
else:
- result.num = a
- result.den = b
+ result.num = a.int
+ result.den = b.int
return result
elif x > mediant:
a = ac
@@ -69,8 +69,8 @@ proc toRationalSub(x: float, n: int): Rational[int] =
c = ac
d = bd
if (b > n):
- return initRational(c, d)
- return initRational(a, b)
+ return initRational(c.int, d.int)
+ return initRational(a.int, b.int)
proc toRational*(x: float, n: int = high(int)): Rational[int] =
## Calculate the best rational numerator and denominator
diff --git a/lib/pure/selectors.nim b/lib/pure/selectors.nim
index 89e92c1333..098b78c959 100644
--- a/lib/pure/selectors.nim
+++ b/lib/pure/selectors.nim
@@ -132,11 +132,12 @@ elif defined(linux):
s.fds[fd].events = events
proc unregister*(s: var Selector, fd: SocketHandle) =
- if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fd, nil) != 0:
- let err = osLastError()
- if err.cint notin {ENOENT, EBADF}:
- # TODO: Why do we sometimes get an EBADF? Is this normal?
- raiseOSError(err)
+ if s.fds[fd].events != {}:
+ if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fd, nil) != 0:
+ let err = osLastError()
+ if err.cint notin {ENOENT, EBADF}:
+ # TODO: Why do we sometimes get an EBADF? Is this normal?
+ raiseOSError(err)
s.fds.del(fd)
proc close*(s: var Selector) =
diff --git a/lib/pure/smtp.nim b/lib/pure/smtp.nim
index b2adac2f3d..0507129021 100644
--- a/lib/pure/smtp.nim
+++ b/lib/pure/smtp.nim
@@ -20,9 +20,9 @@
## var msg = createMessage("Hello from Nim's SMTP",
## "Hello!.\n Is this awesome or what?",
## @["foo@gmail.com"])
-## var smtp = connect("smtp.gmail.com", 465, true, true)
-## smtp.auth("username", "password")
-## smtp.sendmail("username@gmail.com", @["foo@gmail.com"], $msg)
+## var smtpConn = connect("smtp.gmail.com", Port 465, true, true)
+## smtpConn.auth("username", "password")
+## smtpConn.sendmail("username@gmail.com", @["foo@gmail.com"], $msg)
##
##
## For SSL support this module relies on OpenSSL. If you want to
@@ -31,6 +31,8 @@
import net, strutils, strtabs, base64, os
import asyncnet, asyncdispatch
+export Port
+
type
Smtp* = object
sock: Socket
@@ -258,8 +260,8 @@ when not defined(testing) and isMainModule:
# "Hello, my name is dom96.\n What\'s yours?", @["dominik@localhost"])
#echo(msg)
- #var smtp = connect("localhost", 25, False, True)
- #smtp.sendmail("root@localhost", @["dominik@localhost"], $msg)
+ #var smtpConn = connect("localhost", Port 25, false, true)
+ #smtpConn.sendmail("root@localhost", @["dominik@localhost"], $msg)
#echo(decode("a17sm3701420wbe.12"))
proc main() {.async.} =
diff --git a/lib/pure/streams.nim b/lib/pure/streams.nim
index c606b4680c..eea06f4ce5 100644
--- a/lib/pure/streams.nim
+++ b/lib/pure/streams.nim
@@ -306,67 +306,67 @@ proc peekLine*(s: Stream): TaintedString =
defer: setPosition(s, pos)
result = readLine(s)
-type
- StringStream* = ref StringStreamObj ## a stream that encapsulates a string
- StringStreamObj* = object of StreamObj
- data*: string
- pos: int
-
-{.deprecated: [PStringStream: StringStream, TStringStream: StringStreamObj].}
-
-proc ssAtEnd(s: Stream): bool =
- var s = StringStream(s)
- return s.pos >= s.data.len
-
-proc ssSetPosition(s: Stream, pos: int) =
- var s = StringStream(s)
- s.pos = clamp(pos, 0, s.data.len)
-
-proc ssGetPosition(s: Stream): int =
- var s = StringStream(s)
- return s.pos
-
-proc ssReadData(s: Stream, buffer: pointer, bufLen: int): int =
- var s = StringStream(s)
- result = min(bufLen, s.data.len - s.pos)
- if result > 0:
- copyMem(buffer, addr(s.data[s.pos]), result)
- inc(s.pos, result)
-
-proc ssPeekData(s: Stream, buffer: pointer, bufLen: int): int =
- var s = StringStream(s)
- result = min(bufLen, s.data.len - s.pos)
- if result > 0:
- copyMem(buffer, addr(s.data[s.pos]), result)
-
-proc ssWriteData(s: Stream, buffer: pointer, bufLen: int) =
- var s = StringStream(s)
- if bufLen <= 0:
- return
- if s.pos + bufLen > s.data.len:
- setLen(s.data, s.pos + bufLen)
- copyMem(addr(s.data[s.pos]), buffer, bufLen)
- inc(s.pos, bufLen)
-
-proc ssClose(s: Stream) =
- var s = StringStream(s)
- s.data = nil
-
-proc newStringStream*(s: string = ""): StringStream =
- ## creates a new stream from the string `s`.
- new(result)
- result.data = s
- result.pos = 0
- result.closeImpl = ssClose
- result.atEndImpl = ssAtEnd
- result.setPositionImpl = ssSetPosition
- result.getPositionImpl = ssGetPosition
- result.readDataImpl = ssReadData
- result.peekDataImpl = ssPeekData
- result.writeDataImpl = ssWriteData
-
when not defined(js):
+ type
+ StringStream* = ref StringStreamObj ## a stream that encapsulates a string
+ StringStreamObj* = object of StreamObj
+ data*: string
+ pos: int
+
+ {.deprecated: [PStringStream: StringStream, TStringStream: StringStreamObj].}
+
+ proc ssAtEnd(s: Stream): bool =
+ var s = StringStream(s)
+ return s.pos >= s.data.len
+
+ proc ssSetPosition(s: Stream, pos: int) =
+ var s = StringStream(s)
+ s.pos = clamp(pos, 0, s.data.len)
+
+ proc ssGetPosition(s: Stream): int =
+ var s = StringStream(s)
+ return s.pos
+
+ proc ssReadData(s: Stream, buffer: pointer, bufLen: int): int =
+ var s = StringStream(s)
+ result = min(bufLen, s.data.len - s.pos)
+ if result > 0:
+ copyMem(buffer, addr(s.data[s.pos]), result)
+ inc(s.pos, result)
+
+ proc ssPeekData(s: Stream, buffer: pointer, bufLen: int): int =
+ var s = StringStream(s)
+ result = min(bufLen, s.data.len - s.pos)
+ if result > 0:
+ copyMem(buffer, addr(s.data[s.pos]), result)
+
+ proc ssWriteData(s: Stream, buffer: pointer, bufLen: int) =
+ var s = StringStream(s)
+ if bufLen <= 0:
+ return
+ if s.pos + bufLen > s.data.len:
+ setLen(s.data, s.pos + bufLen)
+ copyMem(addr(s.data[s.pos]), buffer, bufLen)
+ inc(s.pos, bufLen)
+
+ proc ssClose(s: Stream) =
+ var s = StringStream(s)
+ s.data = nil
+
+ proc newStringStream*(s: string = ""): StringStream =
+ ## creates a new stream from the string `s`.
+ new(result)
+ result.data = s
+ result.pos = 0
+ result.closeImpl = ssClose
+ result.atEndImpl = ssAtEnd
+ result.setPositionImpl = ssSetPosition
+ result.getPositionImpl = ssGetPosition
+ result.readDataImpl = ssReadData
+ result.peekDataImpl = ssPeekData
+ result.writeDataImpl = ssWriteData
+
type
FileStream* = ref FileStreamObj ## a stream that encapsulates a `File`
FileStreamObj* = object of Stream
diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim
new file mode 100644
index 0000000000..89ef2fcd20
--- /dev/null
+++ b/lib/pure/strmisc.nim
@@ -0,0 +1,83 @@
+#
+#
+# Nim's Runtime Library
+# (c) Copyright 2016 Joey Payne
+#
+# See the file "copying.txt", included in this
+# distribution, for details about the copyright.
+#
+
+## This module contains various string utility routines that are uncommonly
+## used in comparison to `strutils `_.
+
+import strutils
+
+{.deadCodeElim: on.}
+
+proc expandTabs*(s: string, tabSize: int = 8): string {.noSideEffect,
+ procvar.} =
+ ## Expand tab characters in `s` by `tabSize` spaces
+
+ result = newStringOfCap(s.len + s.len shr 2)
+ var pos = 0
+
+ template addSpaces(n) =
+ for j in 0 ..< n:
+ result.add(' ')
+ pos += 1
+
+ for i in 0 ..< len(s):
+ let c = s[i]
+ if c == '\t':
+ let
+ denominator = if tabSize > 0: tabSize else: 1
+ numSpaces = tabSize - pos mod denominator
+
+ addSpaces(numSpaces)
+ else:
+ result.add(c)
+ pos += 1
+ if c == '\l':
+ pos = 0
+
+proc partition*(s: string, sep: string,
+ right: bool = false): (string, string, string)
+ {.noSideEffect, procvar.} =
+ ## Split the string at the first or last occurrence of `sep` into a 3-tuple
+ ##
+ ## Returns a 3 string tuple of (beforeSep, `sep`, afterSep) or
+ ## (`s`, "", "") if `sep` is not found and `right` is false or
+ ## ("", "", `s`) if `sep` is not found and `right` is true
+ let position = if right: s.rfind(sep) else: s.find(sep)
+ if position != -1:
+ return (s[0 ..< position], sep, s[position + sep.len ..< s.len])
+ return if right: ("", "", s) else: (s, "", "")
+
+proc rpartition*(s: string, sep: string): (string, string, string)
+ {.noSideEffect, procvar.} =
+ ## Split the string at the last occurrence of `sep` into a 3-tuple
+ ##
+ ## Returns a 3 string tuple of (beforeSep, `sep`, afterSep) or
+ ## ("", "", `s`) if `sep` is not found
+ return partition(s, sep, right = true)
+
+when isMainModule:
+ doAssert expandTabs("\t", 4) == " "
+ doAssert expandTabs("\tfoo\t", 4) == " foo "
+ doAssert expandTabs("\tfoo\tbar", 4) == " foo bar"
+ doAssert expandTabs("\tfoo\tbar\t", 4) == " foo bar "
+ doAssert expandTabs("", 4) == ""
+ doAssert expandTabs("", 0) == ""
+ doAssert expandTabs("\t\t\t", 0) == ""
+
+ doAssert partition("foo:bar", ":") == ("foo", ":", "bar")
+ doAssert partition("foobarbar", "bar") == ("foo", "bar", "bar")
+ doAssert partition("foobarbar", "bank") == ("foobarbar", "", "")
+ doAssert partition("foobarbar", "foo") == ("", "foo", "barbar")
+ doAssert partition("foofoobar", "bar") == ("foofoo", "bar", "")
+
+ doAssert rpartition("foo:bar", ":") == ("foo", ":", "bar")
+ doAssert rpartition("foobarbar", "bar") == ("foobar", "bar", "")
+ doAssert rpartition("foobarbar", "bank") == ("", "", "foobarbar")
+ doAssert rpartition("foobarbar", "foo") == ("", "foo", "barbar")
+ doAssert rpartition("foofoobar", "bar") == ("foofoo", "bar", "")
diff --git a/lib/pure/strscans.nim b/lib/pure/strscans.nim
new file mode 100644
index 0000000000..246f018c50
--- /dev/null
+++ b/lib/pure/strscans.nim
@@ -0,0 +1,522 @@
+#
+#
+# Nim's Runtime Library
+# (c) Copyright 2016 Andreas Rumpf
+#
+# See the file "copying.txt", included in this
+# distribution, for details about the copyright.
+#
+
+##[
+This module contains a `scanf`:idx: macro that can be used for extracting
+substrings from an input string. This is often easier than regular expressions.
+Some examples as an apetizer:
+
+.. code-block:: nim
+ # check if input string matches a triple of integers:
+ const input = "(1,2,4)"
+ var x, y, z: int
+ if scanf(input, "($i,$i,$i)", x, y, z):
+ echo "matches and x is ", x, " y is ", y, " z is ", z
+
+ # check if input string matches an ISO date followed by an identifier followed
+ # by whitespace and a floating point number:
+ var year, month, day: int
+ var identifier: string
+ var myfloat: float
+ if scanf(input, "$i-$i-$i $w$s$f", year, month, day, identifier, myfloat):
+ echo "yes, we have a match!"
+
+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``.
+``$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.
+``$$`` Matches a single dollar sign.
+``$.`` Matches if the end of the input string has been reached.
+``$*`` Matches until the token following the ``$*`` was found.
+ The match is allowed to be of 0 length.
+``$+`` Matches until the token following the ``$+`` was found.
+ The match must consist of at least one char.
+``${foo}`` User defined matcher. Uses the proc ``foo`` to perform
+ the match. See below for more details.
+``$[foo]`` Call user defined proc ``foo`` to **skip** some optional
+ parts in the input string. See below for more details.
+================= ========================================================
+
+Even though ``$*`` and ``$+`` look similar to the regular expressions ``.*``
+and ``.+`` they work quite differently, there is no non-deterministic
+state machine involved and the matches are non-greedy. ``[$*]``
+matches ``[xyz]`` via ``parseutils.parseUntil``.
+
+Furthermore no backtracking is performed, if parsing fails after a value
+has already been bound to a matched subexpression this value is not restored
+to its original value. This rarely causes problems in practice and if it does
+for you, it's easy enough to bind to a temporary variable first.
+
+
+Startswith vs full match
+========================
+
+``scanf`` returns true if the input string **starts with** the specified
+pattern. If instead it should only return true if theres is also nothing
+left in the input, append ``$.`` to your pattern.
+
+
+User definable matchers
+=======================
+
+One very nice advantage over regular expressions is that ``scanf`` is
+extensible with ordinary Nim procs. The proc is either enclosed in ``${}``
+or in ``$[]``. ``${}`` matches and binds the result
+to a variable (that was passed to the ``scanf`` macro) while ``$[]`` merely
+optional tokens.
+
+
+In this example, we define a helper proc ``skipSep`` that skips some separators
+which we then use in our scanf pattern to help us in the matching process:
+
+.. code-block:: nim
+
+ proc someSep(input: string; start: int; seps: set[char] = {':','-','.'}): int =
+ # Note: The parameters and return value must match to what ``scanf`` requires
+ result = 0
+ while input[start+result] in seps: inc result
+
+ if scanf(input, "$w${someSep}$w", key, value):
+ ...
+
+It also possible to pass arguments to a user definable matcher:
+
+.. code-block:: nim
+
+ proc ndigits(input: string; start: int; intVal: var int; n: int): int =
+ # matches exactly ``n`` digits. Matchers need to return 0 if nothing
+ # matched or otherwise the number of processed chars.
+ var x = 0
+ var i = 0
+ while i < n and i+start < input.len and input[i+start] in {'0'..'9'}:
+ x = x * 10 + input[i+start].ord - '0'.ord
+ inc i
+ # only overwrite if we had a match
+ if i == n:
+ result = n
+ intVal = x
+
+ # match an ISO date extracting year, month, day at the same time.
+ # Also ensure the input ends after the ISO date:
+ var year, month, day: int
+ if scanf("2013-01-03", "${ndigits(4)}-${ndigits(2)}-${ndigits(2)}$.", year, month, day):
+ ...
+
+]##
+
+
+import macros, parseutils
+
+proc conditionsToIfChain(n, idx, res: NimNode; start: int): NimNode =
+ assert n.kind == nnkStmtList
+ if start >= n.len: return newAssignment(res, newLit true)
+ var ifs: NimNode = nil
+ if n[start+1].kind == nnkEmpty:
+ ifs = conditionsToIfChain(n, idx, res, start+3)
+ else:
+ ifs = newIfStmt((n[start+1],
+ newTree(nnkStmtList, newCall(bindSym"inc", idx, n[start+2]),
+ conditionsToIfChain(n, idx, res, start+3))))
+ result = newTree(nnkStmtList, n[start], ifs)
+
+proc notZero(x: NimNode): NimNode = newCall(bindSym"!=", x, newLit 0)
+
+proc buildUserCall(x: string; args: varargs[NimNode]): NimNode =
+ let y = parseExpr(x)
+ result = newTree(nnkCall)
+ if y.kind in nnkCallKinds: result.add y[0]
+ else: result.add y
+ for a in args: result.add a
+ if y.kind in nnkCallKinds:
+ for i in 1..=", idx, newCall(bindSym"len", input)))
+ else:
+ result.add res
+
+template atom*(input: string; idx: int; c: char): bool =
+ ## Used in scanp for the matching of atoms (usually chars).
+ input[idx] == c
+
+template atom*(input: string; idx: int; s: set[char]): bool =
+ input[idx] in s
+
+#template prepare*(input: string): int = 0
+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.
+ type StmtTriple = tuple[init, cond, action: NimNode]
+
+ template interf(x): untyped = bindSym(x, brForceOpen)
+
+ proc toIfChain(n: seq[StmtTriple]; idx, res: NimNode; start: int): NimNode =
+ if start >= n.len: return newAssignment(res, newLit true)
+ var ifs: NimNode = nil
+ if n[start].cond.kind == nnkEmpty:
+ ifs = toIfChain(n, idx, res, start+1)
+ else:
+ ifs = newIfStmt((n[start].cond,
+ newTree(nnkStmtList, n[start].action,
+ toIfChain(n, idx, res, start+1))))
+ result = newTree(nnkStmtList, n[start].init, ifs)
+
+ proc attach(x, attached: NimNode): NimNode =
+ if attached == nil: x
+ else: newStmtList(attached, x)
+
+ proc placeholder(n, x, j: NimNode): NimNode =
+ if n.kind == nnkPrefix and n[0].eqIdent("$"):
+ let n1 = n[1]
+ if n1.eqIdent"_" or n1.eqIdent"current":
+ result = newTree(nnkBracketExpr, x, j)
+ elif n1.eqIdent"input":
+ result = x
+ elif n1.eqIdent"i" or n1.eqIdent"index":
+ result = j
+ else:
+ error("unknown pattern " & repr(n))
+ else:
+ result = copyNimNode(n)
+ for i in 0 ..< n.len:
+ result.add placeholder(n[i], x, j)
+
+ proc atm(it, input, idx, attached: NimNode): StmtTriple =
+ template `!!`(x): untyped = attach(x, attached)
+ case it.kind
+ of nnkIdent:
+ var resLen = genSym(nskLet, "resLen")
+ result = (newLetStmt(resLen, newCall(it, input, idx)),
+ newCall(interf"success", resLen),
+ !!newCall(interf"nxt", input, idx, resLen))
+ of nnkCallKinds:
+ # *{'A'..'Z'} !! s.add(!_)
+ template buildWhile(init, cond, action): untyped =
+ while true:
+ init
+ if not cond: break
+ action
+
+ # (x) a # bind action a to (x)
+ if it[0].kind == nnkPar and it.len == 2:
+ result = atm(it[0], input, idx, placeholder(it[1], input, idx))
+ elif it.kind == nnkInfix and it[0].eqIdent"->":
+ # bind matching to some action:
+ result = atm(it[1], input, idx, placeholder(it[2], input, idx))
+ elif it.kind == nnkInfix and it[0].eqIdent"as":
+ let cond = if it[1].kind in nnkCallKinds: placeholder(it[1], input, idx)
+ else: newCall(it[1], input, idx)
+ result = (newLetStmt(it[2], cond),
+ newCall(interf"success", it[2]),
+ !!newCall(interf"nxt", input, idx, it[2]))
+ elif it.kind == nnkPrefix and it[0].eqIdent"*":
+ let (init, cond, action) = atm(it[1], input, idx, attached)
+ result = (getAst(buildWhile(init, cond, action)),
+ newEmptyNode(), newEmptyNode())
+ elif it.kind == nnkPrefix and it[0].eqIdent"+":
+ # x+ is the same as xx*
+ result = atm(newTree(nnkPar, it[1], newTree(nnkPrefix, ident"*", it[1])),
+ input, idx, attached)
+ elif it.kind == nnkPrefix and it[0].eqIdent"?":
+ # optional.
+ let (init, cond, action) = atm(it[1], input, idx, attached)
+ if cond.kind == nnkEmpty:
+ error("'?' operator applied to a non-condition")
+ else:
+ result = (newTree(nnkStmtList, init, newIfStmt((cond, action))),
+ newEmptyNode(), newEmptyNode())
+ elif it.kind == nnkPrefix and it[0].eqIdent"~":
+ # not operator
+ let (init, cond, action) = atm(it[1], input, idx, attached)
+ if cond.kind == nnkEmpty:
+ error("'~' operator applied to a non-condition")
+ else:
+ result = (init, newCall(bindSym"not", cond), action)
+ elif it.kind == nnkInfix and it[0].eqIdent"|":
+ let a = atm(it[1], input, idx, attached)
+ let b = atm(it[2], input, idx, attached)
+ if a.cond.kind == nnkEmpty or b.cond.kind == nnkEmpty:
+ error("'|' operator applied to a non-condition")
+ else:
+ result = (newStmtList(a.init,
+ newIfStmt((a.cond, a.action), (newTree(nnkStmtListExpr, b.init, b.cond), b.action))),
+ newEmptyNode(), newEmptyNode())
+ elif it.kind == nnkInfix and it[0].eqIdent"^*":
+ # a ^* b is rewritten to: (a *(b a))?
+ #exprList = expr ^+ comma
+ template tmp(a, b): untyped = ?(a, *(b, a))
+ result = atm(getAst(tmp(it[1], it[2])), input, idx, attached)
+
+ elif it.kind == nnkInfix and it[0].eqIdent"^+":
+ # a ^* b is rewritten to: (a +(b a))?
+ template tmp(a, b): untyped = (a, *(b, a))
+ result = atm(getAst(tmp(it[1], it[2])), input, idx, attached)
+ elif it.kind == nnkCommand and it.len == 2 and it[0].eqIdent"pred":
+ # enforce that the wrapped call is interpreted as a predicate, not a non-terminal:
+ result = (newEmptyNode(), placeholder(it[1], input, idx), newEmptyNode())
+ else:
+ var resLen = genSym(nskLet, "resLen")
+ result = (newLetStmt(resLen, placeholder(it, input, idx)),
+ newCall(interf"success", resLen), !!newCall(interf"nxt", input, idx, resLen))
+ of nnkStrLit..nnkTripleStrLit:
+ var resLen = genSym(nskLet, "resLen")
+ result = (newLetStmt(resLen, newCall(interf"skip", input, it, idx)),
+ newCall(interf"success", resLen), !!newCall(interf"nxt", input, idx, resLen))
+ of nnkCurly, nnkAccQuoted, nnkCharLit:
+ result = (newEmptyNode(), newCall(interf"atom", input, idx, it), !!newCall(interf"nxt", input, idx))
+ of nnkCurlyExpr:
+ if it.len == 3 and it[1].kind == nnkIntLit and it[2].kind == nnkIntLit:
+ var h = newTree(nnkPar, it[0])
+ for count in 2..it[1].intVal: h.add(it[0])
+ for count in it[1].intVal .. it[2].intVal-1: h.add(newTree(nnkPrefix, ident"?", it[0]))
+ result = atm(h, input, idx, attached)
+ elif it.len == 2 and it[1].kind == nnkIntLit:
+ var h = newTree(nnkPar, it[0])
+ for count in 2..it[1].intVal: h.add(it[0])
+ result = atm(h, input, idx, attached)
+ else:
+ error("invalid pattern")
+ of nnkPar:
+ if it.len == 1:
+ result = atm(it[0], input, idx, attached)
+ else:
+ # concatenation:
+ var conds: seq[StmtTriple] = @[]
+ for x in it: conds.add atm(x, input, idx, attached)
+ var res = genSym(nskVar, "res")
+ result = (newStmtList(newVarStmt(res, newLit false),
+ toIfChain(conds, idx, res, 0)), res, newEmptyNode())
+ else:
+ error("invalid pattern")
+
+ #var idx = genSym(nskVar, "idx")
+ var res = genSym(nskVar, "res")
+ result = newTree(nnkStmtListExpr, #newVarStmt(idx, newCall(interf"prepare", input)),
+ newVarStmt(res, newLit false))
+ var conds: seq[StmtTriple] = @[]
+ for it in pattern:
+ conds.add atm(it, input, idx, nil)
+ result.add toIfChain(conds, idx, res, 0)
+ result.add res
+ when defined(debugScanp):
+ echo repr result
+
+
+when isMainModule:
+ proc twoDigits(input: string; x: var int; start: int): int =
+ if input[start] == '0' and input[start+1] == '0':
+ result = 2
+ x = 13
+ else:
+ result = 0
+
+ proc someSep(input: string; start: int; seps: set[char] = {';',',','-','.'}): int =
+ result = 0
+ while input[start+result] in seps: inc result
+
+ proc demangle(s: string; res: var string; start: int): int =
+ while s[result+start] in {'_', '@'}: inc result
+ res = ""
+ while result+start < s.len and s[result+start] > ' ' and s[result+start] != '_':
+ res.add s[result+start]
+ inc result
+ while result+start < s.len and s[result+start] > ' ':
+ inc result
+
+ proc parseGDB(resp: string): seq[string] =
+ const
+ digits = {'0'..'9'}
+ hexdigits = digits + {'a'..'f', 'A'..'F'}
+ whites = {' ', '\t', '\C', '\L'}
+ result = @[]
+ var idx = 0
+ while true:
+ var prc = ""
+ var info = ""
+ if scanp(resp, idx, *`whites`, '#', *`digits`, +`whites`, ?("0x", *`hexdigits`, " in "),
+ demangle($input, prc, $index), *`whites`, '(', * ~ ')', ')',
+ *`whites`, "at ", +(~{'\C', '\L', '\0'} -> info.add($_)) ):
+ result.add prc & " " & info
+ else:
+ break
+
+ var key, val: string
+ var intval: int
+ var floatval: float
+ doAssert scanf("abc:: xyz 89 33.25", "$w$s::$s$w$s$i $f", key, val, intval, floatVal)
+ doAssert key == "abc"
+ doAssert val == "xyz"
+ doAssert intval == 89
+ doAssert floatVal == 33.25
+
+ let xx = scanf("$abc", "$$$i", intval)
+ doAssert xx == false
+
+
+ let xx2 = scanf("$1234", "$$$i", intval)
+ doAssert xx2
+
+ let yy = scanf(";.--Breakpoint00 [output]", "$[someSep]Breakpoint${twoDigits}$[someSep({';','.','-'})] [$+]$.", intVal, key)
+ doAssert yy
+ doAssert key == "output"
+ doAssert intVal == 13
+
+ var ident = ""
+ var idx = 0
+ let zz = scanp("foobar x x x xWZ", idx, +{'a'..'z'} -> add(ident, $_), *(*{' ', '\t'}, "x"), ~'U', "Z")
+ doAssert zz
+ doAssert ident == "foobar"
+
+ const digits = {'0'..'9'}
+ var year = 0
+ var idx2 = 0
+ if scanp("201655-8-9", idx2, `digits`{4,6} -> (year = year * 10 + ord($_) - ord('0')), "-8", "-9"):
+ doAssert year == 201655
+
+ const gdbOut = """
+ #0 @foo_96013_1208911747@8 (x0=...)
+ at c:/users/anwender/projects/nim/temp.nim:11
+ #1 0x00417754 in tempInit000 () at c:/users/anwender/projects/nim/temp.nim:13
+ #2 0x0041768d in NimMainInner ()
+ at c:/users/anwender/projects/nim/lib/system.nim:2605
+ #3 0x004176b1 in NimMain ()
+ at c:/users/anwender/projects/nim/lib/system.nim:2613
+ #4 0x004176db in main (argc=1, args=0x712cc8, env=0x711ca8)
+ at c:/users/anwender/projects/nim/lib/system.nim:2620"""
+ const result = @["foo c:/users/anwender/projects/nim/temp.nim:11",
+ "tempInit000 c:/users/anwender/projects/nim/temp.nim:13",
+ "NimMainInner c:/users/anwender/projects/nim/lib/system.nim:2605",
+ "NimMain c:/users/anwender/projects/nim/lib/system.nim:2613",
+ "main c:/users/anwender/projects/nim/lib/system.nim:2620"]
+ doAssert parseGDB(gdbOut) == result
diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim
index f2c1e77e18..bfc32bc71d 100644
--- a/lib/pure/strutils.nim
+++ b/lib/pure/strutils.nim
@@ -14,6 +14,8 @@
## `_.
import parseutils
+from math import pow, round, floor, log10
+from algorithm import reverse
{.deadCodeElim: on.}
@@ -24,6 +26,12 @@ include "system/inclrtl"
{.pop.}
+# Support old split with set[char]
+when defined(nimOldSplit):
+ {.pragma: deprecatedSplit, deprecated.}
+else:
+ {.pragma: deprecatedSplit.}
+
type
CharSet* {.deprecated.} = set[char] # for compatibility with Nim
{.deprecated: [TCharSet: CharSet].}
@@ -62,8 +70,8 @@ const
## doAssert "01234".find(invalid) == -1
## doAssert "01A34".find(invalid) == 2
-proc isAlpha*(c: char): bool {.noSideEffect, procvar,
- rtl, extern: "nsuIsAlphaChar".}=
+proc isAlphaAscii*(c: char): bool {.noSideEffect, procvar,
+ rtl, extern: "nsuIsAlphaAsciiChar".}=
## Checks whether or not `c` is alphabetical.
##
## This checks a-z, A-Z ASCII characters only.
@@ -83,27 +91,27 @@ proc isDigit*(c: char): bool {.noSideEffect, procvar,
## This checks 0-9 ASCII characters only.
return c in Digits
-proc isSpace*(c: char): bool {.noSideEffect, procvar,
- rtl, extern: "nsuIsSpaceChar".}=
+proc isSpaceAscii*(c: char): bool {.noSideEffect, procvar,
+ rtl, extern: "nsuIsSpaceAsciiChar".}=
## Checks whether or not `c` is a whitespace character.
return c in Whitespace
-proc isLower*(c: char): bool {.noSideEffect, procvar,
- rtl, extern: "nsuIsLowerChar".}=
+proc isLowerAscii*(c: char): bool {.noSideEffect, procvar,
+ rtl, extern: "nsuIsLowerAsciiChar".}=
## Checks whether or not `c` is a lower case character.
##
## This checks ASCII characters only.
return c in {'a'..'z'}
-proc isUpper*(c: char): bool {.noSideEffect, procvar,
- rtl, extern: "nsuIsUpperChar".}=
+proc isUpperAscii*(c: char): bool {.noSideEffect, procvar,
+ rtl, extern: "nsuIsUpperAsciiChar".}=
## Checks whether or not `c` is an upper case character.
##
## This checks ASCII characters only.
return c in {'A'..'Z'}
-proc isAlpha*(s: string): bool {.noSideEffect, procvar,
- rtl, extern: "nsuIsAlphaStr".}=
+proc isAlphaAscii*(s: string): bool {.noSideEffect, procvar,
+ rtl, extern: "nsuIsAlphaAsciiStr".}=
## Checks whether or not `s` is alphabetical.
##
## This checks a-z, A-Z ASCII characters only.
@@ -115,7 +123,7 @@ proc isAlpha*(s: string): bool {.noSideEffect, procvar,
result = true
for c in s:
- result = c.isAlpha() and result
+ result = c.isAlphaAscii() and result
proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar,
rtl, extern: "nsuIsAlphaNumericStr".}=
@@ -147,8 +155,8 @@ proc isDigit*(s: string): bool {.noSideEffect, procvar,
for c in s:
result = c.isDigit() and result
-proc isSpace*(s: string): bool {.noSideEffect, procvar,
- rtl, extern: "nsuIsSpaceStr".}=
+proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar,
+ rtl, extern: "nsuIsSpaceAsciiStr".}=
## Checks whether or not `s` is completely whitespace.
##
## Returns true if all characters in `s` are whitespace
@@ -158,10 +166,11 @@ proc isSpace*(s: string): bool {.noSideEffect, procvar,
result = true
for c in s:
- result = c.isSpace() and result
+ if not c.isSpaceAscii():
+ return false
-proc isLower*(s: string): bool {.noSideEffect, procvar,
- rtl, extern: "nsuIsLowerStr".}=
+proc isLowerAscii*(s: string): bool {.noSideEffect, procvar,
+ rtl, extern: "nsuIsLowerAsciiStr".}=
## Checks whether or not `s` contains all lower case characters.
##
## This checks ASCII characters only.
@@ -172,10 +181,10 @@ proc isLower*(s: string): bool {.noSideEffect, procvar,
result = true
for c in s:
- result = c.isLower() and result
+ result = c.isLowerAscii() and result
-proc isUpper*(s: string): bool {.noSideEffect, procvar,
- rtl, extern: "nsuIsUpperStr".}=
+proc isUpperAscii*(s: string): bool {.noSideEffect, procvar,
+ rtl, extern: "nsuIsUpperAsciiStr".}=
## Checks whether or not `s` contains all upper case characters.
##
## This checks ASCII characters only.
@@ -186,10 +195,10 @@ proc isUpper*(s: string): bool {.noSideEffect, procvar,
result = true
for c in s:
- result = c.isUpper() and result
+ result = c.isUpperAscii() and result
-proc toLower*(c: char): char {.noSideEffect, procvar,
- rtl, extern: "nsuToLowerChar".} =
+proc toLowerAscii*(c: char): char {.noSideEffect, procvar,
+ rtl, extern: "nsuToLowerAsciiChar".} =
## Converts `c` into lower case.
##
## This works only for the letters ``A-Z``. See `unicode.toLower
@@ -200,8 +209,8 @@ proc toLower*(c: char): char {.noSideEffect, procvar,
else:
result = c
-proc toLower*(s: string): string {.noSideEffect, procvar,
- rtl, extern: "nsuToLowerStr".} =
+proc toLowerAscii*(s: string): string {.noSideEffect, procvar,
+ rtl, extern: "nsuToLowerAsciiStr".} =
## Converts `s` into lower case.
##
## This works only for the letters ``A-Z``. See `unicode.toLower
@@ -209,10 +218,10 @@ proc toLower*(s: string): string {.noSideEffect, procvar,
## character.
result = newString(len(s))
for i in 0..len(s) - 1:
- result[i] = toLower(s[i])
+ result[i] = toLowerAscii(s[i])
-proc toUpper*(c: char): char {.noSideEffect, procvar,
- rtl, extern: "nsuToUpperChar".} =
+proc toUpperAscii*(c: char): char {.noSideEffect, procvar,
+ rtl, extern: "nsuToUpperAsciiChar".} =
## Converts `c` into upper case.
##
## This works only for the letters ``A-Z``. See `unicode.toUpper
@@ -223,8 +232,8 @@ proc toUpper*(c: char): char {.noSideEffect, procvar,
else:
result = c
-proc toUpper*(s: string): string {.noSideEffect, procvar,
- rtl, extern: "nsuToUpperStr".} =
+proc toUpperAscii*(s: string): string {.noSideEffect, procvar,
+ rtl, extern: "nsuToUpperAsciiStr".} =
## Converts `s` into upper case.
##
## This works only for the letters ``A-Z``. See `unicode.toUpper
@@ -232,14 +241,145 @@ proc toUpper*(s: string): string {.noSideEffect, procvar,
## character.
result = newString(len(s))
for i in 0..len(s) - 1:
- result[i] = toUpper(s[i])
+ result[i] = toUpperAscii(s[i])
-proc capitalize*(s: string): string {.noSideEffect, procvar,
- rtl, extern: "nsuCapitalize".} =
+proc capitalizeAscii*(s: string): string {.noSideEffect, procvar,
+ rtl, extern: "nsuCapitalizeAscii".} =
## Converts the first character of `s` into upper case.
##
## This works only for the letters ``A-Z``.
- result = toUpper(s[0]) & substr(s, 1)
+ result = toUpperAscii(s[0]) & substr(s, 1)
+
+proc isSpace*(c: char): bool {.noSideEffect, procvar,
+ rtl, deprecated, extern: "nsuIsSpaceChar".}=
+ ## Checks whether or not `c` is a whitespace character.
+ ##
+ ## **Deprecated since version 0.15.0**: use ``isSpaceAscii`` instead.
+ isSpaceAscii(c)
+
+proc isLower*(c: char): bool {.noSideEffect, procvar,
+ rtl, deprecated, extern: "nsuIsLowerChar".}=
+ ## Checks whether or not `c` is a lower case character.
+ ##
+ ## This checks ASCII characters only.
+ ##
+ ## **Deprecated since version 0.15.0**: use ``isLowerAscii`` instead.
+ isLowerAscii(c)
+
+proc isUpper*(c: char): bool {.noSideEffect, procvar,
+ rtl, deprecated, extern: "nsuIsUpperChar".}=
+ ## Checks whether or not `c` is an upper case character.
+ ##
+ ## This checks ASCII characters only.
+ ##
+ ## **Deprecated since version 0.15.0**: use ``isUpperAscii`` instead.
+ isUpperAscii(c)
+
+proc isAlpha*(c: char): bool {.noSideEffect, procvar,
+ rtl, deprecated, extern: "nsuIsAlphaChar".}=
+ ## Checks whether or not `c` is alphabetical.
+ ##
+ ## This checks a-z, A-Z ASCII characters only.
+ ##
+ ## **Deprecated since version 0.15.0**: use ``isAlphaAscii`` instead.
+ isAlphaAscii(c)
+
+proc isAlpha*(s: string): bool {.noSideEffect, procvar,
+ rtl, deprecated, extern: "nsuIsAlphaStr".}=
+ ## Checks whether or not `s` is alphabetical.
+ ##
+ ## This checks a-z, A-Z ASCII characters only.
+ ## Returns true if all characters in `s` are
+ ## alphabetic and there is at least one character
+ ## in `s`.
+ ##
+ ## **Deprecated since version 0.15.0**: use ``isAlphaAscii`` instead.
+ isAlphaAscii(s)
+
+proc isSpace*(s: string): bool {.noSideEffect, procvar,
+ rtl, deprecated, extern: "nsuIsSpaceStr".}=
+ ## Checks whether or not `s` is completely whitespace.
+ ##
+ ## Returns true if all characters in `s` are whitespace
+ ## characters and there is at least one character in `s`.
+ ##
+ ## **Deprecated since version 0.15.0**: use ``isSpaceAscii`` instead.
+ isSpaceAscii(s)
+
+proc isLower*(s: string): bool {.noSideEffect, procvar,
+ rtl, deprecated, extern: "nsuIsLowerStr".}=
+ ## Checks whether or not `s` contains all lower case characters.
+ ##
+ ## This checks ASCII characters only.
+ ## Returns true if all characters in `s` are lower case
+ ## and there is at least one character in `s`.
+ ##
+ ## **Deprecated since version 0.15.0**: use ``isLowerAscii`` instead.
+ isLowerAscii(s)
+
+proc isUpper*(s: string): bool {.noSideEffect, procvar,
+ rtl, deprecated, extern: "nsuIsUpperStr".}=
+ ## Checks whether or not `s` contains all upper case characters.
+ ##
+ ## This checks ASCII characters only.
+ ## Returns true if all characters in `s` are upper case
+ ## and there is at least one character in `s`.
+ ##
+ ## **Deprecated since version 0.15.0**: use ``isUpperAscii`` instead.
+ isUpperAscii(s)
+
+proc toLower*(c: char): char {.noSideEffect, procvar,
+ rtl, deprecated, extern: "nsuToLowerChar".} =
+ ## Converts `c` into lower case.
+ ##
+ ## This works only for the letters ``A-Z``. See `unicode.toLower
+ ## `_ for a version that works for any Unicode
+ ## character.
+ ##
+ ## **Deprecated since version 0.15.0**: use ``toLowerAscii`` instead.
+ toLowerAscii(c)
+
+proc toLower*(s: string): string {.noSideEffect, procvar,
+ rtl, deprecated, extern: "nsuToLowerStr".} =
+ ## Converts `s` into lower case.
+ ##
+ ## This works only for the letters ``A-Z``. See `unicode.toLower
+ ## `_ for a version that works for any Unicode
+ ## character.
+ ##
+ ## **Deprecated since version 0.15.0**: use ``toLowerAscii`` instead.
+ toLowerAscii(s)
+
+proc toUpper*(c: char): char {.noSideEffect, procvar,
+ rtl, deprecated, extern: "nsuToUpperChar".} =
+ ## Converts `c` into upper case.
+ ##
+ ## This works only for the letters ``A-Z``. See `unicode.toUpper
+ ## `_ for a version that works for any Unicode
+ ## character.
+ ##
+ ## **Deprecated since version 0.15.0**: use ``toUpperAscii`` instead.
+ toUpperAscii(c)
+
+proc toUpper*(s: string): string {.noSideEffect, procvar,
+ rtl, deprecated, extern: "nsuToUpperStr".} =
+ ## Converts `s` into upper case.
+ ##
+ ## This works only for the letters ``A-Z``. See `unicode.toUpper
+ ## `_ for a version that works for any Unicode
+ ## character.
+ ##
+ ## **Deprecated since version 0.15.0**: use ``toUpperAscii`` instead.
+ toUpperAscii(s)
+
+proc capitalize*(s: string): string {.noSideEffect, procvar,
+ rtl, deprecated, extern: "nsuCapitalize".} =
+ ## Converts the first character of `s` into upper case.
+ ##
+ ## This works only for the letters ``A-Z``.
+ ##
+ ## **Deprecated since version 0.15.0**: use ``capitalizeAscii`` instead.
+ capitalizeAscii(s)
proc normalize*(s: string): string {.noSideEffect, procvar,
rtl, extern: "nsuNormalize".} =
@@ -268,7 +408,7 @@ proc cmpIgnoreCase*(a, b: string): int {.noSideEffect,
var i = 0
var m = min(a.len, b.len)
while i < m:
- result = ord(toLower(a[i])) - ord(toLower(b[i]))
+ result = ord(toLowerAscii(a[i])) - ord(toLowerAscii(b[i]))
if result != 0: return
inc(i)
result = a.len - b.len
@@ -289,8 +429,8 @@ proc cmpIgnoreStyle*(a, b: string): int {.noSideEffect,
while true:
while a[i] == '_': inc(i)
while b[j] == '_': inc(j) # BUGFIX: typo
- var aa = toLower(a[i])
- var bb = toLower(b[j])
+ var aa = toLowerAscii(a[i])
+ var bb = toLowerAscii(b[j])
result = ord(aa) - ord(bb)
if result != 0 or aa == '\0': break
inc(i)
@@ -324,16 +464,77 @@ proc toOctal*(c: char): string {.noSideEffect, rtl, extern: "nsuToOctal".} =
result[i] = chr(val mod 8 + ord('0'))
val = val div 8
-iterator split*(s: string, seps: set[char] = Whitespace): string =
+proc isNilOrEmpty*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsNilOrEmpty".} =
+ ## Checks if `s` is nil or empty.
+ result = len(s) == 0
+
+proc isNilOrWhitespace*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsNilOrWhitespace".} =
+ ## Checks if `s` is nil or consists entirely of whitespace characters.
+ if len(s) == 0:
+ return true
+
+ result = true
+ for c in s:
+ if not c.isSpaceAscii():
+ return false
+
+proc substrEq(s: string, pos: int, substr: string): bool =
+ var i = 0
+ var length = substr.len
+ while i < length and s[pos+i] == substr[i]:
+ inc i
+
+ return i == length
+
+# --------- Private templates for different split separators -----------
+
+template stringHasSep(s: string, index: int, seps: set[char]): bool =
+ s[index] in seps
+
+template stringHasSep(s: string, index: int, sep: char): bool =
+ s[index] == sep
+
+template stringHasSep(s: string, index: int, sep: string): bool =
+ s.substrEq(index, sep)
+
+template splitCommon(s, sep, maxsplit, sepLen) =
+ ## Common code for split procedures
+ var last = 0
+ var splits = maxsplit
+
+ if len(s) > 0:
+ while last <= len(s):
+ var first = last
+ while last < len(s) and not stringHasSep(s, last, sep):
+ inc(last)
+ if splits == 0: last = len(s)
+ yield substr(s, first, last-1)
+ if splits == 0: break
+ dec(splits)
+ inc(last, sepLen)
+
+template oldSplit(s, seps, maxsplit) =
+ var last = 0
+ var splits = maxsplit
+ assert(not ('\0' in seps))
+ while last < len(s):
+ while s[last] in seps: inc(last)
+ var first = last
+ while last < len(s) and s[last] notin seps: inc(last)
+ if first <= last-1:
+ if splits == 0: last = len(s)
+ yield substr(s, first, last-1)
+ if splits == 0: break
+ dec(splits)
+
+iterator split*(s: string, seps: set[char] = Whitespace,
+ maxsplit: int = -1): string =
## Splits the string `s` into substrings using a group of separators.
##
- ## Substrings are separated by a substring containing only `seps`. Note
- ## that whole sequences of characters found in ``seps`` will be counted as
- ## a single split point and leading/trailing separators will be ignored.
- ## The following example:
+ ## Substrings are separated by a substring containing only `seps`.
##
## .. code-block:: nim
- ## for word in split(" this is an example "):
+ ## for word in split("this\lis an\texample"):
## writeLine(stdout, word)
##
## ...generates this output:
@@ -347,7 +548,7 @@ iterator split*(s: string, seps: set[char] = Whitespace): string =
## And the following code:
##
## .. code-block:: nim
- ## for word in split(";;this;is;an;;example;;;", {';'}):
+ ## for word in split("this:is;an$example", {';', ':', '$'}):
## writeLine(stdout, word)
##
## ...produces the same output as the first example. The code:
@@ -368,22 +569,26 @@ iterator split*(s: string, seps: set[char] = Whitespace): string =
## "08"
## "08.398990"
##
- var last = 0
- assert(not ('\0' in seps))
- while last < len(s):
- while s[last] in seps: inc(last)
- var first = last
- while last < len(s) and s[last] notin seps: inc(last) # BUGFIX!
- if first <= last-1:
- yield substr(s, first, last-1)
+ when defined(nimOldSplit):
+ oldSplit(s, seps, maxsplit)
+ else:
+ splitCommon(s, seps, maxsplit, 1)
-iterator split*(s: string, sep: char): string =
+iterator splitWhitespace*(s: string): string =
+ ## Splits at whitespace.
+ oldSplit(s, Whitespace, -1)
+
+proc splitWhitespace*(s: string): seq[string] {.noSideEffect,
+ rtl, extern: "nsuSplitWhitespace".} =
+ ## The same as the `splitWhitespace <#splitWhitespace.i,string>`_
+ ## iterator, but is a proc that returns a sequence of substrings.
+ accumulateResult(splitWhitespace(s))
+
+iterator split*(s: string, sep: char, maxsplit: int = -1): string =
## Splits the string `s` into substrings using a single separator.
##
## Substrings are separated by the character `sep`.
- ## Unlike the version of the iterator which accepts a set of separator
- ## characters, this proc will not coalesce groups of the
- ## separator, returning a string for each found character. The code:
+ ## The code:
##
## .. code-block:: nim
## for word in split(";;this;is;an;;example;;;", ';'):
@@ -403,28 +608,118 @@ iterator split*(s: string, sep: char): string =
## ""
## ""
##
- var last = 0
- assert('\0' != sep)
- if len(s) > 0:
- # `<=` is correct here for the edge cases!
- while last <= len(s):
- var first = last
- while last < len(s) and s[last] != sep: inc(last)
- yield substr(s, first, last-1)
- inc(last)
+ splitCommon(s, sep, maxsplit, 1)
-iterator split*(s: string, sep: string): string =
+iterator split*(s: string, sep: string, maxsplit: int = -1): string =
## Splits the string `s` into substrings using a string separator.
##
## Substrings are separated by the string `sep`.
- var last = 0
+ ## The code:
+ ##
+ ## .. code-block:: nim
+ ## for word in split("thisDATAisDATAcorrupted", "DATA"):
+ ## writeLine(stdout, word)
+ ##
+ ## Results in:
+ ##
+ ## .. code-block::
+ ## "this"
+ ## "is"
+ ## "corrupted"
+ ##
+
+ splitCommon(s, sep, maxsplit, sep.len)
+
+template rsplitCommon(s, sep, maxsplit, sepLen) =
+ ## Common code for rsplit functions
+ var
+ last = s.len - 1
+ first = last
+ splits = maxsplit
+ startPos = 0
+
if len(s) > 0:
- while last <= len(s):
- var first = last
- while last < len(s) and s.substr(last, last + = -1:
+ while first >= 0 and not stringHasSep(s, first, sep):
+ dec(first)
+
+ if splits == 0:
+ # No more splits means set first to the beginning
+ first = -1
+
+ if first == -1:
+ startPos = 0
+ else:
+ startPos = first + sepLen
+
+ yield substr(s, startPos, last)
+
+ if splits == 0:
+ break
+
+ dec(splits)
+ dec(first)
+
+ last = first
+
+iterator rsplit*(s: string, seps: set[char] = Whitespace,
+ maxsplit: int = -1): string =
+ ## Splits the string `s` into substrings from the right using a
+ ## string separator. Works exactly the same as `split iterator
+ ## <#split.i,string,char>`_ except in reverse order.
+ ##
+ ## .. code-block:: nim
+ ## for piece in "foo bar".rsplit(WhiteSpace):
+ ## echo piece
+ ##
+ ## Results in:
+ ##
+ ## .. code-block:: nim
+ ## "bar"
+ ## "foo"
+ ##
+ ## Substrings are separated from the right by the set of chars `seps`
+
+ rsplitCommon(s, seps, maxsplit, 1)
+
+iterator rsplit*(s: string, sep: char,
+ maxsplit: int = -1): string =
+ ## Splits the string `s` into substrings from the right using a
+ ## string separator. Works exactly the same as `split iterator
+ ## <#split.i,string,char>`_ except in reverse order.
+ ##
+ ## .. code-block:: nim
+ ## for piece in "foo:bar".rsplit(':'):
+ ## echo piece
+ ##
+ ## Results in:
+ ##
+ ## .. code-block:: nim
+ ## "bar"
+ ## "foo"
+ ##
+ ## Substrings are separated from the right by the char `sep`
+ rsplitCommon(s, sep, maxsplit, 1)
+
+iterator rsplit*(s: string, sep: string, maxsplit: int = -1,
+ keepSeparators: bool = false): string =
+ ## Splits the string `s` into substrings from the right using a
+ ## string separator. Works exactly the same as `split iterator
+ ## <#split.i,string,string>`_ except in reverse order.
+ ##
+ ## .. code-block:: nim
+ ## for piece in "foothebar".rsplit("the"):
+ ## echo piece
+ ##
+ ## Results in:
+ ##
+ ## .. code-block:: nim
+ ## "bar"
+ ## "foo"
+ ##
+ ## Substrings are separated from the right by the string `sep`
+ rsplitCommon(s, sep, maxsplit, sep.len)
iterator splitLines*(s: string): string =
## Splits the string `s` into its containing lines.
@@ -493,25 +788,92 @@ proc countLines*(s: string): int {.noSideEffect,
else: discard
inc i
-proc split*(s: string, seps: set[char] = Whitespace): seq[string] {.
+proc split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): seq[string] {.
noSideEffect, rtl, extern: "nsuSplitCharSet".} =
## The same as the `split iterator <#split.i,string,set[char]>`_, but is a
## proc that returns a sequence of substrings.
- accumulateResult(split(s, seps))
+ accumulateResult(split(s, seps, maxsplit))
-proc split*(s: string, sep: char): seq[string] {.noSideEffect,
+proc split*(s: string, sep: char, maxsplit: int = -1): seq[string] {.noSideEffect,
rtl, extern: "nsuSplitChar".} =
## The same as the `split iterator <#split.i,string,char>`_, but is a proc
## that returns a sequence of substrings.
- accumulateResult(split(s, sep))
+ accumulateResult(split(s, sep, maxsplit))
-proc split*(s: string, sep: string): seq[string] {.noSideEffect,
+proc split*(s: string, sep: string, maxsplit: int = -1): seq[string] {.noSideEffect,
rtl, extern: "nsuSplitString".} =
## Splits the string `s` into substrings using a string separator.
##
## Substrings are separated by the string `sep`. This is a wrapper around the
## `split iterator <#split.i,string,string>`_.
- accumulateResult(split(s, sep))
+ accumulateResult(split(s, sep, maxsplit))
+
+proc rsplit*(s: string, seps: set[char] = Whitespace,
+ maxsplit: int = -1): seq[string]
+ {.noSideEffect, rtl, extern: "nsuRSplitCharSet".} =
+ ## The same as the `rsplit iterator <#rsplit.i,string,set[char]>`_, but is a
+ ## proc that returns a sequence of substrings.
+ ##
+ ## A possible common use case for `rsplit` is path manipulation,
+ ## particularly on systems that don't use a common delimiter.
+ ##
+ ## For example, if a system had `#` as a delimiter, you could
+ ## do the following to get the tail of the path:
+ ##
+ ## .. code-block:: nim
+ ## var tailSplit = rsplit("Root#Object#Method#Index", {'#'}, maxsplit=1)
+ ##
+ ## Results in `tailSplit` containing:
+ ##
+ ## .. code-block:: nim
+ ## @["Root#Object#Method", "Index"]
+ ##
+ accumulateResult(rsplit(s, seps, maxsplit))
+ result.reverse()
+
+proc rsplit*(s: string, sep: char, maxsplit: int = -1): seq[string]
+ {.noSideEffect, rtl, extern: "nsuRSplitChar".} =
+ ## The same as the `split iterator <#rsplit.i,string,char>`_, but is a proc
+ ## that returns a sequence of substrings.
+ ##
+ ## A possible common use case for `rsplit` is path manipulation,
+ ## particularly on systems that don't use a common delimiter.
+ ##
+ ## For example, if a system had `#` as a delimiter, you could
+ ## do the following to get the tail of the path:
+ ##
+ ## .. code-block:: nim
+ ## var tailSplit = rsplit("Root#Object#Method#Index", '#', maxsplit=1)
+ ##
+ ## Results in `tailSplit` containing:
+ ##
+ ## .. code-block:: nim
+ ## @["Root#Object#Method", "Index"]
+ ##
+ accumulateResult(rsplit(s, sep, maxsplit))
+ result.reverse()
+
+proc rsplit*(s: string, sep: string, maxsplit: int = -1): seq[string]
+ {.noSideEffect, rtl, extern: "nsuRSplitString".} =
+ ## The same as the `split iterator <#rsplit.i,string,string>`_, but is a proc
+ ## that returns a sequence of substrings.
+ ##
+ ## A possible common use case for `rsplit` is path manipulation,
+ ## particularly on systems that don't use a common delimiter.
+ ##
+ ## For example, if a system had `#` as a delimiter, you could
+ ## do the following to get the tail of the path:
+ ##
+ ## .. code-block:: nim
+ ## var tailSplit = rsplit("Root#Object#Method#Index", "#", maxsplit=1)
+ ##
+ ## Results in `tailSplit` containing:
+ ##
+ ## .. code-block:: nim
+ ## @["Root#Object#Method", "Index"]
+ ##
+ accumulateResult(rsplit(s, sep, maxsplit))
+ result.reverse()
proc toHex*(x: BiggestInt, len: Positive): string {.noSideEffect,
rtl, extern: "nsuToHex".} =
@@ -530,6 +892,10 @@ proc toHex*(x: BiggestInt, len: Positive): string {.noSideEffect,
# handle negative overflow
if n == 0 and x < 0: n = -1
+proc toHex*[T](x: T): string =
+ ## Shortcut for ``toHex(x, T.sizeOf * 2)``
+ toHex(x, T.sizeOf * 2)
+
proc intToStr*(x: int, minchars: Positive = 1): string {.noSideEffect,
rtl, extern: "nsuIntToStr".} =
## Converts `x` to its decimal representation.
@@ -560,6 +926,24 @@ proc parseBiggestInt*(s: string): BiggestInt {.noSideEffect, procvar,
if L != s.len or L == 0:
raise newException(ValueError, "invalid integer: " & s)
+proc parseUInt*(s: string): uint {.noSideEffect, procvar,
+ rtl, extern: "nsuParseUInt".} =
+ ## Parses a decimal unsigned integer value contained in `s`.
+ ##
+ ## If `s` is not a valid integer, `ValueError` is raised.
+ var L = parseutils.parseUInt(s, result, 0)
+ if L != s.len or L == 0:
+ raise newException(ValueError, "invalid unsigned integer: " & s)
+
+proc parseBiggestUInt*(s: string): uint64 {.noSideEffect, procvar,
+ rtl, extern: "nsuParseBiggestUInt".} =
+ ## Parses a decimal unsigned integer value contained in `s`.
+ ##
+ ## If `s` is not a valid integer, `ValueError` is raised.
+ var L = parseutils.parseBiggestUInt(s, result, 0)
+ if L != s.len or L == 0:
+ raise newException(ValueError, "invalid unsigned integer: " & s)
+
proc parseFloat*(s: string): float {.noSideEffect, procvar,
rtl, extern: "nsuParseFloat".} =
## Parses a decimal floating point value contained in `s`. If `s` is not
@@ -651,7 +1035,7 @@ proc repeat*(s: string, n: Natural): string {.noSideEffect,
result = newStringOfCap(n * s.len)
for i in 1..n: result.add(s)
-template spaces*(n: Natural): string = repeat(' ',n)
+template spaces*(n: Natural): string = repeat(' ', n)
## Returns a String with `n` space characters. You can use this proc
## to left align strings. Example:
##
@@ -766,8 +1150,7 @@ proc indent*(s: string, count: Natural, padding: string = " "): string
{.noSideEffect, rtl, extern: "nsuIndent".} =
## Indents each line in ``s`` by ``count`` amount of ``padding``.
##
- ## **Note:** This currently does not preserve the specific new line characters
- ## used.
+ ## **Note:** This does not preserve the new line characters used in ``s``.
result = ""
var i = 0
for line in s.splitLines():
@@ -778,32 +1161,39 @@ proc indent*(s: string, count: Natural, padding: string = " "): string
result.add(line)
i.inc
-proc unindent*(s: string, eatAllIndent = false): string {.
- noSideEffect, rtl, extern: "nsuUnindent".} =
- ## Unindents `s`.
- result = newStringOfCap(s.len)
+proc unindent*(s: string, count: Natural, padding: string = " "): string
+ {.noSideEffect, rtl, extern: "nsuUnindent".} =
+ ## Unindents each line in ``s`` by ``count`` amount of ``padding``.
+ ##
+ ## **Note:** This does not preserve the new line characters used in ``s``.
+ result = ""
var i = 0
- var pattern = true
- var indent = 0
- while s[i] == ' ': inc i
- var level = if i == 0: -1 else: i
- while i < s.len:
- if s[i] == ' ':
- if i > 0 and s[i-1] in {'\l', '\c'}:
- pattern = true
- indent = 0
- if pattern:
- inc(indent)
- if indent > level and not eatAllIndent:
- result.add(s[i])
- if level < 0: level = indent
- else:
- # a space somewhere: do not delete
- result.add(s[i])
- else:
- pattern = false
- result.add(s[i])
- inc i
+ for line in s.splitLines():
+ if i != 0:
+ result.add("\n")
+ var indentCount = 0
+ for j in 0..= leftPadding and i < leftPadding + s.len:
+ # we are where the string should be located
+ result[i] = s[i-leftPadding]
+ else:
+ # we are either before or after where
+ # the string s should go
+ result[i] = fillChar
+
proc count*(s: string, sub: string, overlapping: bool = false): int {.
noSideEffect, rtl, extern: "nsuCountString".} =
## Count the occurrences of a substring `sub` in the string `s`.
@@ -1420,28 +1846,216 @@ proc formatFloat*(f: float, format: FloatFormatMode = ffDefault,
## after the decimal point for Nim's ``float`` type.
result = formatBiggestFloat(f, format, precision, decimalSep)
-proc formatSize*(bytes: BiggestInt, decimalSep = '.'): string =
- ## Rounds and formats `bytes`. Examples:
+proc trimZeros*(x: var string) {.noSideEffect.} =
+ ## Trim trailing zeros from a formatted floating point
+ ## value (`x`). Modifies the passed value.
+ var spl: seq[string]
+ if x.contains('.') or x.contains(','):
+ if x.contains('e'):
+ spl= x.split('e')
+ x = spl[0]
+ while x[x.high] == '0':
+ x.setLen(x.len-1)
+ if x[x.high] in [',', '.']:
+ x.setLen(x.len-1)
+ if spl.len > 0:
+ x &= "e" & spl[1]
+
+type
+ BinaryPrefixMode* = enum ## the different names for binary prefixes
+ bpIEC, # use the IEC/ISO standard prefixes such as kibi
+ bpColloquial # use the colloquial kilo, mega etc
+
+proc formatSize*(bytes: int64,
+ decimalSep = '.',
+ prefix = bpIEC,
+ includeSpace = false): string {.noSideEffect.} =
+ ## Rounds and formats `bytes`.
+ ##
+ ## By default, uses the IEC/ISO standard binary prefixes, so 1024 will be
+ ## formatted as 1KiB. Set prefix to `bpColloquial` to use the colloquial
+ ## names from the SI standard (e.g. k for 1000 being reused as 1024).
+ ##
+ ## `includeSpace` can be set to true to include the (SI preferred) space
+ ## between the number and the unit (e.g. 1 KiB).
+ ##
+ ## Examples:
##
## .. code-block:: nim
##
- ## formatSize(1'i64 shl 31 + 300'i64) == "2.204GB"
- ## formatSize(4096) == "4KB"
+ ## formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB"
+ ## formatSize((2.234*1024*1024).int) == "2.234MiB"
+ ## formatSize(4096, includeSpace=true) == "4 KiB"
+ ## formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB"
+ ## formatSize(4096) == "4KiB"
+ ## formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB"
##
- template frmt(a, b, c: expr): expr =
- let bs = $b
- insertSep($a) & decimalSep & bs.substr(0, 2) & c
- let gigabytes = bytes shr 30
- let megabytes = bytes shr 20
- let kilobytes = bytes shr 10
- if gigabytes != 0:
- result = frmt(gigabytes, megabytes, "GB")
- elif megabytes != 0:
- result = frmt(megabytes, kilobytes, "MB")
- elif kilobytes != 0:
- result = frmt(kilobytes, bytes, "KB")
+ const iecPrefixes = ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"]
+ const collPrefixes = ["", "k", "M", "G", "T", "P", "E", "Z", "Y"]
+ var
+ xb: int64 = bytes
+ fbytes: float
+ last_xb: int64 = bytes
+ matchedIndex: int
+ prefixes: array[9, string]
+ if prefix == bpColloquial:
+ prefixes = collPrefixes
else:
- result = insertSep($bytes) & "B"
+ prefixes = iecPrefixes
+
+ # Iterate through prefixes seeing if value will be greater than
+ # 0 in each case
+ for index in 1..= 1000.0:
+ significand *= 0.001
+ fexponent += 3
+ # Components of the result:
+ result = significand.formatBiggestFloat(ffDecimal, precision, decimalSep='.')
+ exponent = fexponent.int()
+
+ splitResult = result.split('.')
+ result = splitResult[0]
+ # result should have at most one decimal character
+ if splitResult.len() > 1:
+ # If trim is set, we get rid of trailing zeros. Don't use trimZeros here as
+ # we can be a bit more efficient through knowledge that there will never be
+ # an exponent in this part.
+ if trim:
+ while splitResult[1].endsWith("0"):
+ # Trim last character
+ splitResult[1].setLen(splitResult[1].len-1)
+ if splitResult[1].len() > 0:
+ result &= decimalSep & splitResult[1]
+ else:
+ result &= decimalSep & splitResult[1]
+
+ # Combine the results accordingly
+ if siPrefix and exponent != 0:
+ var p = getPrefix(exponent)
+ if p != ' ':
+ suffix = " " & p
+ exponent = 0 # Exponent replaced by SI prefix
+ if suffix == "" and unit != nil:
+ suffix = " "
+ if unit != nil:
+ suffix &= unit
+ if exponent != 0:
+ result &= "e" & $exponent
+ result &= suffix
proc findNormalized(x: string, inArray: openArray[string]): int =
var i = 0
@@ -1637,9 +2251,14 @@ when isMainModule:
["1,0e-11", "1,0e-011"]
doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c"
- when not defined(testing):
- echo formatSize(1'i64 shl 31 + 300'i64) # == "4,GB"
- echo formatSize(1'i64 shl 31)
+
+ block: # formatSize tests
+ doAssert formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB"
+ doAssert formatSize((2.234*1024*1024).int) == "2.234MiB"
+ doAssert formatSize(4096) == "4KiB"
+ doAssert formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB"
+ doAssert formatSize(4096, includeSpace=true) == "4 KiB"
+ doAssert formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB"
doAssert "$animal eats $food." % ["animal", "The cat", "food", "fish"] ==
"The cat eats fish."
@@ -1652,6 +2271,11 @@ when isMainModule:
doAssert parseEnum("invalid enum value", enC) == enC
+ doAssert center("foo", 13) == " foo "
+ doAssert center("foo", 0) == "foo"
+ doAssert center("foo", 3, fillChar = 'a') == "foo"
+ doAssert center("foo", 10, fillChar = '\t') == "\t\t\tfoo\t\t\t\t"
+
doAssert count("foofoofoo", "foofoo") == 1
doAssert count("foofoofoo", "foofoo", overlapping = true) == 2
doAssert count("foofoofoo", 'f') == 3
@@ -1668,13 +2292,13 @@ when isMainModule:
doAssert " foo\n bar".indent(4, "Q") == "QQQQ foo\nQQQQ bar"
- doAssert isAlpha('r')
- doAssert isAlpha('A')
- doAssert(not isAlpha('$'))
+ doAssert isAlphaAscii('r')
+ doAssert isAlphaAscii('A')
+ doAssert(not isAlphaAscii('$'))
- doAssert isAlpha("Rasp")
- doAssert isAlpha("Args")
- doAssert(not isAlpha("$Tomato"))
+ doAssert isAlphaAscii("Rasp")
+ doAssert isAlphaAscii("Args")
+ doAssert(not isAlphaAscii("$Tomato"))
doAssert isAlphaNumeric('3')
doAssert isAlphaNumeric('R')
@@ -1693,35 +2317,131 @@ when isMainModule:
doAssert(not isDigit("12.33"))
doAssert(not isDigit("A45b"))
- doAssert isSpace('\t')
- doAssert isSpace('\l')
- doAssert(not isSpace('A'))
+ doAssert isSpaceAscii('\t')
+ doAssert isSpaceAscii('\l')
+ doAssert(not isSpaceAscii('A'))
- doAssert isSpace("\t\l \v\r\f")
- doAssert isSpace(" ")
- doAssert(not isSpace("ABc \td"))
+ doAssert isSpaceAscii("\t\l \v\r\f")
+ doAssert isSpaceAscii(" ")
+ doAssert(not isSpaceAscii("ABc \td"))
- doAssert isLower('a')
- doAssert isLower('z')
- doAssert(not isLower('A'))
- doAssert(not isLower('5'))
- doAssert(not isLower('&'))
+ doAssert(isNilOrEmpty(""))
+ doAssert(isNilOrEmpty(nil))
+ doAssert(not isNilOrEmpty("test"))
+ doAssert(not isNilOrEmpty(" "))
- doAssert isLower("abcd")
- doAssert(not isLower("abCD"))
- doAssert(not isLower("33aa"))
+ doAssert(isNilOrWhitespace(""))
+ doAssert(isNilOrWhitespace(nil))
+ doAssert(isNilOrWhitespace(" "))
+ doAssert(isNilOrWhitespace("\t\l \v\r\f"))
+ doAssert(not isNilOrWhitespace("ABc \td"))
- doAssert isUpper('A')
- doAssert(not isUpper('b'))
- doAssert(not isUpper('5'))
- doAssert(not isUpper('%'))
+ doAssert isLowerAscii('a')
+ doAssert isLowerAscii('z')
+ doAssert(not isLowerAscii('A'))
+ doAssert(not isLowerAscii('5'))
+ doAssert(not isLowerAscii('&'))
+
+ doAssert isLowerAscii("abcd")
+ doAssert(not isLowerAscii("abCD"))
+ doAssert(not isLowerAscii("33aa"))
+
+ doAssert isUpperAscii('A')
+ doAssert(not isUpperAscii('b'))
+ doAssert(not isUpperAscii('5'))
+ doAssert(not isUpperAscii('%'))
+
+ doAssert isUpperAscii("ABC")
+ doAssert(not isUpperAscii("AAcc"))
+ doAssert(not isUpperAscii("A#$"))
+
+ doAssert rsplit("foo bar", seps=Whitespace) == @["foo", "bar"]
+ doAssert rsplit(" foo bar", seps=Whitespace, maxsplit=1) == @[" foo", "bar"]
+ doAssert rsplit(" foo bar ", seps=Whitespace, maxsplit=1) == @[" foo bar", ""]
+ doAssert rsplit(":foo:bar", sep=':') == @["", "foo", "bar"]
+ doAssert rsplit(":foo:bar", sep=':', maxsplit=2) == @["", "foo", "bar"]
+ doAssert rsplit(":foo:bar", sep=':', maxsplit=3) == @["", "foo", "bar"]
+ doAssert rsplit("foothebar", sep="the") == @["foo", "bar"]
- doAssert isUpper("ABC")
- doAssert(not isUpper("AAcc"))
- doAssert(not isUpper("A#$"))
doAssert(unescape(r"\x013", "", "") == "\x013")
doAssert join(["foo", "bar", "baz"]) == "foobarbaz"
doAssert join(@["foo", "bar", "baz"], ", ") == "foo, bar, baz"
doAssert join([1, 2, 3]) == "123"
doAssert join(@[1, 2, 3], ", ") == "1, 2, 3"
+
+ doAssert """~~!!foo
+~~!!bar
+~~!!baz""".unindent(2, "~~!!") == "foo\nbar\nbaz"
+
+ doAssert """~~!!foo
+~~!!bar
+~~!!baz""".unindent(2, "~~!!aa") == "~~!!foo\n~~!!bar\n~~!!baz"
+ doAssert """~~foo
+~~ bar
+~~ baz""".unindent(4, "~") == "foo\n bar\n baz"
+ doAssert """foo
+bar
+ baz
+ """.unindent(4) == "foo\nbar\nbaz\n"
+ doAssert """foo
+ bar
+ baz
+ """.unindent(2) == "foo\n bar\n baz\n"
+ doAssert """foo
+ bar
+ baz
+ """.unindent(100) == "foo\nbar\nbaz\n"
+
+ doAssert """foo
+ foo
+ bar
+ """.unindent() == "foo\nfoo\nbar\n"
+
+ let s = " this is an example "
+ let s2 = ":this;is;an:example;;"
+
+ doAssert s.split() == @["", "this", "is", "an", "example", "", ""]
+ doAssert s2.split(seps={':', ';'}) == @["", "this", "is", "an", "example", "", ""]
+ doAssert s.split(maxsplit=4) == @["", "this", "is", "an", "example "]
+ doAssert s.split(' ', maxsplit=1) == @["", "this is an example "]
+ doAssert s.split(" ", maxsplit=4) == @["", "this", "is", "an", "example "]
+
+ block: # formatEng tests
+ doAssert formatEng(0, 2, trim=false) == "0.00"
+ doAssert formatEng(0, 2) == "0"
+ doAssert formatEng(53, 2, trim=false) == "53.00"
+ doAssert formatEng(0.053, 2, trim=false) == "53.00e-3"
+ doAssert formatEng(0.053, 4, trim=false) == "53.0000e-3"
+ doAssert formatEng(0.053, 4, trim=true) == "53e-3"
+ doAssert formatEng(0.053, 0) == "53e-3"
+ doAssert formatEng(52731234) == "52.731234e6"
+ doAssert formatEng(-52731234) == "-52.731234e6"
+ doAssert formatEng(52731234, 1) == "52.7e6"
+ doAssert formatEng(-52731234, 1) == "-52.7e6"
+ doAssert formatEng(52731234, 1, decimalSep=',') == "52,7e6"
+ doAssert formatEng(-52731234, 1, decimalSep=',') == "-52,7e6"
+
+ doAssert formatEng(4100, siPrefix=true, unit="V") == "4.1 kV"
+ doAssert formatEng(4.1, siPrefix=true, unit="V") == "4.1 V"
+ doAssert formatEng(4.1, siPrefix=true) == "4.1" # Note lack of space
+ doAssert formatEng(4100, siPrefix=true) == "4.1 k"
+ doAssert formatEng(4.1, siPrefix=true, unit="") == "4.1 " # Includes space
+ doAssert formatEng(4100, siPrefix=true, unit="") == "4.1 k"
+ doAssert formatEng(4100) == "4.1e3"
+ doAssert formatEng(4100, unit="V") == "4.1e3 V"
+ doAssert formatEng(4100, unit="") == "4.1e3 " # Space with unit=""
+ # Don't use SI prefix as number is too big
+ doAssert formatEng(3.1e22, siPrefix=true, unit="a") == "31e21 a"
+ # Don't use SI prefix as number is too small
+ doAssert formatEng(3.1e-25, siPrefix=true, unit="A") == "310e-27 A"
+
+ block: # startsWith / endsWith char tests
+ var s = "abcdef"
+ doAssert s.startsWith('a')
+ doAssert s.startsWith('b') == false
+ doAssert s.endsWith('f')
+ doAssert s.endsWith('a') == false
+ doAssert s.endsWith('\0') == false
+
+ #echo("strutils tests passed")
diff --git a/lib/pure/subexes.nim b/lib/pure/subexes.nim
index 5824ace811..351b3c0867 100644
--- a/lib/pure/subexes.nim
+++ b/lib/pure/subexes.nim
@@ -9,7 +9,7 @@
## Nim support for `substitution expressions`:idx: (`subex`:idx:).
##
-## .. include:: ../doc/subexes.txt
+## .. include:: ../../doc/subexes.txt
##
{.push debugger:off .} # the user does not want to trace a part
@@ -390,11 +390,11 @@ when isMainModule:
doAssert(("type MyEnum* = enum\n $', '2i'\n '{..}" % ["fieldA",
"fieldB", "FiledClkad", "fieldD", "fieldE", "longishFieldName"]).replace(" \n", "\n") ==
- strutils.unindent """
+ strutils.unindent("""
type MyEnum* = enum
fieldA, fieldB,
FiledClkad, fieldD,
- fieldE, longishFieldName""")
+ fieldE, longishFieldName""", 6))
doAssert subex"$1($', '{2..})" % ["f", "a", "b", "c"] == "f(a, b, c)"
@@ -404,8 +404,8 @@ when isMainModule:
doAssert((subex("type\n Enum = enum\n $', '40c'\n '{..}") % [
"fieldNameA", "fieldNameB", "fieldNameC", "fieldNameD"]).replace(" \n", "\n") ==
- strutils.unindent """
+ strutils.unindent("""
type
Enum = enum
fieldNameA, fieldNameB, fieldNameC,
- fieldNameD""")
+ fieldNameD""", 6))
diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim
index 60f064e7c7..1f34ec07e4 100644
--- a/lib/pure/terminal.nim
+++ b/lib/pure/terminal.nim
@@ -384,7 +384,7 @@ proc setForegroundColor*(f: File, fg: ForegroundColor, bright=false) =
var old = getAttributes(h) and not 0x0007
if bright:
old = old or FOREGROUND_INTENSITY
- const lookup: array [ForegroundColor, int] = [
+ const lookup: array[ForegroundColor, int] = [
0,
(FOREGROUND_RED),
(FOREGROUND_GREEN),
@@ -406,7 +406,7 @@ proc setBackgroundColor*(f: File, bg: BackgroundColor, bright=false) =
var old = getAttributes(h) and not 0x0070
if bright:
old = old or BACKGROUND_INTENSITY
- const lookup: array [BackgroundColor, int] = [
+ const lookup: array[BackgroundColor, int] = [
0,
(BACKGROUND_RED),
(BACKGROUND_GREEN),
@@ -493,17 +493,21 @@ template styledEcho*(args: varargs[expr]): expr =
## Echoes styles arguments to stdout using ``styledWriteLine``.
callStyledEcho(args)
-when defined(nimdoc):
- proc getch*(): char =
- ## Read a single character from the terminal, blocking until it is entered.
- ## The character is not printed to the terminal. This is not available for
- ## Windows.
- discard
-elif not defined(windows):
- proc getch*(): char =
- ## Read a single character from the terminal, blocking until it is entered.
- ## The character is not printed to the terminal. This is not available for
- ## Windows.
+proc getch*(): char =
+ ## Read a single character from the terminal, blocking until it is entered.
+ ## The character is not printed to the terminal.
+ when defined(windows):
+ let fd = getStdHandle(STD_INPUT_HANDLE)
+ var keyEvent = KEY_EVENT_RECORD()
+ var numRead: cint
+ while true:
+ # Block until character is entered
+ doAssert(waitForSingleObject(fd, INFINITE) == WAIT_OBJECT_0)
+ doAssert(readConsoleInput(fd, addr(keyEvent), 1, addr(numRead)) != 0)
+ if numRead == 0 or keyEvent.eventType != 1 or keyEvent.bKeyDown == 0:
+ continue
+ return char(keyEvent.uChar)
+ else:
let fd = getFileHandle(stdin)
var oldMode: Termios
discard fd.tcgetattr(addr oldMode)
diff --git a/lib/pure/times.nim b/lib/pure/times.nim
index 29ae52d479..d6eb29e1c8 100644
--- a/lib/pure/times.nim
+++ b/lib/pure/times.nim
@@ -64,8 +64,9 @@ when defined(posix) and not defined(JS):
proc posix_gettimeofday(tp: var Timeval, unused: pointer = nil) {.
importc: "gettimeofday", header: "".}
+ when not defined(freebsd) and not defined(netbsd) and not defined(openbsd):
+ var timezone {.importc, header: "".}: int
var
- timezone {.importc, header: "".}: int
tzname {.importc, header: "" .}: array[0..1, cstring]
# we also need tzset() to make sure that tzname is initialized
proc tzset() {.importc, header: "".}
@@ -94,7 +95,8 @@ elif defined(windows):
elif defined(JS):
type
- Time* {.importc.} = object
+ Time* = ref TimeObj
+ TimeObj {.importc.} = object
getDay: proc (): int {.tags: [], raises: [], benign.}
getFullYear: proc (): int {.tags: [], raises: [], benign.}
getHours: proc (): int {.tags: [], raises: [], benign.}
@@ -182,7 +184,16 @@ proc getGMTime*(t: Time): TimeInfo {.tags: [TimeEffect], raises: [], benign.}
## converts the calendar time `t` to broken-down time representation,
## expressed in Coordinated Universal Time (UTC).
-proc timeInfoToTime*(timeInfo: TimeInfo): Time {.tags: [], benign.}
+proc timeInfoToTime*(timeInfo: TimeInfo): Time {.tags: [], benign, deprecated.}
+ ## converts a broken-down time structure to
+ ## calendar time representation. The function ignores the specified
+ ## contents of the structure members `weekday` and `yearday` and recomputes
+ ## them from the other information in the broken-down time structure.
+ ##
+ ## **Warning:** This procedure is deprecated since version 0.14.0.
+ ## Use ``toTime`` instead.
+
+proc toTime*(timeInfo: TimeInfo): Time {.tags: [], benign.}
## converts a broken-down time structure to
## calendar time representation. The function ignores the specified
## contents of the structure members `weekday` and `yearday` and recomputes
@@ -356,16 +367,19 @@ proc `+`*(a: TimeInfo, interval: TimeInterval): TimeInfo =
##
## **Note:** This has been only briefly tested and it may not be
## very accurate.
- let t = toSeconds(timeInfoToTime(a))
+ let t = toSeconds(toTime(a))
let secs = toSeconds(a, interval)
- result = getLocalTime(fromSeconds(t + secs))
+ if a.tzname == "UTC":
+ result = getGMTime(fromSeconds(t + secs))
+ else:
+ result = getLocalTime(fromSeconds(t + secs))
proc `-`*(a: TimeInfo, interval: TimeInterval): TimeInfo =
## subtracts ``interval`` time from TimeInfo ``a``.
##
## **Note:** This has been only briefly tested, it is inaccurate especially
## when you subtract so much that you reach the Julian calendar.
- let t = toSeconds(timeInfoToTime(a))
+ let t = toSeconds(toTime(a))
var intval: TimeInterval
intval.milliseconds = - interval.milliseconds
intval.seconds = - interval.seconds
@@ -375,7 +389,10 @@ proc `-`*(a: TimeInfo, interval: TimeInterval): TimeInfo =
intval.months = - interval.months
intval.years = - interval.years
let secs = toSeconds(a, intval)
- result = getLocalTime(fromSeconds(t + secs))
+ if a.tzname == "UTC":
+ result = getGMTime(fromSeconds(t + secs))
+ else:
+ result = getLocalTime(fromSeconds(t + secs))
proc miliseconds*(t: TimeInterval): int {.deprecated.} = t.milliseconds
@@ -407,18 +424,32 @@ when not defined(JS):
when not defined(JS):
# C wrapper:
+ when defined(freebsd) or defined(netbsd) or defined(openbsd):
+ type
+ StructTM {.importc: "struct tm", final.} = object
+ second {.importc: "tm_sec".},
+ minute {.importc: "tm_min".},
+ hour {.importc: "tm_hour".},
+ monthday {.importc: "tm_mday".},
+ month {.importc: "tm_mon".},
+ year {.importc: "tm_year".},
+ weekday {.importc: "tm_wday".},
+ yearday {.importc: "tm_yday".},
+ isdst {.importc: "tm_isdst".}: cint
+ gmtoff {.importc: "tm_gmtoff".}: clong
+ else:
+ type
+ StructTM {.importc: "struct tm", final.} = object
+ second {.importc: "tm_sec".},
+ minute {.importc: "tm_min".},
+ hour {.importc: "tm_hour".},
+ monthday {.importc: "tm_mday".},
+ month {.importc: "tm_mon".},
+ year {.importc: "tm_year".},
+ weekday {.importc: "tm_wday".},
+ yearday {.importc: "tm_yday".},
+ isdst {.importc: "tm_isdst".}: cint
type
- StructTM {.importc: "struct tm", final.} = object
- second {.importc: "tm_sec".},
- minute {.importc: "tm_min".},
- hour {.importc: "tm_hour".},
- monthday {.importc: "tm_mday".},
- month {.importc: "tm_mon".},
- year {.importc: "tm_year".},
- weekday {.importc: "tm_wday".},
- yearday {.importc: "tm_yday".},
- isdst {.importc: "tm_isdst".}: cint
-
TimeInfoPtr = ptr StructTM
Clock {.importc: "clock_t".} = distinct int
@@ -446,30 +477,53 @@ when not defined(JS):
# our own procs on top of that:
proc tmToTimeInfo(tm: StructTM, local: bool): TimeInfo =
const
- weekDays: array [0..6, WeekDay] = [
+ weekDays: array[0..6, WeekDay] = [
dSun, dMon, dTue, dWed, dThu, dFri, dSat]
- TimeInfo(second: int(tm.second),
- minute: int(tm.minute),
- hour: int(tm.hour),
- monthday: int(tm.monthday),
- month: Month(tm.month),
- year: tm.year + 1900'i32,
- weekday: weekDays[int(tm.weekday)],
- yearday: int(tm.yearday),
- isDST: tm.isdst > 0,
- tzname: if local:
- if tm.isdst > 0:
- getTzname().DST
+ when defined(freebsd) or defined(netbsd) or defined(openbsd):
+ TimeInfo(second: int(tm.second),
+ minute: int(tm.minute),
+ hour: int(tm.hour),
+ monthday: int(tm.monthday),
+ month: Month(tm.month),
+ year: tm.year + 1900'i32,
+ weekday: weekDays[int(tm.weekday)],
+ yearday: int(tm.yearday),
+ isDST: tm.isdst > 0,
+ tzname: if local:
+ if tm.isdst > 0:
+ getTzname().DST
+ else:
+ getTzname().nonDST
else:
- getTzname().nonDST
- else:
- "UTC",
- timezone: if local: getTimezone() else: 0
- )
+ "UTC",
+ # BSD stores in `gmtoff` offset east of UTC in seconds,
+ # but posix systems using west of UTC in seconds
+ timezone: if local: -(tm.gmtoff) else: 0
+ )
+ else:
+ TimeInfo(second: int(tm.second),
+ minute: int(tm.minute),
+ hour: int(tm.hour),
+ monthday: int(tm.monthday),
+ month: Month(tm.month),
+ year: tm.year + 1900'i32,
+ weekday: weekDays[int(tm.weekday)],
+ yearday: int(tm.yearday),
+ isDST: tm.isdst > 0,
+ tzname: if local:
+ if tm.isdst > 0:
+ getTzname().DST
+ else:
+ getTzname().nonDST
+ else:
+ "UTC",
+ timezone: if local: getTimezone() else: 0
+ )
+
proc timeInfoToTM(t: TimeInfo): StructTM =
const
- weekDays: array [WeekDay, int8] = [1'i8,2'i8,3'i8,4'i8,5'i8,6'i8,0'i8]
+ weekDays: array[WeekDay, int8] = [1'i8,2'i8,3'i8,4'i8,5'i8,6'i8,0'i8]
result.second = t.second
result.minute = t.minute
result.hour = t.hour
@@ -517,6 +571,11 @@ when not defined(JS):
# because the header of mktime is broken in my version of libc
return mktime(timeInfoToTM(cTimeInfo))
+ proc toTime(timeInfo: TimeInfo): Time =
+ var cTimeInfo = timeInfo # for C++ we have to make a copy,
+ # because the header of mktime is broken in my version of libc
+ return mktime(timeInfoToTM(cTimeInfo))
+
proc toStringTillNL(p: cstring): string =
result = ""
var i = 0
@@ -550,7 +609,14 @@ when not defined(JS):
return ($tzname[0], $tzname[1])
proc getTimezone(): int =
- return timezone
+ when defined(freebsd) or defined(netbsd) or defined(openbsd):
+ var a = timec(nil)
+ let lt = localtime(addr(a))
+ # BSD stores in `gmtoff` offset east of UTC in seconds,
+ # but posix systems using west of UTC in seconds
+ return -(lt.gmtoff)
+ else:
+ return timezone
proc fromSeconds(since1970: float): Time = Time(since1970)
@@ -586,7 +652,7 @@ elif defined(JS):
return newDate()
const
- weekDays: array [0..6, WeekDay] = [
+ weekDays: array[0..6, WeekDay] = [
dSun, dMon, dTue, dWed, dThu, dFri, dSat]
proc getLocalTime(t: Time): TimeInfo =
@@ -618,7 +684,16 @@ elif defined(JS):
result.setFullYear(timeInfo.year)
result.setDate(timeInfo.monthday)
- proc `$`(timeInfo: TimeInfo): string = return $(timeInfoToTime(timeInfo))
+ proc toTime*(timeInfo: TimeInfo): Time =
+ result = internGetTime()
+ result.setSeconds(timeInfo.second)
+ result.setMinutes(timeInfo.minute)
+ result.setHours(timeInfo.hour)
+ result.setMonth(ord(timeInfo.month))
+ result.setFullYear(timeInfo.year)
+ result.setDate(timeInfo.monthday)
+
+ proc `$`(timeInfo: TimeInfo): string = return $(toTime(timeInfo))
proc `$`(time: Time): string = return $time.toLocaleString()
proc `-` (a, b: Time): int64 =
@@ -708,24 +783,24 @@ proc years*(y: int): TimeInterval {.inline.} =
proc `+=`*(t: var Time, ti: TimeInterval) =
## modifies `t` by adding the interval `ti`
- t = timeInfoToTime(getLocalTime(t) + ti)
+ t = toTime(getLocalTime(t) + ti)
proc `+`*(t: Time, ti: TimeInterval): Time =
## adds the interval `ti` to Time `t`
## by converting to localTime, adding the interval, and converting back
##
## ``echo getTime() + 1.day``
- result = timeInfoToTime(getLocalTime(t) + ti)
+ result = toTime(getLocalTime(t) + ti)
proc `-=`*(t: var Time, ti: TimeInterval) =
## modifies `t` by subtracting the interval `ti`
- t = timeInfoToTime(getLocalTime(t) - ti)
+ t = toTime(getLocalTime(t) - ti)
proc `-`*(t: Time, ti: TimeInterval): Time =
## adds the interval `ti` to Time `t`
##
## ``echo getTime() - 1.day``
- result = timeInfoToTime(getLocalTime(t) - ti)
+ result = toTime(getLocalTime(t) - ti)
proc formatToken(info: TimeInfo, token: string, buf: var string) =
## Helper of the format proc to parse individual tokens.
@@ -923,21 +998,14 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) =
info.monthday = value[j..j+1].parseInt()
j += 2
of "ddd":
- case value[j..j+2].toLower()
- of "sun":
- info.weekday = dSun
- of "mon":
- info.weekday = dMon
- of "tue":
- info.weekday = dTue
- of "wed":
- info.weekday = dWed
- of "thu":
- info.weekday = dThu
- of "fri":
- info.weekday = dFri
- of "sat":
- info.weekday = dSat
+ case value[j..j+2].toLowerAscii()
+ of "sun": info.weekday = dSun
+ of "mon": info.weekday = dMon
+ of "tue": info.weekday = dTue
+ of "wed": info.weekday = dWed
+ of "thu": info.weekday = dThu
+ of "fri": info.weekday = dFri
+ of "sat": info.weekday = dSat
else:
raise newException(ValueError,
"Couldn't parse day of week (ddd), got: " & value[j..j+2])
@@ -991,31 +1059,19 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) =
j += 2
info.month = Month(month-1)
of "MMM":
- case value[j..j+2].toLower():
- of "jan":
- info.month = mJan
- of "feb":
- info.month = mFeb
- of "mar":
- info.month = mMar
- of "apr":
- info.month = mApr
- of "may":
- info.month = mMay
- of "jun":
- info.month = mJun
- of "jul":
- info.month = mJul
- of "aug":
- info.month = mAug
- of "sep":
- info.month = mSep
- of "oct":
- info.month = mOct
- of "nov":
- info.month = mNov
- of "dec":
- info.month = mDec
+ case value[j..j+2].toLowerAscii():
+ of "jan": info.month = mJan
+ of "feb": info.month = mFeb
+ of "mar": info.month = mMar
+ of "apr": info.month = mApr
+ of "may": info.month = mMay
+ of "jun": info.month = mJun
+ of "jul": info.month = mJul
+ of "aug": info.month = mAug
+ of "sep": info.month = mSep
+ of "oct": info.month = mOct
+ of "nov": info.month = mNov
+ of "dec": info.month = mDec
else:
raise newException(ValueError,
"Couldn't parse month (MMM), got: " & value)
@@ -1112,7 +1168,7 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) =
"Couldn't parse timezone offset (zzz), got: " & value[j])
j += 6
of "ZZZ":
- info.tzname = value[j..j+2].toUpper()
+ info.tzname = value[j..j+2].toUpperAscii()
j += 3
else:
# Ignore the token and move forward in the value string by the same length
@@ -1193,7 +1249,7 @@ proc parse*(value, layout: string): TimeInfo =
parseToken(info, token, value, j)
token = ""
# Reset weekday as it might not have been provided and the default may be wrong
- info.weekday = getLocalTime(timeInfoToTime(info)).weekday
+ info.weekday = getLocalTime(toTime(info)).weekday
return info
# Leap year calculations are adapted from:
@@ -1204,7 +1260,7 @@ proc parse*(value, layout: string): TimeInfo =
proc countLeapYears*(yearSpan: int): int =
## Returns the number of leap years spanned by a given number of years.
##
- ## Note: for leap years, start date is assumed to be 1 AD.
+ ## **Note:** For leap years, start date is assumed to be 1 AD.
## counts the number of leap years up to January 1st of a given year.
## Keep in mind that if specified year is a leap year, the leap day
## has not happened before January 1st of that year.
@@ -1239,13 +1295,14 @@ proc getDayOfWeek*(day, month, year: int): WeekDay =
y = year - a
m = month + (12*a) - 2
d = (day + y + (y div 4) - (y div 100) + (y div 400) + (31*m) div 12) mod 7
- # The value of d is 0 for a Sunday, 1 for a Monday, 2 for a Tuesday, etc. so we must correct
- # for the WeekDay type.
+ # The value of d is 0 for a Sunday, 1 for a Monday, 2 for a Tuesday, etc.
+ # so we must correct for the WeekDay type.
if d == 0: return dSun
result = (d-1).WeekDay
proc getDayOfWeekJulian*(day, month, year: int): WeekDay =
- ## Returns the day of the week enum from day, month and year, according to the Julian calendar.
+ ## Returns the day of the week enum from day, month and year,
+ ## according to the Julian calendar.
# Day & month start from one.
let
a = (14 - month) div 12
@@ -1254,8 +1311,11 @@ proc getDayOfWeekJulian*(day, month, year: int): WeekDay =
d = (5 + day + y + (y div 4) + (31*m) div 12) mod 7
result = d.WeekDay
-proc timeToTimeInfo*(t: Time): TimeInfo =
+proc timeToTimeInfo*(t: Time): TimeInfo {.deprecated.} =
## Converts a Time to TimeInfo.
+ ##
+ ## **Warning:** This procedure is deprecated since version 0.14.0.
+ ## Use ``getLocalTime`` or ``getGMTime`` instead.
let
secs = t.toSeconds().int
daysSinceEpoch = secs div secondsInDay
@@ -1286,34 +1346,21 @@ proc timeToTimeInfo*(t: Time): TimeInfo =
s = daySeconds mod secondsInMin
result = TimeInfo(year: y, yearday: yd, month: m, monthday: md, weekday: wd, hour: h, minute: mi, second: s)
-proc timeToTimeInterval*(t: Time): TimeInterval =
+proc timeToTimeInterval*(t: Time): TimeInterval {.deprecated.} =
## Converts a Time to a TimeInterval.
-
- let
- secs = t.toSeconds().int
- daysSinceEpoch = secs div secondsInDay
- (yearsSinceEpoch, daysRemaining) = countYearsAndDays(daysSinceEpoch)
- daySeconds = secs mod secondsInDay
-
- result.years = yearsSinceEpoch + epochStartYear
-
- var
- mon = mJan
- days = daysRemaining
- daysInMonth = getDaysInMonth(mon, result.years)
-
- # calculate month and day remainder
- while days > daysInMonth and mon <= mDec:
- days -= daysInMonth
- mon.inc
- daysInMonth = getDaysInMonth(mon, result.years)
-
- result.months = mon.int + 1 # month is 1 indexed int
- result.days = days
- result.hours = daySeconds div secondsInHour + 1
- result.minutes = (daySeconds mod secondsInHour) div secondsInMin
- result.seconds = daySeconds mod secondsInMin
+ ##
+ ## **Warning:** This procedure is deprecated since version 0.14.0.
+ ## Use ``toTimeInterval`` instead.
# Milliseconds not available from Time
+ var tInfo = t.getLocalTime()
+ initInterval(0, tInfo.second, tInfo.minute, tInfo.hour, tInfo.weekday.ord, tInfo.month.ord, tInfo.year)
+
+proc toTimeInterval*(t: Time): TimeInterval =
+ ## Converts a Time to a TimeInterval.
+ # Milliseconds not available from Time
+ var tInfo = t.getLocalTime()
+ initInterval(0, tInfo.second, tInfo.minute, tInfo.hour, tInfo.weekday.ord, tInfo.month.ord, tInfo.year)
+
when isMainModule:
# this is testing non-exported function
diff --git a/lib/pure/unicode.nim b/lib/pure/unicode.nim
index 45f52eb7fb..0cbe8de7a2 100644
--- a/lib/pure/unicode.nim
+++ b/lib/pure/unicode.nim
@@ -14,7 +14,7 @@
include "system/inclrtl"
type
- RuneImpl = int # underlying type of Rune
+ RuneImpl = int32 # underlying type of Rune
Rune* = distinct RuneImpl ## type that can hold any Unicode character
Rune16* = distinct int16 ## 16 bit Unicode character
@@ -135,45 +135,62 @@ proc runeAt*(s: string, i: Natural): Rune =
## Returns the unicode character in ``s`` at byte index ``i``
fastRuneAt(s, i, result, false)
-proc toUTF8*(c: Rune): string {.rtl, extern: "nuc$1".} =
- ## Converts a rune into its UTF-8 representation
+template fastToUTF8Copy*(c: Rune, s: var string, pos: int, doInc = true) =
+ ## Copies UTF-8 representation of `c` into the preallocated string `s`
+ ## starting at position `pos`. If `doInc == true`, `pos` is incremented
+ ## by the number of bytes that have been processed.
+ ##
+ ## To be the most efficient, make sure `s` is preallocated
+ ## with an additional amount equal to the byte length of
+ ## `c`.
var i = RuneImpl(c)
if i <=% 127:
- result = newString(1)
- result[0] = chr(i)
+ s.setLen(pos+1)
+ s[pos+0] = chr(i)
+ when doInc: inc(pos)
elif i <=% 0x07FF:
- result = newString(2)
- result[0] = chr((i shr 6) or 0b110_00000)
- result[1] = chr((i and ones(6)) or 0b10_0000_00)
+ s.setLen(pos+2)
+ s[pos+0] = chr((i shr 6) or 0b110_00000)
+ s[pos+1] = chr((i and ones(6)) or 0b10_0000_00)
+ when doInc: inc(pos, 2)
elif i <=% 0xFFFF:
- result = newString(3)
- result[0] = chr(i shr 12 or 0b1110_0000)
- result[1] = chr(i shr 6 and ones(6) or 0b10_0000_00)
- result[2] = chr(i and ones(6) or 0b10_0000_00)
+ s.setLen(pos+3)
+ s[pos+0] = chr(i shr 12 or 0b1110_0000)
+ s[pos+1] = chr(i shr 6 and ones(6) or 0b10_0000_00)
+ s[pos+2] = chr(i and ones(6) or 0b10_0000_00)
+ when doInc: inc(pos, 3)
elif i <=% 0x001FFFFF:
- result = newString(4)
- result[0] = chr(i shr 18 or 0b1111_0000)
- result[1] = chr(i shr 12 and ones(6) or 0b10_0000_00)
- result[2] = chr(i shr 6 and ones(6) or 0b10_0000_00)
- result[3] = chr(i and ones(6) or 0b10_0000_00)
+ s.setLen(pos+4)
+ s[pos+0] = chr(i shr 18 or 0b1111_0000)
+ s[pos+1] = chr(i shr 12 and ones(6) or 0b10_0000_00)
+ s[pos+2] = chr(i shr 6 and ones(6) or 0b10_0000_00)
+ s[pos+3] = chr(i and ones(6) or 0b10_0000_00)
+ when doInc: inc(pos, 4)
elif i <=% 0x03FFFFFF:
- result = newString(5)
- result[0] = chr(i shr 24 or 0b111110_00)
- result[1] = chr(i shr 18 and ones(6) or 0b10_0000_00)
- result[2] = chr(i shr 12 and ones(6) or 0b10_0000_00)
- result[3] = chr(i shr 6 and ones(6) or 0b10_0000_00)
- result[4] = chr(i and ones(6) or 0b10_0000_00)
+ s.setLen(pos+5)
+ s[pos+0] = chr(i shr 24 or 0b111110_00)
+ s[pos+1] = chr(i shr 18 and ones(6) or 0b10_0000_00)
+ s[pos+2] = chr(i shr 12 and ones(6) or 0b10_0000_00)
+ s[pos+3] = chr(i shr 6 and ones(6) or 0b10_0000_00)
+ s[pos+4] = chr(i and ones(6) or 0b10_0000_00)
+ when doInc: inc(pos, 5)
elif i <=% 0x7FFFFFFF:
- result = newString(6)
- result[0] = chr(i shr 30 or 0b1111110_0)
- result[1] = chr(i shr 24 and ones(6) or 0b10_0000_00)
- result[2] = chr(i shr 18 and ones(6) or 0b10_0000_00)
- result[3] = chr(i shr 12 and ones(6) or 0b10_0000_00)
- result[4] = chr(i shr 6 and ones(6) or 0b10_0000_00)
- result[5] = chr(i and ones(6) or 0b10_0000_00)
+ s.setLen(pos+6)
+ s[pos+0] = chr(i shr 30 or 0b1111110_0)
+ s[pos+1] = chr(i shr 24 and ones(6) or 0b10_0000_00)
+ s[pos+2] = chr(i shr 18 and ones(6) or 0b10_0000_00)
+ s[pos+3] = chr(i shr 12 and ones(6) or 0b10_0000_00)
+ s[pos+4] = chr(i shr 6 and ones(6) or 0b10_0000_00)
+ s[pos+5] = chr(i and ones(6) or 0b10_0000_00)
+ when doInc: inc(pos, 6)
else:
discard # error, exception?
+proc toUTF8*(c: Rune): string {.rtl, extern: "nuc$1".} =
+ ## Converts a rune into its UTF-8 representation
+ result = ""
+ fastToUTF8Copy(c, result, 0, false)
+
proc `$`*(rune: Rune): string =
## Converts a Rune to a string
rune.toUTF8
@@ -183,6 +200,105 @@ proc `$`*(runes: seq[Rune]): string =
result = ""
for rune in runes: result.add(rune.toUTF8)
+proc runeOffset*(s: string, pos:Natural, start: Natural = 0): int =
+ ## Returns the byte position of unicode character
+ ## at position pos in s with an optional start byte position.
+ ## returns the special value -1 if it runs out of the string
+ ##
+ ## Beware: This can lead to unoptimized code and slow execution!
+ ## Most problems are solve more efficient by using an iterator
+ ## or conversion to a seq of Rune.
+ var
+ i = 0
+ o = start
+ while i < pos:
+ o += runeLenAt(s, o)
+ if o >= s.len:
+ return -1
+ inc i
+ return o
+
+proc runeAtPos*(s: string, pos: int): Rune =
+ ## Returns the unicode character at position pos
+ ##
+ ## Beware: This can lead to unoptimized code and slow execution!
+ ## Most problems are solve more efficient by using an iterator
+ ## or conversion to a seq of Rune.
+ fastRuneAt(s, runeOffset(s, pos), result, false)
+
+proc runeStrAtPos*(s: string, pos: Natural): string =
+ ## Returns the unicode character at position pos as UTF8 String
+ ##
+ ## Beware: This can lead to unoptimized code and slow execution!
+ ## Most problems are solve more efficient by using an iterator
+ ## or conversion to a seq of Rune.
+ let o = runeOffset(s, pos)
+ s[o.. (o+runeLenAt(s, o)-1)]
+
+proc runeReverseOffset*(s: string, rev:Positive): (int, int) =
+ ## Returns a tuple with the the byte offset of the
+ ## unicode character at position ``rev`` in s counting
+ ## from the end (starting with 1) and the total
+ ## number of runes in the string. Returns a negative value
+ ## for offset if there are to few runes in the string to
+ ## satisfy the request.
+ ##
+ ## Beware: This can lead to unoptimized code and slow execution!
+ ## Most problems are solve more efficient by using an iterator
+ ## or conversion to a seq of Rune.
+ var
+ a = rev.int
+ o = 0
+ x = 0
+ while o < s.len:
+ let r = runeLenAt(s, o)
+ o += r
+ if a < 0:
+ x += r
+ dec a
+
+ if a > 0:
+ return (-a, rev.int-a)
+ return (x, -a+rev.int)
+
+proc runeSubStr*(s: string, pos:int, len:int = int.high): string =
+ ## Returns the UTF-8 substring starting at codepoint pos
+ ## with len codepoints. If pos or len is negativ they count from
+ ## the end of the string. If len is not given it means the longest
+ ## possible string.
+ ##
+ ## (Needs some examples)
+ if pos < 0:
+ let (o, rl) = runeReverseOffset(s, -pos)
+ if len >= rl:
+ result = s[o.. s.len-1]
+ elif len < 0:
+ let e = rl + len
+ if e < 0:
+ result = ""
+ else:
+ result = s[o.. runeOffset(s, e-(rl+pos) , o)-1]
+ else:
+ result = s[o.. runeOffset(s, len, o)-1]
+ else:
+ let o = runeOffset(s, pos)
+ if o < 0:
+ result = ""
+ elif len == int.high:
+ result = s[o.. s.len-1]
+ elif len < 0:
+ let (e, rl) = runeReverseOffset(s, -len)
+ discard rl
+ if e <= 0:
+ result = ""
+ else:
+ result = s[o.. e-1]
+ else:
+ var e = runeOffset(s, len, o)
+ if e < 0:
+ e = s.len
+ result = s[o.. e-1]
+
const
alphaRanges = [
0x00d8, 0x00f6, # -
@@ -1148,7 +1264,7 @@ const
0x01f1, 501, #
0x01f3, 499] #
-proc binarySearch(c: RuneImpl, tab: openArray[RuneImpl], len, stride: int): int =
+proc binarySearch(c: RuneImpl, tab: openArray[int], len, stride: int): int =
var n = len
var t = 0
while n > 1:
@@ -1253,8 +1369,210 @@ proc isCombining*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} =
(c >= 0x20d0 and c <= 0x20ff) or
(c >= 0xfe20 and c <= 0xfe2f))
+template runeCheck(s, runeProc) =
+ ## Common code for rune.isLower, rune.isUpper, etc
+ result = if len(s) == 0: false else: true
+
+ var
+ i = 0
+ rune: Rune
+
+ while i < len(s) and result:
+ fastRuneAt(s, i, rune, doInc=true)
+ result = runeProc(rune) and result
+
+proc isUpper*(s: string): bool {.noSideEffect, procvar,
+ rtl, extern: "nuc$1Str".} =
+ ## Returns true iff `s` contains all upper case unicode characters.
+ runeCheck(s, isUpper)
+
+proc isLower*(s: string): bool {.noSideEffect, procvar,
+ rtl, extern: "nuc$1Str".} =
+ ## Returns true iff `s` contains all lower case unicode characters.
+ runeCheck(s, isLower)
+
+proc isAlpha*(s: string): bool {.noSideEffect, procvar,
+ rtl, extern: "nuc$1Str".} =
+ ## Returns true iff `s` contains all alphabetic unicode characters.
+ runeCheck(s, isAlpha)
+
+proc isSpace*(s: string): bool {.noSideEffect, procvar,
+ rtl, extern: "nuc$1Str".} =
+ ## Returns true iff `s` contains all whitespace unicode characters.
+ runeCheck(s, isWhiteSpace)
+
+template convertRune(s, runeProc) =
+ ## Convert runes in `s` using `runeProc` as the converter.
+ result = newString(len(s))
+
+ var
+ i = 0
+ lastIndex = 0
+ rune: Rune
+
+ while i < len(s):
+ lastIndex = i
+ fastRuneAt(s, i, rune, doInc=true)
+ rune = runeProc(rune)
+
+ rune.fastToUTF8Copy(result, lastIndex)
+
+proc toUpper*(s: string): string {.noSideEffect, procvar,
+ rtl, extern: "nuc$1Str".} =
+ ## Converts `s` into upper-case unicode characters.
+ convertRune(s, toUpper)
+
+proc toLower*(s: string): string {.noSideEffect, procvar,
+ rtl, extern: "nuc$1Str".} =
+ ## Converts `s` into lower-case unicode characters.
+ convertRune(s, toLower)
+
+proc swapCase*(s: string): string {.noSideEffect, procvar,
+ rtl, extern: "nuc$1".} =
+ ## Swaps the case of unicode characters in `s`
+ ##
+ ## Returns a new string such that the cases of all unicode characters
+ ## are swapped if possible
+
+ var
+ i = 0
+ lastIndex = 0
+ rune: Rune
+
+ result = newString(len(s))
+
+ while i < len(s):
+ lastIndex = i
+
+ fastRuneAt(s, i, rune)
+
+ if rune.isUpper():
+ rune = rune.toLower()
+ elif rune.isLower():
+ rune = rune.toUpper()
+
+ rune.fastToUTF8Copy(result, lastIndex)
+
+proc capitalize*(s: string): string {.noSideEffect, procvar,
+ rtl, extern: "nuc$1".} =
+ ## Converts the first character of `s` into an upper-case unicode character.
+ if len(s) == 0:
+ return s
+
+ var
+ rune: Rune
+ i = 0
+
+ fastRuneAt(s, i, rune, doInc=true)
+
+ result = $toUpper(rune) & substr(s, i)
+
+proc translate*(s: string, replacements: proc(key: string): string): string {.
+ rtl, extern: "nuc$1".} =
+ ## Translates words in a string using the `replacements` proc to substitute
+ ## words inside `s` with their replacements
+ ##
+ ## `replacements` is any proc that takes a word and returns
+ ## a new word to fill it's place.
+
+ # Allocate memory for the new string based on the old one.
+ # If the new string length is less than the old, no allocations
+ # will be needed. If the new string length is greater than the
+ # old, then maybe only one allocation is needed
+ result = newStringOfCap(s.len)
+
+ var
+ index = 0
+ lastIndex = 0
+ wordStart = 0
+ inWord = false
+ rune: Rune
+
+ while index < len(s):
+ lastIndex = index
+
+ fastRuneAt(s, index, rune)
+
+ let whiteSpace = rune.isWhiteSpace()
+
+ if whiteSpace and inWord:
+ # If we've reached the end of a word
+ let word = s[wordStart ..< lastIndex]
+ result.add(replacements(word))
+ result.add($rune)
+
+ inWord = false
+ elif not whiteSpace and not inWord:
+ # If we've hit a non space character and
+ # are not currently in a word, track
+ # the starting index of the word
+ inWord = true
+ wordStart = lastIndex
+ elif whiteSpace:
+ result.add($rune)
+
+ if wordStart < len(s) and inWord:
+ # Get the trailing word at the end
+ let word = s[wordStart .. ^1]
+ result.add(replacements(word))
+
+proc title*(s: string): string {.noSideEffect, procvar,
+ rtl, extern: "nuc$1".} =
+ ## Converts `s` to a unicode title.
+ ##
+ ## Returns a new string such that the first character
+ ## in each word inside `s` is capitalized
+
+ var
+ i = 0
+ lastIndex = 0
+ rune: Rune
+
+ result = newString(len(s))
+
+ var firstRune = true
+
+ while i < len(s):
+ lastIndex = i
+
+ fastRuneAt(s, i, rune)
+
+ if not rune.isWhiteSpace() and firstRune:
+ rune = rune.toUpper()
+ firstRune = false
+ elif rune.isWhiteSpace():
+ firstRune = true
+
+ rune.fastToUTF8Copy(result, lastIndex)
+
+proc isTitle*(s: string): bool {.noSideEffect, procvar,
+ rtl, extern: "nuc$1Str".}=
+ ## Checks whether or not `s` is a unicode title.
+ ##
+ ## Returns true if the first character in each word inside `s`
+ ## are upper case and there is at least one character in `s`.
+ if s.len() == 0:
+ return false
+
+ result = true
+
+ var
+ i = 0
+ rune: Rune
+
+ var firstRune = true
+
+ while i < len(s) and result:
+ fastRuneAt(s, i, rune, doInc=true)
+
+ if not rune.isWhiteSpace() and firstRune:
+ result = rune.isUpper() and result
+ firstRune = false
+ elif rune.isWhiteSpace():
+ firstRune = true
+
iterator runes*(s: string): Rune =
- ## Iterates over any unicode character of the string ``s``
+ ## Iterates over any unicode character of the string ``s`` returning runes
var
i = 0
result: Rune
@@ -1262,6 +1580,14 @@ iterator runes*(s: string): Rune =
fastRuneAt(s, i, result, true)
yield result
+iterator utf8*(s: string): string =
+ ## Iterates over any unicode character of the string ``s`` returning utf8 values
+ var o = 0
+ while o < s.len:
+ let n = runeLenAt(s, o)
+ yield s[o.. (o+n-1)]
+ o += n
+
proc toRunes*(s: string): seq[Rune] =
## Obtains a sequence containing the Runes in ``s``
result = newSeq[Rune]()
@@ -1352,6 +1678,101 @@ when isMainModule:
compared = (someString == $someRunes)
doAssert compared == true
+ proc test_replacements(word: string): string =
+ case word
+ of "two":
+ return "2"
+ of "foo":
+ return "BAR"
+ of "βeta":
+ return "beta"
+ of "alpha":
+ return "αlpha"
+ else:
+ return "12345"
+
+ doAssert translate("two not alpha foo βeta", test_replacements) == "2 12345 αlpha BAR beta"
+ doAssert translate(" two not foo βeta ", test_replacements) == " 2 12345 BAR beta "
+
+ doAssert title("foo bar") == "Foo Bar"
+ doAssert title("αlpha βeta γamma") == "Αlpha Βeta Γamma"
+ doAssert title("") == ""
+
+ doAssert capitalize("βeta") == "Βeta"
+ doAssert capitalize("foo") == "Foo"
+ doAssert capitalize("") == ""
+
+ doAssert isTitle("Foo")
+ doAssert(not isTitle("Foo bar"))
+ doAssert(not isTitle("αlpha Βeta"))
+ doAssert(isTitle("Αlpha Βeta Γamma"))
+ doAssert(not isTitle("fFoo"))
+
+ doAssert swapCase("FooBar") == "fOObAR"
+ doAssert swapCase(" ") == " "
+ doAssert swapCase("Αlpha Βeta Γamma") == "αLPHA βETA γAMMA"
+ doAssert swapCase("a✓B") == "A✓b"
+ doAssert swapCase("") == ""
+
+ doAssert isAlpha("r")
+ doAssert isAlpha("α")
+ doAssert(not isAlpha("$"))
+ doAssert(not isAlpha(""))
+
+ doAssert isAlpha("Βeta")
+ doAssert isAlpha("Args")
+ doAssert(not isAlpha("$Foo✓"))
+
+ doAssert isSpace("\t")
+ doAssert isSpace("\l")
+ doAssert(not isSpace("Β"))
+ doAssert(not isSpace("Βeta"))
+
+ doAssert isSpace("\t\l \v\r\f")
+ doAssert isSpace(" ")
+ doAssert(not isSpace(""))
+ doAssert(not isSpace("ΑΓc \td"))
+
+ doAssert isLower("a")
+ doAssert isLower("γ")
+ doAssert(not isLower("Γ"))
+ doAssert(not isLower("4"))
+ doAssert(not isLower(""))
+
+ doAssert isLower("abcdγ")
+ doAssert(not isLower("abCDΓ"))
+ doAssert(not isLower("33aaΓ"))
+
+ doAssert isUpper("Γ")
+ doAssert(not isUpper("b"))
+ doAssert(not isUpper("α"))
+ doAssert(not isUpper("✓"))
+ doAssert(not isUpper(""))
+
+ doAssert isUpper("ΑΒΓ")
+ doAssert(not isUpper("AAccβ"))
+ doAssert(not isUpper("A#$β"))
+
+ doAssert toUpper("Γ") == "Γ"
+ doAssert toUpper("b") == "B"
+ doAssert toUpper("α") == "Α"
+ doAssert toUpper("✓") == "✓"
+ doAssert toUpper("") == ""
+
+ doAssert toUpper("ΑΒΓ") == "ΑΒΓ"
+ doAssert toUpper("AAccβ") == "AACCΒ"
+ doAssert toUpper("A✓$β") == "A✓$Β"
+
+ doAssert toLower("a") == "a"
+ doAssert toLower("γ") == "γ"
+ doAssert toLower("Γ") == "γ"
+ doAssert toLower("4") == "4"
+ doAssert toLower("") == ""
+
+ doAssert toLower("abcdγ") == "abcdγ"
+ doAssert toLower("abCDΓ") == "abcdγ"
+ doAssert toLower("33aaΓ") == "33aaγ"
+
doAssert reversed("Reverse this!") == "!siht esreveR"
doAssert reversed("先秦兩漢") == "漢兩秦先"
doAssert reversed("as⃝df̅") == "f̅ds⃝a"
@@ -1360,3 +1781,44 @@ when isMainModule:
const test = "as⃝"
doAssert lastRune(test, test.len-1)[1] == 3
doAssert graphemeLen("è", 0) == 2
+
+ # test for rune positioning and runeSubStr()
+ let s = "Hänsel ««: 10,00€"
+
+ var t = ""
+ for c in s.utf8:
+ t.add c
+
+ doAssert(s == t)
+
+ doAssert(runeReverseOffset(s, 1) == (20, 18))
+ doAssert(runeReverseOffset(s, 19) == (-1, 18))
+
+ doAssert(runeStrAtPos(s, 0) == "H")
+ doAssert(runeSubStr(s, 0, 1) == "H")
+ doAssert(runeStrAtPos(s, 10) == ":")
+ doAssert(runeSubStr(s, 10, 1) == ":")
+ doAssert(runeStrAtPos(s, 9) == "«")
+ doAssert(runeSubStr(s, 9, 1) == "«")
+ doAssert(runeStrAtPos(s, 17) == "€")
+ doAssert(runeSubStr(s, 17, 1) == "€")
+ # echo runeStrAtPos(s, 18) # index error
+
+ doAssert(runeSubStr(s, 0) == "Hänsel ««: 10,00€")
+ doAssert(runeSubStr(s, -18) == "Hänsel ««: 10,00€")
+ doAssert(runeSubStr(s, 10) == ": 10,00€")
+ doAssert(runeSubStr(s, 18) == "")
+ doAssert(runeSubStr(s, 0, 10) == "Hänsel ««")
+
+ doAssert(runeSubStr(s, 12) == "10,00€")
+ doAssert(runeSubStr(s, -6) == "10,00€")
+
+ doAssert(runeSubStr(s, 12, 5) == "10,00")
+ doAssert(runeSubStr(s, 12, -1) == "10,00")
+ doAssert(runeSubStr(s, -6, 5) == "10,00")
+ doAssert(runeSubStr(s, -6, -1) == "10,00")
+
+ doAssert(runeSubStr(s, 0, 100) == "Hänsel ««: 10,00€")
+ doAssert(runeSubStr(s, -100, 100) == "Hänsel ««: 10,00€")
+ doAssert(runeSubStr(s, 0, -100) == "")
+ doAssert(runeSubStr(s, 100, -100) == "")
diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim
index aca9d51e22..92ddc3e758 100644
--- a/lib/pure/unittest.nim
+++ b/lib/pure/unittest.nim
@@ -41,7 +41,11 @@ when not defined(ECMAScript):
import terminal
type
- TestStatus* = enum OK, FAILED ## The status of a test when it is done.
+ TestStatus* = enum ## The status of a test when it is done.
+ OK,
+ FAILED,
+ SKIPPED
+
OutputLevel* = enum ## The output verbosity of the tests.
PRINT_ALL, ## Print as much as possible.
PRINT_FAILURES, ## Print only the failed tests.
@@ -73,7 +77,7 @@ checkpoints = @[]
proc shouldRun(testName: string): bool =
result = true
-template suite*(name: expr, body: stmt): stmt {.immediate, dirty.} =
+template suite*(name, body) {.dirty.} =
## Declare a test suite identified by `name` with optional ``setup``
## and/or ``teardown`` section.
##
@@ -102,13 +106,13 @@ template suite*(name: expr, body: stmt): stmt {.immediate, dirty.} =
## [OK] 2 + 2 = 4
## [OK] (2 + -2) != 4
block:
- template setup(setupBody: stmt): stmt {.immediate, dirty.} =
+ template setup(setupBody: untyped) {.dirty.} =
var testSetupIMPLFlag = true
- template testSetupIMPL: stmt {.immediate, dirty.} = setupBody
+ template testSetupIMPL: untyped {.dirty.} = setupBody
- template teardown(teardownBody: stmt): stmt {.immediate, dirty.} =
+ template teardown(teardownBody: untyped) {.dirty.} =
var testTeardownIMPLFlag = true
- template testTeardownIMPL: stmt {.immediate, dirty.} = teardownBody
+ template testTeardownIMPL: untyped {.dirty.} = teardownBody
body
@@ -120,14 +124,18 @@ proc testDone(name: string, s: TestStatus) =
template rawPrint() = echo("[", $s, "] ", name)
when not defined(ECMAScript):
if colorOutput and not defined(ECMAScript):
- var color = (if s == OK: fgGreen else: fgRed)
+ var color = case s
+ of OK: fgGreen
+ of FAILED: fgRed
+ of SKIPPED: fgYellow
+ else: fgWhite
styledEcho styleBright, color, "[", $s, "] ", fgWhite, name
else:
rawPrint()
else:
rawPrint()
-template test*(name: expr, body: stmt): stmt {.immediate, dirty.} =
+template test*(name, body) {.dirty.} =
## Define a single test case identified by `name`.
##
## .. code-block:: nim
@@ -203,7 +211,22 @@ template fail* =
checkpoints = @[]
-macro check*(conditions: stmt): stmt {.immediate.} =
+template skip* =
+ ## Makes test to be skipped. Should be used directly
+ ## in case when it is not possible to perform test
+ ## for reasons depending on outer environment,
+ ## or certain application logic conditions or configurations.
+ ##
+ ## .. code-block:: nim
+ ##
+ ## if not isGLConextCreated():
+ ## skip()
+ bind checkpoints
+
+ testStatusIMPL = SKIPPED
+ checkpoints = @[]
+
+macro check*(conditions: untyped): untyped =
## Verify if a statement or a list of statements is true.
## A helpful error message and set checkpoints are printed out on
## failure (if ``outputLevel`` is not ``PRINT_NONE``).
@@ -236,31 +259,34 @@ macro check*(conditions: stmt): stmt {.immediate.} =
proc inspectArgs(exp: NimNode): NimNode =
result = copyNimTree(exp)
- for i in countup(1, exp.len - 1):
- if exp[i].kind notin nnkLiterals:
- inc counter
- var arg = newIdentNode(":p" & $counter)
- var argStr = exp[i].toStrLit
- var paramAst = exp[i]
- if exp[i].kind == nnkIdent:
- argsPrintOuts.add getAst(print(argStr, paramAst))
- if exp[i].kind in nnkCallKinds:
- var callVar = newIdentNode(":c" & $counter)
- argsAsgns.add getAst(asgn(callVar, paramAst))
- result[i] = callVar
- argsPrintOuts.add getAst(print(argStr, callVar))
- if exp[i].kind == nnkExprEqExpr:
- # ExprEqExpr
- # Ident !"v"
- # IntLit 2
- result[i] = exp[i][1]
- if exp[i].typekind notin {ntyTypeDesc}:
- argsAsgns.add getAst(asgn(arg, paramAst))
- argsPrintOuts.add getAst(print(argStr, arg))
- if exp[i].kind != nnkExprEqExpr:
- result[i] = arg
- else:
- result[i][1] = arg
+ if exp[0].kind == nnkIdent and
+ $exp[0] in ["and", "or", "not", "in", "notin", "==", "<=",
+ ">=", "<", ">", "!=", "is", "isnot"]:
+ for i in countup(1, exp.len - 1):
+ if exp[i].kind notin nnkLiterals:
+ inc counter
+ var arg = newIdentNode(":p" & $counter)
+ var argStr = exp[i].toStrLit
+ var paramAst = exp[i]
+ if exp[i].kind == nnkIdent:
+ argsPrintOuts.add getAst(print(argStr, paramAst))
+ if exp[i].kind in nnkCallKinds:
+ var callVar = newIdentNode(":c" & $counter)
+ argsAsgns.add getAst(asgn(callVar, paramAst))
+ result[i] = callVar
+ argsPrintOuts.add getAst(print(argStr, callVar))
+ if exp[i].kind == nnkExprEqExpr:
+ # ExprEqExpr
+ # Ident !"v"
+ # IntLit 2
+ result[i] = exp[i][1]
+ if exp[i].typekind notin {ntyTypeDesc}:
+ argsAsgns.add getAst(asgn(arg, paramAst))
+ argsPrintOuts.add getAst(print(argStr, arg))
+ if exp[i].kind != nnkExprEqExpr:
+ result[i] = arg
+ else:
+ result[i][1] = arg
case checked.kind
of nnkCallKinds:
@@ -292,7 +318,7 @@ macro check*(conditions: stmt): stmt {.immediate.} =
result = getAst(rewrite(checked, checked.lineinfo, checked.toStrLit))
-template require*(conditions: stmt): stmt {.immediate.} =
+template require*(conditions: untyped) =
## Same as `check` except any failed test causes the program to quit
## immediately. Any teardown statements are not executed and the failed
## test output is not generated.
@@ -302,7 +328,7 @@ template require*(conditions: stmt): stmt {.immediate.} =
check conditions
abortOnError = savedAbortOnError
-macro expect*(exceptions: varargs[expr], body: stmt): stmt {.immediate.} =
+macro expect*(exceptions: varargs[typed], body: untyped): untyped =
## Test if `body` raises an exception found in the passed `exceptions`.
## The test passes if the raised exception is part of the acceptable
## exceptions. Otherwise, it fails.
@@ -310,7 +336,7 @@ macro expect*(exceptions: varargs[expr], body: stmt): stmt {.immediate.} =
##
## .. code-block:: nim
##
- ## import math
+ ## import math, random
## proc defectiveRobot() =
## randomize()
## case random(1..4)
diff --git a/lib/pure/xmldom.nim b/lib/pure/xmldom.nim
index 6cf837f257..559f45348c 100644
--- a/lib/pure/xmldom.nim
+++ b/lib/pure/xmldom.nim
@@ -51,6 +51,9 @@ const
# Illegal characters
illegalChars = {'>', '<', '&', '"'}
+ # standard xml: attribute names
+ # see https://www.w3.org/XML/1998/namespace
+ stdattrnames = ["lang", "space", "base", "id"]
type
Feature = tuple[name: string, version: string]
@@ -229,12 +232,15 @@ proc createAttributeNS*(doc: PDocument, namespaceURI: string, qualifiedName: str
raise newException(EInvalidCharacterErr, "Invalid character")
# Exceptions
if qualifiedName.contains(':'):
+ let qfnamespaces = qualifiedName.toLower().split(':')
if isNil(namespaceURI):
raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil")
- elif qualifiedName.split(':')[0].toLower() == "xml" and namespaceURI != "http://www.w3.org/XML/1998/namespace":
+ elif qfnamespaces[0] == "xml" and
+ namespaceURI != "http://www.w3.org/XML/1998/namespace" and
+ qfnamespaces[1] notin stdattrnames:
raise newException(ENamespaceErr,
"When the namespace prefix is \"xml\" namespaceURI has to be \"http://www.w3.org/XML/1998/namespace\"")
- elif qualifiedName.split(':')[1].toLower() == "xmlns" and namespaceURI != "http://www.w3.org/2000/xmlns/":
+ elif qfnamespaces[1] == "xmlns" and namespaceURI != "http://www.w3.org/2000/xmlns/":
raise newException(ENamespaceErr,
"When the namespace prefix is \"xmlns\" namespaceURI has to be \"http://www.w3.org/2000/xmlns/\"")
@@ -305,9 +311,12 @@ proc createElement*(doc: PDocument, tagName: string): PElement =
proc createElementNS*(doc: PDocument, namespaceURI: string, qualifiedName: string): PElement =
## Creates an element of the given qualified name and namespace URI.
if qualifiedName.contains(':'):
+ let qfnamespaces = qualifiedName.toLower().split(':')
if isNil(namespaceURI):
raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil")
- elif qualifiedName.split(':')[0].toLower() == "xml" and namespaceURI != "http://www.w3.org/XML/1998/namespace":
+ elif qfnamespaces[0] == "xml" and
+ namespaceURI != "http://www.w3.org/XML/1998/namespace" and
+ qfnamespaces[1] notin stdattrnames:
raise newException(ENamespaceErr,
"When the namespace prefix is \"xml\" namespaceURI has to be \"http://www.w3.org/XML/1998/namespace\"")
diff --git a/lib/stdlib.nimble b/lib/stdlib.nimble
index e8bb364f16..4b0066ee86 100644
--- a/lib/stdlib.nimble
+++ b/lib/stdlib.nimble
@@ -1,6 +1,6 @@
[Package]
name = "stdlib"
-version = "0.13.0"
+version = "0.14.3"
author = "Dominik Picheta"
description = "Nim's standard library."
license = "MIT"
diff --git a/lib/system.nim b/lib/system.nim
index da37512d63..6af5dc01ff 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -66,8 +66,10 @@ type
`ref`* {.magic: Pointer.}[T] ## built-in generic traced pointer type
`nil` {.magic: "Nil".}
- expr* {.magic: Expr.} ## meta type to denote an expression (for templates)
- stmt* {.magic: Stmt.} ## meta type to denote a statement (for templates)
+ expr* {.magic: Expr, deprecated.} ## meta type to denote an expression (for templates)
+ ## **Deprecated** since version 0.15. Use ``untyped`` instead.
+ stmt* {.magic: Stmt, deprecated.} ## meta type to denote a statement (for templates)
+ ## **Deprecated** since version 0.15. Use ``typed`` instead.
typedesc* {.magic: TypeDesc.} ## meta type to denote a type description
void* {.magic: "VoidType".} ## meta type to denote the absence of any type
auto* {.magic: Expr.} ## meta type for automatic type determination
@@ -302,8 +304,7 @@ proc `==` *(x, y: pointer): bool {.magic: "EqRef", noSideEffect.}
## echo (a == b) # true due to the special meaning of `nil`/0 as a pointer
proc `==` *(x, y: string): bool {.magic: "EqStr", noSideEffect.}
## Checks for equality between two `string` variables
-proc `==` *(x, y: cstring): bool {.magic: "EqCString", noSideEffect.}
- ## Checks for equality between two `cstring` variables
+
proc `==` *(x, y: char): bool {.magic: "EqCh", noSideEffect.}
## Checks for equality between two `char` variables
proc `==` *(x, y: bool): bool {.magic: "EqB", noSideEffect.}
@@ -339,15 +340,15 @@ proc `<` *[T](x, y: ref T): bool {.magic: "LtPtr", noSideEffect.}
proc `<` *[T](x, y: ptr T): bool {.magic: "LtPtr", noSideEffect.}
proc `<` *(x, y: pointer): bool {.magic: "LtPtr", noSideEffect.}
-template `!=` * (x, y: expr): expr {.immediate.} =
+template `!=` * (x, y: untyped): untyped =
## unequals operator. This is a shorthand for ``not (x == y)``.
not (x == y)
-template `>=` * (x, y: expr): expr {.immediate.} =
+template `>=` * (x, y: untyped): untyped =
## "is greater or equals" operator. This is the same as ``y <= x``.
y <= x
-template `>` * (x, y: expr): expr {.immediate.} =
+template `>` * (x, y: untyped): untyped =
## "is greater" operator. This is the same as ``y < x``.
y < x
@@ -588,6 +589,9 @@ proc sizeof*[T](x: T): int {.magic: "SizeOf", noSideEffect.}
## that one never needs to know ``x``'s size. As a special semantic rule,
## ``x`` may also be a type identifier (``sizeof(int)`` is valid).
##
+ ## Limitations: If used within nim VM context ``sizeof`` will only work
+ ## for simple types.
+ ##
## .. code-block:: nim
## sizeof('A') #=> 1
## sizeof(2) #=> 8
@@ -667,6 +671,12 @@ proc newSeq*[T](len = 0.Natural): seq[T] =
## #inputStrings[3] = "out of bounds"
newSeq(result, len)
+proc newSeqOfCap*[T](cap: Natural): seq[T] {.
+ magic: "NewSeqOfCap", noSideEffect.} =
+ ## creates a new sequence of type ``seq[T]`` with length 0 and capacity
+ ## ``cap``.
+ discard
+
proc len*[TOpenArray: openArray|varargs](x: TOpenArray): int {.
magic: "LengthOpenArray", noSideEffect.}
proc len*(x: string): int {.magic: "LengthStr", noSideEffect.}
@@ -1090,13 +1100,13 @@ proc contains*[T](s: Slice[T], value: T): bool {.noSideEffect, inline.} =
## assert((1..3).contains(4) == false)
result = s.a <= value and value <= s.b
-template `in` * (x, y: expr): expr {.immediate, dirty.} = contains(y, x)
+template `in` * (x, y: untyped): untyped {.dirty.} = contains(y, x)
## Sugar for contains
##
## .. code-block:: Nim
## assert(1 in (1..3) == true)
## assert(5 in (1..3) == false)
-template `notin` * (x, y: expr): expr {.immediate, dirty.} = not contains(y, x)
+template `notin` * (x, y: untyped): untyped {.dirty.} = not contains(y, x)
## Sugar for not containing
##
## .. code-block:: Nim
@@ -1115,7 +1125,7 @@ proc `is` *[T, S](x: T, y: S): bool {.magic: "Is", noSideEffect.}
##
## assert(test[int](3) == 3)
## assert(test[string]("xyz") == 0)
-template `isnot` *(x, y: expr): expr {.immediate.} = not (x is y)
+template `isnot` *(x, y: untyped): untyped = not (x is y)
## Negated version of `is`. Equivalent to ``not(x is y)``.
proc `of` *[T, S](x: T, y: S): bool {.magic: "Of", noSideEffect.}
@@ -1291,7 +1301,7 @@ const
when hasThreadSupport and defined(tcc) and not compileOption("tlsEmulation"):
# tcc doesn't support TLS
{.error: "``--tlsEmulation:on`` must be used when using threads with tcc backend".}
-
+
when defined(boehmgc):
when defined(windows):
const boehmLib = "boehmgc.dll"
@@ -1521,7 +1531,7 @@ type # these work for most platforms:
## This is the same as the type ``unsigned long long`` in *C*.
cstringArray* {.importc: "char**", nodecl.} = ptr
- array [0..ArrayDummySize, cstring]
+ array[0..ArrayDummySize, cstring]
## This is binary compatible to the type ``char**`` in *C*. The array's
## high value is large enough to disable bounds checking in practice.
## Use `cstringArrayToSeq` to convert it into a ``seq[string]``.
@@ -1591,34 +1601,32 @@ proc substr*(s: string, first, last: int): string {.
## is used instead: This means ``substr`` can also be used to `cut`:idx:
## or `limit`:idx: a string's length.
-when not defined(nimscript):
- proc zeroMem*(p: pointer, size: Natural) {.importc, noDecl, benign.}
+when not defined(nimscript) and not defined(JS):
+ proc zeroMem*(p: pointer, size: Natural) {.inline, benign.}
## overwrites the contents of the memory at ``p`` with the value 0.
## Exactly ``size`` bytes will be overwritten. Like any procedure
## dealing with raw memory this is *unsafe*.
- proc copyMem*(dest, source: pointer, size: Natural) {.
- importc: "memcpy", header: "", benign.}
+ proc copyMem*(dest, source: pointer, size: Natural) {.inline, benign.}
## copies the contents from the memory at ``source`` to the memory
## at ``dest``. Exactly ``size`` bytes will be copied. The memory
## regions may not overlap. Like any procedure dealing with raw
## memory this is *unsafe*.
- proc moveMem*(dest, source: pointer, size: Natural) {.
- importc: "memmove", header: "