merged devel into epc

This commit is contained in:
Simon Hafner 2015-05-08 06:40:34 +05:00
commit f5cca89610
183 changed files with 5295 additions and 4043 deletions

View file

@ -221,7 +221,7 @@ proc spawn*[TIn](p: var TActorPool[TIn, void], input: TIn,
setupTask()
schedule()
when isMainModule:
when not defined(testing) and isMainModule:
var
a: TActorPool[int, void]
createActorPool(a)

View file

@ -40,8 +40,8 @@ proc reverse*[T](a: var openArray[T]) =
proc reversed*[T](a: openArray[T], first, last: Natural): seq[T] =
## returns the reverse of the array `a[first..last]`.
result = newSeq[T](last - first + 1)
var x = first
var y = last
var x = first.int
var y = last.int
while x <= last:
result[x] = a[y]
dec(y)

View file

@ -634,6 +634,93 @@ when defined(windows) or defined(nimdoc):
# free ``ol``.
return retFuture
proc recvInto*(socket: TAsyncFD, buf: cstring, size: int,
flags = {SocketFlag.SafeDisconn}): Future[int] =
## Reads **up to** ``size`` bytes from ``socket`` into ``buf``, which must
## at least be of that size. Returned future will complete once all the
## data requested is read, a part of the data has been read, or the socket
## has disconnected in which case the future will complete with a value of
## ``0``.
##
## **Warning**: The ``Peek`` socket flag is not supported on Windows.
# Things to note:
# * When WSARecv completes immediately then ``bytesReceived`` is very
# unreliable.
# * Still need to implement message-oriented socket disconnection,
# '\0' in the message currently signifies a socket disconnect. Who
# knows what will happen when someone sends that to our socket.
verifyPresence(socket)
assert SocketFlag.Peek notin flags, "Peek not supported on Windows."
var retFuture = newFuture[int]("recvInto")
#buf[] = '\0'
var dataBuf: TWSABuf
dataBuf.buf = buf
dataBuf.len = size
var bytesReceived: Dword
var flagsio = flags.toOSFlags().Dword
var ol = PCustomOverlapped()
GC_ref(ol)
ol.data = TCompletionData(fd: socket, cb:
proc (fd: TAsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished:
if errcode == OSErrorCode(-1):
if bytesCount == 0 and dataBuf.buf[0] == '\0':
retFuture.complete(0)
else:
retFuture.complete(bytesCount)
else:
if flags.isDisconnectionError(errcode):
retFuture.complete(0)
else:
retFuture.fail(newException(OSError, osErrorMsg(errcode)))
if dataBuf.buf != nil:
dataBuf.buf = nil
)
let ret = WSARecv(socket.SocketHandle, addr dataBuf, 1, addr bytesReceived,
addr flagsio, cast[POVERLAPPED](ol), nil)
if ret == -1:
let err = osLastError()
if err.int32 != ERROR_IO_PENDING:
if dataBuf.buf != nil:
dataBuf.buf = nil
GC_unref(ol)
if flags.isDisconnectionError(err):
retFuture.complete(0)
else:
retFuture.fail(newException(OSError, osErrorMsg(err)))
elif ret == 0 and bytesReceived == 0 and dataBuf.buf[0] == '\0':
# We have to ensure that the buffer is empty because WSARecv will tell
# us immediately when it was disconnected, even when there is still
# data in the buffer.
# We want to give the user as much data as we can. So we only return
# the empty string (which signals a disconnection) when there is
# nothing left to read.
retFuture.complete(0)
# TODO: "For message-oriented sockets, where a zero byte message is often
# allowable, a failure with an error code of WSAEDISCON is used to
# indicate graceful closure."
# ~ http://msdn.microsoft.com/en-us/library/ms741688%28v=vs.85%29.aspx
else:
# Request to read completed immediately.
# From my tests bytesReceived isn't reliable.
let realSize =
if bytesReceived == 0:
size
else:
bytesReceived
assert realSize <= size
retFuture.complete(realSize)
# We don't deallocate ``ol`` here because even though this completed
# immediately poll will still be notified about its completion and it will
# free ``ol``.
return retFuture
proc send*(socket: TAsyncFD, data: string,
flags = {SocketFlag.SafeDisconn}): Future[void] =
## Sends ``data`` to ``socket``. The returned future will complete once all
@ -983,6 +1070,30 @@ else:
addRead(socket, cb)
return retFuture
proc recvInto*(socket: TAsyncFD, buf: cstring, size: int,
flags = {SocketFlag.SafeDisconn}): Future[int] =
var retFuture = newFuture[int]("recvInto")
proc cb(sock: TAsyncFD): bool =
result = true
let res = recv(sock.SocketHandle, buf, size.cint,
flags.toOSFlags())
if res < 0:
let lastError = osLastError()
if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}:
if flags.isDisconnectionError(lastError):
retFuture.complete(0)
else:
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
else:
result = false # We still want this callback to be called.
else:
retFuture.complete(res)
# TODO: The following causes a massive slowdown.
#if not cb(socket):
addRead(socket, cb)
return retFuture
proc send*(socket: TAsyncFD, data: string,
flags = {SocketFlag.SafeDisconn}): Future[void] =
var retFuture = newFuture[void]("send")

View file

@ -300,7 +300,7 @@ proc newAsyncFtpClient*(address: string, port = Port(21),
result.dsockConnected = false
result.csock = newAsyncSocket()
when isMainModule:
when not defined(testing) and isMainModule:
var ftp = newAsyncFtpClient("example.com", user = "test", pass = "test")
proc main(ftp: AsyncFtpClient) {.async.} =
await ftp.connect()

View file

@ -23,8 +23,7 @@
## proc cb(req: Request) {.async.} =
## await req.respond(Http200, "Hello World")
##
## asyncCheck server.serve(Port(8080), cb)
## runForever()
## waitFor server.serve(Port(8080), cb)
import strtabs, asyncnet, asyncdispatch, parseutils, uri, strutils
type
@ -109,22 +108,19 @@ proc sendHeaders*(req: Request, headers: StringTableRef): Future[void] =
addHeaders(msg, headers)
return req.client.send(msg)
proc respond*(req: Request, code: HttpCode,
content: string, headers = newStringTable()) {.async.} =
proc respond*(req: Request, code: HttpCode, content: string,
headers: StringTableRef = nil): Future[void] =
## Responds to the request with the specified ``HttpCode``, headers and
## content.
##
## This procedure will **not** close the client socket.
var customHeaders = headers
customHeaders["Content-Length"] = $content.len
var msg = "HTTP/1.1 " & $code & "\c\L"
msg.addHeaders(customHeaders)
await req.client.send(msg & "\c\L" & content)
proc newRequest(): Request =
result.headers = newStringTable(modeCaseInsensitive)
result.hostname = ""
result.body = ""
if headers != nil:
msg.addHeaders(headers)
msg.add("Content-Length: " & $content.len & "\c\L\c\L")
msg.add(content)
result = req.client.send(msg)
proc parseHeader(line: string): tuple[key, value: string] =
var i = 0
@ -149,59 +145,65 @@ proc sendStatus(client: AsyncSocket, status: string): Future[void] =
proc processClient(client: AsyncSocket, address: string,
callback: proc (request: Request):
Future[void] {.closure, gcsafe.}) {.async.} =
var request: Request
request.url = initUri()
request.headers = newStringTable(modeCaseInsensitive)
var line = newStringOfCap(80)
var key, value = ""
while not client.isClosed:
# GET /path HTTP/1.1
# Header: val
# \n
var request = newRequest()
request.hostname = address
request.headers.clear(modeCaseInsensitive)
request.hostname.shallowCopy(address)
assert client != nil
request.client = client
# First line - GET /path HTTP/1.1
let line = await client.recvLine() # TODO: Timeouts.
line.setLen(0)
await client.recvLineInto(addr line) # TODO: Timeouts.
if line == "":
client.close()
return
let lineParts = line.split(' ')
if lineParts.len != 3:
await request.respond(Http400, "Invalid request. Got: " & line)
continue
let reqMethod = lineParts[0]
let path = lineParts[1]
let protocol = lineParts[2]
var i = 0
for linePart in line.split(' '):
case i
of 0: request.reqMethod.shallowCopy(linePart.normalize)
of 1: parseUri(linePart, request.url)
of 2:
try:
request.protocol = parseProtocol(linePart)
except ValueError:
asyncCheck request.respond(Http400,
"Invalid request protocol. Got: " & linePart)
continue
else:
await request.respond(Http400, "Invalid request. Got: " & line)
continue
inc i
# Headers
var i = 0
while true:
i = 0
let headerLine = await client.recvLine()
if headerLine == "":
line.setLen(0)
await client.recvLineInto(addr line)
if line == "":
client.close(); return
if headerLine == "\c\L": break
# TODO: Compiler crash
#let (key, value) = parseHeader(headerLine)
let kv = parseHeader(headerLine)
request.headers[kv.key] = kv.value
if line == "\c\L": break
let (key, value) = parseHeader(line)
request.headers[key] = value
request.reqMethod = reqMethod
request.url = parseUri(path)
try:
request.protocol = protocol.parseProtocol()
except ValueError:
asyncCheck request.respond(Http400, "Invalid request protocol. Got: " &
protocol)
continue
if reqMethod.normalize == "post":
if request.reqMethod == "post":
# Check for Expect header
if request.headers.hasKey("Expect"):
if request.headers["Expect"].toLower == "100-continue":
await client.sendStatus("100 Continue")
else:
await client.sendStatus("417 Expectation Failed")
# Read the body
# - Check for Content-length header
if request.headers.hasKey("Content-Length"):
@ -215,11 +217,11 @@ proc processClient(client: AsyncSocket, address: string,
await request.respond(Http400, "Bad Request. No Content-Length.")
continue
case reqMethod.normalize
case request.reqMethod
of "get", "post", "head", "put", "delete", "trace", "options", "connect", "patch":
await callback(request)
else:
await request.respond(Http400, "Invalid request method. Got: " & reqMethod)
await request.respond(Http400, "Invalid request method. Got: " & request.reqMethod)
# Persistent connections
if (request.protocol == HttpVer11 and
@ -247,7 +249,7 @@ proc serve*(server: AsyncHttpServer, port: Port,
server.socket.setSockOpt(OptReuseAddr, true)
server.socket.bindAddr(port, address)
server.socket.listen()
while true:
# TODO: Causes compiler crash.
#var (address, client) = await server.socket.acceptAddr()
@ -260,7 +262,7 @@ proc close*(server: AsyncHttpServer) =
## Terminates the async http server instance.
server.socket.close()
when isMainModule:
when not defined(testing) and isMainModule:
proc main =
var server = newAsyncHttpServer()
proc cb(req: Request) {.async.} =

View file

@ -660,7 +660,7 @@ proc len*(disp: Dispatcher): int =
## Retrieves the amount of delegates in ``disp``.
return disp.delegates.len
when isMainModule:
when not defined(testing) and isMainModule:
proc testConnect(s: AsyncSocket, no: int) =
echo("Connected! " & $no)

View file

@ -24,7 +24,7 @@
##
## Chat server
## ^^^^^^^^^^^
##
##
## The following example demonstrates a simple chat server.
##
## .. code-block::nim
@ -182,26 +182,30 @@ proc connect*(socket: AsyncSocket, address: string, port: Port,
sslSetConnectState(socket.sslHandle)
sslLoop(socket, flags, sslDoHandshake(socket.sslHandle))
proc readInto(buf: cstring, size: int, socket: AsyncSocket,
flags: set[SocketFlag]): Future[int] {.async.} =
template readInto(buf: cstring, size: int, socket: AsyncSocket,
flags: set[SocketFlag]): int =
## Reads **up to** ``size`` bytes from ``socket`` into ``buf``. Note that
## this is a template and not a proc.
var res = 0
if socket.isSsl:
when defined(ssl):
# SSL mode.
sslLoop(socket, flags,
sslRead(socket.sslHandle, buf, size.cint))
result = opResult
res = opResult
else:
var data = await recv(socket.fd.TAsyncFD, size, flags)
if data.len != 0:
copyMem(buf, addr data[0], data.len)
var recvIntoFut = recvInto(socket.fd.TAsyncFD, buf, size, flags)
yield recvIntoFut
# Not in SSL mode.
result = data.len
res = recvIntoFut.read()
res
proc readIntoBuf(socket: AsyncSocket,
flags: set[SocketFlag]): Future[int] {.async.} =
result = await readInto(addr socket.buffer[0], BufferSize, socket, flags)
template readIntoBuf(socket: AsyncSocket,
flags: set[SocketFlag]): int =
var size = readInto(addr socket.buffer[0], BufferSize, socket, flags)
socket.currPos = 0
socket.bufLen = result
socket.bufLen = size
size
proc recv*(socket: AsyncSocket, size: int,
flags = {SocketFlag.SafeDisconn}): Future[string] {.async.} =
@ -222,10 +226,11 @@ proc recv*(socket: AsyncSocket, size: int,
## to be read then the future will complete with a value of ``""``.
if socket.isBuffered:
result = newString(size)
shallow(result)
let originalBufPos = socket.currPos
if socket.bufLen == 0:
let res = await socket.readIntoBuf(flags - {SocketFlag.Peek})
let res = socket.readIntoBuf(flags - {SocketFlag.Peek})
if res == 0:
result.setLen(0)
return
@ -236,7 +241,7 @@ proc recv*(socket: AsyncSocket, size: int,
if SocketFlag.Peek in flags:
# We don't want to get another buffer if we're peeking.
break
let res = await socket.readIntoBuf(flags - {SocketFlag.Peek})
let res = socket.readIntoBuf(flags - {SocketFlag.Peek})
if res == 0:
break
@ -251,7 +256,7 @@ proc recv*(socket: AsyncSocket, size: int,
result.setLen(read)
else:
result = newString(size)
let read = await readInto(addr result[0], size, socket, flags)
let read = readInto(addr result[0], size, socket, flags)
result.setLen(read)
proc send*(socket: AsyncSocket, data: string,
@ -302,15 +307,14 @@ proc accept*(socket: AsyncSocket,
retFut.complete(future.read.client)
return retFut
proc recvLine*(socket: AsyncSocket,
flags = {SocketFlag.SafeDisconn}): Future[string] {.async.} =
## Reads a line of data from ``socket``. Returned future will complete once
## a full line is read or an error occurs.
proc recvLineInto*(socket: AsyncSocket, resString: ptr string,
flags = {SocketFlag.SafeDisconn}) {.async.} =
## Reads a line of data from ``socket`` into ``resString``.
##
## If a full line is read ``\r\L`` is not
## added to ``line``, however if solely ``\r\L`` is read then ``line``
## will be set to it.
##
##
## If the socket is disconnected, ``line`` will be set to ``""``.
##
## If the socket is disconnected in the middle of a line (before ``\r\L``
@ -318,27 +322,32 @@ proc recvLine*(socket: AsyncSocket,
## The partial line **will be lost**.
##
## **Warning**: The ``Peek`` flag is not yet implemented.
##
## **Warning**: ``recvLine`` on unbuffered sockets assumes that the protocol
## uses ``\r\L`` to delimit a new line.
template addNLIfEmpty(): stmt =
if result.len == 0:
result.add("\c\L")
##
## **Warning**: ``recvLineInto`` on unbuffered sockets assumes that the
## protocol uses ``\r\L`` to delimit a new line.
##
## **Warning**: ``recvLineInto`` currently uses a raw pointer to a string for
## performance reasons. This will likely change soon to use FutureVars.
assert SocketFlag.Peek notin flags ## TODO:
result = newFuture[void]("asyncnet.recvLineInto")
template addNLIfEmpty(): stmt =
if resString[].len == 0:
resString[].add("\c\L")
if socket.isBuffered:
result = ""
if socket.bufLen == 0:
let res = await socket.readIntoBuf(flags)
let res = socket.readIntoBuf(flags)
if res == 0:
return
var lastR = false
while true:
if socket.currPos >= socket.bufLen:
let res = await socket.readIntoBuf(flags)
let res = socket.readIntoBuf(flags)
if res == 0:
result = ""
break
resString[].setLen(0)
return
case socket.buffer[socket.currPos]
of '\r':
@ -353,24 +362,53 @@ proc recvLine*(socket: AsyncSocket,
socket.currPos.inc()
return
else:
result.add socket.buffer[socket.currPos]
resString[].add socket.buffer[socket.currPos]
socket.currPos.inc()
else:
result = ""
var c = ""
while true:
c = await recv(socket, 1, flags)
let recvFut = recv(socket, 1, flags)
c = recvFut.read()
if c.len == 0:
return ""
resString[].setLen(0)
return
if c == "\r":
c = await recv(socket, 1, flags) # Skip \L
let recvFut = recv(socket, 1, flags) # Skip \L
c = recvFut.read()
assert c == "\L"
addNLIfEmpty()
return
elif c == "\L":
addNLIfEmpty()
return
add(result.string, c)
resString[].add c
proc recvLine*(socket: AsyncSocket,
flags = {SocketFlag.SafeDisconn}): Future[string] {.async.} =
## Reads a line of data from ``socket``. Returned future will complete once
## a full line is read or an error occurs.
##
## If a full line is read ``\r\L`` is not
## added to ``line``, however if solely ``\r\L`` is read then ``line``
## will be set to it.
##
## If the socket is disconnected, ``line`` will be set to ``""``.
##
## If the socket is disconnected in the middle of a line (before ``\r\L``
## is read) then line will be set to ``""``.
## The partial line **will be lost**.
##
## **Warning**: The ``Peek`` flag is not yet implemented.
##
## **Warning**: ``recvLine`` on unbuffered sockets assumes that the protocol
## uses ``\r\L`` to delimit a new line.
template addNLIfEmpty(): stmt =
if result.len == 0:
result.add("\c\L")
assert SocketFlag.Peek notin flags ## TODO:
result = ""
await socket.recvLineInto(addr result, flags)
proc listen*(socket: AsyncSocket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} =
## Marks ``socket`` as accepting connections.
@ -458,7 +496,7 @@ proc isClosed*(socket: AsyncSocket): bool =
## Determines whether the socket has been closed.
return socket.closed
when isMainModule:
when not defined(testing) and isMainModule:
type
TestCases = enum
HighClient, LowClient, LowServer
@ -500,11 +538,11 @@ when isMainModule:
proc (future: Future[void]) =
echo("Send")
client.close()
var f = accept(sock)
f.callback = onAccept
var f = accept(sock)
f.callback = onAccept
runForever()

View file

@ -16,33 +16,33 @@ import times
## Vectors are implemented as direction vectors, ie. when transformed with a matrix
## the translation part of matrix is ignored. The coordinate system used is
## right handed, because its compatible with 2d coordinate system (rotation around
## zaxis equals 2d rotation).
## zaxis equals 2d rotation).
## Operators `+` , `-` , `*` , `/` , `+=` , `-=` , `*=` and `/=` are implemented
## for vectors and scalars.
##
##
## Quick start example:
##
##
## # Create a matrix which first rotates, then scales and at last translates
##
##
## var m:TMatrix3d=rotate(PI,vector3d(1,1,2.5)) & scale(2.0) & move(100.0,200.0,300.0)
##
##
## # Create a 3d point at (100,150,200) and a vector (5,2,3)
##
## var pt:TPoint3d=point3d(100.0,150.0,200.0)
##
##
## var pt:TPoint3d=point3d(100.0,150.0,200.0)
##
## var vec:TVector3d=vector3d(5.0,2.0,3.0)
##
##
##
##
## pt &= m # transforms pt in place
##
##
## var pt2:TPoint3d=pt & m #concatenates pt with m and returns a new point
##
##
## var vec2:TVector3d=vec & m #concatenates vec with m and returns a new vector
type
type
TMatrix3d* =object
## Implements a row major 3d matrix, which means
## transformations are applied the order they are concatenated.
@ -53,12 +53,12 @@ type
## [ tx ty tz tw ]
ax*,ay*,az*,aw*, bx*,by*,bz*,bw*, cx*,cy*,cz*,cw*, tx*,ty*,tz*,tw*:float
TPoint3d* = object
## Implements a non-homegeneous 2d point stored as
## Implements a non-homegeneous 2d point stored as
## an `x` , `y` and `z` coordinate.
x*,y*,z*:float
TVector3d* = object
## Implements a 3d **direction vector** stored as
## an `x` , `y` and `z` coordinate. Direction vector means,
TVector3d* = object
## Implements a 3d **direction vector** stored as
## an `x` , `y` and `z` coordinate. Direction vector means,
## that when transforming a vector with a matrix, the translational
## part of the matrix is ignored.
x*,y*,z*:float
@ -67,7 +67,7 @@ type
# Some forward declarations
proc matrix3d*(ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw:float):TMatrix3d {.noInit.}
## Creates a new 4x4 3d transformation matrix.
## Creates a new 4x4 3d transformation matrix.
## `ax` , `ay` , `az` is the local x axis.
## `bx` , `by` , `bz` is the local y axis.
## `cx` , `cy` , `cz` is the local z axis.
@ -76,7 +76,7 @@ proc vector3d*(x,y,z:float):TVector3d {.noInit,inline.}
## Returns a new 3d vector (`x`,`y`,`z`)
proc point3d*(x,y,z:float):TPoint3d {.noInit,inline.}
## Returns a new 4d point (`x`,`y`,`z`)
proc tryNormalize*(v:var TVector3d):bool
proc tryNormalize*(v:var TVector3d):bool
## Modifies `v` to have a length of 1.0, keeping its angle.
## If `v` has zero length (and thus no angle), it is left unmodified and false is
## returned, otherwise true is returned.
@ -85,7 +85,7 @@ proc tryNormalize*(v:var TVector3d):bool
let
IDMATRIX*:TMatrix3d=matrix3d(
1.0,0.0,0.0,0.0,
1.0,0.0,0.0,0.0,
0.0,1.0,0.0,0.0,
0.0,0.0,1.0,0.0,
0.0,0.0,0.0,1.0)
@ -114,20 +114,20 @@ proc safeArccos(v:float):float=
## due to rounding issues
return arccos(clamp(v,-1.0,1.0))
template makeBinOpVector(s:expr)=
template makeBinOpVector(s:expr)=
## implements binary operators + , - , * and / for vectors
proc s*(a,b:TVector3d):TVector3d {.inline,noInit.} =
proc s*(a,b:TVector3d):TVector3d {.inline,noInit.} =
vector3d(s(a.x,b.x),s(a.y,b.y),s(a.z,b.z))
proc s*(a:TVector3d,b:float):TVector3d {.inline,noInit.} =
proc s*(a:TVector3d,b:float):TVector3d {.inline,noInit.} =
vector3d(s(a.x,b),s(a.y,b),s(a.z,b))
proc s*(a:float,b:TVector3d):TVector3d {.inline,noInit.} =
proc s*(a:float,b:TVector3d):TVector3d {.inline,noInit.} =
vector3d(s(a,b.x),s(a,b.y),s(a,b.z))
template makeBinOpAssignVector(s:expr)=
template makeBinOpAssignVector(s:expr)=
## implements inplace binary operators += , -= , /= and *= for vectors
proc s*(a:var TVector3d,b:TVector3d) {.inline.} =
proc s*(a:var TVector3d,b:TVector3d) {.inline.} =
s(a.x,b.x) ; s(a.y,b.y) ; s(a.z,b.z)
proc s*(a:var TVector3d,b:float) {.inline.} =
proc s*(a:var TVector3d,b:float) {.inline.} =
s(a.x,b) ; s(a.y,b) ; s(a.z,b)
@ -188,20 +188,20 @@ proc scale*(s:float):TMatrix3d {.noInit.} =
proc scale*(s:float,org:TPoint3d):TMatrix3d {.noInit.} =
## Returns a new scaling matrix using, `org` as scale origin.
result.setElements(s,0,0,0, 0,s,0,0, 0,0,s,0,
result.setElements(s,0,0,0, 0,s,0,0, 0,0,s,0,
org.x-s*org.x,org.y-s*org.y,org.z-s*org.z,1.0)
proc stretch*(sx,sy,sz:float):TMatrix3d {.noInit.} =
## Returns new a stretch matrix, which is a
## scale matrix with non uniform scale in x,y and z.
result.setElements(sx,0,0,0, 0,sy,0,0, 0,0,sz,0, 0,0,0,1)
proc stretch*(sx,sy,sz:float,org:TPoint3d):TMatrix3d {.noInit.} =
## Returns a new stretch matrix, which is a
## scale matrix with non uniform scale in x,y and z.
## `org` is used as stretch origin.
result.setElements(sx,0,0,0, 0,sy,0,0, 0,0,sz,0, org.x-sx*org.x,org.y-sy*org.y,org.z-sz*org.z,1)
proc move*(dx,dy,dz:float):TMatrix3d {.noInit.} =
## Returns a new translation matrix.
result.setElements(1,0,0,0, 0,1,0,0, 0,0,1,0, dx,dy,dz,1)
@ -235,7 +235,7 @@ proc rotate*(angle:float,axis:TVector3d):TMatrix3d {.noInit.}=
uvomc=normax.x*normax.y*omc
uwomc=normax.x*normax.z*omc
vwomc=normax.y*normax.z*omc
result.setElements(
u2+(1.0-u2)*cs, uvomc+wsi, uwomc-vsi, 0.0,
uvomc-wsi, v2+(1.0-v2)*cs, vwomc+usi, 0.0,
@ -248,11 +248,11 @@ proc rotate*(angle:float,org:TPoint3d,axis:TVector3d):TMatrix3d {.noInit.}=
# see PDF document http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.pdf
# for how this is computed
var normax=axis
if not normax.tryNormalize: #simplifies matrix computation below a lot
raise newException(DivByZeroError,"Cannot rotate around zero length axis")
let
u=normax.x
v=normax.y
@ -272,7 +272,7 @@ proc rotate*(angle:float,org:TPoint3d,axis:TVector3d):TMatrix3d {.noInit.}=
uvomc=normax.x*normax.y*omc
uwomc=normax.x*normax.z*omc
vwomc=normax.y*normax.z*omc
result.setElements(
u2+(v2+w2)*cs, uvomc+wsi, uwomc-vsi, 0.0,
uvomc-wsi, v2+(u2+w2)*cs, vwomc+usi, 0.0,
@ -305,7 +305,7 @@ proc rotateY*(angle:float):TMatrix3d {.noInit.}=
0,1,0,0,
s,0,c,0,
0,0,0,1)
proc rotateZ*(angle:float):TMatrix3d {.noInit.}=
## Creates a matrix that rotates around the z-axis with `angle` radians,
## which is also called a 'yaw' matrix.
@ -317,19 +317,19 @@ proc rotateZ*(angle:float):TMatrix3d {.noInit.}=
-s,c,0,0,
0,0,1,0,
0,0,0,1)
proc isUniform*(m:TMatrix3d,tol=1.0e-6):bool=
## Checks if the transform is uniform, that is
## Checks if the transform is uniform, that is
## perpendicular axes of equal length, which means (for example)
## it cannot transform a sphere into an ellipsoid.
## `tol` is used as tolerance for both equal length comparison
## `tol` is used as tolerance for both equal length comparison
## and perpendicular comparison.
#dot product=0 means perpendicular coord. system, check xaxis vs yaxis and xaxis vs zaxis
if abs(m.ax*m.bx+m.ay*m.by+m.az*m.bz)<=tol and # x vs y
abs(m.ax*m.cx+m.ay*m.cy+m.az*m.cz)<=tol and #x vs z
abs(m.bx*m.cx+m.by*m.cy+m.bz*m.cz)<=tol: #y vs z
#subtract squared lengths of axes to check if uniform scaling:
let
sqxlen=(m.ax*m.ax+m.ay*m.ay+m.az*m.az)
@ -340,16 +340,16 @@ proc isUniform*(m:TMatrix3d,tol=1.0e-6):bool=
return false
proc mirror*(planeperp:TVector3d):TMatrix3d {.noInit.}=
## Creates a matrix that mirrors over the plane that has `planeperp` as normal,
## and passes through origo. `planeperp` does not need to be normalized.
# https://en.wikipedia.org/wiki/Transformation_matrix
var n=planeperp
if not n.tryNormalize:
raise newException(DivByZeroError,"Cannot mirror over a plane with a zero length normal")
let
a=n.x
b=n.y
@ -357,7 +357,7 @@ proc mirror*(planeperp:TVector3d):TMatrix3d {.noInit.}=
ab=a*b
ac=a*c
bc=b*c
result.setElements(
1-2*a*a , -2*ab,-2*ac,0,
-2*ab , 1-2*b*b, -2*bc, 0,
@ -376,7 +376,7 @@ proc mirror*(org:TPoint3d,planeperp:TVector3d):TMatrix3d {.noInit.}=
var n=planeperp
if not n.tryNormalize:
raise newException(DivByZeroError,"Cannot mirror over a plane with a zero length normal")
let
a=n.x
b=n.y
@ -390,7 +390,7 @@ proc mirror*(org:TPoint3d,planeperp:TVector3d):TMatrix3d {.noInit.}=
tx=org.x
ty=org.y
tz=org.z
result.setElements(
1-2*aa , -2*ab,-2*ac,0,
-2*ab , 1-2*bb, -2*bc, 0,
@ -402,8 +402,8 @@ proc mirror*(org:TPoint3d,planeperp:TVector3d):TMatrix3d {.noInit.}=
proc determinant*(m:TMatrix3d):float=
## Computes the determinant of matrix `m`.
# This computation is gotten from ratsimp(optimize(determinant(m)))
# This computation is gotten from ratsimp(optimize(determinant(m)))
# in maxima CAS
let
O1=m.cx*m.tw-m.cw*m.tx
@ -423,10 +423,10 @@ proc inverse*(m:TMatrix3d):TMatrix3d {.noInit.}=
## Computes the inverse of matrix `m`. If the matrix
## determinant is zero, thus not invertible, a EDivByZero
## will be raised.
# this computation comes from optimize(invert(m)) in maxima CAS
let
let
det=m.determinant
O2=m.cy*m.tw-m.cw*m.ty
O3=m.cz*m.tw-m.cw*m.tz
@ -464,7 +464,7 @@ proc inverse*(m:TMatrix3d):TMatrix3d {.noInit.}=
proc equals*(m1:TMatrix3d,m2:TMatrix3d,tol=1.0e-6):bool=
## Checks if all elements of `m1`and `m2` is equal within
## a given tolerance `tol`.
return
return
abs(m1.ax-m2.ax)<=tol and
abs(m1.ay-m2.ay)<=tol and
abs(m1.az-m2.az)<=tol and
@ -486,11 +486,11 @@ proc `=~`*(m1,m2:TMatrix3d):bool=
## Checks if `m1` and `m2` is approximately equal, using a
## tolerance of 1e-6.
equals(m1,m2)
proc transpose*(m:TMatrix3d):TMatrix3d {.noInit.}=
## Returns the transpose of `m`
result.setElements(m.ax,m.bx,m.cx,m.tx,m.ay,m.by,m.cy,m.ty,m.az,m.bz,m.cz,m.tz,m.aw,m.bw,m.cw,m.tw)
proc getXAxis*(m:TMatrix3d):TVector3d {.noInit.}=
## Gets the local x axis of `m`
result.x=m.ax
@ -509,26 +509,26 @@ proc getZAxis*(m:TMatrix3d):TVector3d {.noInit.}=
result.y=m.cy
result.z=m.cz
proc `$`*(m:TMatrix3d):string=
## String representation of `m`
return rtos(m.ax) & "," & rtos(m.ay) & "," &rtos(m.az) & "," & rtos(m.aw) &
"\n" & rtos(m.bx) & "," & rtos(m.by) & "," &rtos(m.bz) & "," & rtos(m.bw) &
"\n" & rtos(m.cx) & "," & rtos(m.cy) & "," &rtos(m.cz) & "," & rtos(m.cw) &
"\n" & rtos(m.tx) & "," & rtos(m.ty) & "," &rtos(m.tz) & "," & rtos(m.tw)
return rtos(m.ax) & "," & rtos(m.ay) & "," & rtos(m.az) & "," & rtos(m.aw) &
"\n" & rtos(m.bx) & "," & rtos(m.by) & "," & rtos(m.bz) & "," & rtos(m.bw) &
"\n" & rtos(m.cx) & "," & rtos(m.cy) & "," & rtos(m.cz) & "," & rtos(m.cw) &
"\n" & rtos(m.tx) & "," & rtos(m.ty) & "," & rtos(m.tz) & "," & rtos(m.tw)
proc apply*(m:TMatrix3d, x,y,z:var float, translate=false)=
## Applies transformation `m` onto `x` , `y` , `z` , optionally
## using the translation part of the matrix.
let
let
oldx=x
oldy=y
oldz=z
x=m.cx*oldz+m.bx*oldy+m.ax*oldx
y=m.cy*oldz+m.by*oldy+m.ay*oldx
z=m.cz*oldz+m.bz*oldy+m.az*oldx
if translate:
x+=m.tx
y+=m.ty
@ -552,13 +552,13 @@ proc `len=`*(v:var TVector3d,newlen:float) {.noInit.} =
## an arbitrary vector of the requested length is returned.
let fac=newlen/v.len
if newlen==0.0:
v.x=0.0
v.y=0.0
v.z=0.0
return
if fac==Inf or fac==NegInf:
#to short for float accuracy
#do as good as possible:
@ -588,7 +588,7 @@ proc `&` *(v:TVector3d,m:TMatrix3d):TVector3d {.noInit.} =
## Concatenate vector `v` with a transformation matrix.
## Transforming a vector ignores the translational part
## of the matrix.
# | AX AY AZ AW |
# | X Y Z 1 | * | BX BY BZ BW |
# | CX CY CZ CW |
@ -605,12 +605,12 @@ proc `&=` *(v:var TVector3d,m:TMatrix3d) {.noInit.} =
## Applies transformation `m` onto `v` in place.
## Transforming a vector ignores the translational part
## of the matrix.
# | AX AY AZ AW |
# | X Y Z 1 | * | BX BY BZ BW |
# | CX CY CZ CW |
# | 0 0 0 1 |
let
newx=m.cx*v.z+m.bx*v.y+m.ax*v.x
newy=m.cy*v.z+m.by*v.y+m.ay*v.x
@ -620,38 +620,38 @@ proc `&=` *(v:var TVector3d,m:TMatrix3d) {.noInit.} =
proc transformNorm*(v:var TVector3d,m:TMatrix3d)=
## Applies a normal direction transformation `m` onto `v` in place.
## The resulting vector is *not* normalized. Transforming a vector ignores the
## translational part of the matrix. If the matrix is not invertible
## The resulting vector is *not* normalized. Transforming a vector ignores the
## translational part of the matrix. If the matrix is not invertible
## (determinant=0), an EDivByZero will be raised.
# transforming a normal is done by transforming
# by the transpose of the inverse of the original matrix
# Major reason this simple function is here is that this function can be optimized in the future,
# (possibly by hardware) as well as having a consistent API with the 2d version.
v&=transpose(inverse(m))
proc transformInv*(v:var TVector3d,m:TMatrix3d)=
## Applies the inverse of `m` on vector `v`. Transforming a vector ignores
## the translational part of the matrix. Transforming a vector ignores the
## Applies the inverse of `m` on vector `v`. Transforming a vector ignores
## the translational part of the matrix. Transforming a vector ignores the
## translational part of the matrix.
## If the matrix is not invertible (determinant=0), an EDivByZero
## will be raised.
# Major reason this simple function is here is that this function can be optimized in the future,
# (possibly by hardware) as well as having a consistent API with the 2d version.
v&=m.inverse
proc transformNormInv*(vec:var TVector3d,m:TMatrix3d)=
## Applies an inverse normal direction transformation `m` onto `v` in place.
## This is faster than creating an inverse
## matrix and transformNorm(...) it. Transforming a vector ignores the
## This is faster than creating an inverse
## matrix and transformNorm(...) it. Transforming a vector ignores the
## translational part of the matrix.
# see vector2d:s equivalent for a deeper look how/why this works
vec&=m.transpose
proc tryNormalize*(v:var TVector3d):bool=
proc tryNormalize*(v:var TVector3d):bool=
## Modifies `v` to have a length of 1.0, keeping its angle.
## If `v` has zero length (and thus no angle), it is left unmodified and false is
## returned, otherwise true is returned.
@ -663,26 +663,26 @@ proc tryNormalize*(v:var TVector3d):bool=
v.x/=mag
v.y/=mag
v.z/=mag
return true
proc normalize*(v:var TVector3d) {.inline.}=
proc normalize*(v:var TVector3d) {.inline.}=
## Modifies `v` to have a length of 1.0, keeping its angle.
## If `v` has zero length, an EDivByZero will be raised.
if not tryNormalize(v):
raise newException(DivByZeroError,"Cannot normalize zero length vector")
proc rotate*(vec:var TVector3d,angle:float,axis:TVector3d)=
## Rotates `vec` in place, with `angle` radians over `axis`, which passes
## Rotates `vec` in place, with `angle` radians over `axis`, which passes
## through origo.
# see PDF document http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.pdf
# for how this is computed
var normax=axis
if not normax.tryNormalize:
raise newException(DivByZeroError,"Cannot rotate around zero length axis")
let
cs=cos(angle)
si=sin(angle)
@ -694,11 +694,11 @@ proc rotate*(vec:var TVector3d,angle:float,axis:TVector3d)=
y=vec.y
z=vec.z
uxyzomc=(u*x+v*y+w*z)*omc
vec.x=u*uxyzomc+x*cs+(v*z-w*y)*si
vec.y=v*uxyzomc+y*cs+(w*x-u*z)*si
vec.z=w*uxyzomc+z*cs+(u*y-v*x)*si
proc scale*(v:var TVector3d,s:float)=
## Scales the vector in place with factor `s`
v.x*=s
@ -713,12 +713,12 @@ proc stretch*(v:var TVector3d,sx,sy,sz:float)=
proc mirror*(v:var TVector3d,planeperp:TVector3d)=
## Computes the mirrored vector of `v` over the plane
## that has `planeperp` as normal direction.
## that has `planeperp` as normal direction.
## `planeperp` does not need to be normalized.
var n=planeperp
n.normalize
let
x=v.x
y=v.y
@ -729,7 +729,7 @@ proc mirror*(v:var TVector3d,planeperp:TVector3d)=
ac=a*c
ab=a*b
bc=b*c
v.x= -2*(ac*z+ab*y+a*a*x)+x
v.y= -2*(bc*z+b*b*y+ab*x)+y
v.z= -2*(c*c*z+bc*y+ac*x)+z
@ -740,7 +740,7 @@ proc `-` *(v:TVector3d):TVector3d=
result.x= -v.x
result.y= -v.y
result.z= -v.z
# declare templated binary operators
makeBinOpVector(`+`)
makeBinOpVector(`-`)
@ -752,7 +752,7 @@ makeBinOpAssignVector(`*=`)
makeBinOpAssignVector(`/=`)
proc dot*(v1,v2:TVector3d):float {.inline.}=
## Computes the dot product of two vectors.
## Computes the dot product of two vectors.
## Returns 0.0 if the vectors are perpendicular.
return v1.x*v2.x+v1.y*v2.y+v1.z*v2.z
@ -769,12 +769,12 @@ proc cross*(v1,v2:TVector3d):TVector3d {.inline.}=
proc equals*(v1,v2:TVector3d,tol=1.0e-6):bool=
## Checks if two vectors approximately equals with a tolerance.
return abs(v2.x-v1.x)<=tol and abs(v2.y-v1.y)<=tol and abs(v2.z-v1.z)<=tol
proc `=~` *(v1,v2:TVector3d):bool=
## Checks if two vectors approximately equals with a
## Checks if two vectors approximately equals with a
## hardcoded tolerance 1e-6
equals(v1,v2)
proc angleTo*(v1,v2:TVector3d):float=
## Returns the smallest angle between v1 and v2,
## which is in range 0-PI
@ -801,7 +801,7 @@ proc arbitraryAxis*(norm:TVector3d):TMatrix3d {.noInit.}=
ay=cross(norm,ax)
ay.normalize()
az=cross(ax,ay)
result.setElements(
ax.x,ax.y,ax.z,0.0,
ay.x,ay.y,ay.z,0.0,
@ -811,20 +811,20 @@ proc arbitraryAxis*(norm:TVector3d):TMatrix3d {.noInit.}=
proc bisect*(v1,v2:TVector3d):TVector3d {.noInit.}=
## Computes the bisector between v1 and v2 as a normalized vector.
## If one of the input vectors has zero length, a normalized version
## of the other is returned. If both input vectors has zero length,
## of the other is returned. If both input vectors has zero length,
## an arbitrary normalized vector `v1` is returned.
var
vmag1=v1.len
vmag2=v2.len
# zero length vector equals arbitrary vector, just change
# zero length vector equals arbitrary vector, just change
# magnitude to one to avoid zero division
if vmag1==0.0:
if vmag1==0.0:
if vmag2==0: #both are zero length return any normalized vector
return XAXIS
vmag1=1.0
if vmag2==0.0: vmag2=1.0
if vmag2==0.0: vmag2=1.0
let
x1=v1.x/vmag1
y1=v1.y/vmag1
@ -832,14 +832,14 @@ proc bisect*(v1,v2:TVector3d):TVector3d {.noInit.}=
x2=v2.x/vmag2
y2=v2.y/vmag2
z2=v2.z/vmag2
result.x=(x1 + x2) * 0.5
result.y=(y1 + y2) * 0.5
result.z=(z1 + z2) * 0.5
if not result.tryNormalize():
# This can happen if vectors are colinear. In this special case
# there are actually inifinitely many bisectors, we select just
# there are actually inifinitely many bisectors, we select just
# one of them.
result=v1.cross(XAXIS)
if result.sqrLen<1.0e-9:
@ -857,14 +857,14 @@ proc point3d*(x,y,z:float):TPoint3d=
result.x=x
result.y=y
result.z=z
proc sqrDist*(a,b:TPoint3d):float=
## Computes the squared distance between `a`and `b`
let dx=b.x-a.x
let dy=b.y-a.y
let dz=b.z-a.z
result=dx*dx+dy*dy+dz*dz
proc dist*(a,b:TPoint3d):float {.inline.}=
## Computes the absolute distance between `a`and `b`
result=sqrt(sqrDist(a,b))
@ -876,7 +876,7 @@ proc `$` *(p:TPoint3d):string=
result.add(rtos(p.y))
result.add(",")
result.add(rtos(p.z))
proc `&`*(p:TPoint3d,m:TMatrix3d):TPoint3d=
## Concatenates a point `p` with a transform `m`,
## resulting in a new, transformed point.
@ -893,18 +893,18 @@ proc `&=` *(p:var TPoint3d,m:TMatrix3d)=
p.x=m.cx*z+m.bx*y+m.ax*x+m.tx
p.y=m.cy*z+m.by*y+m.ay*x+m.ty
p.z=m.cz*z+m.bz*y+m.az*x+m.tz
proc transformInv*(p:var TPoint3d,m:TMatrix3d)=
## Applies the inverse of transformation `m` onto `p` in place.
## If the matrix is not invertable (determinant=0) , EDivByZero will
## be raised.
# can possibly be more optimized in the future so use this function when possible
p&=inverse(m)
proc `+`*(p:TPoint3d,v:TVector3d):TPoint3d {.noInit,inline.} =
## Adds a vector `v` to a point `p`, resulting
## Adds a vector `v` to a point `p`, resulting
## in a new point.
result.x=p.x+v.x
result.y=p.y+v.y
@ -917,7 +917,7 @@ proc `+=`*(p:var TPoint3d,v:TVector3d) {.noInit,inline.} =
p.z+=v.z
proc `-`*(p:TPoint3d,v:TVector3d):TPoint3d {.noInit,inline.} =
## Subtracts a vector `v` from a point `p`, resulting
## Subtracts a vector `v` from a point `p`, resulting
## in a new point.
result.x=p.x-v.x
result.y=p.y-v.y
@ -933,37 +933,37 @@ proc `-=`*(p:var TPoint3d,v:TVector3d) {.noInit,inline.} =
## Subtracts a vector `v` from a point `p` in place.
p.x-=v.x
p.y-=v.y
p.z-=v.z
p.z-=v.z
proc equals(p1,p2:TPoint3d,tol=1.0e-6):bool {.inline.}=
## Checks if two points approximately equals with a tolerance.
return abs(p2.x-p1.x)<=tol and abs(p2.y-p1.y)<=tol and abs(p2.z-p1.z)<=tol
proc `=~`*(p1,p2:TPoint3d):bool {.inline.}=
## Checks if two vectors approximately equals with a
## Checks if two vectors approximately equals with a
## hardcoded tolerance 1e-6
equals(p1,p2)
proc rotate*(p:var TPoint3d,rad:float,axis:TVector3d)=
## Rotates point `p` in place `rad` radians about an axis
## Rotates point `p` in place `rad` radians about an axis
## passing through origo.
var v=vector3d(p.x,p.y,p.z)
v.rotate(rad,axis) # reuse this code here since doing the same thing and quite complicated
p.x=v.x
p.y=v.y
p.z=v.z
proc rotate*(p:var TPoint3d,angle:float,org:TPoint3d,axis:TVector3d)=
## Rotates point `p` in place `rad` radians about an axis
## Rotates point `p` in place `rad` radians about an axis
## passing through `org`
# see PDF document http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.pdf
# for how this is computed
var normax=axis
normax.normalize
let
cs=cos(angle)
omc=1.0-cs
@ -987,17 +987,17 @@ proc rotate*(p:var TPoint3d,angle:float,org:TPoint3d,axis:TVector3d)=
bv=b*v
cw=c*w
uxmvymwz=ux-vy-wz
p.x=(a*(vv+ww)-u*(bv+cw-uxmvymwz))*omc + x*cs + (b*w+v*z-c*v-w*y)*si
p.y=(b*(uu+ww)-v*(au+cw-uxmvymwz))*omc + y*cs + (c*u-a*w+w*x-u*z)*si
p.z=(c*(uu+vv)-w*(au+bv-uxmvymwz))*omc + z*cs + (a*v+u*y-b*u-v*x)*si
proc scale*(p:var TPoint3d,fac:float) {.inline.}=
## Scales a point in place `fac` times with world origo as origin.
p.x*=fac
p.y*=fac
p.z*=fac
proc scale*(p:var TPoint3d,fac:float,org:TPoint3d){.inline.}=
## Scales the point in place `fac` times with `org` as origin.
p.x=(p.x - org.x) * fac + org.x
@ -1005,7 +1005,7 @@ proc scale*(p:var TPoint3d,fac:float,org:TPoint3d){.inline.}=
p.z=(p.z - org.z) * fac + org.z
proc stretch*(p:var TPoint3d,facx,facy,facz:float){.inline.}=
## Scales a point in place non uniformly `facx` , `facy` , `facz` times
## Scales a point in place non uniformly `facx` , `facy` , `facz` times
## with world origo as origin.
p.x*=facx
p.y*=facy
@ -1017,7 +1017,7 @@ proc stretch*(p:var TPoint3d,facx,facy,facz:float,org:TPoint3d){.inline.}=
p.x=(p.x - org.x) * facx + org.x
p.y=(p.y - org.y) * facy + org.y
p.z=(p.z - org.z) * facz + org.z
proc move*(p:var TPoint3d,dx,dy,dz:float){.inline.}=
## Translates a point `dx` , `dy` , `dz` in place.
@ -1033,7 +1033,7 @@ proc move*(p:var TPoint3d,v:TVector3d){.inline.}=
proc area*(a,b,c:TPoint3d):float {.inline.}=
## Computes the area of the triangle thru points `a` , `b` and `c`
# The area of a planar 3d quadliteral is the magnitude of the cross
# product of two edge vectors. Taking this time 0.5 gives the triangle area.
return cross(b-a,c-a).len*0.5

View file

@ -525,7 +525,7 @@ proc get*[K,V](table: var PConcTable[K,V], key: var K): V =
#Tests ----------------------------
when isMainModule:
when not defined(testing) and isMainModule:
import locks, times, mersenne
const

View file

@ -286,18 +286,19 @@ proc `$`*[T](c: CritBitTree[T]): string =
result.add("}")
when isMainModule:
import sequtils
var r: CritBitTree[void]
r.incl "abc"
r.incl "xyz"
r.incl "def"
r.incl "definition"
r.incl "prefix"
doAssert r.contains"def"
#r.del "def"
for w in r.items:
echo w
for w in r.itemsWithPrefix("de"):
echo w
r.excl "def"
assert toSeq(r.items) == @["abc", "definition", "prefix", "xyz"]
assert toSeq(r.itemsWithPrefix("de")) == @["definition"]

View file

@ -198,14 +198,21 @@ proc empty*(s: IntSet): bool {.inline, deprecated.} =
result = s.counter == 0
when isMainModule:
import sequtils, algorithm
var x = initIntSet()
x.incl(1)
x.incl(2)
x.incl(7)
x.incl(1056)
for e in items(x): echo e
var y: TIntSet
var xs = toSeq(items(x))
xs.sort(cmp[int])
assert xs == @[1, 2, 7, 1056]
var y: IntSet
assign(y, x)
for e in items(y): echo e
var ys = toSeq(items(y))
ys.sort(cmp[int])
assert ys == @[1, 2, 7, 1056]

View file

@ -492,9 +492,8 @@ when isMainModule:
block: # filter iterator test
let numbers = @[1, 4, 5, 8, 9, 7, 4]
for n in filter(numbers, proc (x: int): bool = x mod 2 == 0):
echo($n)
# echoes 4, 8, 4 in separate lines
assert toSeq(filter(numbers, proc (x: int): bool = x mod 2 == 0)) ==
@[4, 8, 4]
block: # keepIf test
var floats = @[13.0, 12.5, 5.8, 2.0, 6.1, 9.9, 10.1]
@ -616,4 +615,5 @@ when isMainModule:
#doAssert a.repeat(-1) == @[] # will not compile!
doAssert b.repeat(3) == @[]
echo "Finished doc tests"
when not defined(testing):
echo "Finished doc tests"

View file

@ -970,6 +970,7 @@ when isMainModule and not defined(release):
if s <= i or mustRehash(s, i):
echo "performance issue: rightSize() will not elide enlarge() at ", i
echo "Micro tests run successfully."
when not defined(testing):
echo "Micro tests run successfully."
testModule()

View file

@ -819,15 +819,18 @@ proc enlarge[A](t: var CountTable[A]) =
swap(t.data, n)
proc `[]=`*[A](t: var CountTable[A], key: A, val: int) =
## puts a (key, value)-pair into `t`. `val` has to be positive.
## puts a (key, value)-pair into `t`.
assert val > 0
var h = rawGet(t, key)
if h >= 0:
t.data[h].val = val
else:
h = -1 - h
t.data[h].key = key
t.data[h].val = val
if mustRehash(len(t.data), t.counter): enlarge(t)
rawInsert(t, t.data, key, val)
inc(t.counter)
#h = -1 - h
#t.data[h].key = key
#t.data[h].val = val
proc initCountTable*[A](initialSize=64): CountTable[A] =
## creates a new count table that is empty.
@ -984,6 +987,22 @@ proc sort*[A](t: CountTableRef[A]) =
## `t` in the sorted order.
t[].sort
proc merge*[A](s: var CountTable[A], t: CountTable[A]) =
## merges the second table into the first one
for key, value in t:
s.inc(key, value)
proc merge*[A](s, t: CountTable[A]): CountTable[A] =
## merges the two tables into a new one
result = initCountTable[A](nextPowerOfTwo(max(s.len, t.len)))
for table in @[s, t]:
for key, value in table:
result.inc(key, value)
proc merge*[A](s, t: CountTableRef[A]) =
## merges the second table into the first one
s[].merge(t[])
when isMainModule:
type
Person = object
@ -1012,3 +1031,48 @@ when isMainModule:
s2[p2] = 45_000
s3[p1] = 30_000
s3[p2] = 45_000
var
t1 = initCountTable[string]()
t2 = initCountTable[string]()
t1.inc("foo")
t1.inc("bar", 2)
t1.inc("baz", 3)
t2.inc("foo", 4)
t2.inc("bar")
t2.inc("baz", 11)
merge(t1, t2)
assert(t1["foo"] == 5)
assert(t1["bar"] == 3)
assert(t1["baz"] == 14)
let
t1r = newCountTable[string]()
t2r = newCountTable[string]()
t1r.inc("foo")
t1r.inc("bar", 2)
t1r.inc("baz", 3)
t2r.inc("foo", 4)
t2r.inc("bar")
t2r.inc("baz", 11)
merge(t1r, t2r)
assert(t1r["foo"] == 5)
assert(t1r["bar"] == 3)
assert(t1r["baz"] == 14)
var
t1l = initCountTable[string]()
t2l = initCountTable[string]()
t1l.inc("foo")
t1l.inc("bar", 2)
t1l.inc("baz", 3)
t2l.inc("foo", 4)
t2l.inc("bar")
t2l.inc("baz", 11)
let
t1merging = t1l
t2merging = t2l
let merged = merge(t1merging, t2merging)
assert(merged["foo"] == 5)
assert(merged["bar"] == 3)
assert(merged["baz"] == 14)

View file

@ -78,7 +78,7 @@ proc advice*(s: var ThreadPoolState): ThreadPoolAdvice =
result = doNothing
inc s.calls
when isMainModule:
when not defined(testing) and isMainModule:
proc busyLoop() =
while true:
discard random(80)

View file

@ -300,7 +300,7 @@ proc setMinPoolSize*(size: range[1..MaxThreadPoolSize]) =
minPoolSize = size
proc setMaxPoolSize*(size: range[1..MaxThreadPoolSize]) =
## sets the minimal thread pool size. The default value of this
## sets the maximal thread pool size. The default value of this
## is ``MaxThreadPoolSize``.
maxPoolSize = size
if currentPoolSize > maxPoolSize:

View file

@ -56,6 +56,12 @@ proc setCookie*(key, value: string, expires: TimeInfo,
when isMainModule:
var tim = Time(int(getTime()) + 76 * (60 * 60 * 24))
echo(setCookie("test", "value", tim.getGMTime()))
let cookie = setCookie("test", "value", tim.getGMTime())
when not defined(testing):
echo cookie
let start = "Set-Cookie: test=value; Expires="
assert cookie[0..start.high] == start
echo parseCookies("uid=1; kp=2")
let table = parseCookies("uid=1; kp=2")
assert table["uid"] == "1"
assert table["kp"] == "2"

View file

@ -451,7 +451,7 @@ proc convert*(s: string, destEncoding = "UTF-8",
finally:
close(c)
when isMainModule:
when not defined(testing) and isMainModule:
let
orig = "öäüß"
cp1252 = convert(orig, "CP1252", "UTF-8")

View file

@ -198,7 +198,7 @@ proc register*(d: Dispatcher, monitor: FSMonitor,
var deleg = toDelegate(monitor)
d.register(deleg)
when isMainModule:
when not defined(testing) and isMainModule:
proc main =
var disp = newDispatcher()
var monitor = newMonitor()

View file

@ -593,7 +593,7 @@ proc register*(d: Dispatcher, ftp: AsyncFTPClient): Delegate {.discardable.} =
ftp.disp = d
return ftp.disp.register(ftp.csock)
when isMainModule:
when not defined(testing) and isMainModule:
proc main =
var d = newDispatcher()
let hev =
@ -629,7 +629,7 @@ when isMainModule:
if not d.poll(): break
main()
when isMainModule and false:
when not defined(testing) and isMainModule:
var ftp = ftpClient("example.com", user = "foo", pass = "bar")
ftp.connect()
echo ftp.pwd()

View file

@ -9,7 +9,7 @@
## The ``gentabs`` module implements an efficient hash table that is a
## key-value mapping. The keys are required to be strings, but the values
## may be any Nim or user defined type. This module supports matching
## may be any Nim or user defined type. This module supports matching
## of keys in case-sensitive, case-insensitive and style-insensitive modes.
{.deprecated.}
@ -22,7 +22,7 @@ type
modeCaseSensitive, ## case sensitive matching of keys
modeCaseInsensitive, ## case insensitive matching of keys
modeStyleInsensitive ## style sensitive matching of keys
TGenKeyValuePair[T] = tuple[key: string, val: T]
TGenKeyValuePairSeq[T] = seq[TGenKeyValuePair[T]]
TGenTable*[T] = object of RootObj
@ -83,7 +83,7 @@ proc rawGet[T](tbl: PGenTable[T], key: string): int =
h = nextTry(h, high(tbl.data))
result = - 1
proc rawInsert[T](tbl: PGenTable[T], data: var TGenKeyValuePairSeq[T],
proc rawInsert[T](tbl: PGenTable[T], data: var TGenKeyValuePairSeq[T],
key: string, val: T) =
var h: THash
h = myhash(tbl, key) and high(data)
@ -96,7 +96,7 @@ proc enlarge[T](tbl: PGenTable[T]) =
var n: TGenKeyValuePairSeq[T]
newSeq(n, len(tbl.data) * growthFactor)
for i in countup(0, high(tbl.data)):
if not isNil(tbl.data[i].key):
if not isNil(tbl.data[i].key):
rawInsert[T](tbl, n, tbl.data[i].key, tbl.data[i].val)
swap(tbl.data, n)
@ -141,20 +141,20 @@ when isMainModule:
assert(not x.hasKey("NOPE")) # ...but key "NOPE" is not in the table.
for k,v in pairs(x): # make sure the 'pairs' iterator works
assert(x[k]==v)
#
# Verify a table of user-defined types
#
type
TMyType = tuple[first, second: string] # a pair of strings
var y = newGenTable[TMyType](modeCaseInsensitive) # hash table where each
# value is TMyType tuple
#var junk: TMyType = ("OK", "Here")
#echo junk.first, " ", junk.second
y["Hello"] = ("Hello", "World")
y["Goodbye"] = ("Goodbye", "Everyone")
#y["Hello"] = TMyType( ("Hello", "World") )
@ -163,31 +163,44 @@ when isMainModule:
assert( not isNil(y["Hello"].first) )
assert( y["Hello"].first == "Hello" )
assert( y["Hello"].second == "World" )
#
# Verify table of tables
#
var z: PGenTable[ PGenTable[int] ] # hash table where each value is
var z: PGenTable[ PGenTable[int] ] # hash table where each value is
# a hash table of ints
z = newGenTable[PGenTable[int]](modeCaseInsensitive)
z["first"] = newGenTable[int](modeCaseInsensitive)
z["first"]["one"] = 1
z["first"]["two"] = 2
z["first"]["three"] = 3
z["second"] = newGenTable[int](modeCaseInsensitive)
z["second"]["red"] = 10
z["second"]["blue"] = 20
assert(len(z) == 2) # length of outer table
assert(len(z["first"]) == 3) # length of "first" table
assert(len(z["second"]) == 2) # length of "second" table
assert( z["first"]["one"] == 1) # retrieve from first inner table
assert( z["second"]["red"] == 10) # retrieve from second inner table
for k,v in pairs(z):
echo( "$# ($#) ->" % [k,$len(v)] )
#for k2,v2 in pairs(v):
# echo( " $# <-> $#" % [k2,$v2] )
echo()
when false:
# disabled: depends on hash order:
var output = ""
for k, v in pairs(z):
output.add( "$# ($#) ->\L" % [k,$len(v)] )
for k2,v2 in pairs(v):
output.add( " $# <-> $#\L" % [k2,$v2] )
let expected = unindent """
first (3) ->
two <-> 2
three <-> 3
one <-> 1
second (2) ->
red <-> 10
blue <-> 20
"""
assert output == expected

View file

@ -37,29 +37,29 @@
## h = h !& hash(x.bar)
## result = !$h
import
import
strutils
type
THash* = int ## a hash value; hash tables using these values should
type
THash* = int ## a hash value; hash tables using these values should
## always have a size of a power of two and can use the ``and``
## operator instead of ``mod`` for truncation of the hash value.
proc `!&`*(h: THash, val: int): THash {.inline.} =
proc `!&`*(h: THash, val: int): THash {.inline.} =
## mixes a hash value `h` with `val` to produce a new hash value. This is
## only needed if you need to implement a hash proc for a new datatype.
result = h +% val
result = result +% result shl 10
result = result xor (result shr 6)
proc `!$`*(h: THash): THash {.inline.} =
proc `!$`*(h: THash): THash {.inline.} =
## finishes the computation of the hash value. This is
## only needed if you need to implement a hash proc for a new datatype.
result = h +% h shl 3
result = result xor (result shr 11)
result = result +% result shl 15
proc hashData*(data: pointer, size: int): THash =
proc hashData*(data: pointer, size: int): THash =
## hashes an array of bytes of size `size`
var h: THash = 0
when defined(js):
@ -69,7 +69,7 @@ proc hashData*(data: pointer, size: int): THash =
var p = cast[cstring](data)
var i = 0
var s = size
while s > 0:
while s > 0:
h = h !& ord(p[i])
inc(i)
dec(s)
@ -78,7 +78,7 @@ proc hashData*(data: pointer, size: int): THash =
when defined(js):
var objectID = 0
proc hash*(x: pointer): THash {.inline.} =
proc hash*(x: pointer): THash {.inline.} =
## efficient hashing of pointers
when defined(js):
asm """
@ -93,7 +93,7 @@ proc hash*(x: pointer): THash {.inline.} =
"""
else:
result = (cast[THash](x)) shr 3 # skip the alignment
when not defined(booting):
proc hash*[T: proc](x: T): THash {.inline.} =
## efficient hashing of proc vars; closures are supported too.
@ -101,58 +101,65 @@ when not defined(booting):
result = hash(rawProc(x)) !& hash(rawEnv(x))
else:
result = hash(pointer(x))
proc hash*(x: int): THash {.inline.} =
proc hash*(x: int): THash {.inline.} =
## efficient hashing of integers
result = x
proc hash*(x: int64): THash {.inline.} =
proc hash*(x: int64): THash {.inline.} =
## efficient hashing of integers
result = toU32(x)
proc hash*(x: char): THash {.inline.} =
proc hash*(x: char): THash {.inline.} =
## efficient hashing of characters
result = ord(x)
proc hash*(x: string): THash =
proc hash*(x: string): THash =
## efficient hashing of strings
var h: THash = 0
for i in 0..x.len-1:
for i in 0..x.len-1:
h = h !& ord(x[i])
result = !$h
proc hashIgnoreStyle*(x: string): THash =
proc hashIgnoreStyle*(x: string): THash =
## efficient hashing of strings; style is ignored
var h: THash = 0
for i in 0..x.len-1:
for i in 0..x.len-1:
var c = x[i]
if c == '_':
if c == '_':
continue # skip _
if c in {'A'..'Z'}:
if c in {'A'..'Z'}:
c = chr(ord(c) + (ord('a') - ord('A'))) # toLower()
h = h !& ord(c)
result = !$h
proc hashIgnoreCase*(x: string): THash =
proc hashIgnoreCase*(x: string): THash =
## efficient hashing of strings; case is ignored
var h: THash = 0
for i in 0..x.len-1:
for i in 0..x.len-1:
var c = x[i]
if c in {'A'..'Z'}:
if c in {'A'..'Z'}:
c = chr(ord(c) + (ord('a') - ord('A'))) # toLower()
h = h !& ord(c)
result = !$h
proc hash*[T: tuple](x: T): THash =
## efficient hashing of tuples.
for f in fields(x):
result = result !& hash(f)
result = !$result
proc hash*(x: float): THash {.inline.} =
var y = x + 1.0
result = cast[ptr THash](addr(y))[]
# Forward declarations before methods that hash containers. This allows
# containers to contain other containers
proc hash*[A](x: openArray[A]): THash
proc hash*[A](x: set[A]): THash
proc hash*[T: tuple](x: T): THash =
## efficient hashing of tuples.
for f in fields(x):
result = result !& hash(f)
result = !$result
proc hash*[A](x: openArray[A]): THash =
for it in items(x): result = result !& hash(it)
result = !$result
@ -160,3 +167,4 @@ proc hash*[A](x: openArray[A]): THash =
proc hash*[A](x: set[A]): THash =
for it in items(x): result = result !& hash(it)
result = !$result

View file

@ -483,7 +483,8 @@ macro `var`*(e: expr): expr {.immediate.} =
result = xmlCheckedTag(e, "var", commonAttr)
when isMainModule:
var nim = "Nim"
echo h1(a(href="http://nim-lang.org", nim))
echo form(action="test", `accept-charset` = "Content-Type")
let nim = "Nim"
assert h1(a(href="http://nim-lang.org", nim)) ==
"""<h1><a href="http://nim-lang.org">Nim</a></h1>"""
assert form(action="test", `accept-charset` = "Content-Type") ==
"""<form action="test" accept-charset="Content-Type"></form>"""

View file

@ -593,7 +593,7 @@ proc loadHtml*(path: string): XmlNode =
var errors: seq[string] = @[]
result = loadHtml(path, errors)
when isMainModule:
when not defined(testing) and isMainModule:
import os
var errors: seq[string] = @[]

View file

@ -819,7 +819,7 @@ proc get*(client: AsyncHttpClient, url: string): Future[Response] {.async.} =
result = await client.request(redirectTo, httpGET)
lastUrl = redirectTo
when isMainModule:
when not defined(testing) and isMainModule:
when true:
# Async
proc main() {.async.} =

View file

@ -514,7 +514,7 @@ proc close*(h: PAsyncHTTPServer) =
## Closes the ``PAsyncHTTPServer``.
h.asyncSocket.close()
when isMainModule:
when not defined(testing) and isMainModule:
var counter = 0
var s: TServer

View file

@ -808,22 +808,25 @@ proc `[]=`*(obj: JsonNode, key: string, val: JsonNode) =
return
obj.fields.add((key, val))
proc `{}`*(node: JsonNode, key: string): JsonNode =
## Transverses the node and gets the given value. If any of the
## names does not exist, returns nil
proc `{}`*(node: JsonNode, keys: varargs[string]): JsonNode =
## Traverses the node and gets the given value. If any of the
## keys do not exist, returns nil. Also returns nil if one of the
## intermediate data structures is not an object
result = node
if isNil(node): return nil
result = result[key]
for key in keys:
if isNil(result) or result.kind!=JObject:
return nil
result=result[key]
proc `{}=`*(node: JsonNode, names: varargs[string], value: JsonNode) =
## Transverses the node and tries to set the value at the given location
## to `value` If any of the names are missing, they are added
proc `{}=`*(node: JsonNode, keys: varargs[string], value: JsonNode) =
## Traverses the node and tries to set the value at the given location
## to `value` If any of the keys are missing, they are added
var node = node
for i in 0..(names.len-2):
if isNil(node[names[i]]):
node[names[i]] = newJObject()
node = node[names[i]]
node[names[names.len-1]] = value
for i in 0..(keys.len-2):
if isNil(node[keys[i]]):
node[keys[i]] = newJObject()
node = node[keys[i]]
node[keys[keys.len-1]] = value
proc delete*(obj: JsonNode, key: string) =
## Deletes ``obj[key]`` preserving the order of the other (key, value)-pairs.
@ -1150,19 +1153,22 @@ when false:
when isMainModule:
#var node = parse("{ \"test\": null }")
#echo(node.existsKey("test56"))
var parsed = parseFile("tests/testdata/jsontest.json")
var parsed2 = parseFile("tests/testdata/jsontest2.json")
echo(parsed)
echo()
echo(pretty(parsed, 2))
echo()
echo(parsed["keyÄÖöoßß"])
echo()
echo(pretty(parsed2))
try:
echo(parsed["key2"][12123])
raise newException(ValueError, "That line was expected to fail")
except IndexError: echo()
when not defined(testing):
echo(parsed)
echo()
echo(pretty(parsed, 2))
echo()
echo(parsed["keyÄÖöoßß"])
echo()
echo(pretty(parsed2))
try:
echo(parsed["key2"][12123])
raise newException(ValueError, "That line was expected to fail")
except IndexError: echo()
let testJson = parseJson"""{ "a": [1, 2, 3, 4], "b": "asd" }"""
# nil passthrough
@ -1186,9 +1192,17 @@ when isMainModule:
except:
assert(false, "EInvalidIndex thrown for valid index")
assert(testJson{"b"}.str=="asd", "Couldn't fetch a singly nested key with {}")
assert(isNil(testJson{"nonexistent"}), "Non-existent keys should return nil")
assert(parsed2{"repository", "description"}.str=="IRC Library for Haskell", "Couldn't fetch via multiply nested key using {}")
assert(isNil(testJson{"a", "b"}), "Indexing through a list should return nil")
assert(isNil(testJson{"a", "b"}), "Indexing through a list should return nil")
assert(testJson{"a"}==parseJson"[1, 2, 3, 4]", "Didn't return a non-JObject when there was one to be found")
assert(isNil(parseJson("[1, 2, 3]"){"foo"}), "Indexing directly into a list should return nil")
# Generator:
var j = %* [{"name": "John", "age": 30}, {"name": "Susan", "age": 31}]
assert j == %[%{"name": %"John", "age": %30}, %{"name": %"Susan", "age": %31}]
assert j == %[%{"name": %"John", "age": %30}, %{"name": %"Susan", "age": %31}]
var j2 = %*
[
@ -1216,12 +1230,13 @@ when isMainModule:
}
]
assert j3 == %[%{"name": %"John", "age": %30}, %{"name": %"Susan", "age": %31}]
discard """
while true:
var json = stdin.readLine()
var node = parse(json)
echo(node)
echo()
echo()
"""
when not defined(testing):
discard """
while true:
var json = stdin.readLine()
var node = parse(json)
echo(node)
echo()
echo()
"""

View file

@ -278,7 +278,7 @@ proc getLogFilter*(): Level =
# --------------
when isMainModule:
when not defined(testing) and isMainModule:
var L = newConsoleLogger()
var fL = newFileLogger("test.log", fmtStr = verboseFmtStr)
var rL = newRollingFileLogger("rolling.log", fmtStr = verboseFmtStr)

View file

@ -15,8 +15,8 @@
## type than its compiletime type:
##
## .. code-block:: nim
##
## type
##
## type
## TA = object
## TB = object of TA
## f: int
@ -28,6 +28,8 @@
## new(b)
## a = b
## echo($$a[]) # produces "{}", not "{f: 0}"
##
## **Note**: The ``to`` and ``$$`` operations are available at compile-time!
import streams, typeinfo, json, intsets, tables
@ -38,7 +40,12 @@ proc storeAny(s: Stream, a: TAny, stored: var IntSet) =
case a.kind
of akNone: assert false
of akBool: s.write($getBool(a))
of akChar: s.write(escapeJson($getChar(a)))
of akChar:
let ch = getChar(a)
if ch < '\128':
s.write(escapeJson($ch))
else:
s.write($int(ch))
of akArray, akSequence:
if a.kind == akSequence and isNil(a): s.write("null")
else:
@ -92,7 +99,7 @@ proc storeAny(s: Stream, a: TAny, stored: var IntSet) =
proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
case a.kind
of akNone: assert false
of akBool:
of akBool:
case p.kind
of jsonFalse: setBiggestInt(a, 0)
of jsonTrue: setBiggestInt(a, 1)
@ -105,8 +112,12 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
setBiggestInt(a, ord(x[0]))
next(p)
return
elif p.kind == jsonInt:
setBiggestInt(a, getInt(p))
next(p)
return
raiseParseErr(p, "string of length 1 expected for a char")
of akEnum:
of akEnum:
if p.kind == jsonString:
setBiggestInt(a, getEnumOrdinal(a, p.str))
next(p)
@ -122,7 +133,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
if p.kind == jsonArrayEnd: next(p)
else: raiseParseErr(p, "']' end of array expected")
of akSequence:
case p.kind
case p.kind
of jsonNull:
setPointer(a, nil)
next(p)
@ -143,7 +154,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
if p.kind != jsonObjectStart: raiseParseErr(p, "'{' expected for an object")
next(p)
while p.kind != jsonObjectEnd and p.kind != jsonEof:
if p.kind != jsonString:
if p.kind != jsonString:
raiseParseErr(p, "string expected for a field name")
var fieldName = p.str
next(p)
@ -160,7 +171,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
if p.kind == jsonArrayEnd: next(p)
else: raiseParseErr(p, "']' end of array expected")
of akPtr, akRef:
case p.kind
case p.kind
of jsonNull:
setPointer(a, nil)
next(p)
@ -170,7 +181,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
of jsonArrayStart:
next(p)
if a.kind == akRef: invokeNew(a)
else: setPointer(a, alloc0(a.baseTypeSize))
else: setPointer(a, alloc0(a.baseTypeSize))
if p.kind == jsonInt:
t[p.getInt] = getPointer(a)
next(p)
@ -179,8 +190,8 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
if p.kind == jsonArrayEnd: next(p)
else: raiseParseErr(p, "']' end of ref-address pair expected")
else: raiseParseErr(p, "int for pointer type expected")
of akProc, akPointer, akCString:
case p.kind
of akProc, akPointer, akCString:
case p.kind
of jsonNull:
setPointer(a, nil)
next(p)
@ -189,7 +200,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
next(p)
else: raiseParseErr(p, "int for pointer type expected")
of akString:
case p.kind
case p.kind
of jsonNull:
setPointer(a, nil)
next(p)
@ -197,7 +208,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
setString(a, p.str)
next(p)
else: raiseParseErr(p, "string expected")
of akInt..akInt64, akUInt..akUInt64:
of akInt..akInt64, akUInt..akUInt64:
if p.kind == jsonInt:
setBiggestInt(a, getInt(p))
next(p)
@ -243,22 +254,22 @@ proc to*[T](data: string): T =
## reads data and transforms it to a ``T``.
var tab = initTable[BiggestInt, pointer]()
loadAny(newStringStream(data), toAny(result), tab)
when isMainModule:
when not defined(testing) and isMainModule:
template testit(x: expr) = echo($$to[type(x)]($$x))
var x: array[0..4, array[0..4, string]] = [
["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"],
["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"],
["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"],
["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"],
["test", "1", "2", "3", "4"]]
testit(x)
var test2: tuple[name: string, s: uint] = ("tuple test", 56u)
testit(test2)
type
TE = enum
blah, blah2
TestObj = object
test, asd: int
case test2: TE
@ -266,7 +277,7 @@ when isMainModule:
help: string
else:
nil
PNode = ref TNode
TNode = object
next, prev: PNode
@ -294,7 +305,7 @@ when isMainModule:
test4.a = "ref string test: A"
test4.b = "ref string test: B"
testit(test4)
var test5 = @[(0,1),(2,3),(4,5)]
testit(test5)
@ -305,7 +316,7 @@ when isMainModule:
echo($$test7)
testit(test7)
type
type
TA {.inheritable.} = object
TB = object of TA
f: int

View file

@ -85,7 +85,7 @@ proc fac*(n: int): int {.noSideEffect.} =
proc isPowerOfTwo*(x: int): bool {.noSideEffect.} =
## returns true, if `x` is a power of two, false otherwise.
## Zero and negative numbers are not a power of two.
return (x != 0) and ((x and (x - 1)) == 0)
return (x > 0) and ((x and (x - 1)) == 0)
proc nextPowerOfTwo*(x: int): int {.noSideEffect.} =
## returns `x` rounded up to the nearest power of two.
@ -114,18 +114,23 @@ proc sum*[T](x: openArray[T]): T {.noSideEffect.} =
## If `x` is empty, 0 is returned.
for i in items(x): result = result + i
proc mean*(x: openArray[float]): float {.noSideEffect.} =
## computes the mean of the elements in `x`.
## If `x` is empty, NaN is returned.
result = sum(x) / toFloat(len(x))
template toFloat(f: float): float = f
proc variance*(x: openArray[float]): float {.noSideEffect.} =
proc mean*[T](x: openArray[T]): float {.noSideEffect.} =
## computes the mean of the elements in `x`, which are first converted to floats.
## If `x` is empty, NaN is returned.
## ``toFloat(x: T): float`` must be defined.
for i in items(x): result = result + toFloat(i)
result = result / toFloat(len(x))
proc variance*[T](x: openArray[T]): float {.noSideEffect.} =
## computes the variance of the elements in `x`.
## If `x` is empty, NaN is returned.
## ``toFloat(x: T): float`` must be defined.
result = 0.0
var m = mean(x)
for i in 0 .. high(x):
var diff = x[i] - m
for i in items(x):
var diff = toFloat(i) - m
result = result + diff*diff
result = result / toFloat(len(x))
@ -372,7 +377,9 @@ when isMainModule and not defined(JS):
randomize(seed)
for i in 0..SIZE-1:
assert buf[i] == random(high(int)), "non deterministic random seeding"
echo "random values equal after reseeding"
when not defined(testing):
echo "random values equal after reseeding"
# Check for no side effect annotation
proc mySqrt(num: float): float {.noSideEffect.} =

View file

@ -32,7 +32,7 @@ proc getNum*(m: var MersenneTwister): int =
return int(y)
# Test
when isMainModule:
when not defined(testing) and isMainModule:
var mt = newMersenneTwister(2525)
for i in 0..99:

View file

@ -518,5 +518,5 @@ proc register*(mimedb: var MimeDB, ext: string, mimetype: string) =
when isMainModule:
var m = newMimetypes()
echo m.getMimetype("mp4")
echo m.getExt("text/html")
assert m.getMimetype("mp4") == "video/mp4"
assert m.getExt("text/html") == "html"

View file

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

View file

@ -174,7 +174,7 @@ proc terminate*(p: Process) {.rtl, extern: "nosp$1", tags: [].}
proc kill*(p: Process) {.rtl, extern: "nosp$1", tags: [].}
## Kill the process `p`. On Posix OSes the procedure sends ``SIGKILL`` to
## the process. On Windows ``kill()`` is simply an alias for ``terminate()``.
proc running*(p: Process): bool {.rtl, extern: "nosp$1", tags: [].}
## Returns true iff the process `p` is still running. Returns immediately.
@ -666,7 +666,7 @@ elif not defined(useNimRtl):
data.workingDir = workingDir
when declared(posix_spawn) and not defined(useFork) and
when declared(posix_spawn) and not defined(useFork) and
not defined(useClone) and not defined(linux):
pid = startProcessAuxSpawn(data)
else:
@ -823,7 +823,7 @@ elif not defined(useNimRtl):
discard execvp(data.sysCommand, data.sysArgs)
else:
when defined(uClibc):
# uClibc environment (OpenWrt included) doesn't have the full execvpe
# uClibc environment (OpenWrt included) doesn't have the full execvpe
discard execve(data.sysCommand, data.sysArgs, data.sysEnv)
else:
discard execvpe(data.sysCommand, data.sysArgs, data.sysEnv)
@ -864,9 +864,9 @@ elif not defined(useNimRtl):
raiseOsError(osLastError())
proc kill(p: Process) =
if kill(p.id, SIGKILL) != 0'i32:
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
@ -883,7 +883,7 @@ elif not defined(useNimRtl):
var ret = waitpid(p.id, p.exitCode, WNOHANG)
var b = ret == int(p.id)
if b: result = -1
if p.exitCode == -3: result = -1
if not WIFEXITED(p.exitCode): result = -1
else: result = p.exitCode.int shr 8
proc createStream(stream: var Stream, handle: var FileHandle,
@ -907,7 +907,7 @@ elif not defined(useNimRtl):
createStream(p.errStream, p.errHandle, fmRead)
return p.errStream
proc csystem(cmd: cstring): cint {.nodecl, importc: "system",
proc csystem(cmd: cstring): cint {.nodecl, importc: "system",
header: "<stdlib.h>".}
proc execCmd(command: string): int =

View file

@ -166,7 +166,7 @@ proc close*(my: var CsvParser) {.inline.} =
## closes the parser `my` and its associated input stream.
lexbase.close(my)
when isMainModule:
when not defined(testing) and isMainModule:
import os
var s = newFileStream(paramStr(1), fmRead)
if s == nil: quit("cannot open the file" & paramStr(1))

View file

@ -1330,7 +1330,7 @@ proc renderSQL*(n: SqlNode): string =
result = ""
ra(n, result, 0)
when isMainModule:
when not defined(testing) and isMainModule:
echo(renderSQL(parseSQL(newStringStream("""
CREATE TYPE happiness AS ENUM ('happy', 'very happy', 'ecstatic');
CREATE TABLE holidays (

View file

@ -323,8 +323,12 @@ iterator interpolatedFragments*(s: string): tuple[kind: InterpolatedKind,
i = j
when isMainModule:
for k, v in interpolatedFragments("$test{} $this is ${an{ example}} "):
echo "(", k, ", \"", v, "\")"
import sequtils
let input = "$test{} $this is ${an{ example}} "
let expected = @[(ikVar, "test"), (ikStr, "{} "), (ikVar, "this"),
(ikStr, " is "), (ikExpr, "an{ example}"), (ikStr, " ")]
assert toSeq(interpolatedFragments(input)) == expected
var value = 0
discard parseHex("0x38", value)
assert value == 56

View file

@ -628,7 +628,7 @@ proc next*(my: var XmlParser) =
my.kind = xmlError
my.state = stateNormal
when isMainModule:
when not defined(testing) and isMainModule:
import os
var s = newFileStream(paramStr(1), fmRead)
if s == nil: quit("cannot open the file" & paramStr(1))

File diff suppressed because it is too large Load diff

View file

@ -1080,7 +1080,7 @@ proc assertListsIdentical(listA, listB: seq[string]) =
assert(item == listB[i])
i = i + 1
when isMainModule:
when not defined(testing) and isMainModule:
when false:
var r = open()

View file

@ -44,16 +44,13 @@ proc decimalToRoman*(number: range[1..3_999]): string =
("XC", 90), ("L", 50), ("XL", 40), ("X", 10), ("IX", 9),
("V", 5), ("IV", 4), ("I", 1)]
result = ""
var decVal = number
var decVal: int = number
for key, val in items(romanComposites):
while decVal >= val:
dec(decVal, val)
result.add(key)
when isMainModule:
import math
randomize()
for i in 1 .. 100:
var rnd = 1 + random(3990)
assert rnd == rnd.decimalToRoman.romanToDecimal
for i in 1 .. 3_999:
assert i == i.decimalToRoman.romanToDecimal

View file

@ -296,7 +296,7 @@ proc contains*(s: Selector, key: SelectorKey): bool =
TReadyInfo: ReadyInfo, PSelector: Selector].}
when isMainModule and not defined(nimdoc):
when not defined(testing) and isMainModule and not defined(nimdoc):
# Select()
import sockets
type

View file

@ -253,7 +253,7 @@ proc close*(smtp: AsyncSmtp) {.async.} =
await smtp.sock.send("QUIT\c\L")
smtp.sock.close()
when isMainModule:
when not defined(testing) and isMainModule:
#var msg = createMessage("Test subject!",
# "Hello, my name is dom96.\n What\'s yours?", @["dominik@localhost"])
#echo(msg)

View file

@ -168,6 +168,12 @@ proc newStringTable*(mode: StringTableMode): StringTableRef {.
result.counter = 0
newSeq(result.data, startSize)
proc clear*(s: StringTableRef, mode: StringTableMode) =
## resets a string table to be empty again.
s.mode = mode
s.counter = 0
s.data.setLen(startSize)
proc newStringTable*(keyValuePairs: varargs[string],
mode: StringTableMode): StringTableRef {.
rtl, extern: "nst$1WithPairs".} =
@ -227,7 +233,7 @@ proc `$`*(t: StringTableRef): string {.rtl, extern: "nstDollar".} =
result = "{:}"
else:
result = "{"
for key, val in pairs(t):
for key, val in pairs(t):
if result.len > 1: result.add(", ")
result.add(key)
result.add(": ")

View file

@ -169,14 +169,12 @@ proc cmpIgnoreStyle*(a, b: string): int {.noSideEffect,
{.pop.}
proc strip*(s: string, leading = true, trailing = true): string {.noSideEffect,
rtl, extern: "nsuStrip".} =
## Strips whitespace from `s` and returns the resulting string.
proc strip*(s: string, leading = true, trailing = true, chars: set[char] = Whitespace): string
{.noSideEffect, rtl, extern: "nsuStrip".} =
## Strips `chars` from `s` and returns the resulting string.
##
## If `leading` is true, leading whitespace is stripped.
## If `trailing` is true, trailing whitespace is stripped.
const
chars: set[char] = Whitespace
## If `leading` is true, leading `chars` are stripped.
## If `trailing` is true, trailing `chars` are stripped.
var
first = 0
last = len(s)-1
@ -1402,14 +1400,21 @@ when isMainModule:
doAssert align("a", 0) == "a"
doAssert align("1232", 6) == " 1232"
doAssert align("1232", 6, '#') == "##1232"
echo wordWrap(""" this is a long text -- muchlongerthan10chars and here
it goes""", 10, false)
let
inp = """ this is a long text -- muchlongerthan10chars and here
it goes"""
outp = " this is a\nlong text\n--\nmuchlongerthan10chars\nand here\nit goes"
doAssert wordWrap(inp, 10, false) == outp
doAssert formatBiggestFloat(0.00000000001, ffDecimal, 11) == "0.00000000001"
doAssert formatBiggestFloat(0.00000000001, ffScientific, 1) == "1.0e-11"
doAssert formatBiggestFloat(0.00000000001, ffScientific, 1) in
["1.0e-11", "1.0e-011"]
doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c"
echo formatSize(1'i64 shl 31 + 300'i64) # == "4,GB"
echo formatSize(1'i64 shl 31)
when not defined(testing):
echo formatSize(1'i64 shl 31 + 300'i64) # == "4,GB"
echo formatSize(1'i64 shl 31)
doAssert "$animal eats $food." % ["animal", "The cat", "food", "fish"] ==
"The cat eats fish."
@ -1426,3 +1431,11 @@ when isMainModule:
doAssert count("foofoofoo", "foofoo", overlapping = true) == 2
doAssert count("foofoofoo", 'f') == 3
doAssert count("foofoofoobar", {'f','b'}) == 4
doAssert strip(" foofoofoo ") == "foofoofoo"
doAssert strip("sfoofoofoos", chars = {'s'}) == "foofoofoo"
doAssert strip("barfoofoofoobar", chars = {'b', 'a', 'r'}) == "foofoofoo"
doAssert strip("stripme but don't strip this stripme",
chars = {'s', 't', 'r', 'i', 'p', 'm', 'e'}) == " but don't strip this "
doAssert strip("sfoofoofoos", leading = false, chars = {'s'}) == "sfoofoofoo"
doAssert strip("sfoofoofoos", trailing = false, chars = {'s'}) == "foofoofoos"

View file

@ -386,8 +386,13 @@ when isMainModule:
longishA,
longish)"""
echo "type TMyEnum* = enum\n $', '2i'\n '{..}" % ["fieldA",
"fieldB", "FiledClkad", "fieldD", "fieldE", "longishFieldName"]
assert "type TMyEnum* = enum\n $', '2i'\n '{..}" % ["fieldA",
"fieldB", "FiledClkad", "fieldD", "fieldE", "longishFieldName"] ==
strutils.unindent """
type TMyEnum* = enum
fieldA, fieldB,
FiledClkad, fieldD,
fieldE, longishFieldName"""
doAssert subex"$1($', '{2..})" % ["f", "a", "b", "c"] == "f(a, b, c)"
@ -395,7 +400,12 @@ when isMainModule:
doAssert subex"$['''|'|''''|']']#" % "0" == "'|"
echo subex("type\n TEnum = enum\n $', '40c'\n '{..}") % [
"fieldNameA", "fieldNameB", "fieldNameC", "fieldNameD"]
assert subex("type\n TEnum = enum\n $', '40c'\n '{..}") % [
"fieldNameA", "fieldNameB", "fieldNameC", "fieldNameD"] ==
strutils.unindent """
type
TEnum = enum
fieldNameA, fieldNameB, fieldNameC,
fieldNameD"""

View file

@ -51,12 +51,13 @@ else:
proc setRaw(fd: FileHandle, time: cint = TCSAFLUSH) =
var mode: Termios
discard fd.tcgetattr(addr mode)
mode.iflag = mode.iflag and not Tcflag(BRKINT or ICRNL or INPCK or ISTRIP or IXON)
mode.oflag = mode.oflag and not Tcflag(OPOST)
mode.cflag = (mode.cflag and not Tcflag(CSIZE or PARENB)) or CS8
mode.lflag = mode.lflag and not Tcflag(ECHO or ICANON or IEXTEN or ISIG)
mode.cc[VMIN] = 1.cuchar
mode.cc[VTIME] = 0.cuchar
mode.c_iflag = mode.c_iflag and not Tcflag(BRKINT or ICRNL or INPCK or
ISTRIP or IXON)
mode.c_oflag = mode.c_oflag and not Tcflag(OPOST)
mode.c_cflag = (mode.c_cflag and not Tcflag(CSIZE or PARENB)) or CS8
mode.c_lflag = mode.c_lflag and not Tcflag(ECHO or ICANON or IEXTEN or ISIG)
mode.c_cc[VMIN] = 1.cuchar
mode.c_cc[VTIME] = 0.cuchar
discard fd.tcsetattr(time, addr mode)
proc setCursorPos*(x, y: int) =
@ -375,7 +376,7 @@ when not defined(windows):
result = stdin.readChar()
discard fd.tcsetattr(TCSADRAIN, addr oldMode)
when isMainModule:
when not defined(testing) and isMainModule:
system.addQuitProc(resetAttributes)
write(stdout, "never mind")
eraseLine()

View file

@ -119,7 +119,7 @@ type
## in the range 0 to 23.
monthday*: range[1..31] ## The day of the month, in the range 1 to 31.
month*: Month ## The current month.
year*: range[-10_000..10_000] ## The current year.
year*: int ## The current year.
weekday*: WeekDay ## The current day of the week.
yearday*: range[0..365] ## The number of days since January 1,
## in the range 0 to 365.
@ -379,7 +379,7 @@ when not defined(JS):
result.hour = t.hour
result.monthday = t.monthday
result.month = ord(t.month)
result.year = t.year - 1900
result.year = cint(t.year - 1900)
result.weekday = weekDays[t.weekday]
result.yearday = t.yearday
result.isdst = if t.isDST: 1 else: 0
@ -764,7 +764,7 @@ 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():
case value[j..j+2].toLower()
of "sun":
info.weekday = dSun
of "mon":
@ -1036,8 +1036,6 @@ when isMainModule:
# Tue 19 Jan 03:14:07 GMT 2038
var t = getGMTime(fromSeconds(2147483647))
echo t.format("ddd dd MMM hh:mm:ss ZZZ yyyy")
echo t.format("ddd ddMMMhhmmssZZZyyyy")
assert t.format("ddd dd MMM hh:mm:ss ZZZ yyyy") == "Tue 19 Jan 03:14:07 UTC 2038"
assert t.format("ddd ddMMMhh:mm:ssZZZyyyy") == "Tue 19Jan03:14:07UTC2038"
@ -1070,46 +1068,48 @@ when isMainModule:
var f = "dddd at hh:mmtt on MMM d, yyyy"
assert($s.parse(f) == "Tue Dec 15 09:04:00 2015")
# ANSIC = "Mon Jan _2 15:04:05 2006"
s = "Mon Jan 2 15:04:05 2006"
f = "ddd MMM d HH:mm:ss yyyy"
assert($s.parse(f) == "Mon Jan 2 15:04:05 2006")
s = "Thu Jan 12 15:04:05 2006"
f = "ddd MMM dd HH:mm:ss yyyy"
assert($s.parse(f) == "Thu Jan 12 15:04:05 2006")
# UnixDate = "Mon Jan _2 15:04:05 MST 2006"
s = "Mon Jan 2 15:04:05 MST 2006"
f = "ddd MMM d HH:mm:ss ZZZ yyyy"
assert($s.parse(f) == "Mon Jan 2 15:04:05 2006")
s = "Thu Jan 12 15:04:05 MST 2006"
f = "ddd MMM dd HH:mm:ss ZZZ yyyy"
assert($s.parse(f) == "Thu Jan 12 15:04:05 2006")
# RubyDate = "Mon Jan 02 15:04:05 -0700 2006"
s = "Mon Jan 02 15:04:05 -07:00 2006"
s = "Thu Jan 12 15:04:05 -07:00 2006"
f = "ddd MMM dd HH:mm:ss zzz yyyy"
assert($s.parse(f) == "Mon Jan 2 15:04:05 2006")
assert($s.parse(f) == "Thu Jan 12 15:04:05 2006")
# RFC822 = "02 Jan 06 15:04 MST"
s = "02 Jan 06 15:04 MST"
s = "12 Jan 16 15:04 MST"
f = "dd MMM yy HH:mm ZZZ"
assert($s.parse(f) == "Mon Jan 2 15:04:00 2006")
assert($s.parse(f) == "Tue Jan 12 15:04:00 2016")
# RFC822Z = "02 Jan 06 15:04 -0700" # RFC822 with numeric zone
s = "02 Jan 06 15:04 -07:00"
s = "12 Jan 16 15:04 -07:00"
f = "dd MMM yy HH:mm zzz"
assert($s.parse(f) == "Mon Jan 2 15:04:00 2006")
assert($s.parse(f) == "Tue Jan 12 15:04:00 2016")
# RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
s = "Monday, 02-Jan-06 15:04:05 MST"
s = "Monday, 12-Jan-06 15:04:05 MST"
f = "dddd, dd-MMM-yy HH:mm:ss ZZZ"
assert($s.parse(f) == "Mon Jan 2 15:04:05 2006")
assert($s.parse(f) == "Thu Jan 12 15:04:05 2006")
# RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
s = "Mon, 02 Jan 2006 15:04:05 MST"
s = "Thu, 12 Jan 2006 15:04:05 MST"
f = "ddd, dd MMM yyyy HH:mm:ss ZZZ"
assert($s.parse(f) == "Mon Jan 2 15:04:05 2006")
assert($s.parse(f) == "Thu Jan 12 15:04:05 2006")
# RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" # RFC1123 with numeric zone
s = "Mon, 02 Jan 2006 15:04:05 -07:00"
s = "Thu, 12 Jan 2006 15:04:05 -07:00"
f = "ddd, dd MMM yyyy HH:mm:ss zzz"
assert($s.parse(f) == "Mon Jan 2 15:04:05 2006")
assert($s.parse(f) == "Thu Jan 12 15:04:05 2006")
# RFC3339 = "2006-01-02T15:04:05Z07:00"
s = "2006-01-02T15:04:05Z-07:00"
s = "2006-01-12T15:04:05Z-07:00"
f = "yyyy-MM-ddTHH:mm:ssZzzz"
assert($s.parse(f) == "Mon Jan 2 15:04:05 2006")
assert($s.parse(f) == "Thu Jan 12 15:04:05 2006")
# RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
s = "2006-01-02T15:04:05.999999999Z-07:00"
s = "2006-01-12T15:04:05.999999999Z-07:00"
f = "yyyy-MM-ddTHH:mm:ss.999999999Zzzz"
assert($s.parse(f) == "Mon Jan 2 15:04:05 2006")
assert($s.parse(f) == "Thu Jan 12 15:04:05 2006")
# Kitchen = "3:04PM"
s = "3:04PM"
f = "h:mmtt"
echo "Kitchen: " & $s.parse(f)
assert "15:04:00" in $s.parse(f)
when not defined(testing):
echo "Kitchen: " & $s.parse(f)

View file

@ -105,6 +105,31 @@ template fastRuneAt*(s: string, i: int, result: expr, doInc = true) =
result = Rune(ord(s[i]))
when doInc: inc(i)
proc validateUtf8*(s: string): int =
## returns the position of the invalid byte in ``s`` if the string ``s`` does
## not hold valid UTF-8 data. Otherwise -1 is returned.
var i = 0
let L = s.len
while i < L:
if ord(s[i]) <=% 127:
inc(i)
elif ord(s[i]) shr 5 == 0b110:
if i+1 < L and ord(s[i+1]) shr 6 == 0b10: inc(i, 2)
else: return i
elif ord(s[i]) shr 4 == 0b1110:
if i+2 < L and ord(s[i+1]) shr 6 == 0b10 and ord(s[i+2]) shr 6 == 0b10:
inc i, 3
else: return i
elif ord(s[i]) shr 3 == 0b11110:
if i+3 < L and ord(s[i+1]) shr 6 == 0b10 and
ord(s[i+2]) shr 6 == 0b10 and
ord(s[i+3]) shr 6 == 0b10:
inc i, 4
else: return i
else:
return i
return -1
proc runeAt*(s: string, i: Natural): Rune =
## returns the unicode character in `s` at byte index `i`
fastRuneAt(s, i, result, false)

View file

@ -70,5 +70,5 @@ proc unidecode*(s: string): string =
when isMainModule:
loadUnidecodeTable("lib/pure/unidecode/unidecode.dat")
echo unidecode("Äußerst")
assert unidecode("Äußerst") == "Ausserst"

View file

@ -53,10 +53,10 @@ proc parseAuthority(authority: string, result: var Uri) =
while true:
case authority[i]
of '@':
result.password = result.port
result.port = ""
result.username = result.hostname
result.hostname = ""
swap result.password, result.port
result.port.setLen(0)
swap result.username, result.hostname
result.hostname.setLen(0)
inPort = false
of ':':
inPort = true
@ -75,7 +75,7 @@ proc parsePath(uri: string, i: var int, result: var Uri) =
# The 'mailto' scheme's PATH actually contains the hostname/username
if result.scheme.toLower == "mailto":
parseAuthority(result.path, result)
result.path = ""
result.path.setLen(0)
if uri[i] == '?':
i.inc # Skip '?'
@ -85,13 +85,21 @@ proc parsePath(uri: string, i: var int, result: var Uri) =
i.inc # Skip '#'
i.inc parseUntil(uri, result.anchor, {}, i)
proc initUri(): Uri =
proc initUri*(): Uri =
## Initializes a URI.
result = Uri(scheme: "", username: "", password: "", hostname: "", port: "",
path: "", query: "", anchor: "")
proc parseUri*(uri: string): Uri =
## Parses a URI.
result = initUri()
proc resetUri(uri: var Uri) =
for f in uri.fields:
when f is string:
f.setLen(0)
else:
f = false
proc parseUri*(uri: string, result: var Uri) =
## Parses a URI. The `result` variable will be cleared before.
resetUri(result)
var i = 0
@ -105,7 +113,7 @@ proc parseUri*(uri: string): Uri =
if uri[i] != ':':
# Assume this is a reference URI (relative URI)
i = 0
result.scheme = ""
result.scheme.setLen(0)
parsePath(uri, i, result)
return
i.inc # Skip ':'
@ -124,6 +132,11 @@ proc parseUri*(uri: string): Uri =
# Path
parsePath(uri, i, result)
proc parseUri*(uri: string): Uri =
## Parses a URI and returns it.
result = initUri()
parseUri(uri, result)
proc removeDotSegments(path: string): string =
var collection: seq[string] = @[]
let endsWithSlash = path[path.len-1] == '/'

View file

@ -155,7 +155,7 @@ proc loadXMLFile*(path: string): PDocument =
return loadXMLStream(s)
when isMainModule:
when not defined(testing) and isMainModule:
var xml = loadXMLFile("nim/xmldom/test.xml")
#echo(xml.getElementsByTagName("m:test2")[0].namespaceURI)
#echo(xml.getElementsByTagName("bla:test")[0].namespaceURI)

View file

@ -143,7 +143,7 @@ proc loadXml*(path: string): XmlNode =
result = loadXml(path, errors)
if errors.len > 0: raiseInvalidXml(errors)
when isMainModule:
when not defined(testing) and isMainModule:
import os
var errors: seq[string] = @[]

View file

@ -353,5 +353,6 @@ proc findAll*(n: XmlNode, tag: string): seq[XmlNode] =
findAll(n, tag, result)
when isMainModule:
assert """<a href="http://nim-lang.org">Nim rules.</a>""" ==
let link = "http://nim-lang.org"
assert """<a href="""" & escape(link) & """">Nim rules.</a>""" ==
$(<>a(href="http://nim-lang.org", newText("Nim rules.")))