Merge branch 'devel' into pr_tables_lent_iter

This commit is contained in:
ringabout
2025-05-26 19:37:29 +08:00
committed by GitHub
202 changed files with 5959 additions and 1150 deletions

View File

@@ -470,6 +470,13 @@ typedef char* NCSTRING;
#define NIM_STRLIT_FLAG ((NU)(1) << ((NIM_INTBITS) - 2)) /* This has to be the same as system.strlitFlag! */
/* unused in codegen after 2.2 but keep for compatibility: */
#define STRING_LITERAL(name, str, length) \
static const struct { \
TGenericSeq Sup; \
NIM_CHAR data[(length) + 1]; \
} name = {{length, (NI) ((NU)length | NIM_STRLIT_FLAG)}, str}
/* declared size of a sequence/variable length array: */
#if defined(__cplusplus) && defined(__clang__)
# define SEQ_DECL_SIZE 1
@@ -485,13 +492,22 @@ typedef char* NCSTRING;
#define paramCount() cmdCount
// NAN definition copied from math.h included in the Windows SDK version 10.0.14393.0
#ifndef NAN
#ifndef NAN /* use __builtin_nanf which is faster, if available */
# if defined(__GNUC__)
# define NAN (__builtin_nanf(""))
# elif defined(__clang__) /* XXX: writing __has_builtin this line cause MSVC complains. */
# if __has_builtin (__builtin_nanf)
# define NAN (__builtin_nanf(""))
# endif
# endif
#endif
#ifndef NAN /* modified from math.h included in the Windows SDK version 10.0.26100.0 */
# ifndef _HUGE_ENUF
# define _HUGE_ENUF 1e+300 // _HUGE_ENUF*_HUGE_ENUF must overflow
# define _HUGE_ENUF 1e+300 /* _HUGE_ENUF*_HUGE_ENUF must overflow */
# endif
# define NAN_INFINITY ((float)(_HUGE_ENUF * _HUGE_ENUF))
# define NAN ((float)(NAN_INFINITY * 0.0F))
# define NAN (-(float)(NAN_INFINITY * 0.0F))
#endif
#ifndef INF

View File

@@ -215,6 +215,11 @@ when defined(osx): # 2001 POSIX evidently does not concern Apple
# present size & has no good reason to call this unless it is growing.
if fcntl(a1, F_PREALLOCATE, fst.addr) != cint(-1): ftruncate(a1, a2 + a3)
else: cint(-1)
elif defined(openbsd):
proc posix_fallocate*(a1: cint, a2, a3: Off): cint =
# above assumption: "has no good reason to call this unless it is growing."
# man ftruncate "it will be extended as if by writing bytes with the value zero."
return ftruncate(a1, a2 + a3)
else:
proc posix_fallocate*(a1: cint, a2, a3: Off): cint {.
importc, header: "<fcntl.h>".}
@@ -1099,7 +1104,9 @@ when not defined(lwip):
# Meanwhile, BSD derivatives had used unsigned int; we will use this
# for the else case, because it is more widely cloned than SVR4's
# behavior.
when defined(linux) or defined(haiku):
# Finally, bionic libc (Android) also uses unsigned int, despite being
# a Linux.
when defined(linux) and not defined(android) or defined(haiku):
type
Tnfds* {.importc: "nfds_t", header: "<poll.h>".} = culong
elif defined(zephyr):

View File

@@ -46,7 +46,7 @@ template createCb(futTyp, strName, identName, futureVarCompletions: untyped) =
{.gcsafe.}:
next.addCallback(cast[proc() {.closure, gcsafe.}](proc =
identName(fut, it)))
except:
except Exception:
futureVarCompletions
if fut.finished:
# Take a look at tasyncexceptions for the bug which this fixes.

View File

@@ -126,8 +126,6 @@ type
when defineSsl:
sslHandle: SslPtr
sslContext: SslContext
bioIn: BIO
bioOut: BIO
sslNoShutdown: bool
domain: Domain
sockType: SockType
@@ -207,7 +205,10 @@ proc newAsyncSocket*(domain, sockType, protocol: cint,
Protocol(protocol), buffered, inheritable)
when defineSsl:
proc getSslError(socket: AsyncSocket, err: cint): cint =
proc raiseSslHandleError =
raiseSSLError("The SSL Handle is closed/unset")
proc getSslError(socket: AsyncSocket, flags: set[SocketFlag], err: cint): cint =
assert socket.isSsl
assert err < 0
var ret = SSL_get_error(socket.sslHandle, err.cint)
@@ -220,65 +221,65 @@ when defineSsl:
return ret
of SSL_ERROR_WANT_X509_LOOKUP:
raiseSSLError("Function for x509 lookup has been called.")
of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
of SSL_ERROR_SYSCALL:
socket.sslNoShutdown = true
let osErr = osLastError()
if not flags.isDisconnectionError(osErr):
var errStr = "IO error has occurred"
let sslErr = ERR_peek_last_error()
if sslErr == 0 and err == 0:
errStr.add ' '
errStr.add "because an EOF was observed that violates the protocol"
elif sslErr == 0 and err == -1:
errStr.add ' '
errStr.add "in the BIO layer"
else:
let errStr = $ERR_error_string(sslErr, nil)
raiseSSLError(errStr & ": " & errStr)
raiseOSError(osErr, errStr)
else:
return ret
of SSL_ERROR_SSL:
socket.sslNoShutdown = true
raiseSSLError()
else: raiseSSLError("Unknown Error")
proc sendPendingSslData(socket: AsyncSocket,
flags: set[SocketFlag]) {.async.} =
let len = bioCtrlPending(socket.bioOut)
if len > 0:
var data = newString(len)
let read = bioRead(socket.bioOut, cast[cstring](addr data[0]), len)
assert read != 0
if read < 0:
raiseSSLError()
data.setLen(read)
await socket.fd.AsyncFD.send(data, flags)
proc appeaseSsl(socket: AsyncSocket, flags: set[SocketFlag],
sslError: cint): owned(Future[bool]) {.async.} =
proc handleSslFailure(socket: AsyncSocket, flags: set[SocketFlag], sslError: cint): Future[bool] =
## Returns `true` if `socket` is still connected, otherwise `false`.
result = true
let retFut = newFuture[bool]("asyncnet.handleSslFailure")
case sslError
of SSL_ERROR_WANT_WRITE:
await sendPendingSslData(socket, flags)
of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT:
addWrite(socket.fd.AsyncFD, proc (sock: AsyncFD): bool =
retFut.complete(true)
return true
)
of SSL_ERROR_WANT_READ:
var data = await recv(socket.fd.AsyncFD, BufferSize, flags)
let length = len(data)
if length > 0:
let ret = bioWrite(socket.bioIn, cast[cstring](addr data[0]), length.cint)
if ret < 0:
raiseSSLError()
elif length == 0:
# connection not properly closed by remote side or connection dropped
SSL_set_shutdown(socket.sslHandle, SSL_RECEIVED_SHUTDOWN)
result = false
addRead(socket.fd.AsyncFD, proc (sock: AsyncFD): bool =
retFut.complete(true)
return true
)
of SSL_ERROR_SYSCALL:
assert flags.isDisconnectionError(osLastError())
retFut.complete(false)
else:
raiseSSLError("Cannot appease SSL.")
raiseSSLError("Cannot handle SSL failure.")
return retFut
template sslLoop(socket: AsyncSocket, flags: set[SocketFlag],
op: untyped) =
var opResult {.inject.} = -1.cint
while opResult < 0:
if socket.sslHandle == nil:
raiseSslHandleError()
ErrClearError()
# Call the desired operation.
opResult = op
let err =
if opResult < 0:
getSslError(socket, opResult.cint)
else:
SSL_ERROR_NONE
# Send any remaining pending SSL data.
await sendPendingSslData(socket, flags)
# If the operation failed, try to see if SSL has some data to read
# or write.
if opResult < 0:
let fut = appeaseSsl(socket, flags, err.cint)
yield fut
if not fut.read():
let err = getSslError(socket, flags, opResult.cint)
let connected = await handleSslFailure(socket, flags, err.cint)
if not connected:
# Socket disconnected.
if SocketFlag.SafeDisconn in flags:
opResult = 0.cint
@@ -306,14 +307,15 @@ proc connect*(socket: AsyncSocket, address: string, port: Port) {.async.} =
await connect(socket.fd.AsyncFD, address, port, socket.domain)
if socket.isSsl:
when defineSsl:
if socket.sslHandle == nil:
raiseSslHandleError()
if not isIpAddress(address):
# Set the SNI address for this connection. This call can fail if
# we're not using TLSv1+.
discard SSL_set_tlsext_host_name(socket.sslHandle, address)
let flags = {SocketFlag.SafeDisconn}
sslSetConnectState(socket.sslHandle)
sslLoop(socket, flags, sslDoHandshake(socket.sslHandle))
sslLoop(socket, flags, SSL_connect(socket.sslHandle))
template readInto(buf: pointer, size: int, socket: AsyncSocket,
flags: set[SocketFlag]): int =
@@ -450,7 +452,6 @@ proc send*(socket: AsyncSocket, buf: pointer, size: int,
when defineSsl:
sslLoop(socket, flags,
sslWrite(socket.sslHandle, cast[cstring](buf), size.cint))
await sendPendingSslData(socket, flags)
else:
await send(socket.fd.AsyncFD, buf, size, flags)
@@ -464,52 +465,9 @@ proc send*(socket: AsyncSocket, data: string,
var copy = data
sslLoop(socket, flags,
sslWrite(socket.sslHandle, cast[cstring](addr copy[0]), copy.len.cint))
await sendPendingSslData(socket, flags)
else:
await send(socket.fd.AsyncFD, data, flags)
proc acceptAddr*(socket: AsyncSocket, flags = {SocketFlag.SafeDisconn},
inheritable = defined(nimInheritHandles)):
owned(Future[tuple[address: string, client: AsyncSocket]]) =
## Accepts a new connection. Returns a future containing the client socket
## corresponding to that connection and the remote address of the client.
##
## If `inheritable` is false (the default), the resulting client socket will
## not be inheritable by child processes.
##
## The future will complete when the connection is successfully accepted.
var retFuture = newFuture[tuple[address: string, client: AsyncSocket]]("asyncnet.acceptAddr")
var fut = acceptAddr(socket.fd.AsyncFD, flags, inheritable)
fut.callback =
proc (future: Future[tuple[address: string, client: AsyncFD]]) =
assert future.finished
if future.failed:
retFuture.fail(future.readError)
else:
let resultTup = (future.read.address,
newAsyncSocket(future.read.client, socket.domain,
socket.sockType, socket.protocol, socket.isBuffered, inheritable))
retFuture.complete(resultTup)
return retFuture
proc accept*(socket: AsyncSocket,
flags = {SocketFlag.SafeDisconn}): owned(Future[AsyncSocket]) =
## Accepts a new connection. Returns a future containing the client socket
## corresponding to that connection.
## If `inheritable` is false (the default), the resulting client socket will
## not be inheritable by child processes.
## The future will complete when the connection is successfully accepted.
var retFut = newFuture[AsyncSocket]("asyncnet.accept")
var fut = acceptAddr(socket, flags)
fut.callback =
proc (future: Future[tuple[address: string, client: AsyncSocket]]) =
assert future.finished
if future.failed:
retFut.fail(future.readError)
else:
retFut.complete(future.read.client)
return retFut
proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string],
flags = {SocketFlag.SafeDisconn}, maxLength = MaxLineLength) {.async.} =
## Reads a line of data from `socket` into `resString`.
@@ -727,6 +685,8 @@ proc close*(socket: AsyncSocket) =
defer:
socket.fd.AsyncFD.closeSocket()
socket.closed = true # TODO: Add extra debugging checks for this.
when defineSsl:
socket.sslHandle = nil
when defineSsl:
if socket.isSsl:
@@ -763,9 +723,8 @@ when defineSsl:
if socket.sslHandle == nil:
raiseSSLError()
socket.bioIn = bioNew(bioSMem())
socket.bioOut = bioNew(bioSMem())
sslSetBio(socket.sslHandle, socket.bioIn, socket.bioOut)
if SSL_set_fd(socket.sslHandle, socket.fd) != 1:
raiseSSLError()
socket.sslNoShutdown = true
@@ -782,6 +741,8 @@ when defineSsl:
##
## **Disclaimer**: This code is not well tested, may be very unsafe and
## prone to security vulnerabilities.
if socket.isSsl:
return
wrapSocket(ctx, socket)
case handshake
@@ -805,6 +766,48 @@ when defineSsl:
else:
result = getPeerCertificates(socket.sslHandle)
proc acceptAddr*(socket: AsyncSocket, flags = {SocketFlag.SafeDisconn},
inheritable = defined(nimInheritHandles)):
owned(Future[tuple[address: string, client: AsyncSocket]]) {.async.} =
## Accepts a new connection. Returns a future containing the client socket
## corresponding to that connection and the remote address of the client.
##
## If `inheritable` is false (the default), the resulting client socket will
## not be inheritable by child processes.
##
## The future will complete when the connection is successfully accepted.
let (address, fd) = await acceptAddr(socket.fd.AsyncFD, flags, inheritable)
let client = newAsyncSocket(fd, socket.domain, socket.sockType,
socket.protocol, socket.isBuffered, inheritable)
result = (address, client)
if socket.isSsl:
when defineSsl:
if socket.sslContext == nil:
raiseSSLError("The SSL Context is closed/unset")
wrapSocket(socket.sslContext, result.client)
if result.client.sslHandle == nil:
raiseSslHandleError()
let flags = {SocketFlag.SafeDisconn}
sslLoop(result.client, flags, SSL_accept(result.client.sslHandle))
proc accept*(socket: AsyncSocket,
flags = {SocketFlag.SafeDisconn}): owned(Future[AsyncSocket]) =
## Accepts a new connection. Returns a future containing the client socket
## corresponding to that connection.
## If `inheritable` is false (the default), the resulting client socket will
## not be inheritable by child processes.
## The future will complete when the connection is successfully accepted.
var retFut = newFuture[AsyncSocket]("asyncnet.accept")
var fut = acceptAddr(socket, flags)
fut.callback =
proc (future: Future[tuple[address: string, client: AsyncSocket]]) =
assert future.finished
if future.failed:
retFut.fail(future.readError)
else:
retFut.complete(future.read.client)
return retFut
proc getSockOpt*(socket: AsyncSocket, opt: SOBool, level = SOL_SOCKET): bool {.
tags: [ReadIOEffect].} =
## Retrieves option `opt` as a boolean value.

View File

@@ -231,6 +231,18 @@ func deduplicate*[T](s: openArray[T], isSorted: bool = false): seq[T] =
for itm in items(s):
if not result.contains(itm): result.add(itm)
proc min*[T](x: openArray[T], cmp: proc(a, b: T): int): T {.effectsOf: cmp.} =
## The minimum value of `x`.
result = x[0]
for i in 1..high(x):
if cmp(x[i], result) < 0: result = x[i]
proc max*[T](x: openArray[T], cmp: proc(a, b: T): int): T {.effectsOf: cmp.} =
## The maximum value of `x`.
result = x[0]
for i in 1..high(x):
if cmp(result, x[i]) < 0: result = x[i]
func minIndex*[T](s: openArray[T]): int {.since: (1, 1).} =
## Returns the index of the minimum value of `s`.
## `T` needs to have a `<` operator.
@@ -248,6 +260,20 @@ func minIndex*[T](s: openArray[T]): int {.since: (1, 1).} =
for i in 1..high(s):
if s[i] < s[result]: result = i
func minIndex*[T](s: openArray[T], cmp: proc(a, b: T): int): int {.effectsOf: cmp.} =
## Returns the index of the minimum value of `s`.
runnableExamples:
import std/sugar
let s1 = @["foo","bar", "hello"]
let s2 = @[2..4, 1..3, 6..10]
assert minIndex(s1, proc (a, b: string): int = a.len - b.len) == 0
assert minIndex(s2, (a, b) => a.a - b.a) == 1
for i in 1..high(s):
if cmp(s[i], s[result]) < 0: result = i
func maxIndex*[T](s: openArray[T]): int {.since: (1, 1).} =
## Returns the index of the maximum value of `s`.
## `T` needs to have a `<` operator.
@@ -265,15 +291,35 @@ func maxIndex*[T](s: openArray[T]): int {.since: (1, 1).} =
for i in 1..high(s):
if s[i] > s[result]: result = i
func maxIndex*[T](s: openArray[T], cmp: proc(a, b: T): int): int {.effectsOf: cmp.} =
## Returns the index of the maximum value of `s`.
runnableExamples:
import std/sugar
let s1 = @["foo","bar", "hello"]
let s2 = @[2..4, 1..3, 6..10]
assert maxIndex(s1, proc (a, b: string): int = a.len - b.len) == 2
assert maxIndex(s2, (a, b) => a.a - b.a) == 2
for i in 1..high(s):
if cmp(s[result], s[i]) < 0: result = i
func minmax*[T](x: openArray[T]): (T, T) =
## The minimum and maximum values of `x`. `T` needs to have a `<` operator.
var l = x[0]
var h = x[0]
for i in 1..high(x):
if x[i] < l: l = x[i]
if h < x[i]: h = x[i]
elif h < x[i]: h = x[i]
result = (l, h)
func minmax*[T](x: openArray[T], cmp: proc(a, b: T): int): (T, T) {.effectsOf: cmp.} =
## The minimum and maximum values of `x`.
result = (x[0], x[0])
for i in 1..high(x):
if cmp(x[i], result[0]) < 0: result[0] = x[i]
elif cmp(result[1], x[i]) < 0: result[1] = x[i]
template zipImpl(s1, s2, retType: untyped): untyped =
proc zip*[S, T](s1: openArray[S], s2: openArray[T]): retType =

View File

@@ -30,7 +30,7 @@ proc rawGetDeep[X, A](t: X, key: A, hc: var Hash): int {.inline, outParamsAt: [3
rawGetDeepImpl()
proc rawInsert[X, A, B](t: var X, data: var KeyValuePairSeq[A, B],
key: A, val: sink B, hc: Hash, h: Hash) =
key: sink A, val: sink B, hc: Hash, h: Hash) =
rawInsertImpl()
template checkIfInitialized() =

View File

@@ -107,7 +107,7 @@ runnableExamples:
## container (e.g. string, sequence or array), as it is a mapping where the
## items are the keys, and their number of occurrences are the values.
## For that purpose `toCountTable proc<#toCountTable,openArray[A]>`_
## comes handy:
## comes in handy:
runnableExamples:
let myString = "abracadabra"
@@ -281,7 +281,7 @@ proc initTable*[A, B](initialSize = defaultInitialSize): Table[A, B] =
result = default(Table[A, B])
initImpl(result, initialSize)
proc `[]=`*[A, B](t: var Table[A, B], key: A, val: sink B) =
proc `[]=`*[A, B](t: var Table[A, B], key: sink A, val: sink B) =
## Inserts a `(key, value)` pair into `t`.
##
## See also:
@@ -494,7 +494,7 @@ proc len*[A, B](t: Table[A, B]): int =
result = t.counter
proc add*[A, B](t: var Table[A, B], key: A, val: sink B) {.deprecated:
proc add*[A, B](t: var Table[A, B], key: sink A, val: sink B) {.deprecated:
"Deprecated since v1.4; it was more confusing than useful, use `[]=`".} =
## Puts a new `(key, value)` pair into `t` even if `t[key]` already exists.
##
@@ -676,6 +676,68 @@ template withValue*[A, B](t: var Table[A, B], key: A,
else:
body2
template withValue*[A, B](t: Table[A, B], key: A,
value, body1, body2: untyped) =
## Retrieves the value at `t[key]` if it exists, assigns
## it to the variable `value` and executes `body`
runnableExamples:
type
User = object
name: string
proc `=copy`(dest: var User, source: User) {.error.}
proc exec(t: Table[int, User]) =
t.withValue(1, value):
assert value.name == "Hello"
do:
doAssert false
var executedElseBranch = false
t.withValue(521, value):
doAssert false
do:
executedElseBranch = true
assert executedElseBranch
var t = initTable[int, User]()
t[1] = User(name: "Hello")
t.exec()
mixin rawGet
var hc: Hash
var index = rawGet(t, key, hc)
if index > 0:
let value {.cursor, inject.} = t.data[index].val
body1
else:
body2
template withValue*[A, B](t: Table[A, B], key: A,
value, body: untyped) =
## Retrieves the value at `t[key]` if it exists, assigns
## it to the variable `value` and executes `body`
runnableExamples:
type
User = object
name: string
proc `=copy`(dest: var User, source: User) {.error.}
proc exec(t: Table[int, User]) =
t.withValue(1, value):
assert value.name == "Hello"
t.withValue(521, value):
doAssert false
var t = initTable[int, User]()
t[1] = User(name: "Hello")
t.exec()
withValue(t, key, value, body):
discard
iterator pairs*[A, B](t: Table[A, B]): (lent A, lent B) =
## Iterates over any `(key, value)` pair in the table `t`.
@@ -888,7 +950,7 @@ proc `[]`*[A, B](t: TableRef[A, B], key: A): var B =
result = t[][key]
proc `[]=`*[A, B](t: TableRef[A, B], key: A, val: sink B) =
proc `[]=`*[A, B](t: TableRef[A, B], key: sink A, val: sink B) =
## Inserts a `(key, value)` pair into `t`.
##
## See also:
@@ -1045,7 +1107,7 @@ proc len*[A, B](t: TableRef[A, B]): int =
result = t.counter
proc add*[A, B](t: TableRef[A, B], key: A, val: sink B) {.deprecated:
proc add*[A, B](t: TableRef[A, B], key: sink A, val: sink B) {.deprecated:
"Deprecated since v1.4; it was more confusing than useful, use `[]=`".} =
## Puts a new `(key, value)` pair into `t` even if `t[key]` already exists.
##
@@ -1297,7 +1359,7 @@ proc rawGet[A, B](t: OrderedTable[A, B], key: A, hc: var Hash): int =
proc rawInsert[A, B](t: var OrderedTable[A, B],
data: var OrderedKeyValuePairSeq[A, B],
key: A, val: sink B, hc: Hash, h: Hash) =
key: sink A, val: sink B, hc: Hash, h: Hash) =
rawInsertImpl()
data[h].next = -1
if t.first < 0: t.first = h
@@ -1349,7 +1411,7 @@ proc initOrderedTable*[A, B](initialSize = defaultInitialSize): OrderedTable[A,
result = default(OrderedTable[A, B])
initImpl(result, initialSize)
proc `[]=`*[A, B](t: var OrderedTable[A, B], key: A, val: sink B) =
proc `[]=`*[A, B](t: var OrderedTable[A, B], key: sink A, val: sink B) =
## Inserts a `(key, value)` pair into `t`.
##
## See also:
@@ -1547,7 +1609,7 @@ proc len*[A, B](t: OrderedTable[A, B]): int {.inline.} =
result = t.counter
proc add*[A, B](t: var OrderedTable[A, B], key: A, val: sink B) {.deprecated:
proc add*[A, B](t: var OrderedTable[A, B], key: sink A, val: sink B) {.deprecated:
"Deprecated since v1.4; it was more confusing than useful, use `[]=`".} =
## Puts a new `(key, value)` pair into `t` even if `t[key]` already exists.
##
@@ -1907,7 +1969,7 @@ proc `[]`*[A, B](t: OrderedTableRef[A, B], key: A): var B =
echo a['z']
result = t[][key]
proc `[]=`*[A, B](t: OrderedTableRef[A, B], key: A, val: sink B) =
proc `[]=`*[A, B](t: OrderedTableRef[A, B], key: sink A, val: sink B) =
## Inserts a `(key, value)` pair into `t`.
##
## See also:
@@ -2048,7 +2110,7 @@ proc len*[A, B](t: OrderedTableRef[A, B]): int {.inline.} =
result = t.counter
proc add*[A, B](t: OrderedTableRef[A, B], key: A, val: sink B) {.deprecated:
proc add*[A, B](t: OrderedTableRef[A, B], key: sink A, val: sink B) {.deprecated:
"Deprecated since v1.4; it was more confusing than useful, use `[]=`".} =
## Puts a new `(key, value)` pair into `t` even if `t[key]` already exists.
##
@@ -2267,19 +2329,15 @@ iterator mvalues*[A, B](t: OrderedTableRef[A, B]): var B =
yield t.data[h].val
assert(len(t) == L, "the length of the table changed while iterating over it")
# -------------------------------------------------------------------------
# ------------------------------ CountTable -------------------------------
# -------------------------------------------------------------------------
type
CountTable*[A] = object
## Hash table that counts the number of each key.
## Hash table that counts the number of each key. Unlike `Table<#Table>`_,
## this uses a zero count to signal "empty" & so does not cache hash values
## for comparison reduction or resize acceleration.
##
## For creating an empty CountTable, use `initCountTable proc
## <#initCountTable>`_.
@@ -2674,10 +2732,6 @@ iterator mvalues*[A](t: var CountTable[A]): var int =
# ---------------------------------------------------------------------------
# ---------------------------- CountTableRef --------------------------------
# ---------------------------------------------------------------------------

View File

@@ -46,10 +46,10 @@ proc setFileSize(fh: FileHandle, newFileSize = -1, oldSize = -1): OSErrorCode =
when defined(windows):
var sizeHigh = int32(newFileSize shr 32)
let sizeLow = int32(newFileSize and 0xffffffff)
let status = setFilePointer(fh, sizeLow, addr(sizeHigh), FILE_BEGIN)
let status = setFilePointer(Handle fh, sizeLow, addr(sizeHigh), FILE_BEGIN)
let lastErr = osLastError()
if (status == INVALID_SET_FILE_POINTER and lastErr.int32 != NO_ERROR) or
setEndOfFile(fh) == 0:
setEndOfFile(Handle fh) == 0:
result = lastErr
else:
if newFileSize > oldSize: # grow the file

View File

@@ -285,6 +285,19 @@ proc listen*(socket: SocketHandle, backlog = SOMAXCONN): cint {.tags: [
else:
result = posix.listen(socket, cint(backlog))
proc getAddrInfo*(address: string, port: Port, hints: AddrInfo): ptr AddrInfo =
##
##
## .. warning:: The resulting `ptr AddrInfo` must be freed using `freeAddrInfo`!
result = nil
let socketPort = if hints.ai_socktype == toInt(SOCK_RAW): "" else: $port
var gaiResult = getaddrinfo(address, socketPort.cstring, addr(hints), result)
if gaiResult != 0'i32:
when useWinVersion or defined(freertos) or defined(nuttx):
raiseOSError(osLastError())
else:
raiseOSError(osLastError(), $gai_strerror(gaiResult))
proc getAddrInfo*(address: string, port: Port, domain: Domain = AF_INET,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): ptr AddrInfo =
@@ -296,7 +309,7 @@ proc getAddrInfo*(address: string, port: Port, domain: Domain = AF_INET,
ai_socktype: toInt(sockType),
ai_protocol: toInt(protocol)
)
result = nil
# OpenBSD doesn't support AI_V4MAPPED and doesn't define the macro AI_V4MAPPED.
# FreeBSD, Haiku don't support AI_V4MAPPED but defines the macro.
# https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198092
@@ -305,13 +318,7 @@ proc getAddrInfo*(address: string, port: Port, domain: Domain = AF_INET,
not defined(android) and not defined(haiku):
if domain == AF_INET6:
hints.ai_flags = AI_V4MAPPED
let socketPort = if sockType == SOCK_RAW: "" else: $port
var gaiResult = getaddrinfo(address, socketPort.cstring, addr(hints), result)
if gaiResult != 0'i32:
when useWinVersion or defined(freertos) or defined(nuttx):
raiseOSError(osLastError())
else:
raiseOSError(osLastError(), $gai_strerror(gaiResult))
result = getAddrInfo(address, port, hints)
proc ntohl*(x: uint32): uint32 =
## Converts 32-bit unsigned integers from network to host byte order.
@@ -723,7 +730,7 @@ when useNimNetLite:
##
## Similar to POSIX's `getsockname`:idx:.
template sockGetNameOrRaiseError(socket: untyped, name: untyped) =
var namelen = sizeof(socket).SockLen
var namelen = sizeof(name).SockLen
if getsockname(socket, cast[ptr SockAddr](addr(name)),
addr(namelen)) == -1'i32:
raiseOSError(osLastError())

View File

@@ -845,11 +845,11 @@ when weirdTarget or defined(windows) or defined(posix) or defined(nintendoswitch
result = default(FileInfo)
when defined(windows):
var rawInfo: BY_HANDLE_FILE_INFORMATION
# We have to use the super special '_get_osfhandle' call (wrapped above)
# We have to use the super special '_get_osfhandle' call (wrapped in winlean)
# To transform the C file descriptor to a native file handle.
var realHandle = get_osfhandle(handle)
var realHandle = get_osfhandle(handle.cint)
if getFileInformationByHandle(realHandle, addr rawInfo) == 0:
raiseOSError(osLastError(), $handle)
raiseOSError(osLastError(), $(int handle))
rawToFormalFileInfo(rawInfo, "", result)
else:
var rawInfo: Stat = default(Stat)

View File

@@ -546,8 +546,8 @@ when defined(windows) and not defined(useNimRtl):
raiseOSError(osLastError())
proc fileClose[T: Handle | FileHandle](h: var T) {.inline.} =
if h > 4:
closeHandleCheck(h)
if h.int > 4:
closeHandleCheck(Handle h)
h = INVALID_HANDLE_VALUE.T
proc hsClose(s: Stream) =
@@ -574,8 +574,8 @@ when defined(windows) and not defined(useNimRtl):
addr bytesWritten, nil)
if a == 0: raiseOSError(osLastError())
proc newFileHandleStream(handle: Handle): owned FileHandleStream =
result = FileHandleStream(handle: handle, closeImpl: hsClose, atEndImpl: hsAtEnd,
proc newFileHandleStream(handle: FileHandle): owned FileHandleStream =
result = FileHandleStream(handle: Handle handle, closeImpl: hsClose, atEndImpl: hsAtEnd,
readDataImpl: hsReadData, writeDataImpl: hsWriteData)
proc buildCommandLine(a: string, args: openArray[string]): string =
@@ -888,7 +888,7 @@ when defined(windows) and not defined(useNimRtl):
assert readfds.len <= MAXIMUM_WAIT_OBJECTS
var rfds: WOHandleArray
for i in 0..readfds.len()-1:
rfds[i] = readfds[i].outHandle #fProcessHandle
rfds[i] = readfds[i].outHandle.Handle #fProcessHandle
var ret = waitForMultipleObjects(readfds.len.int32,
addr(rfds), 0'i32, timeout.int32)
@@ -904,7 +904,7 @@ when defined(windows) and not defined(useNimRtl):
proc hasData*(p: Process): bool =
var x: int32
if peekNamedPipe(p.outHandle, lpTotalBytesAvail = addr x):
if peekNamedPipe(p.outHandle.Handle, lpTotalBytesAvail = addr x):
result = x > 0
elif not defined(useNimRtl):

View File

@@ -540,10 +540,17 @@ proc loadConfig*(stream: Stream, filename: string = "[stream]"): Config =
proc loadConfig*(filename: string): Config =
## Loads the specified configuration file into a new Config instance.
let file = open(filename, fmRead)
let fileStream = newFileStream(file)
defer: fileStream.close()
result = fileStream.loadConfig(filename)
when nimvm:
# HACK: As a workaround,
# since open() using {.importc.} is not available on NimScript.
let stringStream = newStringStream(readFile(filename))
defer: stringStream.close()
result = stringStream.loadConfig(filename)
else:
let file = open(filename, fmRead)
let fileStream = newFileStream(file)
defer: fileStream.close()
result = fileStream.loadConfig(filename)
proc replace(s: string): string =
var d = ""

View File

@@ -105,7 +105,8 @@ proc newPipeOutStream*[T](s: sink (ref T)): owned PipeOutStream[T] =
new(result)
for dest, src in fields((ref T)(result)[], s[]):
dest = src
wasMoved(s[])
{.cast(raises: []), cast(tags: []).}:
wasMoved(s[])
if result.readLineImpl != nil:
result.baseReadLineImpl = result.readLineImpl
result.readLineImpl = posReadLine[T]

View File

@@ -512,7 +512,7 @@ macro scanTuple*(input: untyped; pattern: static[string]; matcherTypes: varargs[
inc userMatches
else: discard
inc p
result.add nnkTupleConstr.newTree(newCall(ident("scanf"), input, newStrLitNode(pattern)))
result.add nnkTupleConstr.newTree(newCall(bindSym("scanf"), input, newStrLitNode(pattern)))
for arg in arguments:
result[^1][0].add arg
result[^1].add arg

View File

@@ -1081,7 +1081,7 @@ func fromBin*[T: SomeInteger](s: string): T =
doAssert fromBin[uint8](s) == 153
doAssert s.fromBin[:int16] == 0b1110_1110_1001_1001'i16
doAssert s.fromBin[:uint64] == 1216933529'u64
result = T(0)
let p = parseutils.parseBin(s, result)
if p != s.len or p == 0:
raise newException(ValueError, "invalid binary integer: " & s)
@@ -1104,7 +1104,7 @@ func fromOct*[T: SomeInteger](s: string): T =
doAssert fromOct[uint8](s) == 255'u8
doAssert s.fromOct[:int16] == 24063'i16
doAssert s.fromOct[:uint64] == 21913087'u64
result = T(0)
let p = parseutils.parseOct(s, result)
if p != s.len or p == 0:
raise newException(ValueError, "invalid oct integer: " & s)
@@ -1127,7 +1127,7 @@ func fromHex*[T: SomeInteger](s: string): T =
doAssert fromHex[uint8](s) == 246'u8
doAssert s.fromHex[:int16] == -29194'i16
doAssert s.fromHex[:uint64] == 305499638'u64
result = T(0)
let p = parseutils.parseHex(s, result)
if p != s.len or p == 0:
raise newException(ValueError, "invalid hex integer: " & s)
@@ -2202,7 +2202,8 @@ func replace*(s, sub: string, by = ""): string {.rtl,
## * `replace func<#replace,string,char,char>`_ for replacing
## single characters
## * `replaceWord func<#replaceWord,string,string,string>`_
## * `multiReplace func<#multiReplace,string,varargs[]>`_
## * `multiReplace func<#multiReplace,string,varargs[]>`_ for substrings
## * `multiReplace func<#multiReplace,openArray[char],varargs[]>`_ for single characters
result = ""
let subLen = sub.len
if subLen == 0:
@@ -2245,7 +2246,8 @@ func replace*(s: string, sub, by: char): string {.rtl,
## See also:
## * `find func<#find,string,char,Natural,int>`_
## * `replaceWord func<#replaceWord,string,string,string>`_
## * `multiReplace func<#multiReplace,string,varargs[]>`_
## * `multiReplace func<#multiReplace,string,varargs[]>`_ for substrings
## * `multiReplace func<#multiReplace,openArray[char],varargs[]>`_ for single characters
result = newString(s.len)
var i = 0
while i < s.len:
@@ -2330,7 +2332,38 @@ func multiReplace*(s: string, replacements: varargs[(string, string)]): string =
add result, s[i]
inc(i)
func multiReplace*(s: openArray[char]; replacements: varargs[(set[char], char)]): string {.noinit.} =
## Performs multiple character replacements in a single pass through the input.
##
## `multiReplace` scans the input `s` from left to right and replaces
## characters based on character sets, applying the first matching replacement
## at each position. Useful for sanitizing or transforming strings with
## predefined character mappings.
##
## The order of the `replacements` matters:
## - First matching replacement is applied
## - Subsequent replacements are not considered for the same character
##
## See also:
## - `multiReplace(s: string; replacements: varargs[(string, string)]) <#multiReplace,string,varargs[]>`_,
runnableExamples:
const WinSanitationRules = [
({'\0'..'\31'}, ' '),
({'"'}, '\''),
({'/', '\\', ':', '|'}, '-'),
({'*', '?', '<', '>'}, '_'),
]
# Sanitize a filename with Windows-incompatible characters
const file = "a/file:with?invalid*chars.txt"
doAssert file.multiReplace(WinSanitationRules) == "a-file-with_invalid_chars.txt"
result = newStringUninit(s.len)
for i in 0..<s.len:
var nextChar = s[i]
for subs, by in replacements.items:
if nextChar in subs:
nextChar = by
break
result[i] = nextChar
func insertSep*(s: string, sep = '_', digits = 3): string {.rtl,
extern: "nsuInsertSep".} =

View File

@@ -805,9 +805,13 @@ proc isatty*(f: File): bool =
when defined(posix):
proc isatty(fildes: FileHandle): cint {.
importc: "isatty", header: "<unistd.h>".}
else:
proc isatty(fildes: FileHandle): cint {.
elif defined(windows):
proc c_isatty(fildes: cint): cint {.
importc: "_isatty", header: "<io.h>".}
proc isatty(fildes: FileHandle): cint =
c_isatty(cint(fildes))
else:
{.error: "isatty is not supported on your operating system!".}
result = isatty(getFileHandle(f)) != 0'i32

View File

@@ -2016,7 +2016,6 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int,
var year = takeInt(2..2)
var thisCen = now().year div 100
parsed.year = some(thisCen*100 + year)
result = year > 0
of yyyy:
let year =
if input[i] in {'+', '-'}:

View File

@@ -556,15 +556,16 @@ template test*(name, body) {.dirty.} =
body
{.pop.}
except:
except Exception:
let e = getCurrentException()
let eTypeDesc = "[" & exceptionTypeName(e) & "]"
checkpoint("Unhandled exception: " & getCurrentExceptionMsg() & " " & eTypeDesc)
if e == nil: # foreign
fail()
else:
var stackTrace {.inject.} = e.getStackTrace()
fail()
var stackTrace {.inject.} = e.getStackTrace()
fail()
except:
checkpoint("Unhandled exception: " & getCurrentExceptionMsg() & " [<foreign exception>]")
fail()
finally:
if testStatusIMPL == TestStatus.FAILED:
@@ -760,6 +761,14 @@ macro expect*(exceptions: varargs[typed], body: untyped): untyped =
expect IOError, OSError, ValueError, AssertionDefect:
defectiveRobot()
template expectException(errorTypes, lineInfoLit, body): NimNode {.dirty.} =
try:
body
checkpoint(lineInfoLit & ": Expect Failed, no exception was thrown.")
fail()
except errorTypes:
discard
template expectBody(errorTypes, lineInfoLit, body): NimNode {.dirty.} =
{.push warning[BareExcept]:off.}
try:
@@ -770,17 +779,23 @@ macro expect*(exceptions: varargs[typed], body: untyped): untyped =
fail()
except errorTypes:
discard
except:
except Exception:
let err = getCurrentException()
checkpoint(lineInfoLit & ": Expect Failed, " & $err.name & " was thrown.")
fail()
{.pop.}
var errorTypes = newNimNode(nnkBracket)
var hasException = false
for exp in exceptions:
if exp.strVal == "Exception":
hasException = true
errorTypes.add(exp)
result = getAst(expectBody(errorTypes, errorTypes.lineInfo, body))
if hasException:
result = getAst(expectException(errorTypes, errorTypes.lineInfo, body))
else:
result = getAst(expectBody(errorTypes, errorTypes.lineInfo, body))
proc disableParamFiltering* =
## disables filtering tests with the command line params

View File

@@ -40,9 +40,6 @@ type
## at the end. If the file does not exist, it
## will be created.
FileHandle* = cint ## The type that represents an OS file handle; this is
## useful for low-level file access.
FileSeekPos* = enum ## Position relative to which seek should happen.
# The values are ordered so that they match with stdio
# SEEK_SET, SEEK_CUR and SEEK_END respectively.
@@ -50,6 +47,13 @@ type
fspCur ## Seek relative to current position
fspEnd ## Seek relative to end
when defined(windows):
type FileHandle* = int
## Windows `HANDLE` type, convertible to `winlean.Handle`.
else:
type FileHandle* = cint ## The type that represents an OS file handle; this is
## useful for low-level file access.
# text file handling:
when not defined(nimscript) and not defined(js):
# duplicated between io and ansi_c
@@ -310,12 +314,7 @@ elif defined(windows):
proc getOsfhandle(fd: cint): int {.
importc: "_get_osfhandle", header: "<io.h>".}
type
IoHandle = distinct pointer
## Windows' HANDLE type. Defined as an untyped pointer but is **not**
## one. Named like this to avoid collision with other `system` modules.
proc setHandleInformation(hObject: IoHandle, dwMask, dwFlags: WinDWORD):
proc setHandleInformation(hObject: FileHandle, dwMask, dwFlags: WinDWORD):
WinBOOL {.stdcall, dynlib: "kernel32",
importc: "SetHandleInformation".}
@@ -361,7 +360,7 @@ proc getFileHandle*(f: File): FileHandle =
## Note that on Windows this doesn't return the Windows-specific handle,
## but the C library's notion of a handle, whatever that means.
## Use `getOsFileHandle` instead.
c_fileno(f)
FileHandle c_fileno(f)
proc getOsFileHandle*(f: File): FileHandle =
## Returns the OS file handle of the file `f`. This is only useful for
@@ -390,7 +389,7 @@ when defined(nimdoc) or (defined(posix) and not defined(nimscript)) or defined(w
flags = if inheritable: flags and not FD_CLOEXEC else: flags or FD_CLOEXEC
result = c_fcntl(f, F_SETFD, flags) != -1
else:
result = setHandleInformation(cast[IoHandle](f), HANDLE_FLAG_INHERIT,
result = setHandleInformation(f, HANDLE_FLAG_INHERIT,
inheritable.WinDWORD) != 0
proc readLine*(f: File, line: var string): bool {.tags: [ReadIOEffect],
@@ -423,12 +422,18 @@ proc readLine*(f: File, line: var string): bool {.tags: [ReadIOEffect],
importc: "LocalFree", stdcall, dynlib: "kernel32".}
proc isatty(f: File): bool =
# terminal module also has isatty
when defined(posix):
proc isatty(fildes: FileHandle): cint {.
importc: "isatty", header: "<unistd.h>".}
else:
proc isatty(fildes: FileHandle): cint {.
elif defined(windows):
proc c_isatty(fildes: cint): cint {.
importc: "_isatty", header: "<io.h>".}
proc isatty(fildes: FileHandle): cint =
c_isatty(cint(fildes))
else:
{.error: "isatty is not supported on your operating system!".}
result = isatty(getFileHandle(f)) != 0'i32
# this implies the file is open
@@ -769,10 +774,10 @@ proc open*(f: var File, filehandle: FileHandle,
## The passed file handle will no longer be inheritable.
when not defined(nimInheritHandles) and declared(setInheritable):
let oshandle = when defined(windows): FileHandle getOsfhandle(
filehandle) else: filehandle
cint filehandle) else: filehandle
if not setInheritable(oshandle, false):
return false
f = c_fdopen(filehandle, RawFormatOpen[mode])
f = c_fdopen(cint filehandle, RawFormatOpen[mode])
result = f != nil
proc open*(filename: string,

View File

@@ -161,12 +161,10 @@ else:
proc `=wasMoved`*[T](obj: var T) {.magic: "WasMoved", noSideEffect.} =
## Generic `wasMoved`:idx: implementation that can be overridden.
proc wasMoved*[T](obj: var T) {.inline, noSideEffect.} =
proc wasMoved*[T](obj: var T) {.magic: "WasMoved", noSideEffect.}
## Resets an object `obj` to its initial (binary zero) value to signify
## it was "moved" and to signify its destructor should do nothing and
## ideally be optimized away.
{.cast(raises: []), cast(tags: []).}:
`=wasMoved`(obj)
proc move*[T](x: var T): T {.magic: "Move", noSideEffect.} =
result = x
@@ -1618,7 +1616,7 @@ when not defined(js) and defined(nimV2):
align: int16
depth: int16
display: ptr UncheckedArray[uint32] # classToken
when defined(nimTypeNames) or defined(nimArcIds):
when defined(nimTypeNames) or defined(nimArcIds) or defined(nimOrcLeakDetector):
name: cstring
traceImpl: pointer
typeInfoV1: pointer # for backwards compat, usually nil
@@ -1685,7 +1683,7 @@ when not defined(js):
else:
{.error: "The type T cannot contain managed memory or have destructors".}
proc newStringUninit*(len: Natural): string =
proc newStringUninit*(len: Natural): string {.noSideEffect.} =
## Returns a new string of length `len` but with uninitialized
## content. One needs to fill the string character after character
## with the index operator `s[i]`.
@@ -1696,15 +1694,16 @@ when not defined(js):
result = newString(len)
else:
result = newStringOfCap(len)
when defined(nimSeqsV2):
let s = cast[ptr NimStringV2](addr result)
if len > 0:
{.cast(noSideEffect).}:
when defined(nimSeqsV2):
let s = cast[ptr NimStringV2](addr result)
if len > 0:
s.len = len
s.p.data[len] = '\0'
else:
let s = cast[NimString](result)
s.len = len
s.p.data[len] = '\0'
else:
let s = cast[NimString](result)
s.len = len
s.data[len] = '\0'
s.data[len] = '\0'
else:
proc newStringUninit*(len: Natural): string {.
magic: "NewString", importc: "mnewString", noSideEffect.}
@@ -2311,8 +2310,16 @@ when notJSnotNims and hostOS != "standalone":
##
## .. warning:: Only use this if you know what you are doing.
currException = exc
proc raiseDefect() {.compilerRtl.} =
let e = getCurrentException()
if e of Defect:
reportUnhandledError(e)
rawQuit(1)
elif defined(nimscript):
proc getCurrentException*(): ref Exception {.compilerRtl.} = discard
proc raiseDefect*() {.compilerRtl.} = discard
when notJSnotNims:
{.push stackTrace: off, profiler: off.}
@@ -2715,16 +2722,54 @@ proc procCall*(x: untyped) {.magic: "ProcCall", compileTime.} =
## ```
discard
proc strcmp(a, b: cstring): cint {.noSideEffect,
importc, header: "<string.h>".}
proc `==`*(x, y: cstring): bool {.magic: "EqCString", noSideEffect,
inline.} =
## Checks for equality between two `cstring` variables.
proc strcmp(a, b: cstring): cint {.noSideEffect,
importc, header: "<string.h>".}
if pointer(x) == pointer(y): result = true
elif pointer(x) == nil or pointer(y) == nil: result = false
else: result = strcmp(x, y) == 0
func ltCStringVm(x, y: cstring): bool {.inline.} =
discard "implemented in the vm ops"
func leCStringVm(x, y: cstring): bool {.inline.} =
discard "implemented in the vm ops"
when defined(nimPreviewCStringComparisons):
func `<`*(x, y: cstring): bool {.inline.} =
if x == y:
result = false
elif x == nil:
result = true
elif y == nil:
result = false
else:
when nimvm:
result = ltCStringVm(x, y)
else:
when defined(js):
result = pointer(x) < pointer(y)
else:
result = strcmp(x, y) < 0
func `<=`*(x, y: cstring): bool {.inline.} =
if x == y: result = true
elif x == nil:
result = true
elif y == nil:
result = false
else:
when nimvm:
result = leCStringVm(x, y)
else:
when defined(js):
result = pointer(x) <= pointer(y)
else:
result = strcmp(x, y) <= 0
template closureScope*(body: untyped): untyped =
## Useful when creating a closure in a loop to capture local loop variables by
## their current iteration values.
@@ -2769,41 +2814,87 @@ template once*(body: untyped): untyped =
{.pop.} # warning[GcMem]: off, warning[Uninit]: off
proc substr*(s: openArray[char]): string =
## Copies a slice of `s` into a new string and returns this new
## string.
runnableExamples:
let a = "abcdefgh"
assert a.substr(2, 5) == "cdef"
assert a.substr(2) == "cdefgh"
assert a.substr(5, 99) == "fgh"
result = newString(s.len)
for i, ch in s:
result[i] = ch
template NotJSnotVMnotNims(): static bool = # hack, see: #12517 #12518
when nimvm:
false
else:
notJSnotNims
proc substr*(s: string, first, last: int): string = # A bug with `magic: Slice` requires this to exist this way
## Copies a slice of `s` into a new string and returns this new
## string.
proc substr*(a: openArray[char]): string =
## Returns a new string, copying contents of `a`.
##
## The bounds `first` and `last` denote the indices of
## the first and last characters that shall be copied. If `last`
## is omitted, it is treated as `high(s)`. If `last >= s.len`, `s.len`
## is used instead: This means `substr` can also be used to `cut`:idx:
## or `limit`:idx: a string's length.
## .. warning:: As opposed to other `substr` overloads, no additional input
## validation and clamping is performed!
##
## This proc does not prevent raising an `IndexDefect` when `a` is being
## passed using a `toOpenArray` call with out-of-bounds indexes:
## * `doAssertRaises(IndexDefect): discard "abc".toOpenArray(-9, 9).substr()`
##
## If clamping is required, consider using
## `substr(s: string; first, last: int) <#substr,string,int,int>`_:
## * `doAssert "abc".substr(-9, 9) == "abc"`
runnableExamples:
let a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
assert a.substr() == "abcdefgh"
assert a.toOpenArray(2, 5).substr() == "cdef"
assert a.toOpenArray(2, high(a)).substr() == "cdefgh" # From index 2 to `high(a)`
doAssertRaises(IndexDefect): discard a.toOpenArray(5, 99).substr()
result = newStringUninit(a.len)
when NotJSnotVMnotNims:
if a.len > 0:
copyMem(result[0].addr, a[0].unsafeAddr, a.len)
else:
for i, ch in a:
result[i] = ch
proc substr*(s: string; first, last: int): string = # A bug with `magic: Slice` requires this to exist this way
## Returns a new string containing a substring (slice) of `s`,
## copying characters from index `first` to index `last` inclusive.
##
## Index values are validated and capped:
## - Negative `first` is clamped to 0
## - If `last >= s.len`, it is clamped to `high(s)`
## - If `last < first`, returns an empty string
## This means `substr` can also be used to `cut`:idx: or `limit`:idx:
## a string's length.
##
## .. note::
## If index values are ensured to be in-bounds, for performance
## critical cases consider using a non-clamping overload
## `substr(a: openArray[char]) <#substr,openArray[char]>`_
runnableExamples:
let a = "abcdefgh"
assert a.substr(2, 5) == "cdef"
assert a.substr(2) == "cdefgh"
assert a.substr(5, 99) == "fgh"
let first = max(first, 0)
let L = max(min(last, high(s)) - first + 1, 0)
result = newString(L)
for i in 0 .. L-1:
result[i] = s[i+first]
assert a.substr(2, 5) == "cdef" # Normal substring
# Invalid indexes
assert a.substr(5, 99) == "fgh" # From index 5 to `high(a)`
assert a.substr(42, 99) == "" # `first` out of bounds
assert a.substr(100, 5) == "" # `first > last`
assert a.substr(-1, 2) == "abc" # Negative `first` clamped to 0
let
first = max(first, 0)
last = min(last, high(s))
L = max(last - first + 1, 0)
result = newStringUninit(L)
when NotJSnotVMnotNims:
if L > 0:
copyMem(result[0].addr, s[first].unsafeAddr, L)
else:
for i in 0..<L:
result[i] = s[i + first]
proc substr*(s: string, first = 0): string =
result = substr(s, first, high(s))
## Convenience `substr <#substr,string,int,int>`_ overload that returns
## a substring from `first` to the end of the string.
##
## `first` value is validated and capped:
## - `first >= s.len` returns an empty string
## - Negative `first` is clamped to 0.
runnableExamples:
let a = "abcdefgh"
assert a.substr(2) == "cdefgh" # From index 2 to string end (`high(a)`)
assert a.substr(100) == "" # `first` out of bounds
assert a.substr(-1) == "abcdefgh" # Negative `first` clamped to 0
substr(s, first, high(s))
when defined(nimconfig):
include "system/nimscript"
@@ -2818,8 +2909,10 @@ when not defined(js):
proc toOpenArray*[T](x: seq[T]; first, last: int): openArray[T] {.
magic: "Slice".}
## Allows passing the slice of `x` from the element at `first` to the element
## at `last` to `openArray[T]` parameters without copying it.
## Returns a non-owning slice (a `view`:idx:) of `x` from the element at
## index `first` to `last` inclusive. Allows passing slices without copying,
## as opposed to using the slice operator
## `\`[]\` <#[],openArray[T],HSlice[U: Ordinal,V: Ordinal]>`_.
##
## Example:
## ```nim

View File

@@ -252,12 +252,12 @@ iterator elementsExcept(t, s: CellSet): PCell {.inline.} =
var r = t.head
while r != nil:
let ss = cellSetGet(s, r.key)
var i:uint = 0
var i = 0'u
while int(i) <= high(r.bits):
var w = r.bits[i]
if ss != nil:
w = w and not ss.bits[i]
var j:uint = 0
var j = 0'u
while w != 0:
if (w and 1) != 0:
yield cast[PCell]((r.key shl PageShift) or

View File

@@ -14,20 +14,24 @@ when not defined(nimPreviewSlimSystem):
result = ""
result.addFloat(x)
proc `$`*(x: int): string {.raises: [].} =
## Outplace version of `addInt`.
result = ""
result.addInt(x)
template addIntAlias(T: typedesc) =
proc `$`*(x: T): string {.raises: [].} =
## Outplace version of `addInt`.
result = ""
result.addInt(x)
proc `$`*(x: int64): string {.raises: [].} =
## Outplace version of `addInt`.
result = ""
result.addInt(x)
# need to declare for bit types as well to not clash with converters:
addIntAlias int
addIntAlias int8
addIntAlias int16
addIntAlias int32
addIntAlias int64
proc `$`*(x: uint64): string {.raises: [].} =
## Outplace version of `addInt`.
result = ""
addInt(result, x)
addIntAlias uint
addIntAlias uint8
addIntAlias uint16
addIntAlias uint32
addIntAlias uint64
# same as old `ctfeWhitelist` behavior, whether or not this is a good idea.
template gen(T) =

View File

@@ -42,6 +42,9 @@ proc raiseExceptionEx(e: sink(ref Exception), ename, procname, filename: cstring
proc reraiseException() {.compilerRtl.} =
sysFatal(ReraiseDefect, "no exception to reraise")
proc raiseDefect() {.compilerRtl.} =
sysFatal(ReraiseDefect, "exception handling is not available")
proc writeStackTrace() = discard
proc unsetControlCHook() = discard

View File

@@ -597,7 +597,13 @@ proc sweep(gch: var GcHeap) =
if isCell(x):
# cast to PCell is correct here:
var c = cast[PCell](x)
if c notin gch.marked: freeCyclicCell(gch, c)
if c notin gch.marked:
# Don't free objects that have the ZctFlag set (created in finalizers)
if (c.refcount and ZctFlag) == 0:
freeCyclicCell(gch, c)
else:
# Clear the ZctFlag for the next collection cycle
c.refcount = c.refcount and not ZctFlag
proc markS(gch: var GcHeap, c: PCell) =
gcAssert isAllocatedPtr(gch.region, c), "markS: foreign heap root detected A!"

View File

@@ -46,5 +46,3 @@ else:
{.pragma: compilerRtl, compilerproc.}
{.pragma: benign, gcsafe.}
{.push sinkInference: on.}

View File

@@ -154,6 +154,16 @@ proc raiseException(e: ref Exception, ename: cstring) {.
e.trace = rawWriteStackTrace()
{.emit: "throw `e`;".}
proc raiseDefect() {.compilerproc, asmNoStackFrame.} =
if isNimException():
let e = getCurrentException()
if e of Defect:
if excHandler == 0:
unhandledException(e)
when NimStackTrace:
e.trace = rawWriteStackTrace()
{.emit: "throw `e`;".}
proc reraiseException() {.compilerproc, asmNoStackFrame.} =
if lastJSError == nil:
raise newException(ReraiseDefect, "no exception to reraise")

View File

@@ -55,7 +55,8 @@ elif defined(gogc):
include system / mm / go
elif (defined(nogc) or defined(gcDestructors)) and defined(useMalloc):
include system / mm / malloc
when not defined(useNimRtl):
include system / mm / malloc
when defined(nogc):
proc GC_getStatistics(): string = ""

View File

@@ -14,21 +14,26 @@ proc rangeBase(T: typedesc): typedesc {.magic: "TypeTrait".}
proc repr*(x: NimNode): string {.magic: "Repr", noSideEffect.}
proc repr*(x: int): string =
## Same as $x
$x
template dollarAlias(T: typedesc) =
proc repr*(x: T): string {.noSideEffect.} =
## Same as $x
$x
proc repr*(x: int64): string =
## Same as $x
$x
# need to declare for bit types as well to not clash with converters:
dollarAlias int
dollarAlias int8
dollarAlias int16
dollarAlias int32
dollarAlias int64
proc repr*(x: uint64): string {.noSideEffect.} =
## Same as $x
$x
dollarAlias uint
dollarAlias uint8
dollarAlias uint16
dollarAlias uint32
dollarAlias uint64
proc repr*(x: float): string =
## Same as $x
$x
dollarAlias float
dollarAlias float32
proc repr*(x: bool): string {.magic: "BoolToStr", noSideEffect.}
## repr for a boolean argument. Returns `x`

View File

@@ -300,7 +300,7 @@ proc setLengthSeq(seq: PGenericSeq, elemSize, elemAlign, newLen: int): PGenericS
zeroMem(dataPointer(result, elemAlign, elemSize, newLen), (result.len-%newLen) *% elemSize)
result.len = newLen
proc setLengthSeqV2(s: PGenericSeq, typ: PNimType, newLen: int): PGenericSeq {.
proc setLengthSeqV2(s: PGenericSeq, typ: PNimType, newLen: int, isTrivial: bool): PGenericSeq {.
compilerRtl.} =
sysAssert typ.kind == tySequence, "setLengthSeqV2: type is not a seq"
if s == nil:
@@ -334,7 +334,8 @@ proc setLengthSeqV2(s: PGenericSeq, typ: PNimType, newLen: int): PGenericSeq {.
# presence of user defined destructors, the user will expect the cell to be
# "destroyed" thus creating the same problem. We can destroy the cell in the
# finalizer of the sequence, but this makes destruction non-deterministic.
zeroMem(dataPointer(result, elemAlign, elemSize, newLen), (result.len-%newLen) *% elemSize)
if not isTrivial: # optimization for trivial types
zeroMem(dataPointer(result, elemAlign, elemSize, newLen), (result.len-%newLen) *% elemSize)
else:
result = s
zeroMem(dataPointer(result, elemAlign, elemSize, result.len), (newLen-%result.len) *% elemSize)

View File

@@ -437,7 +437,7 @@ type
fd_count*: cint # unsigned
fd_array*: array[0..FD_SETSIZE-1, SocketHandle]
AddrInfo* = object
AddrInfo* {.importc: "ADDRINFOA", header: "ws2tcpip.h".} = object
ai_flags*: cint ## Input flags.
ai_family*: cint ## Address family of socket.
ai_socktype*: cint ## Socket type.
@@ -815,7 +815,7 @@ proc WSASendTo*(s: SocketHandle, buf: ptr TWSABuf, bufCount: DWORD,
completionProc: POVERLAPPED_COMPLETION_ROUTINE): cint {.
stdcall, importc: "WSASendTo", dynlib: "Ws2_32.dll".}
proc get_osfhandle*(fd:FileHandle): Handle {.
proc get_osfhandle*(fd: cint): Handle {.
importc: "_get_osfhandle", header:"<io.h>".}
proc getSystemTimes*(lpIdleTime, lpKernelTime,