Merge branch 'devel' of github.com:nim-lang/Nim into devel
This commit is contained in:
commit
1bbab827c4
115 changed files with 1279 additions and 1542 deletions
|
|
@ -113,7 +113,9 @@ type
|
|||
|
||||
type
|
||||
NimIdent* = object of RootObj
|
||||
## represents a Nim identifier in the AST
|
||||
## represents a Nim identifier in the AST. **Note**: This is only
|
||||
## rarely useful, for identifier construction from a string
|
||||
## use ``ident"abc"``.
|
||||
|
||||
NimSymObj = object # hidden
|
||||
NimSym* = ref NimSymObj
|
||||
|
|
@ -129,7 +131,11 @@ const
|
|||
nnkCallKinds* = {nnkCall, nnkInfix, nnkPrefix, nnkPostfix, nnkCommand,
|
||||
nnkCallStrLit}
|
||||
|
||||
proc `!`*(s: string): NimIdent {.magic: "StrToIdent", noSideEffect.}
|
||||
proc `!`*(s: string): NimIdent {.magic: "StrToIdent", noSideEffect, deprecated.}
|
||||
## constructs an identifier from the string `s`
|
||||
## **Deprecated since version 0.18.0**: Use ``toNimIdent`` instead.
|
||||
|
||||
proc toNimIdent*(s: string): NimIdent {.magic: "StrToIdent", noSideEffect.}
|
||||
## constructs an identifier from the string `s`
|
||||
|
||||
proc `$`*(i: NimIdent): string {.magic: "IdentToStr", noSideEffect.}
|
||||
|
|
@ -237,7 +243,7 @@ proc `ident=`*(n: NimNode, val: NimIdent) {.magic: "NSetIdent", noSideEffect.}
|
|||
proc `strVal=`*(n: NimNode, val: string) {.magic: "NSetStrVal", noSideEffect.}
|
||||
|
||||
proc newNimNode*(kind: NimNodeKind,
|
||||
lineInfoFrom: NimNode=nil): NimNode
|
||||
lineInfoFrom: NimNode = nil): NimNode
|
||||
{.magic: "NNewNimNode", noSideEffect.}
|
||||
## Creates a new AST node of the specified kind.
|
||||
##
|
||||
|
|
@ -290,7 +296,7 @@ proc newIdentNode*(i: NimIdent): NimNode {.compileTime.} =
|
|||
proc newIdentNode*(i: string): NimNode {.compileTime.} =
|
||||
## creates an identifier node from `i`
|
||||
result = newNimNode(nnkIdent)
|
||||
result.ident = !i
|
||||
result.ident = toNimIdent i
|
||||
|
||||
|
||||
type
|
||||
|
|
@ -400,7 +406,7 @@ proc quote*(bl: typed, op = "``"): NimNode {.magic: "QuoteAst", noSideEffect.}
|
|||
##
|
||||
## .. code-block:: nim
|
||||
##
|
||||
## macro check(ex: expr): stmt =
|
||||
## macro check(ex: untyped): typed =
|
||||
## # this is a simplified version of the check macro from the
|
||||
## # unittest module.
|
||||
##
|
||||
|
|
@ -477,7 +483,6 @@ proc newLit*(c: char): NimNode {.compileTime.} =
|
|||
result = newNimNode(nnkCharLit)
|
||||
result.intVal = ord(c)
|
||||
|
||||
|
||||
proc newLit*(i: int): NimNode {.compileTime.} =
|
||||
## produces a new integer literal node.
|
||||
result = newNimNode(nnkIntLit)
|
||||
|
|
@ -581,7 +586,7 @@ proc newLit*[T](arg: seq[T]): NimNode {.compileTime.} =
|
|||
proc newLit*(arg: tuple): NimNode {.compileTime.} =
|
||||
result = nnkPar.newTree
|
||||
for a,b in arg.fieldPairs:
|
||||
result.add nnkExprColonExpr.newTree( newIdentNode(a), newLit(b) )
|
||||
result.add nnkExprColonExpr.newTree(newIdentNode(a), newLit(b))
|
||||
|
||||
proc newLit*(s: string): NimNode {.compileTime.} =
|
||||
## produces a new string literal node.
|
||||
|
|
@ -615,7 +620,7 @@ proc treeRepr*(n: NimNode): string {.compileTime, benign.} =
|
|||
of nnkCharLit..nnkInt64Lit: res.add(" " & $n.intVal)
|
||||
of nnkFloatLit..nnkFloat64Lit: res.add(" " & $n.floatVal)
|
||||
of nnkStrLit..nnkTripleStrLit: res.add(" " & $n.strVal)
|
||||
of nnkIdent: res.add(" !\"" & $n.ident & '"')
|
||||
of nnkIdent: res.add(" ident\"" & $n.ident & '"')
|
||||
of nnkSym: res.add(" \"" & $n.symbol & '"')
|
||||
of nnkNone: assert false
|
||||
else:
|
||||
|
|
@ -640,7 +645,7 @@ proc lispRepr*(n: NimNode): string {.compileTime, benign.} =
|
|||
of nnkCharLit..nnkInt64Lit: add(result, $n.intVal)
|
||||
of nnkFloatLit..nnkFloat64Lit: add(result, $n.floatVal)
|
||||
of nnkStrLit..nnkTripleStrLit: add(result, $n.strVal)
|
||||
of nnkIdent: add(result, "!\"" & $n.ident & '"')
|
||||
of nnkIdent: add(result, "ident\"" & $n.ident & '"')
|
||||
of nnkSym: add(result, $n.symbol)
|
||||
of nnkNone: assert false
|
||||
else:
|
||||
|
|
@ -664,7 +669,7 @@ proc astGenRepr*(n: NimNode): string {.compileTime, benign.} =
|
|||
## .. code-block:: nim
|
||||
## nnkStmtList.newTree(
|
||||
## nnkCommand.newTree(
|
||||
## newIdentNode(!"echo"),
|
||||
## newIdentNode("echo"),
|
||||
## newLit("Hello world")
|
||||
## )
|
||||
## )
|
||||
|
|
@ -718,7 +723,7 @@ proc astGenRepr*(n: NimNode): string {.compileTime, benign.} =
|
|||
of nnkIntLit..nnkInt64Lit: res.add($n.intVal)
|
||||
of nnkFloatLit..nnkFloat64Lit: res.add($n.floatVal)
|
||||
of nnkStrLit..nnkTripleStrLit: res.add($n.strVal.escape())
|
||||
of nnkIdent: res.add("!" & ($n.ident).escape())
|
||||
of nnkIdent: res.add(($n.ident).escape())
|
||||
of nnkSym: res.add(($n.symbol).escape())
|
||||
of nnkNone: assert false
|
||||
else:
|
||||
|
|
@ -898,6 +903,48 @@ proc newIfStmt*(branches: varargs[tuple[cond, body: NimNode]]):
|
|||
for i in branches:
|
||||
result.add(newNimNode(nnkElifBranch).add(i.cond, i.body))
|
||||
|
||||
proc newEnum*(name: NimNode, fields: openArray[NimNode],
|
||||
public, pure: bool): NimNode {.compileTime.} =
|
||||
|
||||
## Creates a new enum. `name` must be an ident. Fields are allowed to be
|
||||
## either idents or EnumFieldDef
|
||||
##
|
||||
## .. code-block:: nim
|
||||
##
|
||||
## newEnum(
|
||||
## name = ident("Colors"),
|
||||
## fields = [ident("Blue"), ident("Red")],
|
||||
## public = true, pure = false)
|
||||
##
|
||||
## # type Colors* = Blue Red
|
||||
##
|
||||
|
||||
expectKind name, nnkIdent
|
||||
doAssert len(fields) > 0, "Enum must contain at least one field"
|
||||
for field in fields:
|
||||
expectKind field, {nnkIdent, nnkEnumFieldDef}
|
||||
|
||||
let enumBody = newNimNode(nnkEnumTy).add(newEmptyNode()).add(fields)
|
||||
var typeDefArgs = [name, newEmptyNode(), enumBody]
|
||||
|
||||
if public:
|
||||
let postNode = newNimNode(nnkPostfix).add(
|
||||
newIdentNode("*"), typeDefArgs[0])
|
||||
|
||||
typeDefArgs[0] = postNode
|
||||
|
||||
if pure:
|
||||
let pragmaNode = newNimNode(nnkPragmaExpr).add(
|
||||
typeDefArgs[0],
|
||||
add(newNimNode(nnkPragma), newIdentNode("pure")))
|
||||
|
||||
typeDefArgs[0] = pragmaNode
|
||||
|
||||
let
|
||||
typeDef = add(newNimNode(nnkTypeDef), typeDefArgs)
|
||||
typeSect = add(newNimNode(nnkTypeSection), typeDef)
|
||||
|
||||
return typeSect
|
||||
|
||||
proc copyChildrenTo*(src, dest: NimNode) {.compileTime.}=
|
||||
## Copy all children from `src` to `dest`
|
||||
|
|
@ -1015,7 +1062,7 @@ template findChild*(n: NimNode; cond: untyped): NimNode {.dirty.} =
|
|||
##
|
||||
## .. code-block:: nim
|
||||
## var res = findChild(n, it.kind == nnkPostfix and
|
||||
## it.basename.ident == !"foo")
|
||||
## it.basename.ident == toNimIdent"foo")
|
||||
block:
|
||||
var res: NimNode
|
||||
for it in n.children:
|
||||
|
|
@ -1049,7 +1096,7 @@ proc basename*(a: NimNode): NimNode =
|
|||
|
||||
proc `basename=`*(a: NimNode; val: string) {.compileTime.}=
|
||||
case a.kind
|
||||
of nnkIdent: macros.`ident=`(a, !val)
|
||||
of nnkIdent: macros.`ident=`(a, toNimIdent val)
|
||||
of nnkPostfix, nnkPrefix: a[1] = ident(val)
|
||||
else:
|
||||
quit "Do not know how to get basename of (" & treeRepr(a) & ")\n" & repr(a)
|
||||
|
|
@ -1110,7 +1157,7 @@ proc eqIdent*(node: NimNode; s: string): bool {.compileTime.} =
|
|||
## other ways like ``node.ident`` are much more error-prone, unfortunately.
|
||||
case node.kind
|
||||
of nnkIdent:
|
||||
result = node.ident == !s
|
||||
result = node.ident == toNimIdent s
|
||||
of nnkSym:
|
||||
result = eqIdent($node.symbol, s)
|
||||
of nnkOpenSymChoice, nnkClosedSymChoice:
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ export nativesockets
|
|||
|
||||
{.warning: "rawsockets module is deprecated, use nativesockets instead".}
|
||||
|
||||
template newRawSocket*(domain, sockType, protocol: cint): expr =
|
||||
template newRawSocket*(domain, sockType, protocol: cint): untyped =
|
||||
{.warning: "newRawSocket is deprecated, use newNativeSocket instead".}
|
||||
newNativeSocket(domain, sockType, protocol)
|
||||
|
||||
template newRawSocket*(domain: Domain = AF_INET,
|
||||
sockType: SockType = SOCK_STREAM,
|
||||
protocol: Protocol = IPPROTO_TCP): expr =
|
||||
protocol: Protocol = IPPROTO_TCP): untyped =
|
||||
{.warning: "newRawSocket is deprecated, use newNativeSocket instead".}
|
||||
newNativeSocket(domain, sockType, protocol)
|
||||
|
|
|
|||
|
|
@ -58,15 +58,18 @@ type
|
|||
socket: AsyncSocket
|
||||
reuseAddr: bool
|
||||
reusePort: bool
|
||||
maxBody: int ## The maximum content-length that will be read for the body.
|
||||
|
||||
{.deprecated: [TRequest: Request, PAsyncHttpServer: AsyncHttpServer,
|
||||
THttpCode: HttpCode, THttpVersion: HttpVersion].}
|
||||
|
||||
proc newAsyncHttpServer*(reuseAddr = true, reusePort = false): AsyncHttpServer =
|
||||
proc newAsyncHttpServer*(reuseAddr = true, reusePort = false,
|
||||
maxBody = 8388608): AsyncHttpServer =
|
||||
## Creates a new ``AsyncHttpServer`` instance.
|
||||
new result
|
||||
result.reuseAddr = reuseAddr
|
||||
result.reusePort = reusePort
|
||||
result.maxBody = maxBody
|
||||
|
||||
proc addHeaders(msg: var string, headers: HttpHeaders) =
|
||||
for k, v in headers:
|
||||
|
|
@ -122,145 +125,161 @@ proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] =
|
|||
raise newException(ValueError, "Invalid request protocol. Got: " &
|
||||
protocol)
|
||||
result.orig = protocol
|
||||
i.inc protocol.parseInt(result.major, i)
|
||||
i.inc protocol.parseSaturatedNatural(result.major, i)
|
||||
i.inc # Skip .
|
||||
i.inc protocol.parseInt(result.minor, i)
|
||||
i.inc protocol.parseSaturatedNatural(result.minor, i)
|
||||
|
||||
proc sendStatus(client: AsyncSocket, status: string): Future[void] =
|
||||
client.send("HTTP/1.1 " & status & "\c\L\c\L")
|
||||
|
||||
proc processClient(client: AsyncSocket, address: string,
|
||||
callback: proc (request: Request):
|
||||
proc processRequest(server: AsyncHttpServer, req: FutureVar[Request],
|
||||
client: AsyncSocket,
|
||||
address: string, lineFut: FutureVar[string],
|
||||
callback: proc (request: Request):
|
||||
Future[void] {.closure, gcsafe.}) {.async.} =
|
||||
var request: Request
|
||||
request.url = initUri()
|
||||
request.headers = newHttpHeaders()
|
||||
var lineFut = newFutureVar[string]("asynchttpserver.processClient")
|
||||
lineFut.mget() = newStringOfCap(80)
|
||||
var key, value = ""
|
||||
|
||||
while not client.isClosed:
|
||||
# GET /path HTTP/1.1
|
||||
# Header: val
|
||||
# \n
|
||||
request.headers.clear()
|
||||
request.body = ""
|
||||
request.hostname.shallowCopy(address)
|
||||
assert client != nil
|
||||
request.client = client
|
||||
# Alias `request` to `req.mget()` so we don't have to write `mget` everywhere.
|
||||
template request(): Request =
|
||||
req.mget()
|
||||
|
||||
# We should skip at least one empty line before the request
|
||||
# https://tools.ietf.org/html/rfc7230#section-3.5
|
||||
for i in 0..1:
|
||||
lineFut.mget().setLen(0)
|
||||
lineFut.clean()
|
||||
await client.recvLineInto(lineFut, maxLength=maxLine) # TODO: Timeouts.
|
||||
# GET /path HTTP/1.1
|
||||
# Header: val
|
||||
# \n
|
||||
request.headers.clear()
|
||||
request.body = ""
|
||||
request.hostname.shallowCopy(address)
|
||||
assert client != nil
|
||||
request.client = client
|
||||
|
||||
if lineFut.mget == "":
|
||||
client.close()
|
||||
return
|
||||
# We should skip at least one empty line before the request
|
||||
# https://tools.ietf.org/html/rfc7230#section-3.5
|
||||
for i in 0..1:
|
||||
lineFut.mget().setLen(0)
|
||||
lineFut.clean()
|
||||
await client.recvLineInto(lineFut, maxLength=maxLine) # TODO: Timeouts.
|
||||
|
||||
if lineFut.mget.len > maxLine:
|
||||
await request.respondError(Http413)
|
||||
client.close()
|
||||
return
|
||||
if lineFut.mget != "\c\L":
|
||||
break
|
||||
|
||||
# First line - GET /path HTTP/1.1
|
||||
var i = 0
|
||||
for linePart in lineFut.mget.split(' '):
|
||||
case i
|
||||
of 0:
|
||||
try:
|
||||
# TODO: this is likely slow.
|
||||
request.reqMethod = parseEnum[HttpMethod]("http" & linePart)
|
||||
except ValueError:
|
||||
asyncCheck request.respondError(Http400)
|
||||
continue
|
||||
of 1:
|
||||
try:
|
||||
parseUri(linePart, request.url)
|
||||
except ValueError:
|
||||
asyncCheck request.respondError(Http400)
|
||||
continue
|
||||
of 2:
|
||||
try:
|
||||
request.protocol = parseProtocol(linePart)
|
||||
except ValueError:
|
||||
asyncCheck request.respondError(Http400)
|
||||
continue
|
||||
else:
|
||||
await request.respondError(Http400)
|
||||
continue
|
||||
inc i
|
||||
|
||||
# Headers
|
||||
while true:
|
||||
i = 0
|
||||
lineFut.mget.setLen(0)
|
||||
lineFut.clean()
|
||||
await client.recvLineInto(lineFut, maxLength=maxLine)
|
||||
|
||||
if lineFut.mget == "":
|
||||
client.close(); return
|
||||
if lineFut.mget.len > maxLine:
|
||||
await request.respondError(Http413)
|
||||
client.close(); return
|
||||
if lineFut.mget == "\c\L": break
|
||||
let (key, value) = parseHeader(lineFut.mget)
|
||||
request.headers[key] = value
|
||||
# Ensure the client isn't trying to DoS us.
|
||||
if request.headers.len > headerLimit:
|
||||
await client.sendStatus("400 Bad Request")
|
||||
request.client.close()
|
||||
return
|
||||
|
||||
if request.reqMethod == HttpPost:
|
||||
# Check for Expect header
|
||||
if request.headers.hasKey("Expect"):
|
||||
if "100-continue" in request.headers["Expect"]:
|
||||
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"):
|
||||
var contentLength = 0
|
||||
if parseInt(request.headers["Content-Length"],
|
||||
contentLength) == 0:
|
||||
await request.respond(Http400, "Bad Request. Invalid Content-Length.")
|
||||
continue
|
||||
else:
|
||||
request.body = await client.recv(contentLength)
|
||||
if request.body.len != contentLength:
|
||||
await request.respond(Http400, "Bad Request. Content-Length does not match actual.")
|
||||
continue
|
||||
elif request.reqMethod == HttpPost:
|
||||
await request.respond(Http411, "Content-Length required.")
|
||||
continue
|
||||
|
||||
# Call the user's callback.
|
||||
await callback(request)
|
||||
|
||||
if "upgrade" in request.headers.getOrDefault("connection"):
|
||||
if lineFut.mget == "":
|
||||
client.close()
|
||||
return
|
||||
|
||||
# Persistent connections
|
||||
if (request.protocol == HttpVer11 and
|
||||
request.headers.getOrDefault("connection").normalize != "close") or
|
||||
(request.protocol == HttpVer10 and
|
||||
request.headers.getOrDefault("connection").normalize == "keep-alive"):
|
||||
# In HTTP 1.1 we assume that connection is persistent. Unless connection
|
||||
# header states otherwise.
|
||||
# In HTTP 1.0 we assume that the connection should not be persistent.
|
||||
# Unless the connection header states otherwise.
|
||||
discard
|
||||
else:
|
||||
request.client.close()
|
||||
if lineFut.mget.len > maxLine:
|
||||
await request.respondError(Http413)
|
||||
client.close()
|
||||
return
|
||||
if lineFut.mget != "\c\L":
|
||||
break
|
||||
|
||||
# First line - GET /path HTTP/1.1
|
||||
var i = 0
|
||||
for linePart in lineFut.mget.split(' '):
|
||||
case i
|
||||
of 0:
|
||||
try:
|
||||
# TODO: this is likely slow.
|
||||
request.reqMethod = parseEnum[HttpMethod]("http" & linePart)
|
||||
except ValueError:
|
||||
asyncCheck request.respondError(Http400)
|
||||
return
|
||||
of 1:
|
||||
try:
|
||||
parseUri(linePart, request.url)
|
||||
except ValueError:
|
||||
asyncCheck request.respondError(Http400)
|
||||
return
|
||||
of 2:
|
||||
try:
|
||||
request.protocol = parseProtocol(linePart)
|
||||
except ValueError:
|
||||
asyncCheck request.respondError(Http400)
|
||||
return
|
||||
else:
|
||||
await request.respondError(Http400)
|
||||
return
|
||||
inc i
|
||||
|
||||
# Headers
|
||||
while true:
|
||||
i = 0
|
||||
lineFut.mget.setLen(0)
|
||||
lineFut.clean()
|
||||
await client.recvLineInto(lineFut, maxLength=maxLine)
|
||||
|
||||
if lineFut.mget == "":
|
||||
client.close(); return
|
||||
if lineFut.mget.len > maxLine:
|
||||
await request.respondError(Http413)
|
||||
client.close(); return
|
||||
if lineFut.mget == "\c\L": break
|
||||
let (key, value) = parseHeader(lineFut.mget)
|
||||
request.headers[key] = value
|
||||
# Ensure the client isn't trying to DoS us.
|
||||
if request.headers.len > headerLimit:
|
||||
await client.sendStatus("400 Bad Request")
|
||||
request.client.close()
|
||||
return
|
||||
|
||||
if request.reqMethod == HttpPost:
|
||||
# Check for Expect header
|
||||
if request.headers.hasKey("Expect"):
|
||||
if "100-continue" in request.headers["Expect"]:
|
||||
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"):
|
||||
var contentLength = 0
|
||||
if parseSaturatedNatural(request.headers["Content-Length"], contentLength) == 0:
|
||||
await request.respond(Http400, "Bad Request. Invalid Content-Length.")
|
||||
return
|
||||
else:
|
||||
if contentLength > server.maxBody:
|
||||
await request.respondError(Http413)
|
||||
return
|
||||
request.body = await client.recv(contentLength)
|
||||
if request.body.len != contentLength:
|
||||
await request.respond(Http400, "Bad Request. Content-Length does not match actual.")
|
||||
return
|
||||
elif request.reqMethod == HttpPost:
|
||||
await request.respond(Http411, "Content-Length required.")
|
||||
return
|
||||
|
||||
# Call the user's callback.
|
||||
await callback(request)
|
||||
|
||||
if "upgrade" in request.headers.getOrDefault("connection"):
|
||||
return
|
||||
|
||||
# Persistent connections
|
||||
if (request.protocol == HttpVer11 and
|
||||
cmpIgnoreCase(request.headers.getOrDefault("connection"), "close") != 0) or
|
||||
(request.protocol == HttpVer10 and
|
||||
cmpIgnoreCase(request.headers.getOrDefault("connection"), "keep-alive") == 0):
|
||||
# In HTTP 1.1 we assume that connection is persistent. Unless connection
|
||||
# header states otherwise.
|
||||
# In HTTP 1.0 we assume that the connection should not be persistent.
|
||||
# Unless the connection header states otherwise.
|
||||
discard
|
||||
else:
|
||||
request.client.close()
|
||||
return
|
||||
|
||||
proc processClient(server: AsyncHttpServer, client: AsyncSocket, address: string,
|
||||
callback: proc (request: Request):
|
||||
Future[void] {.closure, gcsafe.}) {.async.} =
|
||||
var request = newFutureVar[Request]("asynchttpserver.processClient")
|
||||
request.mget().url = initUri()
|
||||
request.mget().headers = newHttpHeaders()
|
||||
var lineFut = newFutureVar[string]("asynchttpserver.processClient")
|
||||
lineFut.mget() = newStringOfCap(80)
|
||||
|
||||
while not client.isClosed:
|
||||
try:
|
||||
await processRequest(server, request, client, address, lineFut, callback)
|
||||
except:
|
||||
asyncCheck request.mget().respondError(Http500)
|
||||
|
||||
proc serve*(server: AsyncHttpServer, port: Port,
|
||||
callback: proc (request: Request): Future[void] {.closure,gcsafe.},
|
||||
address = "") {.async.} =
|
||||
|
|
@ -280,7 +299,7 @@ proc serve*(server: AsyncHttpServer, port: Port,
|
|||
# TODO: Causes compiler crash.
|
||||
#var (address, client) = await server.socket.acceptAddr()
|
||||
var fut = await server.socket.acceptAddr()
|
||||
asyncCheck processClient(fut.client, fut.address, callback)
|
||||
asyncCheck processClient(server, fut.client, fut.address, callback)
|
||||
#echo(f.isNil)
|
||||
#echo(f.repr)
|
||||
|
||||
|
|
|
|||
|
|
@ -472,27 +472,13 @@ proc stripAwait(node: NimNode): NimNode =
|
|||
proc splitParamType(paramType: NimNode, async: bool): NimNode =
|
||||
result = paramType
|
||||
if paramType.kind == nnkInfix and $paramType[0].ident in ["|", "or"]:
|
||||
let firstType = paramType[1]
|
||||
let firstTypeName = $firstType.ident
|
||||
let secondType = paramType[2]
|
||||
let secondTypeName = $secondType.ident
|
||||
let firstAsync = "async" in ($paramType[1].ident).normalize
|
||||
let secondAsync = "async" in ($paramType[2].ident).normalize
|
||||
|
||||
# Make sure that at least one has the name `async`, otherwise we shouldn't
|
||||
# touch it.
|
||||
if not ("async" in firstTypeName.normalize or
|
||||
"async" in secondTypeName.normalize):
|
||||
return
|
||||
|
||||
if async:
|
||||
if firstTypeName.normalize.startsWith("async"):
|
||||
result = paramType[1]
|
||||
elif secondTypeName.normalize.startsWith("async"):
|
||||
result = paramType[2]
|
||||
else:
|
||||
if not firstTypeName.normalize.startsWith("async"):
|
||||
result = paramType[1]
|
||||
elif not secondTypeName.normalize.startsWith("async"):
|
||||
result = paramType[2]
|
||||
if firstAsync:
|
||||
result = paramType[if async: 1 else: 2]
|
||||
elif secondAsync:
|
||||
result = paramType[if async: 2 else: 1]
|
||||
|
||||
proc stripReturnType(returnType: NimNode): NimNode =
|
||||
# Strip out the 'Future' from 'Future[T]'.
|
||||
|
|
@ -535,4 +521,4 @@ macro multisync*(prc: untyped): untyped =
|
|||
let (sync, asyncPrc) = splitProc(prc)
|
||||
result = newStmtList()
|
||||
result.add(asyncSingleProc(asyncPrc))
|
||||
result.add(sync)
|
||||
result.add(sync)
|
||||
|
|
|
|||
|
|
@ -21,24 +21,18 @@ proc openDefaultBrowser*(url: string) =
|
|||
## opens `url` with the user's default browser. This does not block.
|
||||
##
|
||||
## Under Windows, ``ShellExecute`` is used. Under Mac OS X the ``open``
|
||||
## command is used. Under Unix, it is checked if ``gnome-open`` exists and
|
||||
## used if it does. Next attempt is ``kde-open``, then ``xdg-open``.
|
||||
## Otherwise the environment variable ``BROWSER`` is used to determine the
|
||||
## default browser to use.
|
||||
## command is used. Under Unix, it is checked if ``xdg-open`` exists and
|
||||
## used if it does. Otherwise the environment variable ``BROWSER`` is
|
||||
## used to determine the default browser to use.
|
||||
when defined(windows):
|
||||
when useWinUnicode:
|
||||
var o = newWideCString("open")
|
||||
var u = newWideCString(url)
|
||||
discard shellExecuteW(0'i32, o, u, nil, nil, SW_SHOWNORMAL)
|
||||
else:
|
||||
discard shellExecuteA(0'i32, "open", url, nil, nil, SW_SHOWNORMAL)
|
||||
var o = newWideCString("open")
|
||||
var u = newWideCString(url)
|
||||
discard shellExecuteW(0'i32, o, u, nil, nil, SW_SHOWNORMAL)
|
||||
elif defined(macosx):
|
||||
discard execShellCmd("open " & quoteShell(url))
|
||||
else:
|
||||
const attempts = ["gnome-open ", "kde-open ", "xdg-open "]
|
||||
var u = quoteShell(url)
|
||||
for a in items(attempts):
|
||||
if execShellCmd(a & u) == 0: return
|
||||
if execShellCmd("xdg-open " & u) == 0: return
|
||||
for b in getEnv("BROWSER").string.split(PathSep):
|
||||
try:
|
||||
# we use ``startProcess`` here because we don't want to block!
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@
|
|||
|
||||
include "system/inclrtl"
|
||||
|
||||
import macros
|
||||
|
||||
when not defined(nimhygiene):
|
||||
{.pragma: dirty.}
|
||||
|
||||
|
|
@ -704,6 +706,52 @@ template newSeqWith*(len: int, init: untyped): untyped =
|
|||
result[i] = init
|
||||
result
|
||||
|
||||
proc mapLitsImpl(constructor: NimNode; op: NimNode; nested: bool;
|
||||
filter = nnkLiterals): NimNode =
|
||||
if constructor.kind in filter:
|
||||
result = newNimNode(nnkCall, lineInfoFrom=constructor)
|
||||
result.add op
|
||||
result.add constructor
|
||||
else:
|
||||
result = newNimNode(constructor.kind, lineInfoFrom=constructor)
|
||||
for v in constructor:
|
||||
if nested or v.kind in filter:
|
||||
result.add mapLitsImpl(v, op, nested, filter)
|
||||
else:
|
||||
result.add v
|
||||
|
||||
macro mapLiterals*(constructor, op: untyped;
|
||||
nested = true): untyped =
|
||||
## applies ``op`` to each of the **atomic** literals like ``3``
|
||||
## or ``"abc"`` in the specified ``constructor`` AST. This can
|
||||
## be used to map every array element to some target type:
|
||||
##
|
||||
## Example:
|
||||
##
|
||||
## .. code-block::
|
||||
## let x = mapLiterals([0.1, 1.2, 2.3, 3.4], int)
|
||||
## doAssert x is array[4, int]
|
||||
##
|
||||
## Short notation for:
|
||||
##
|
||||
## .. code-block::
|
||||
## let x = [int(0.1), int(1.2), int(2.3), int(3.4)]
|
||||
##
|
||||
## If ``nested`` is true, the literals are replaced everywhere
|
||||
## in the ``constructor`` AST, otherwise only the first level
|
||||
## is considered:
|
||||
##
|
||||
## .. code-block::
|
||||
## mapLiterals((1, ("abc"), 2), float, nested=false)
|
||||
##
|
||||
## Produces::
|
||||
##
|
||||
## (float(1), ("abc"), float(2))
|
||||
##
|
||||
## There are no constraints for the ``constructor`` AST, it
|
||||
## works for nested tuples of arrays of sets etc.
|
||||
result = mapLitsImpl(constructor, op, nested.boolVal)
|
||||
|
||||
when isMainModule:
|
||||
import strutils
|
||||
block: # concat test
|
||||
|
|
@ -992,5 +1040,11 @@ when isMainModule:
|
|||
seq2D[0][1] = true
|
||||
doAssert seq2D == @[@[true, true], @[true, false], @[false, false], @[false, false]]
|
||||
|
||||
block: # mapLiterals tests
|
||||
let x = mapLiterals([0.1, 1.2, 2.3, 3.4], int)
|
||||
doAssert x is array[4, int]
|
||||
doAssert mapLiterals((1, ("abc"), 2), float, nested=false) == (float(1), "abc", float(2))
|
||||
doAssert mapLiterals(([1], ("abc"), 2), `$`, nested=true) == (["1"], "abc", "2")
|
||||
|
||||
when not defined(testing):
|
||||
echo "Finished doc tests"
|
||||
|
|
|
|||
|
|
@ -87,10 +87,10 @@ proc newSharedString*(s: string): SharedString =
|
|||
result.len = len
|
||||
|
||||
when declared(atomicLoadN):
|
||||
template load(x): expr = atomicLoadN(addr x, ATOMIC_SEQ_CST)
|
||||
template load(x): untyped = atomicLoadN(addr x, ATOMIC_SEQ_CST)
|
||||
else:
|
||||
# XXX Fixme
|
||||
template load(x): expr = x
|
||||
template load(x): untyped = x
|
||||
|
||||
proc add*(s: var SharedString; t: cstring; len: Natural) =
|
||||
if len == 0: return
|
||||
|
|
|
|||
|
|
@ -13,6 +13,15 @@ import strtabs, times
|
|||
|
||||
proc parseCookies*(s: string): StringTableRef =
|
||||
## parses cookies into a string table.
|
||||
##
|
||||
## The proc is meant to parse the Cookie header set by a client, not the
|
||||
## "Set-Cookie" header set by servers.
|
||||
##
|
||||
## Example:
|
||||
##
|
||||
## .. code-block::Nim
|
||||
## doAssert parseCookies("a=1; foo=bar") == {"a": 1, "foo": "bar"}.newStringTable
|
||||
|
||||
result = newStringTable(modeCaseInsensitive)
|
||||
var i = 0
|
||||
while true:
|
||||
|
|
|
|||
|
|
@ -197,4 +197,4 @@ macro dump*(x: typed): untyped =
|
|||
let s = x.toStrLit
|
||||
let r = quote do:
|
||||
debugEcho `s`, " = ", `x`
|
||||
return r
|
||||
return r
|
||||
|
|
|
|||
|
|
@ -1,211 +0,0 @@
|
|||
#
|
||||
#
|
||||
# Nim's Runtime Library
|
||||
# (c) Copyright 2012 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## 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
|
||||
## of keys in case-sensitive, case-insensitive and style-insensitive modes.
|
||||
##
|
||||
## **Warning:** This module is deprecated, new code shouldn't use it!
|
||||
|
||||
{.deprecated.}
|
||||
|
||||
import
|
||||
os, hashes, strutils
|
||||
|
||||
type
|
||||
GenTableMode* = enum ## describes the table's key matching mode
|
||||
modeCaseSensitive, ## case sensitive matching of keys
|
||||
modeCaseInsensitive, ## case insensitive matching of keys
|
||||
modeStyleInsensitive ## style sensitive matching of keys
|
||||
|
||||
GenKeyValuePair[T] = tuple[key: string, val: T]
|
||||
GenKeyValuePairSeq[T] = seq[GenKeyValuePair[T]]
|
||||
GenTable*[T] = object of RootObj
|
||||
counter: int
|
||||
data: GenKeyValuePairSeq[T]
|
||||
mode: GenTableMode
|
||||
|
||||
PGenTable*[T] = ref GenTable[T] ## use this type to declare hash tables
|
||||
|
||||
{.deprecated: [TGenTableMode: GenTableMode, TGenKeyValuePair: GenKeyValuePair,
|
||||
TGenKeyValuePairSeq: GenKeyValuePairSeq, TGenTable: GenTable].}
|
||||
|
||||
const
|
||||
growthFactor = 2
|
||||
startSize = 64
|
||||
|
||||
|
||||
proc len*[T](tbl: PGenTable[T]): int {.inline.} =
|
||||
## returns the number of keys in `tbl`.
|
||||
result = tbl.counter
|
||||
|
||||
iterator pairs*[T](tbl: PGenTable[T]): tuple[key: string, value: T] =
|
||||
## iterates over any (key, value) pair in the table `tbl`.
|
||||
for h in 0..high(tbl.data):
|
||||
if not isNil(tbl.data[h].key):
|
||||
yield (tbl.data[h].key, tbl.data[h].val)
|
||||
|
||||
proc myhash[T](tbl: PGenTable[T], key: string): Hash =
|
||||
case tbl.mode
|
||||
of modeCaseSensitive: result = hashes.hash(key)
|
||||
of modeCaseInsensitive: result = hashes.hashIgnoreCase(key)
|
||||
of modeStyleInsensitive: result = hashes.hashIgnoreStyle(key)
|
||||
|
||||
proc myCmp[T](tbl: PGenTable[T], a, b: string): bool =
|
||||
case tbl.mode
|
||||
of modeCaseSensitive: result = cmp(a, b) == 0
|
||||
of modeCaseInsensitive: result = cmpIgnoreCase(a, b) == 0
|
||||
of modeStyleInsensitive: result = cmpIgnoreStyle(a, b) == 0
|
||||
|
||||
proc mustRehash(length, counter: int): bool =
|
||||
assert(length > counter)
|
||||
result = (length * 2 < counter * 3) or (length - counter < 4)
|
||||
|
||||
proc newGenTable*[T](mode: GenTableMode): PGenTable[T] =
|
||||
## creates a new generic hash table that is empty.
|
||||
new(result)
|
||||
result.mode = mode
|
||||
result.counter = 0
|
||||
newSeq(result.data, startSize)
|
||||
|
||||
proc nextTry(h, maxHash: Hash): Hash {.inline.} =
|
||||
result = ((5 * h) + 1) and maxHash
|
||||
|
||||
proc rawGet[T](tbl: PGenTable[T], key: string): int =
|
||||
var h: Hash
|
||||
h = myhash(tbl, key) and high(tbl.data) # start with real hash value
|
||||
while not isNil(tbl.data[h].key):
|
||||
if myCmp(tbl, tbl.data[h].key, key):
|
||||
return h
|
||||
h = nextTry(h, high(tbl.data))
|
||||
result = - 1
|
||||
|
||||
proc rawInsert[T](tbl: PGenTable[T], data: var GenKeyValuePairSeq[T],
|
||||
key: string, val: T) =
|
||||
var h: Hash
|
||||
h = myhash(tbl, key) and high(data)
|
||||
while not isNil(data[h].key):
|
||||
h = nextTry(h, high(data))
|
||||
data[h].key = key
|
||||
data[h].val = val
|
||||
|
||||
proc enlarge[T](tbl: PGenTable[T]) =
|
||||
var n: GenKeyValuePairSeq[T]
|
||||
newSeq(n, len(tbl.data) * growthFactor)
|
||||
for i in countup(0, high(tbl.data)):
|
||||
if not isNil(tbl.data[i].key):
|
||||
rawInsert[T](tbl, n, tbl.data[i].key, tbl.data[i].val)
|
||||
swap(tbl.data, n)
|
||||
|
||||
proc hasKey*[T](tbl: PGenTable[T], key: string): bool =
|
||||
## returns true iff `key` is in the table `tbl`.
|
||||
result = rawGet(tbl, key) >= 0
|
||||
|
||||
proc `[]`*[T](tbl: PGenTable[T], key: string): T =
|
||||
## retrieves the value at ``tbl[key]``. If `key` is not in `tbl`,
|
||||
## default(T) is returned and no exception is raised. One can check
|
||||
## with ``hasKey`` whether the key exists.
|
||||
var index = rawGet(tbl, key)
|
||||
if index >= 0: result = tbl.data[index].val
|
||||
|
||||
proc `[]=`*[T](tbl: PGenTable[T], key: string, val: T) =
|
||||
## puts a (key, value)-pair into `tbl`.
|
||||
var index = rawGet(tbl, key)
|
||||
if index >= 0:
|
||||
tbl.data[index].val = val
|
||||
else:
|
||||
if mustRehash(len(tbl.data), tbl.counter): enlarge(tbl)
|
||||
rawInsert(tbl, tbl.data, key, val)
|
||||
inc(tbl.counter)
|
||||
|
||||
|
||||
when isMainModule:
|
||||
#
|
||||
# Verify tables of integer values (string keys)
|
||||
#
|
||||
var x = newGenTable[int](modeCaseInsensitive)
|
||||
x["one"] = 1
|
||||
x["two"] = 2
|
||||
x["three"] = 3
|
||||
x["four"] = 4
|
||||
x["five"] = 5
|
||||
assert(len(x) == 5) # length procedure works
|
||||
assert(x["one"] == 1) # case-sensitive lookup works
|
||||
assert(x["ONE"] == 1) # case-insensitive should work for this table
|
||||
assert(x["one"]+x["two"] == 3) # make sure we're getting back ints
|
||||
assert(x.hasKey("one")) # hasKey should return 'true' for a key
|
||||
# of "one"...
|
||||
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
|
||||
MyType = tuple[first, second: string] # a pair of strings
|
||||
{.deprecated: [TMyType: MyType].}
|
||||
|
||||
var y = newGenTable[MyType](modeCaseInsensitive) # hash table where each
|
||||
# value is MyType tuple
|
||||
|
||||
#var junk: MyType = ("OK", "Here")
|
||||
|
||||
#echo junk.first, " ", junk.second
|
||||
|
||||
y["Hello"] = ("Hello", "World")
|
||||
y["Goodbye"] = ("Goodbye", "Everyone")
|
||||
#y["Hello"] = MyType( ("Hello", "World") )
|
||||
#y["Goodbye"] = MyType( ("Goodbye", "Everyone") )
|
||||
|
||||
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
|
||||
# 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
|
||||
|
||||
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
|
||||
|
|
@ -43,6 +43,7 @@ const bsdPlatform = defined(macosx) or defined(freebsd) or
|
|||
defined(dragonfly)
|
||||
|
||||
when defined(nimdoc):
|
||||
type SocketHandle = int
|
||||
type
|
||||
Selector*[T] = ref object
|
||||
## An object which holds descriptors to be checked for read/write status
|
||||
|
|
|
|||
|
|
@ -1849,8 +1849,8 @@ when isMainModule:
|
|||
discard parseJson"""{ invalid"""
|
||||
except:
|
||||
discard
|
||||
# memory diff should less than 2M
|
||||
doAssert(abs(getOccupiedMem() - startMemory) < 2 * 1024 * 1024)
|
||||
# memory diff should less than 4M
|
||||
doAssert(abs(getOccupiedMem() - startMemory) < 4 * 1024 * 1024)
|
||||
|
||||
|
||||
# test `$`
|
||||
|
|
|
|||
|
|
@ -1,87 +0,0 @@
|
|||
#
|
||||
#
|
||||
# Nim's Runtime Library
|
||||
# (c) Copyright 2013 Robert Persson
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## **Warning:** This module will be moved out of the stdlib and into a
|
||||
## Nimble package, don't use it.
|
||||
|
||||
type OneVarFunction* = proc (x: float): float
|
||||
|
||||
{.deprecated: [TOneVarFunction: OneVarFunction].}
|
||||
|
||||
proc brent*(xmin,xmax:float, function:OneVarFunction, tol:float,maxiter=1000):
|
||||
tuple[rootx, rooty: float, success: bool]=
|
||||
## Searches `function` for a root between `xmin` and `xmax`
|
||||
## using brents method. If the function value at `xmin`and `xmax` has the
|
||||
## same sign, `rootx`/`rooty` is set too the extrema value closest to x-axis
|
||||
## and succes is set to false.
|
||||
## Otherwise there exists at least one root and success is set to true.
|
||||
## This root is searched for at most `maxiter` iterations.
|
||||
## If `tol` tolerance is reached within `maxiter` iterations
|
||||
## the root refinement stops and success=true.
|
||||
|
||||
# see http://en.wikipedia.org/wiki/Brent%27s_method
|
||||
var
|
||||
a=xmin
|
||||
b=xmax
|
||||
c=a
|
||||
d=1.0e308
|
||||
fa=function(a)
|
||||
fb=function(b)
|
||||
fc=fa
|
||||
s=0.0
|
||||
fs=0.0
|
||||
mflag:bool
|
||||
i=0
|
||||
tmp2:float
|
||||
|
||||
if fa*fb>=0:
|
||||
if abs(fa)<abs(fb):
|
||||
return (a,fa,false)
|
||||
else:
|
||||
return (b,fb,false)
|
||||
|
||||
if abs(fa)<abs(fb):
|
||||
swap(fa,fb)
|
||||
swap(a,b)
|
||||
|
||||
while fb!=0.0 and abs(a-b)>tol:
|
||||
if fa!=fc and fb!=fc: # inverse quadratic interpolation
|
||||
s = a * fb * fc / (fa - fb) / (fa - fc) + b * fa * fc / (fb - fa) / (fb - fc) + c * fa * fb / (fc - fa) / (fc - fb)
|
||||
else: #secant rule
|
||||
s = b - fb * (b - a) / (fb - fa)
|
||||
tmp2 = (3.0 * a + b) / 4.0
|
||||
if not((s > tmp2 and s < b) or (s < tmp2 and s > b)) or
|
||||
(mflag and abs(s - b) >= (abs(b - c) / 2.0)) or
|
||||
(not mflag and abs(s - b) >= abs(c - d) / 2.0):
|
||||
s=(a+b)/2.0
|
||||
mflag=true
|
||||
else:
|
||||
if (mflag and (abs(b - c) < tol)) or (not mflag and (abs(c - d) < tol)):
|
||||
s=(a+b)/2.0
|
||||
mflag=true
|
||||
else:
|
||||
mflag=false
|
||||
fs = function(s)
|
||||
d = c
|
||||
c = b
|
||||
fc = fb
|
||||
if fa * fs<0.0:
|
||||
b=s
|
||||
fb=fs
|
||||
else:
|
||||
a=s
|
||||
fa=fs
|
||||
if abs(fa)<abs(fb):
|
||||
swap(a,b)
|
||||
swap(fa,fb)
|
||||
inc i
|
||||
if i>maxiter:
|
||||
break
|
||||
|
||||
return (b,fb,true)
|
||||
|
|
@ -1195,14 +1195,15 @@ when defined(nimdoc):
|
|||
## Returns the number of `command line arguments`:idx: given to the
|
||||
## application.
|
||||
##
|
||||
## If your binary was called without parameters this will return zero. You
|
||||
## can later query each individual paramater with `paramStr() <#paramStr>`_
|
||||
## Unlike `argc`:idx: in C, if your binary was called without parameters this
|
||||
## will return zero.
|
||||
## You can query each individual paramater with `paramStr() <#paramStr>`_
|
||||
## or retrieve all of them in one go with `commandLineParams()
|
||||
## <#commandLineParams>`_.
|
||||
##
|
||||
## **Availability**: On Posix there is no portable way to get the command
|
||||
## line from a DLL and thus the proc isn't defined in this environment. You
|
||||
## can test for its availability with `declared() <system.html#declared>`_.
|
||||
## **Availability**: When generating a dynamic library (see --app:lib) on
|
||||
## Posix this proc is not defined.
|
||||
## Test for availability using `declared() <system.html#declared>`_.
|
||||
## Example:
|
||||
##
|
||||
## .. code-block:: nim
|
||||
|
|
@ -1219,13 +1220,14 @@ when defined(nimdoc):
|
|||
## `paramCount() <#paramCount>`_ with this proc you can call the
|
||||
## convenience `commandLineParams() <#commandLineParams>`_.
|
||||
##
|
||||
## It is possible to call ``paramStr(0)`` but this will return OS specific
|
||||
## Similarly to `argv`:idx: in C,
|
||||
## it is possible to call ``paramStr(0)`` but this will return OS specific
|
||||
## contents (usually the name of the invoked executable). You should avoid
|
||||
## this and call `getAppFilename() <#getAppFilename>`_ instead.
|
||||
##
|
||||
## **Availability**: On Posix there is no portable way to get the command
|
||||
## line from a DLL and thus the proc isn't defined in this environment. You
|
||||
## can test for its availability with `declared() <system.html#declared>`_.
|
||||
## **Availability**: When generating a dynamic library (see --app:lib) on
|
||||
## Posix this proc is not defined.
|
||||
## Test for availability using `declared() <system.html#declared>`_.
|
||||
## Example:
|
||||
##
|
||||
## .. code-block:: nim
|
||||
|
|
|
|||
|
|
@ -841,7 +841,7 @@ elif not defined(useNimRtl):
|
|||
var attr: Tposix_spawnattr
|
||||
var fops: Tposix_spawn_file_actions
|
||||
|
||||
template chck(e: expr) =
|
||||
template chck(e: untyped) =
|
||||
if e != 0'i32: raiseOSError(osLastError())
|
||||
|
||||
chck posix_spawn_file_actions_init(fops)
|
||||
|
|
|
|||
|
|
@ -252,6 +252,31 @@ proc parseInt*(s: string, number: var int, start = 0): int {.
|
|||
elif result != 0:
|
||||
number = int(res)
|
||||
|
||||
proc parseSaturatedNatural*(s: string, b: var int, start = 0): int =
|
||||
## parses a natural number into ``b``. This cannot raise an overflow
|
||||
## error. Instead of an ``Overflow`` exception ``high(int)`` is returned.
|
||||
## The number of processed character is returned.
|
||||
## This is usually what you really want to use instead of `parseInt`:idx:.
|
||||
## Example:
|
||||
##
|
||||
## .. code-block:: nim
|
||||
## var res = 0
|
||||
## discard parseSaturatedNatural("848", res)
|
||||
## doAssert res == 848
|
||||
var i = start
|
||||
if s[i] == '+': inc(i)
|
||||
if s[i] in {'0'..'9'}:
|
||||
b = 0
|
||||
while s[i] in {'0'..'9'}:
|
||||
let c = ord(s[i]) - ord('0')
|
||||
if b <= (high(int) - c) div 10:
|
||||
b = b * 10 + c
|
||||
else:
|
||||
b = high(int)
|
||||
inc(i)
|
||||
while s[i] == '_': inc(i) # underscores are allowed and ignored
|
||||
result = i - start
|
||||
|
||||
# overflowChecks doesn't work with BiggestUInt
|
||||
proc rawParseUInt(s: string, b: var BiggestUInt, start = 0): int =
|
||||
var
|
||||
|
|
@ -393,16 +418,43 @@ when isMainModule:
|
|||
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
|
||||
doAssert toSeq(interpolatedFragments(input)) == expected
|
||||
|
||||
var value = 0
|
||||
discard parseHex("0x38", value)
|
||||
assert value == 56
|
||||
doAssert value == 56
|
||||
discard parseHex("0x34", value)
|
||||
assert value == 56 * 256 + 52
|
||||
doAssert value == 56 * 256 + 52
|
||||
value = -1
|
||||
discard parseHex("0x38", value)
|
||||
assert value == -200
|
||||
doAssert value == -200
|
||||
|
||||
value = -1
|
||||
doAssert(parseSaturatedNatural("848", value) == 3)
|
||||
doAssert value == 848
|
||||
|
||||
value = -1
|
||||
discard parseSaturatedNatural("84899999999999999999324234243143142342135435342532453", value)
|
||||
doAssert value == high(int)
|
||||
|
||||
value = -1
|
||||
discard parseSaturatedNatural("9223372036854775808", value)
|
||||
doAssert value == high(int)
|
||||
|
||||
value = -1
|
||||
discard parseSaturatedNatural("9223372036854775807", value)
|
||||
doAssert value == high(int)
|
||||
|
||||
value = -1
|
||||
discard parseSaturatedNatural("18446744073709551616", value)
|
||||
doAssert value == high(int)
|
||||
|
||||
value = -1
|
||||
discard parseSaturatedNatural("18446744073709551615", value)
|
||||
doAssert value == high(int)
|
||||
|
||||
value = -1
|
||||
doAssert(parseSaturatedNatural("1_000_000", value) == 9)
|
||||
doAssert value == 1_000_000
|
||||
|
||||
{.pop.}
|
||||
|
|
|
|||
|
|
@ -1,371 +0,0 @@
|
|||
#
|
||||
#
|
||||
# Nim's Runtime Library
|
||||
# (c) Copyright 2013 Robert Persson
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## **Warning:** This module will be moved out of the stdlib and into a
|
||||
## Nimble package, don't use it.
|
||||
|
||||
import math
|
||||
import strutils
|
||||
import numeric
|
||||
|
||||
type
|
||||
Poly* = object
|
||||
cofs:seq[float]
|
||||
|
||||
{.deprecated: [TPoly: Poly].}
|
||||
|
||||
proc degree*(p:Poly):int=
|
||||
## Returns the degree of the polynomial,
|
||||
## that is the number of coefficients-1
|
||||
return p.cofs.len-1
|
||||
|
||||
|
||||
proc eval*(p:Poly,x:float):float=
|
||||
## Evaluates a polynomial function value for `x`
|
||||
## quickly using Horners method
|
||||
var n=p.degree
|
||||
result=p.cofs[n]
|
||||
dec n
|
||||
while n>=0:
|
||||
result = result*x+p.cofs[n]
|
||||
dec n
|
||||
|
||||
proc `[]` *(p:Poly;idx:int):float=
|
||||
## Gets a coefficient of the polynomial.
|
||||
## p[2] will returns the quadric term, p[3] the cubic etc.
|
||||
## Out of bounds index will return 0.0.
|
||||
if idx<0 or idx>p.degree:
|
||||
return 0.0
|
||||
return p.cofs[idx]
|
||||
|
||||
proc `[]=` *(p:var Poly;idx:int,v:float)=
|
||||
## Sets an coefficient of the polynomial by index.
|
||||
## p[2] set the quadric term, p[3] the cubic etc.
|
||||
## If index is out of range for the coefficients,
|
||||
## the polynomial grows to the smallest needed degree.
|
||||
assert(idx>=0)
|
||||
|
||||
if idx>p.degree: #polynomial must grow
|
||||
var oldlen=p.cofs.len
|
||||
p.cofs.setLen(idx+1)
|
||||
for q in oldlen.. <high(p.cofs):
|
||||
p.cofs[q]=0.0 #new-grown coefficients set to zero
|
||||
|
||||
p.cofs[idx]=v
|
||||
|
||||
|
||||
iterator items*(p:Poly):float=
|
||||
## Iterates through the coefficients of the polynomial.
|
||||
var i=p.degree
|
||||
while i>=0:
|
||||
yield p[i]
|
||||
dec i
|
||||
|
||||
proc clean*(p:var Poly;zerotol=0.0)=
|
||||
## Removes leading zero coefficients of the polynomial.
|
||||
## An optional tolerance can be given for what's considered zero.
|
||||
var n=p.degree
|
||||
var relen=false
|
||||
|
||||
while n>0 and abs(p[n])<=zerotol: # >0 => keep at least one coefficient
|
||||
dec n
|
||||
relen=true
|
||||
|
||||
if relen: p.cofs.setLen(n+1)
|
||||
|
||||
|
||||
proc `$` *(p:Poly):string =
|
||||
## Gets a somewhat reasonable string representation of the polynomial
|
||||
## The format should be compatible with most online function plotters,
|
||||
## for example directly in google search
|
||||
result=""
|
||||
var first=true #might skip + sign if first coefficient
|
||||
|
||||
for idx in countdown(p.degree,0):
|
||||
let a=p[idx]
|
||||
|
||||
if a==0.0:
|
||||
continue
|
||||
|
||||
if a>= 0.0 and not first:
|
||||
result.add('+')
|
||||
first=false
|
||||
|
||||
if a!=1.0 or idx==0:
|
||||
result.add(formatFloat(a,ffDefault,0))
|
||||
if idx>=2:
|
||||
result.add("x^" & $idx)
|
||||
elif idx==1:
|
||||
result.add("x")
|
||||
|
||||
if result=="":
|
||||
result="0"
|
||||
|
||||
|
||||
proc derivative*(p: Poly): Poly=
|
||||
## Returns a new polynomial, which is the derivative of `p`
|
||||
newSeq[float](result.cofs,p.degree)
|
||||
for idx in 0..high(result.cofs):
|
||||
result.cofs[idx]=p.cofs[idx+1]*float(idx+1)
|
||||
|
||||
proc diff*(p:Poly,x:float):float=
|
||||
## Evaluates the differentiation of a polynomial with
|
||||
## respect to `x` quickly using a modifed Horners method
|
||||
var n=p.degree
|
||||
result=p[n]*float(n)
|
||||
dec n
|
||||
while n>=1:
|
||||
result = result*x+p[n]*float(n)
|
||||
dec n
|
||||
|
||||
proc integral*(p:Poly):Poly=
|
||||
## Returns a new polynomial which is the indefinite
|
||||
## integral of `p`. The constant term is set to 0.0
|
||||
newSeq(result.cofs,p.cofs.len+1)
|
||||
result.cofs[0]=0.0 #constant arbitrary term, use 0.0
|
||||
for i in 1..high(result.cofs):
|
||||
result.cofs[i]=p.cofs[i-1]/float(i)
|
||||
|
||||
|
||||
proc integrate*(p:Poly;xmin,xmax:float):float=
|
||||
## Computes the definite integral of `p` between `xmin` and `xmax`
|
||||
## quickly using a modified version of Horners method
|
||||
var
|
||||
n=p.degree
|
||||
s1=p[n]/float(n+1)
|
||||
s2=s1
|
||||
fac:float
|
||||
|
||||
dec n
|
||||
while n>=0:
|
||||
fac=p[n]/float(n+1)
|
||||
s1 = s1*xmin+fac
|
||||
s2 = s2*xmax+fac
|
||||
dec n
|
||||
|
||||
result=s2*xmax-s1*xmin
|
||||
|
||||
proc initPoly*(cofs:varargs[float]):Poly=
|
||||
## Initializes a polynomial with given coefficients.
|
||||
## The most significant coefficient is first, so to create x^2-2x+3:
|
||||
## intiPoly(1.0,-2.0,3.0)
|
||||
if len(cofs)<=0:
|
||||
result.cofs= @[0.0] #need at least one coefficient
|
||||
else:
|
||||
# reverse order of coefficients so indexing matches degree of
|
||||
# coefficient...
|
||||
result.cofs= @[]
|
||||
for idx in countdown(cofs.len-1,0):
|
||||
result.cofs.add(cofs[idx])
|
||||
|
||||
result.clean #remove leading zero terms
|
||||
|
||||
|
||||
proc divMod*(p,d:Poly;q,r:var Poly)=
|
||||
## Divides `p` with `d`, and stores the quotinent in `q` and
|
||||
## the remainder in `d`
|
||||
var
|
||||
pdeg=p.degree
|
||||
ddeg=d.degree
|
||||
power=p.degree-d.degree
|
||||
ratio:float
|
||||
|
||||
r.cofs = p.cofs #initial remainder=numerator
|
||||
if power<0: #denominator is larger than numerator
|
||||
q.cofs= @ [0.0] #quotinent is 0.0
|
||||
return # keep remainder as numerator
|
||||
|
||||
q.cofs=newSeq[float](power+1)
|
||||
|
||||
for i in countdown(pdeg,ddeg):
|
||||
ratio=r.cofs[i]/d.cofs[ddeg]
|
||||
|
||||
q.cofs[i-ddeg]=ratio
|
||||
r.cofs[i]=0.0
|
||||
|
||||
for j in countup(0,<ddeg):
|
||||
var idx=i-ddeg+j
|
||||
r.cofs[idx] = r.cofs[idx] - d.cofs[j]*ratio
|
||||
|
||||
r.clean # drop zero coefficients in remainder
|
||||
|
||||
proc `+` *(p1:Poly,p2:Poly):Poly=
|
||||
## Adds two polynomials
|
||||
var n=max(p1.cofs.len,p2.cofs.len)
|
||||
newSeq(result.cofs,n)
|
||||
|
||||
for idx in countup(0,n-1):
|
||||
result[idx]=p1[idx]+p2[idx]
|
||||
|
||||
result.clean # drop zero coefficients in remainder
|
||||
|
||||
proc `*` *(p1:Poly,p2:Poly):Poly=
|
||||
## Multiplies the polynomial `p1` with `p2`
|
||||
var
|
||||
d1=p1.degree
|
||||
d2=p2.degree
|
||||
n=d1+d2
|
||||
idx:int
|
||||
|
||||
newSeq(result.cofs,n)
|
||||
|
||||
for i1 in countup(0,d1):
|
||||
for i2 in countup(0,d2):
|
||||
idx=i1+i2
|
||||
result[idx]=result[idx]+p1[i1]*p2[i2]
|
||||
|
||||
result.clean
|
||||
|
||||
proc `*` *(p:Poly,f:float):Poly=
|
||||
## Multiplies the polynomial `p` with a real number
|
||||
newSeq(result.cofs,p.cofs.len)
|
||||
for i in 0..high(p.cofs):
|
||||
result[i]=p.cofs[i]*f
|
||||
result.clean
|
||||
|
||||
proc `*` *(f:float,p:Poly):Poly=
|
||||
## Multiplies a real number with a polynomial
|
||||
return p*f
|
||||
|
||||
proc `-`*(p:Poly):Poly=
|
||||
## Negates a polynomial
|
||||
result=p
|
||||
for i in countup(0,<result.cofs.len):
|
||||
result.cofs[i]= -result.cofs[i]
|
||||
|
||||
proc `-` *(p1:Poly,p2:Poly):Poly=
|
||||
## Subtract `p1` with `p2`
|
||||
var n=max(p1.cofs.len,p2.cofs.len)
|
||||
newSeq(result.cofs,n)
|
||||
|
||||
for idx in countup(0,n-1):
|
||||
result[idx]=p1[idx]-p2[idx]
|
||||
|
||||
result.clean # drop zero coefficients in remainder
|
||||
|
||||
proc `/`*(p:Poly,f:float):Poly=
|
||||
## Divides polynomial `p` with a real number `f`
|
||||
newSeq(result.cofs,p.cofs.len)
|
||||
for i in 0..high(p.cofs):
|
||||
result[i]=p.cofs[i]/f
|
||||
result.clean
|
||||
|
||||
proc `/` *(p,q:Poly):Poly=
|
||||
## Divides polynomial `p` with polynomial `q`
|
||||
var dummy:Poly
|
||||
p.divMod(q,result,dummy)
|
||||
|
||||
proc `mod` *(p,q:Poly):Poly=
|
||||
## Computes the polynomial modulo operation,
|
||||
## that is the remainder of `p`/`q`
|
||||
var dummy:Poly
|
||||
p.divMod(q,dummy,result)
|
||||
|
||||
|
||||
proc normalize*(p:var Poly)=
|
||||
## Multiplies the polynomial inplace by a term so that
|
||||
## the leading term is 1.0.
|
||||
## This might lead to an unstable polynomial
|
||||
## if the leading term is zero.
|
||||
p=p/p[p.degree]
|
||||
|
||||
|
||||
proc solveQuadric*(a,b,c:float;zerotol=0.0):seq[float]=
|
||||
## Solves the quadric equation `ax^2+bx+c`, with a possible
|
||||
## tolerance `zerotol` to find roots of curves just 'touching'
|
||||
## the x axis. Returns sequence with 0,1 or 2 solutions.
|
||||
|
||||
var p,q,d:float
|
||||
|
||||
p=b/(2.0*a)
|
||||
|
||||
if p==Inf or p==NegInf: #linear equation..
|
||||
var linrt= -c/b
|
||||
if linrt==Inf or linrt==NegInf: #constant only
|
||||
return @[]
|
||||
return @[linrt]
|
||||
|
||||
q=c/a
|
||||
d=p*p-q
|
||||
|
||||
if d<0.0:
|
||||
#check for inside zerotol range for neg. roots
|
||||
var err=a*p*p-b*p+c #evaluate error at parabola center axis
|
||||
if(err<=zerotol): return @[-p]
|
||||
return @[]
|
||||
else:
|
||||
var sr=sqrt(d)
|
||||
result= @[-sr-p,sr-p]
|
||||
|
||||
proc getRangeForRoots(p:Poly):tuple[xmin,xmax:float]=
|
||||
## helper function for `roots` function
|
||||
## quickly computes a range, guaranteed to contain
|
||||
## all the real roots of the polynomial
|
||||
# see http://www.mathsisfun.com/algebra/polynomials-bounds-zeros.html
|
||||
|
||||
var deg=p.degree
|
||||
var d=p[deg]
|
||||
var bound1,bound2:float
|
||||
|
||||
for i in countup(0,deg):
|
||||
var c=abs(p.cofs[i]/d)
|
||||
bound1=max(bound1,c+1.0)
|
||||
bound2=bound2+c
|
||||
|
||||
bound2=max(1.0,bound2)
|
||||
result.xmax=min(bound1,bound2)
|
||||
result.xmin= -result.xmax
|
||||
|
||||
|
||||
proc addRoot(p:Poly,res:var seq[float],xp0,xp1,tol,zerotol,mergetol:float,maxiter:int)=
|
||||
## helper function for `roots` function
|
||||
## try to do a numeric search for a single root in range xp0-xp1,
|
||||
## adding it to `res` (allocating `res` if nil)
|
||||
var br=brent(xp0,xp1, proc(x:float):float=p.eval(x),tol)
|
||||
if br.success:
|
||||
if res.len==0 or br.rootx>=res[high(res)]+mergetol: #dont add equal roots.
|
||||
res.add(br.rootx)
|
||||
else:
|
||||
#this might be a 'touching' case, check function value against
|
||||
#zero tolerance
|
||||
if abs(br.rooty)<=zerotol:
|
||||
if res.len==0 or br.rootx>=res[high(res)]+mergetol: #dont add equal roots.
|
||||
res.add(br.rootx)
|
||||
|
||||
|
||||
proc roots*(p:Poly,tol=1.0e-9,zerotol=1.0e-6,mergetol=1.0e-12,maxiter=1000):seq[float]=
|
||||
## Computes the real roots of the polynomial `p`
|
||||
## `tol` is the tolerance used to break searching for each root when reached.
|
||||
## `zerotol` is the tolerance, which is 'close enough' to zero to be considered a root
|
||||
## and is used to find roots for curves that only 'touch' the x-axis.
|
||||
## `mergetol` is the tolerance, of which two x-values are considered being the same root.
|
||||
## `maxiter` can be used to limit the number of iterations for each root.
|
||||
## Returns a (possibly empty) sorted sequence with the solutions.
|
||||
var deg=p.degree
|
||||
if deg<=0: #constant only => no roots
|
||||
return @[]
|
||||
elif p.degree==1: #linear
|
||||
var linrt= -p.cofs[0]/p.cofs[1]
|
||||
if linrt==Inf or linrt==NegInf:
|
||||
return @[] #constant only => no roots
|
||||
return @[linrt]
|
||||
elif p.degree==2:
|
||||
return solveQuadric(p.cofs[2],p.cofs[1],p.cofs[0],zerotol)
|
||||
else:
|
||||
# degree >=3 , find min/max points of polynomial with recursive
|
||||
# derivative and do a numerical search for root between each min/max
|
||||
var range=p.getRangeForRoots()
|
||||
var minmax=p.derivative.roots(tol,zerotol,mergetol)
|
||||
result= @[]
|
||||
if minmax!=nil: #ie. we have minimas/maximas in this function
|
||||
for x in minmax.items:
|
||||
addRoot(p,result,range.xmin,x,tol,zerotol,mergetol,maxiter)
|
||||
range.xmin=x
|
||||
addRoot(p,result,range.xmin,range.xmax,tol,zerotol,mergetol,maxiter)
|
||||
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
#
|
||||
#
|
||||
# Nim's Runtime Library
|
||||
# (c) Copyright 2011 Philippe Lhoste
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Module for converting an integer to a Roman numeral.
|
||||
## See http://en.wikipedia.org/wiki/Roman_numerals for reference.
|
||||
##
|
||||
## **Warning:** This module will be moved out of the stdlib and into a
|
||||
## Nimble package, don't use it.
|
||||
|
||||
const
|
||||
RomanNumeralDigits* = {'I', 'i', 'V', 'v', 'X', 'x', 'L', 'l', 'C', 'c',
|
||||
'D', 'd', 'M', 'm'} ## set of all characters a Roman numeral may consist of
|
||||
|
||||
proc romanToDecimal*(romanVal: string): int =
|
||||
## Converts a Roman numeral to its int representation.
|
||||
result = 0
|
||||
var prevVal = 0
|
||||
for i in countdown(romanVal.len - 1, 0):
|
||||
var val = 0
|
||||
case romanVal[i]
|
||||
of 'I', 'i': val = 1
|
||||
of 'V', 'v': val = 5
|
||||
of 'X', 'x': val = 10
|
||||
of 'L', 'l': val = 50
|
||||
of 'C', 'c': val = 100
|
||||
of 'D', 'd': val = 500
|
||||
of 'M', 'm': val = 1000
|
||||
else:
|
||||
raise newException(EInvalidValue, "invalid roman numeral: " & $romanVal)
|
||||
if val >= prevVal:
|
||||
inc(result, val)
|
||||
else:
|
||||
dec(result, val)
|
||||
prevVal = val
|
||||
|
||||
proc decimalToRoman*(number: range[1..3_999]): string =
|
||||
## Converts a number to a Roman numeral.
|
||||
const romanComposites = [
|
||||
("M", 1000), ("CM", 900),
|
||||
("D", 500), ("CD", 400), ("C", 100),
|
||||
("XC", 90), ("L", 50), ("XL", 40), ("X", 10), ("IX", 9),
|
||||
("V", 5), ("IV", 4), ("I", 1)]
|
||||
result = ""
|
||||
var decVal: int = number
|
||||
for key, val in items(romanComposites):
|
||||
while decVal >= val:
|
||||
dec(decVal, val)
|
||||
result.add(key)
|
||||
|
||||
when isMainModule:
|
||||
for i in 1 .. 3_999:
|
||||
assert i == i.decimalToRoman.romanToDecimal
|
||||
|
||||
|
|
@ -1927,7 +1927,7 @@ type
|
|||
{.deprecated: [TFloatFormat: FloatFormatMode].}
|
||||
|
||||
proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault,
|
||||
precision: range[0..32] = 16;
|
||||
precision: range[-1..32] = 16;
|
||||
decimalSep = '.'): string {.
|
||||
noSideEffect, rtl, extern: "nsu$1".} =
|
||||
## Converts a floating point value `f` to a string.
|
||||
|
|
@ -1939,7 +1939,7 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault,
|
|||
## `precision`'s default value is the maximum number of meaningful digits
|
||||
## after the decimal point for Nim's ``biggestFloat`` type.
|
||||
##
|
||||
## If ``precision == 0``, it tries to format it nicely.
|
||||
## If ``precision == -1``, it tries to format it nicely.
|
||||
when defined(js):
|
||||
var res: cstring
|
||||
case format
|
||||
|
|
@ -1961,7 +1961,7 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault,
|
|||
buf {.noinit.}: array[0..2500, char]
|
||||
L: cint
|
||||
frmtstr[0] = '%'
|
||||
if precision > 0:
|
||||
if precision >= 0:
|
||||
frmtstr[1] = '#'
|
||||
frmtstr[2] = '.'
|
||||
frmtstr[3] = '*'
|
||||
|
|
@ -1984,7 +1984,7 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault,
|
|||
# but nothing else is possible:
|
||||
if buf[i] in {'.', ','}: result[i] = decimalsep
|
||||
else: result[i] = buf[i]
|
||||
when defined(vcc):
|
||||
when defined(windows):
|
||||
# VS pre 2015 violates the C standard: "The exponent always contains at
|
||||
# least two digits, and only as many more digits as necessary to
|
||||
# represent the exponent." [C11 §7.21.6.1]
|
||||
|
|
@ -1995,7 +1995,7 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault,
|
|||
result.setLen(result.len - 1)
|
||||
|
||||
proc formatFloat*(f: float, format: FloatFormatMode = ffDefault,
|
||||
precision: range[0..32] = 16; decimalSep = '.'): string {.
|
||||
precision: range[-1..32] = 16; decimalSep = '.'): string {.
|
||||
noSideEffect, rtl, extern: "nsu$1".} =
|
||||
## Converts a floating point value `f` to a string.
|
||||
##
|
||||
|
|
@ -2006,16 +2006,16 @@ proc formatFloat*(f: float, format: FloatFormatMode = ffDefault,
|
|||
## `precision`'s default value is the maximum number of meaningful digits
|
||||
## after the decimal point for Nim's ``float`` type.
|
||||
##
|
||||
## If ``precision == 0``, it tries to format it nicely.
|
||||
## If ``precision == -1``, it tries to format it nicely.
|
||||
##
|
||||
## Examples:
|
||||
##
|
||||
## .. code-block:: nim
|
||||
##
|
||||
## let x = 123.456
|
||||
## echo x.formatFloat() # 123.4560000000000
|
||||
## echo x.formatFloat(ffDecimal, 4) # 123.4560
|
||||
## echo x.formatFloat(ffScientific, 2) # 1.23e+02
|
||||
## doAssert x.formatFloat() == "123.4560000000000"
|
||||
## doAssert x.formatFloat(ffDecimal, 4) == "123.4560"
|
||||
## doAssert x.formatFloat(ffScientific, 2) == "1.23e+02"
|
||||
##
|
||||
result = formatBiggestFloat(f, format, precision, decimalSep)
|
||||
|
||||
|
|
@ -2471,12 +2471,14 @@ when isMainModule:
|
|||
outp = " this is a\nlong text\n--\nmuchlongerthan10chars\nand here\nit goes"
|
||||
doAssert wordWrap(inp, 10, false) == outp
|
||||
|
||||
doAssert formatBiggestFloat(1234.567, ffDecimal, -1) == "1234.567000"
|
||||
doAssert formatBiggestFloat(1234.567, ffDecimal, 0) == "1235."
|
||||
doAssert formatBiggestFloat(1234.567, ffDecimal, 1) == "1234.6"
|
||||
doAssert formatBiggestFloat(0.00000000001, ffDecimal, 11) == "0.00000000001"
|
||||
doAssert formatBiggestFloat(0.00000000001, ffScientific, 1, ',') in
|
||||
["1,0e-11", "1,0e-011"]
|
||||
# bug #6589
|
||||
doAssert formatFloat(123.456, ffScientific, precision=0) in
|
||||
["1.234560e+02", "1.234560e+002"]
|
||||
doAssert formatFloat(123.456, ffScientific, precision = -1) == "1.234560e+02"
|
||||
|
||||
doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c"
|
||||
doAssert "${1}12 ${-1}$2" % ["a", "b"] == "a12 bb"
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ proc name*(t: typedesc): string {.magic: "TypeTrait".}
|
|||
##
|
||||
## proc `$`*(T: typedesc): string = name(T)
|
||||
##
|
||||
## template test(x): stmt =
|
||||
## template test(x): typed =
|
||||
## echo "type: ", type(x), ", value: ", x
|
||||
##
|
||||
## test 42
|
||||
|
|
|
|||
|
|
@ -308,11 +308,16 @@ proc `$`*(u: Uri): string =
|
|||
result.add(":")
|
||||
result.add(u.password)
|
||||
result.add("@")
|
||||
result.add(u.hostname)
|
||||
if u.hostname.endswith('/'):
|
||||
result.add(u.hostname[0..^2])
|
||||
else:
|
||||
result.add(u.hostname)
|
||||
if u.port.len > 0:
|
||||
result.add(":")
|
||||
result.add(u.port)
|
||||
if u.path.len > 0:
|
||||
if u.hostname.len > 0 and u.path[0] != '/':
|
||||
result.add('/')
|
||||
result.add(u.path)
|
||||
if u.query.len > 0:
|
||||
result.add("?")
|
||||
|
|
@ -483,6 +488,34 @@ when isMainModule:
|
|||
let foo = parseUri("http://localhost:9515") / "status"
|
||||
doAssert $foo == "http://localhost:9515/status"
|
||||
|
||||
# bug #6649 #6652
|
||||
block:
|
||||
var foo = parseUri("http://example.com")
|
||||
foo.hostname = "example.com"
|
||||
foo.path = "baz"
|
||||
doAssert $foo == "http://example.com/baz"
|
||||
|
||||
foo.hostname = "example.com/"
|
||||
foo.path = "baz"
|
||||
doAssert $foo == "http://example.com/baz"
|
||||
|
||||
foo.hostname = "example.com"
|
||||
foo.path = "/baz"
|
||||
doAssert $foo == "http://example.com/baz"
|
||||
|
||||
foo.hostname = "example.com/"
|
||||
foo.path = "/baz"
|
||||
doAssert $foo == "http://example.com/baz"
|
||||
|
||||
foo.hostname = "example.com/"
|
||||
foo.port = "8000"
|
||||
foo.path = "baz"
|
||||
doAssert $foo == "http://example.com:8000/baz"
|
||||
|
||||
foo = parseUri("file:/dir/file")
|
||||
foo.path = "relative"
|
||||
doAssert $foo == "file:relative"
|
||||
|
||||
# isAbsolute tests
|
||||
block:
|
||||
doAssert "www.google.com".parseUri().isAbsolute() == false
|
||||
|
|
@ -524,4 +557,4 @@ when isMainModule:
|
|||
doAssert "https://example.com/about/staff.html?".parseUri().isAbsolute == true
|
||||
doAssert "https://example.com/about/staff.html?parameters".parseUri().isAbsolute == true
|
||||
|
||||
echo("All good!")
|
||||
echo("All good!")
|
||||
|
|
|
|||
|
|
@ -456,6 +456,13 @@ type
|
|||
WriteIOEffect* = object of IOEffect ## Effect describing a write IO operation.
|
||||
ExecIOEffect* = object of IOEffect ## Effect describing an executing IO operation.
|
||||
|
||||
StackTraceEntry* = object ## In debug mode exceptions store the stack trace that led
|
||||
## to them. A StackTraceEntry is a single entry of the
|
||||
## stack trace.
|
||||
procname*: cstring ## name of the proc that is currently executing
|
||||
line*: int ## line number of the proc that is currently executing
|
||||
filename*: cstring ## filename of the proc that is currently executing
|
||||
|
||||
Exception* {.compilerproc.} = object of RootObj ## \
|
||||
## Base exception class.
|
||||
##
|
||||
|
|
@ -468,7 +475,10 @@ type
|
|||
msg* {.exportc: "message".}: string ## the exception's message. Not
|
||||
## providing an exception message
|
||||
## is bad style.
|
||||
trace: string
|
||||
when defined(js):
|
||||
trace: string
|
||||
else:
|
||||
trace: seq[StackTraceEntry]
|
||||
up: ref Exception # used for stacking exceptions. Not exported!
|
||||
|
||||
SystemError* = object of Exception ## \
|
||||
|
|
@ -895,7 +905,7 @@ proc `div` *(x, y: int8): int8 {.magic: "DivI", noSideEffect.}
|
|||
proc `div` *(x, y: int16): int16 {.magic: "DivI", noSideEffect.}
|
||||
proc `div` *(x, y: int32): int32 {.magic: "DivI", noSideEffect.}
|
||||
## computes the integer division. This is roughly the same as
|
||||
## ``floor(x/y)``.
|
||||
## ``trunc(x/y)``.
|
||||
##
|
||||
## .. code-block:: Nim
|
||||
## 1 div 2 == 0
|
||||
|
|
@ -1097,7 +1107,7 @@ proc `*`*[T: SomeUnsignedInt](x, y: T): T {.magic: "MulU", noSideEffect.}
|
|||
|
||||
proc `div`*[T: SomeUnsignedInt](x, y: T): T {.magic: "DivU", noSideEffect.}
|
||||
## computes the integer division. This is roughly the same as
|
||||
## ``floor(x/y)``.
|
||||
## ``trunc(x/y)``.
|
||||
##
|
||||
## .. code-block:: Nim
|
||||
## (7 div 5) == 1
|
||||
|
|
@ -2015,19 +2025,19 @@ when defined(nimNewRoof):
|
|||
yield res
|
||||
inc(res)
|
||||
|
||||
iterator `..`*(a, b: int64): int64 {.inline.} =
|
||||
## A special version of ``..`` for ``int64`` only.
|
||||
var res = a
|
||||
while res <= b:
|
||||
yield res
|
||||
inc(res)
|
||||
template dotdotImpl(t) {.dirty.} =
|
||||
iterator `..`*(a, b: t): t {.inline.} =
|
||||
## A type specialized version of ``..`` for convenience so that
|
||||
## mixing integer types work better.
|
||||
var res = a
|
||||
while res <= b:
|
||||
yield res
|
||||
inc(res)
|
||||
|
||||
iterator `..`*(a, b: int32): int32 {.inline.} =
|
||||
## A special version of ``..`` for ``int32`` only.
|
||||
var res = a
|
||||
while res <= b:
|
||||
yield res
|
||||
inc(res)
|
||||
dotdotImpl(int64)
|
||||
dotdotImpl(int32)
|
||||
dotdotImpl(uint64)
|
||||
dotdotImpl(uint32)
|
||||
|
||||
else:
|
||||
iterator countup*[S, T](a: S, b: T, step = 1): T {.inline.} =
|
||||
|
|
@ -2763,9 +2773,9 @@ when defined(nimvarargstyped):
|
|||
## pretends to be free of side effects, so that it can be used for debugging
|
||||
## routines marked as `noSideEffect <manual.html#pragmas-nosideeffect-pragma>`_.
|
||||
else:
|
||||
proc echo*(x: varargs[expr, `$`]) {.magic: "Echo", tags: [WriteIOEffect],
|
||||
proc echo*(x: varargs[untyped, `$`]) {.magic: "Echo", tags: [WriteIOEffect],
|
||||
benign, sideEffect.}
|
||||
proc debugEcho*(x: varargs[expr, `$`]) {.magic: "Echo", noSideEffect,
|
||||
proc debugEcho*(x: varargs[untyped, `$`]) {.magic: "Echo", noSideEffect,
|
||||
tags: [], raises: [].}
|
||||
|
||||
template newException*(exceptn: typedesc, message: string;
|
||||
|
|
@ -3706,7 +3716,7 @@ proc instantiationInfo*(index = -1, fullPaths = false): tuple[
|
|||
## .. code-block:: nim
|
||||
## import strutils
|
||||
##
|
||||
## template testException(exception, code: expr): stmt =
|
||||
## template testException(exception, code: untyped): typed =
|
||||
## try:
|
||||
## let pos = instantiationInfo()
|
||||
## discard(code)
|
||||
|
|
@ -3844,11 +3854,11 @@ type
|
|||
{.deprecated: [PNimrodNode: NimNode].}
|
||||
|
||||
when false:
|
||||
template eval*(blk: stmt): stmt =
|
||||
template eval*(blk: typed): typed =
|
||||
## executes a block of code at compile time just as if it was a macro
|
||||
## optionally, the block can return an AST tree that will replace the
|
||||
## eval expression
|
||||
macro payload: stmt {.gensym.} = blk
|
||||
macro payload: typed {.gensym.} = blk
|
||||
payload()
|
||||
|
||||
when hasAlloc:
|
||||
|
|
|
|||
|
|
@ -213,12 +213,20 @@ proc atomicDec*(memLoc: var int, x: int = 1): int =
|
|||
result = memLoc
|
||||
|
||||
when defined(vcc):
|
||||
proc interlockedCompareExchange64(p: pointer; exchange, comparand: int64): int64
|
||||
{.importc: "_InterlockedCompareExchange64", header: "<intrin.h>".}
|
||||
proc interlockedCompareExchange32(p: pointer; exchange, comparand: int32): int32
|
||||
{.importc: "_InterlockedCompareExchange", header: "<intrin.h>".}
|
||||
proc interlockedCompareExchange8(p: pointer; exchange, comparand: byte): byte
|
||||
{.importc: "_InterlockedCompareExchange64", header: "<intrin.h>".}
|
||||
when defined(cpp):
|
||||
proc interlockedCompareExchange64(p: pointer; exchange, comparand: int64): int64
|
||||
{.importcpp: "_InterlockedCompareExchange64(static_cast<NI64 volatile *>(#), #, #)", header: "<intrin.h>".}
|
||||
proc interlockedCompareExchange32(p: pointer; exchange, comparand: int32): int32
|
||||
{.importcpp: "_InterlockedCompareExchange(static_cast<NI volatile *>(#), #, #)", header: "<intrin.h>".}
|
||||
proc interlockedCompareExchange8(p: pointer; exchange, comparand: byte): byte
|
||||
{.importcpp: "_InterlockedCompareExchange8(static_cast<char volatile *>(#), #, #)", header: "<intrin.h>".}
|
||||
else:
|
||||
proc interlockedCompareExchange64(p: pointer; exchange, comparand: int64): int64
|
||||
{.importc: "_InterlockedCompareExchange64", header: "<intrin.h>".}
|
||||
proc interlockedCompareExchange32(p: pointer; exchange, comparand: int32): int32
|
||||
{.importc: "_InterlockedCompareExchange", header: "<intrin.h>".}
|
||||
proc interlockedCompareExchange8(p: pointer; exchange, comparand: byte): byte
|
||||
{.importc: "_InterlockedCompareExchange8", header: "<intrin.h>".}
|
||||
|
||||
proc cas*[T: bool|int|ptr](p: ptr T; oldValue, newValue: T): bool =
|
||||
when sizeof(T) == 8:
|
||||
|
|
|
|||
|
|
@ -370,7 +370,7 @@ proc commandPrompt() =
|
|||
if dbgUser.len == 0: dbgUser.len = oldLen
|
||||
# now look what we have to do:
|
||||
var i = scanWord(addr dbgUser.data, dbgTemp, 0)
|
||||
template `?`(x: expr): expr = dbgTemp == cstring(x)
|
||||
template `?`(x: untyped): untyped = dbgTemp == cstring(x)
|
||||
if ?"s" or ?"step":
|
||||
dbgState = dbStepInto
|
||||
again = false
|
||||
|
|
|
|||
|
|
@ -155,6 +155,52 @@ when not hasThreadSupport:
|
|||
var
|
||||
tempFrames: array[0..127, PFrame] # should not be alloc'd on stack
|
||||
|
||||
const
|
||||
reraisedFromBegin = -10
|
||||
reraisedFromEnd = -100
|
||||
|
||||
template reraisedFrom(z): untyped =
|
||||
StackTraceEntry(procname: nil, line: z, filename: nil)
|
||||
|
||||
proc auxWriteStackTrace(f: PFrame; s: var seq[StackTraceEntry]) =
|
||||
var
|
||||
it = f
|
||||
i = 0
|
||||
while it != nil:
|
||||
inc(i)
|
||||
it = it.prev
|
||||
var last = i-1
|
||||
if s.isNil:
|
||||
s = newSeq[StackTraceEntry](i)
|
||||
else:
|
||||
last = s.len + i - 1
|
||||
s.setLen(last+1)
|
||||
it = f
|
||||
while it != nil:
|
||||
s[last] = StackTraceEntry(procname: it.procname,
|
||||
line: it.line,
|
||||
filename: it.filename)
|
||||
it = it.prev
|
||||
dec last
|
||||
|
||||
template addFrameEntry(s, f: untyped) =
|
||||
var oldLen = s.len
|
||||
add(s, f.filename)
|
||||
if f.line > 0:
|
||||
add(s, '(')
|
||||
add(s, $f.line)
|
||||
add(s, ')')
|
||||
for k in 1..max(1, 25-(s.len-oldLen)): add(s, ' ')
|
||||
add(s, f.procname)
|
||||
add(s, "\n")
|
||||
|
||||
proc `$`(s: seq[StackTraceEntry]): string =
|
||||
result = newStringOfCap(2000)
|
||||
for i in 0 .. s.len-1:
|
||||
if s[i].line == reraisedFromBegin: result.add "[[reraised from:\n"
|
||||
elif s[i].line == reraisedFromEnd: result.add "]]\n"
|
||||
else: addFrameEntry(result, s[i])
|
||||
|
||||
proc auxWriteStackTrace(f: PFrame, s: var string) =
|
||||
when hasThreadSupport:
|
||||
var
|
||||
|
|
@ -194,17 +240,9 @@ proc auxWriteStackTrace(f: PFrame, s: var string) =
|
|||
if tempFrames[j] == nil:
|
||||
add(s, "(")
|
||||
add(s, $skipped)
|
||||
add(s, " calls omitted) ...")
|
||||
add(s, " calls omitted) ...\n")
|
||||
else:
|
||||
var oldLen = s.len
|
||||
add(s, tempFrames[j].filename)
|
||||
if tempFrames[j].line > 0:
|
||||
add(s, '(')
|
||||
add(s, $tempFrames[j].line)
|
||||
add(s, ')')
|
||||
for k in 1..max(1, 25-(s.len-oldLen)): add(s, ' ')
|
||||
add(s, tempFrames[j].procname)
|
||||
add(s, "\n")
|
||||
addFrameEntry(s, tempFrames[j])
|
||||
|
||||
proc stackTraceAvailable*(): bool
|
||||
|
||||
|
|
@ -221,6 +259,13 @@ when hasSomeStackTrace:
|
|||
auxWriteStackTraceWithBacktrace(s)
|
||||
else:
|
||||
add(s, "No stack traceback available\n")
|
||||
|
||||
proc rawWriteStackTrace(s: var seq[StackTraceEntry]) =
|
||||
when NimStackTrace:
|
||||
auxWriteStackTrace(framePtr, s)
|
||||
else:
|
||||
s = nil
|
||||
|
||||
proc stackTraceAvailable(): bool =
|
||||
when NimStackTrace:
|
||||
if framePtr == nil:
|
||||
|
|
@ -240,12 +285,6 @@ proc quitOrDebug() {.inline.} =
|
|||
else:
|
||||
endbStep() # call the debugger
|
||||
|
||||
when false:
|
||||
proc rawRaise*(e: ref Exception) =
|
||||
## undocumented. Do not use.
|
||||
pushCurrentException(e)
|
||||
c_longjmp(excHandler.context, 1)
|
||||
|
||||
var onUnhandledException*: (proc (errorMsg: string) {.
|
||||
nimcall.}) ## set this error \
|
||||
## handler to override the existing behaviour on an unhandled exception.
|
||||
|
|
@ -282,7 +321,7 @@ proc raiseExceptionAux(e: ref Exception) =
|
|||
when hasSomeStackTrace:
|
||||
var buf = newStringOfCap(2000)
|
||||
if isNil(e.trace): rawWriteStackTrace(buf)
|
||||
else: add(buf, e.trace)
|
||||
else: add(buf, $e.trace)
|
||||
add(buf, "Error: unhandled exception: ")
|
||||
if not isNil(e.msg): add(buf, e.msg)
|
||||
add(buf, " [")
|
||||
|
|
@ -318,12 +357,11 @@ proc raiseException(e: ref Exception, ename: cstring) {.compilerRtl.} =
|
|||
if e.name.isNil: e.name = ename
|
||||
when hasSomeStackTrace:
|
||||
if e.trace.isNil:
|
||||
e.trace = ""
|
||||
rawWriteStackTrace(e.trace)
|
||||
elif framePtr != nil:
|
||||
e.trace.add "[[reraised from:\n"
|
||||
e.trace.add reraisedFrom(reraisedFromBegin)
|
||||
auxWriteStackTrace(framePtr, e.trace)
|
||||
e.trace.add "]]\n"
|
||||
e.trace.add reraisedFrom(reraisedFromEnd)
|
||||
raiseExceptionAux(e)
|
||||
|
||||
proc reraiseException() {.compilerRtl.} =
|
||||
|
|
@ -349,10 +387,15 @@ proc getStackTrace(): string =
|
|||
|
||||
proc getStackTrace(e: ref Exception): string =
|
||||
if not isNil(e) and not isNil(e.trace):
|
||||
result = e.trace
|
||||
result = $e.trace
|
||||
else:
|
||||
result = ""
|
||||
|
||||
proc getStackTraceEntries*(e: ref Exception): seq[StackTraceEntry] =
|
||||
## Returns the attached stack trace to the exception ``e`` as
|
||||
## a ``seq``. This is not yet available for the JS backend.
|
||||
shallowCopy(result, e.trace)
|
||||
|
||||
when defined(nimRequiresNimFrame):
|
||||
proc stackOverflow() {.noinline.} =
|
||||
writeStackTrace()
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ proc internRefcount(p: pointer): int {.exportc: "getRefcount".} =
|
|||
when BitsPerPage mod (sizeof(int)*8) != 0:
|
||||
{.error: "(BitsPerPage mod BitsPerUnit) should be zero!".}
|
||||
|
||||
template color(c): expr = c.refCount and colorMask
|
||||
template color(c): untyped = c.refCount and colorMask
|
||||
template setColor(c, col) =
|
||||
c.refcount = c.refcount and not colorMask or col
|
||||
|
||||
|
|
|
|||
|
|
@ -153,12 +153,12 @@ proc preferSpawn*(): bool =
|
|||
## it is not necessary to call this directly; use 'spawnX' instead.
|
||||
result = gSomeReady.event
|
||||
|
||||
proc spawn*(call: stmt) {.magic: "Spawn".}
|
||||
proc spawn*(call: typed) {.magic: "Spawn".}
|
||||
## always spawns a new task, so that the 'call' is never executed on
|
||||
## the calling thread. 'call' has to be proc call 'p(...)' where 'p'
|
||||
## is gcsafe and has 'void' as the return type.
|
||||
|
||||
template spawnX*(call: stmt) =
|
||||
template spawnX*(call: typed) =
|
||||
## spawns a new task if a CPU core is ready, otherwise executes the
|
||||
## call in the calling thread. Usually it is advised to
|
||||
## use 'spawn' in order to not block the producer for an unknown
|
||||
|
|
|
|||
|
|
@ -322,37 +322,40 @@ proc nimIntToStr(x: int): string {.compilerRtl.} =
|
|||
result.add x
|
||||
|
||||
proc add*(result: var string; x: float) =
|
||||
var buf: array[0..64, char]
|
||||
when defined(nimNoArrayToCstringConversion):
|
||||
var n: int = c_sprintf(addr buf, "%.16g", x)
|
||||
when nimvm:
|
||||
result.add $x
|
||||
else:
|
||||
var n: int = c_sprintf(buf, "%.16g", x)
|
||||
var hasDot = false
|
||||
for i in 0..n-1:
|
||||
if buf[i] == ',':
|
||||
buf[i] = '.'
|
||||
hasDot = true
|
||||
elif buf[i] in {'a'..'z', 'A'..'Z', '.'}:
|
||||
hasDot = true
|
||||
if not hasDot:
|
||||
buf[n] = '.'
|
||||
buf[n+1] = '0'
|
||||
buf[n+2] = '\0'
|
||||
# On Windows nice numbers like '1.#INF', '-1.#INF' or '1.#NAN'
|
||||
# of '-1.#IND' are produced.
|
||||
# We want to get rid of these here:
|
||||
if buf[n-1] in {'n', 'N', 'D', 'd'}:
|
||||
result.add "nan"
|
||||
elif buf[n-1] == 'F':
|
||||
if buf[0] == '-':
|
||||
result.add "-inf"
|
||||
var buf: array[0..64, char]
|
||||
when defined(nimNoArrayToCstringConversion):
|
||||
var n: int = c_sprintf(addr buf, "%.16g", x)
|
||||
else:
|
||||
result.add "inf"
|
||||
else:
|
||||
var i = 0
|
||||
while buf[i] != '\0':
|
||||
result.add buf[i]
|
||||
inc i
|
||||
var n: int = c_sprintf(buf, "%.16g", x)
|
||||
var hasDot = false
|
||||
for i in 0..n-1:
|
||||
if buf[i] == ',':
|
||||
buf[i] = '.'
|
||||
hasDot = true
|
||||
elif buf[i] in {'a'..'z', 'A'..'Z', '.'}:
|
||||
hasDot = true
|
||||
if not hasDot:
|
||||
buf[n] = '.'
|
||||
buf[n+1] = '0'
|
||||
buf[n+2] = '\0'
|
||||
# On Windows nice numbers like '1.#INF', '-1.#INF' or '1.#NAN'
|
||||
# of '-1.#IND' are produced.
|
||||
# We want to get rid of these here:
|
||||
if buf[n-1] in {'n', 'N', 'D', 'd'}:
|
||||
result.add "nan"
|
||||
elif buf[n-1] == 'F':
|
||||
if buf[0] == '-':
|
||||
result.add "-inf"
|
||||
else:
|
||||
result.add "inf"
|
||||
else:
|
||||
var i = 0
|
||||
while buf[i] != '\0':
|
||||
result.add buf[i]
|
||||
inc i
|
||||
|
||||
proc nimFloatToStr(f: float): string {.compilerproc.} =
|
||||
result = newStringOfCap(8)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue