Merge remote-tracking branch 'origin/devel' into initallocator-fix

This commit is contained in:
Jacek Sieka
2016-08-25 22:59:51 +08:00
434 changed files with 22365 additions and 6316 deletions

View File

@@ -9,6 +9,7 @@
## This module contains Nim's support for locks and condition vars.
const insideRLocksModule = false
include "system/syslocks"
type
@@ -63,4 +64,4 @@ template withLock*(a: Lock, body: untyped) =
try:
body
finally:
a.release()
a.release()

View File

@@ -12,7 +12,7 @@ include "system/inclrtl"
## This module contains the interface to the compiler's abstract syntax
## tree (`AST`:idx:). Macros operate on this tree.
## .. include:: ../doc/astspec.txt
## .. include:: ../../doc/astspec.txt
type
NimNodeKind* = enum
@@ -197,6 +197,18 @@ proc typeKind*(n: NimNode): NimTypeKind {.magic: "NGetType", noSideEffect.}
## Returns the type kind of the node 'n' that should represent a type, that
## means the node should have been obtained via `getType`.
proc getTypeInst*(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.}
## Like getType except it includes generic parameters for a specific instance
proc getTypeInst*(n: typedesc): NimNode {.magic: "NGetType", noSideEffect.}
## Like getType except it includes generic parameters for a specific instance
proc getTypeImpl*(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.}
## Like getType except it includes generic parameters for the implementation
proc getTypeImpl*(n: typedesc): NimNode {.magic: "NGetType", noSideEffect.}
## Like getType except it includes generic parameters for the implementation
proc strVal*(n: NimNode): string {.magic: "NStrVal", noSideEffect.}
proc `intVal=`*(n: NimNode, val: BiggestInt) {.magic: "NSetIntVal", noSideEffect.}
@@ -496,7 +508,7 @@ proc lispRepr*(n: NimNode): string {.compileTime, benign.} =
add(result, ")")
macro dumpTree*(s: stmt): stmt {.immediate.} = echo s.treeRepr
macro dumpTree*(s: untyped): untyped = echo s.treeRepr
## Accepts a block of nim code and prints the parsed abstract syntax
## tree using the `toTree` function. Printing is done *at compile time*.
##
@@ -504,17 +516,17 @@ macro dumpTree*(s: stmt): stmt {.immediate.} = echo s.treeRepr
## tree and to discover what kind of nodes must be created to represent
## a certain expression/statement.
macro dumpLisp*(s: stmt): stmt {.immediate.} = echo s.lispRepr
macro dumpLisp*(s: untyped): untyped = echo s.lispRepr
## Accepts a block of nim code and prints the parsed abstract syntax
## tree using the `toLisp` function. Printing is done *at compile time*.
##
## See `dumpTree`.
macro dumpTreeImm*(s: stmt): stmt {.immediate, deprecated.} = echo s.treeRepr
## The ``immediate`` version of `dumpTree`.
macro dumpTreeImm*(s: untyped): untyped {.deprecated.} = echo s.treeRepr
## Deprecated.
macro dumpLispImm*(s: stmt): stmt {.immediate, deprecated.} = echo s.lispRepr
## The ``immediate`` version of `dumpLisp`.
macro dumpLispImm*(s: untyped): untyped {.deprecated.} = echo s.lispRepr
## Deprecated.
proc newEmptyNode*(): NimNode {.compileTime, noSideEffect.} =
@@ -680,8 +692,16 @@ proc `pragma=`*(someProc: NimNode; val: NimNode){.compileTime.}=
assert val.kind in {nnkEmpty, nnkPragma}
someProc[4] = val
proc addPragma*(someProc, pragma: NimNode) {.compileTime.} =
## Adds pragma to routine definition
someProc.expectRoutine
var pragmaNode = someProc.pragma
if pragmaNode.isNil or pragmaNode.kind == nnkEmpty:
pragmaNode = newNimNode(nnkPragma)
someProc.pragma = pragmaNode
pragmaNode.add(pragma)
template badNodeKind(k; f): stmt{.immediate.} =
template badNodeKind(k, f) =
assert false, "Invalid node kind " & $k & " for macros.`" & $f & "`"
proc body*(someProc: NimNode): NimNode {.compileTime.} =
@@ -738,20 +758,19 @@ iterator children*(n: NimNode): NimNode {.inline.} =
for i in 0 ..< n.len:
yield n[i]
template findChild*(n: NimNode; cond: expr): NimNode {.
immediate, dirty.} =
template findChild*(n: NimNode; cond: untyped): NimNode {.dirty.} =
## Find the first child node matching condition (or nil).
##
## .. code-block:: nim
## var res = findChild(n, it.kind == nnkPostfix and
## it.basename.ident == !"foo")
block:
var result: NimNode
var res: NimNode
for it in n.children:
if cond:
result = it
res = it
break
result
res
proc insert*(a: NimNode; pos: int; b: NimNode) {.compileTime.} =
## Insert node B into A at pos
@@ -796,17 +815,17 @@ proc infix*(a: NimNode; op: string;
proc unpackPostfix*(node: NimNode): tuple[node: NimNode; op: string] {.
compileTime.} =
node.expectKind nnkPostfix
result = (node[0], $node[1])
result = (node[1], $node[0])
proc unpackPrefix*(node: NimNode): tuple[node: NimNode; op: string] {.
compileTime.} =
node.expectKind nnkPrefix
result = (node[0], $node[1])
result = (node[1], $node[0])
proc unpackInfix*(node: NimNode): tuple[left: NimNode; op: string;
right: NimNode] {.compileTime.} =
assert node.kind == nnkInfix
result = (node[0], $node[1], node[2])
result = (node[1], $node[0], node[2])
proc copy*(node: NimNode): NimNode {.compileTime.} =
## An alias for copyNimTree().
@@ -818,6 +837,8 @@ proc cmpIgnoreStyle(a, b: cstring): int {.noSideEffect.} =
else: result = c
var i = 0
var j = 0
# first char is case sensitive
if a[0] != b[0]: return 1
while true:
while a[i] == '_': inc(i)
while b[j] == '_': inc(j) # BUGFIX: typo
@@ -828,9 +849,23 @@ proc cmpIgnoreStyle(a, b: cstring): int {.noSideEffect.} =
inc(i)
inc(j)
proc eqIdent* (a, b: string): bool = cmpIgnoreStyle(a, b) == 0
proc eqIdent*(a, b: string): bool = cmpIgnoreStyle(a, b) == 0
## Check if two idents are identical.
proc eqIdent*(node: NimNode; s: string): bool {.compileTime.} =
## Check if node is some identifier node (``nnkIdent``, ``nnkSym``, etc.)
## is the same as ``s``. Note that this is the preferred way to check! Most
## other ways like ``node.ident`` are much more error-prone, unfortunately.
case node.kind
of nnkIdent:
result = node.ident == !s
of nnkSym:
result = eqIdent($node.symbol, s)
of nnkOpenSymChoice, nnkClosedSymChoice:
result = eqIdent($node[0], s)
else:
result = false
proc hasArgOfName* (params: NimNode; name: string): bool {.compiletime.}=
## Search nnkFormalParams for an argument.
assert params.kind == nnkFormalParams
@@ -856,7 +891,7 @@ proc boolVal*(n: NimNode): bool {.compileTime, noSideEffect.} =
else: n == bindSym"true" # hacky solution for now
when not defined(booting):
template emit*(e: static[string]): stmt =
template emit*(e: static[string]): stmt {.deprecated.} =
## accepts a single string argument and treats it as nim code
## that should be inserted verbatim in the program
## Example:
@@ -864,6 +899,7 @@ when not defined(booting):
## .. code-block:: nim
## emit("echo " & '"' & "hello world".toUpper & '"')
##
## Deprecated since version 0.15 since it's so rarely useful.
macro payload: stmt {.gensym.} =
result = parseStmt(e)
payload()

View File

@@ -9,6 +9,7 @@
## This module contains Nim's support for reentrant locks.
const insideRLocksModule = true
include "system/syslocks"
type

View File

@@ -129,10 +129,10 @@ proc ftpClient*(address: string, port = Port(21),
result.csock = socket()
if result.csock == invalidSocket: raiseOSError(osLastError())
template blockingOperation(sock: Socket, body: stmt) {.immediate.} =
template blockingOperation(sock: Socket, body: untyped) =
body
template blockingOperation(sock: asyncio.AsyncSocket, body: stmt) {.immediate.} =
template blockingOperation(sock: asyncio.AsyncSocket, body: untyped) =
sock.setBlocking(true)
body
sock.setBlocking(false)

View File

@@ -711,8 +711,13 @@ proc getHostByAddr*(ip: string): Hostent {.tags: [ReadIOEffect].} =
cint(sockets.AF_INET))
if s == nil: raiseOSError(osLastError())
else:
var s = posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).Socklen,
cint(posix.AF_INET))
var s =
when defined(android4):
posix.gethostbyaddr(cast[cstring](addr(myaddr)), sizeof(myaddr).cint,
cint(posix.AF_INET))
else:
posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).Socklen,
cint(posix.AF_INET))
if s == nil:
raiseOSError(osLastError(), $hstrerror(h_errno))

View File

@@ -210,7 +210,7 @@ proc dbFormat(formatstr: SqlQuery, args: varargs[string]): string {.
add(result, c)
proc prepareFetch(db: var DbConn, query: SqlQuery,
args: varargs[string, `$`]) : TSqlSmallInt {.
args: varargs[string, `$`]): TSqlSmallInt {.
tags: [ReadDbEffect, WriteDbEffect], raises: [DbError].} =
# Prepare a statement, execute it and fetch the data to the driver
# ready for retrieval of the data
@@ -222,9 +222,8 @@ proc prepareFetch(db: var DbConn, query: SqlQuery,
var q = dbFormat(query, args)
db.sqlCheck(SQLPrepare(db.stmt, q.PSQLCHAR, q.len.TSqlSmallInt))
db.sqlCheck(SQLExecute(db.stmt))
var retcode = SQLFetch(db.stmt)
db.sqlCheck(retcode)
result=retcode
result = SQLFetch(db.stmt)
db.sqlCheck(result)
proc prepareFetchDirect(db: var DbConn, query: SqlQuery,
args: varargs[string, `$`]) {.
@@ -250,8 +249,8 @@ proc tryExec*(db: var DbConn, query: SqlQuery, args: varargs[string, `$`]): bool
var
rCnt = -1
res = SQLRowCount(db.stmt, rCnt)
if res != SQL_SUCCESS: dbError(db)
properFreeResult(SQL_HANDLE_STMT, db.stmt)
if res != SQL_SUCCESS: dbError(db)
except: discard
return res == SQL_SUCCESS
@@ -286,10 +285,10 @@ iterator fastRows*(db: var DbConn, query: SqlQuery,
sz: TSqlSmallInt = 0
cCnt: TSqlSmallInt = 0.TSqlSmallInt
res: TSqlSmallInt = 0.TSqlSmallInt
tempcCnt:TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled.
# tempcCnt,A field to store the number of temporary variables, for unknown reasons,
tempcCnt: TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled.
# tempcCnt,A field to store the number of temporary variables, for unknown reasons,
# after performing a sqlgetdata function and circulating variables cCnt value will be changed to 0,
# so the values of the temporary variable to store the cCnt.
# so the values of the temporary variable to store the cCnt.
# After every cycle and specified to cCnt. To ensure the traversal of all fields.
res = db.prepareFetch(query, args)
if res == SQL_NO_DATA:
@@ -308,8 +307,8 @@ iterator fastRows*(db: var DbConn, query: SqlQuery,
cCnt = tempcCnt
yield rowRes
res = SQLFetch(db.stmt)
db.sqlCheck(res)
properFreeResult(SQL_HANDLE_STMT, db.stmt)
db.sqlCheck(res)
iterator instantRows*(db: var DbConn, query: SqlQuery,
args: varargs[string, `$`]): InstantRow
@@ -317,14 +316,14 @@ iterator instantRows*(db: var DbConn, query: SqlQuery,
## Same as fastRows but returns a handle that can be used to get column text
## on demand using []. Returned handle is valid only within the interator body.
var
rowRes: Row
rowRes: Row = @[]
sz: TSqlSmallInt = 0
cCnt: TSqlSmallInt = 0.TSqlSmallInt
res: TSqlSmallInt = 0.TSqlSmallInt
tempcCnt:TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled.
# tempcCnt,A field to store the number of temporary variables, for unknown reasons,
tempcCnt: TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled.
# tempcCnt,A field to store the number of temporary variables, for unknown reasons,
# after performing a sqlgetdata function and circulating variables cCnt value will be changed to 0,
# so the values of the temporary variable to store the cCnt.
# so the values of the temporary variable to store the cCnt.
# After every cycle and specified to cCnt. To ensure the traversal of all fields.
res = db.prepareFetch(query, args)
if res == SQL_NO_DATA:
@@ -343,8 +342,8 @@ iterator instantRows*(db: var DbConn, query: SqlQuery,
cCnt = tempcCnt
yield (row: rowRes, len: cCnt.int)
res = SQLFetch(db.stmt)
db.sqlCheck(res)
properFreeResult(SQL_HANDLE_STMT, db.stmt)
db.sqlCheck(res)
proc `[]`*(row: InstantRow, col: int): string {.inline.} =
## Returns text for given column of the row
@@ -364,10 +363,10 @@ proc getRow*(db: var DbConn, query: SqlQuery,
sz: TSqlSmallInt = 0.TSqlSmallInt
cCnt: TSqlSmallInt = 0.TSqlSmallInt
res: TSqlSmallInt = 0.TSqlSmallInt
tempcCnt:TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled.
## tempcCnt,A field to store the number of temporary variables, for unknown reasons,
tempcCnt: TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled.
## tempcCnt,A field to store the number of temporary variables, for unknown reasons,
## after performing a sqlgetdata function and circulating variables cCnt value will be changed to 0,
## so the values of the temporary variable to store the cCnt.
## so the values of the temporary variable to store the cCnt.
## After every cycle and specified to cCnt. To ensure the traversal of all fields.
res = db.prepareFetch(query, args)
if res == SQL_NO_DATA:
@@ -385,8 +384,8 @@ proc getRow*(db: var DbConn, query: SqlQuery,
cCnt = tempcCnt
res = SQLFetch(db.stmt)
result = rowRes
db.sqlCheck(res)
properFreeResult(SQL_HANDLE_STMT, db.stmt)
db.sqlCheck(res)
proc getAllRows*(db: var DbConn, query: SqlQuery,
args: varargs[string, `$`]): seq[Row] {.
@@ -398,10 +397,10 @@ proc getAllRows*(db: var DbConn, query: SqlQuery,
sz: TSqlSmallInt = 0
cCnt: TSqlSmallInt = 0.TSqlSmallInt
res: TSqlSmallInt = 0.TSqlSmallInt
tempcCnt:TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled.
## tempcCnt,A field to store the number of temporary variables, for unknown reasons,
tempcCnt: TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled.
## tempcCnt,A field to store the number of temporary variables, for unknown reasons,
## after performing a sqlgetdata function and circulating variables cCnt value will be changed to 0,
## so the values of the temporary variable to store the cCnt.
## so the values of the temporary variable to store the cCnt.
## After every cycle and specified to cCnt. To ensure the traversal of all fields.
res = db.prepareFetch(query, args)
if res == SQL_NO_DATA:
@@ -421,8 +420,8 @@ proc getAllRows*(db: var DbConn, query: SqlQuery,
rows.add(rowRes)
res = SQLFetch(db.stmt)
result = rows
db.sqlCheck(res)
properFreeResult(SQL_HANDLE_STMT, db.stmt)
db.sqlCheck(res)
iterator rows*(db: var DbConn, query: SqlQuery,
args: varargs[string, `$`]): Row {.
@@ -544,4 +543,4 @@ proc setEncoding*(connection: DbConn, encoding: string): bool {.
## Sets the encoding of a database connection, returns true for
## success, false for failure.
##result = set_character_set(connection, encoding) == 0
dbError("setEncoding() is currently not implemented by the db_odbc module")
dbError("setEncoding() is currently not implemented by the db_odbc module")

View File

@@ -15,6 +15,8 @@ from math import ceil
import options
from unicode import runeLenAt
export options
## What is NRE?
## ============
@@ -24,46 +26,34 @@ from unicode import runeLenAt
## Licencing
## ---------
##
## PCRE has some additional terms that you must comply with if you use this module.::
## PCRE has `some additional terms`_ that you must agree to in order to use
## this module.
##
## > Copyright (c) 1997-2001 University of Cambridge
## >
## > Permission is granted to anyone to use this software for any purpose on any
## > computer system, and to redistribute it freely, subject to the following
## > restrictions:
## >
## > 1. This software is distributed in the hope that it will be useful,
## > but WITHOUT ANY WARRANTY; without even the implied warranty of
## > MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
## >
## > 2. The origin of this software must not be misrepresented, either by
## > explicit claim or by omission. In practice, this means that if you use
## > PCRE in software that you distribute to others, commercially or
## > otherwise, you must put a sentence like this
## >
## > Regular expression support is provided by the PCRE library package,
## > which is open source software, written by Philip Hazel, and copyright
## > by the University of Cambridge, England.
## >
## > somewhere reasonably visible in your documentation and in any relevant
## > files or online help data or similar. A reference to the ftp site for
## > the source, that is, to
## >
## > ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/
## >
## > should also be given in the documentation. However, this condition is not
## > intended to apply to whole chains of software. If package A includes PCRE,
## > it must acknowledge it, but if package B is software that includes package
## > A, the condition is not imposed on package B (unless it uses PCRE
## > independently).
## >
## > 3. Altered versions must be plainly marked as such, and must not be
## > misrepresented as being the original software.
## >
## > 4. If PCRE is embedded in any software that is released under the GNU
## > General Purpose Licence (GPL), or Lesser General Purpose Licence (LGPL),
## > then the terms of that licence shall supersede any condition above with
## > which it is incompatible.
## .. _`some additional terms`: http://pcre.sourceforge.net/license.txt
##
## Example
## -------
##
## .. code-block:: nim
##
## import nre
##
## let vowels = re"[aeoui]"
##
## for match in "moigagoo".findIter(vowels):
## echo match.matchBounds
## # (a: 1, b: 1)
## # (a: 2, b: 2)
## # (a: 4, b: 4)
## # (a: 6, b: 6)
## # (a: 7, b: 7)
##
## let firstVowel = "foo".find(vowels)
## let hasVowel = firstVowel.isSome()
## if hasVowel:
## let matchBounds = firstVowel.get().captureBounds[-1]
## echo "first vowel @", matchBounds.get().a
## # first vowel @1
# Type definitions {{{
@@ -125,11 +115,11 @@ type
## - ``(*NO_STUDY)`` - turn off studying; study is enabled by default
##
## For more details on the leading option groups, see the `Option
## Setting <http://man7.org/linux/man-pages/man3/pcresyntax.3.html#OPTION_SETTING>`__
## Setting <http://man7.org/linux/man-pages/man3/pcresyntax.3.html#OPTION_SETTING>`_
## and the `Newline
## Convention <http://man7.org/linux/man-pages/man3/pcresyntax.3.html#NEWLINE_CONVENTION>`__
## Convention <http://man7.org/linux/man-pages/man3/pcresyntax.3.html#NEWLINE_CONVENTION>`_
## sections of the `PCRE syntax
## manual <http://man7.org/linux/man-pages/man3/pcresyntax.3.html>`__.
## manual <http://man7.org/linux/man-pages/man3/pcresyntax.3.html>`_.
pattern*: string ## not nil
pcreObj: ptr pcre.Pcre ## not nil
pcreExtra: ptr pcre.ExtraData ## nil
@@ -284,7 +274,7 @@ proc `[]`*(pattern: Captures, name: string): string =
let pattern = RegexMatch(pattern)
return pattern.captures[pattern.pattern.captureNameToId.fget(name)]
template toTableImpl(cond: bool): stmt {.immediate, dirty.} =
template toTableImpl(cond: untyped) {.dirty.} =
for key in RegexMatch(pattern).pattern.captureNameId.keys:
let nextVal = pattern[key]
if cond:
@@ -301,7 +291,7 @@ proc toTable*(pattern: CaptureBounds, default = none(Slice[int])):
result = initTable[string, Option[Slice[int]]]()
toTableImpl(nextVal.isNone)
template itemsImpl(cond: bool): stmt {.immediate, dirty.} =
template itemsImpl(cond: untyped) {.dirty.} =
for i in 0 .. <RegexMatch(pattern).pattern.captureCount:
let nextVal = pattern[i]
# done in this roundabout way to avoid multiple yields (potential code
@@ -493,17 +483,17 @@ proc matchImpl(str: string, pattern: Regex, start, endpos: int, flags: int): Opt
raise RegexInternalError(msg : "Unknown internal error: " & $execRet)
proc match*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[RegexMatch] =
## Like ```find(...)`` <#proc-find>`__, but anchored to the start of the
## Like ```find(...)`` <#proc-find>`_, but anchored to the start of the
## string. This means that ``"foo".match(re"f") == true``, but
## ``"foo".match(re"o") == false``.
return str.matchImpl(pattern, start, endpos, pcre.ANCHORED)
iterator findIter*(str: string, pattern: Regex, start = 0, endpos = int.high): RegexMatch =
## Works the same as ```find(...)`` <#proc-find>`__, but finds every
## Works the same as ```find(...)`` <#proc-find>`_, but finds every
## non-overlapping match. ``"2222".find(re"22")`` is ``"22", "22"``, not
## ``"22", "22", "22"``.
##
## Arguments are the same as ```find(...)`` <#proc-find>`__
## Arguments are the same as ```find(...)`` <#proc-find>`_
##
## Variants:
##
@@ -566,6 +556,16 @@ proc findAll*(str: string, pattern: Regex, start = 0, endpos = int.high): seq[st
for match in str.findIter(pattern, start, endpos):
result.add(match.match)
proc contains*(str: string, pattern: Regex, start = 0, endpos = int.high): bool =
## Determine if the string contains the given pattern between the end and
## start positions:
## - "abc".contains(re"bc") == true
## - "abc".contains(re"cd") == false
## - "abc".contains(re"a", start = 1) == false
##
## Same as ``isSome(str.find(pattern, start, endpos))``.
return isSome(str.find(pattern, start, endpos))
proc split*(str: string, pattern: Regex, maxSplit = -1, start = 0): seq[string] =
## Splits the string with the given regex. This works according to the
## rules that Perl and Javascript use:
@@ -581,7 +581,7 @@ proc split*(str: string, pattern: Regex, maxSplit = -1, start = 0): seq[string]
## strings in the output seq.
## ``"1.2.3".split(re"\.", maxsplit = 2) == @["1", "2.3"]``
##
## ``start`` behaves the same as in ```find(...)`` <#proc-find>`__.
## ``start`` behaves the same as in ```find(...)`` <#proc-find>`_.
result = @[]
var lastIdx = start
var splits = 0
@@ -625,7 +625,7 @@ proc split*(str: string, pattern: Regex, maxSplit = -1, start = 0): seq[string]
result.add(str.substr(bounds.b + 1, str.high))
template replaceImpl(str: string, pattern: Regex,
replacement: expr): stmt {.immediate, dirty.} =
replacement: untyped) {.dirty.} =
# XXX seems very similar to split, maybe I can reduce code duplication
# somehow?
result = ""

View File

@@ -105,7 +105,8 @@ else:
proc readLineFromStdin*(prompt: string): TaintedString {.
tags: [ReadIOEffect, WriteIOEffect].} =
var buffer = linenoise.readLine(prompt)
if isNil(buffer): quit(0)
if isNil(buffer):
raise newException(IOError, "Linenoise returned nil")
result = TaintedString($buffer)
if result.string.len > 0:
historyAdd(buffer)
@@ -114,12 +115,12 @@ else:
proc readLineFromStdin*(prompt: string, line: var TaintedString): bool {.
tags: [ReadIOEffect, WriteIOEffect].} =
var buffer = linenoise.readLine(prompt)
if isNil(buffer): quit(0)
if isNil(buffer):
raise newException(IOError, "Linenoise returned nil")
line = TaintedString($buffer)
if line.string.len > 0:
historyAdd(buffer)
linenoise.free(buffer)
# XXX how to determine CTRL+D?
result = true
proc readPasswordFromStdin*(prompt: string, password: var TaintedString):

View File

@@ -7,8 +7,11 @@
# distribution, for details about the copyright.
#
## Regular expression support for Nim. Deprecated. Consider using the ``nre``
## or ``pegs`` modules instead.
## Regular expression support for Nim. This module still has some
## obscure bugs and limitations,
## consider using the ``nre`` or ``pegs`` modules instead.
## We had to de-deprecate this module since too much code relies on it
## and many people prefer its API over ``nre``'s.
##
## **Note:** The 're' proc defaults to the **extended regular expression
## syntax** which lets you use whitespace freely to make your regexes readable.
@@ -22,14 +25,12 @@
## though.
## PRCE's licence follows:
##
## .. include:: ../doc/regexprs.txt
## .. include:: ../../doc/regexprs.txt
##
import
pcre, strutils, rtarrays
{.deprecated.}
const
MaxSubpatterns* = 20
## defines the maximum number of subpatterns that can be captured.
@@ -78,7 +79,7 @@ proc finalizeRegEx(x: Regex) =
if not isNil(x.e):
pcre.free_substring(cast[cstring](x.e))
proc re*(s: string, flags = {reExtended, reStudy}): Regex {.deprecated.} =
proc re*(s: string, flags = {reExtended, reStudy}): Regex =
## Constructor of regular expressions. Note that Nim's
## extended raw string literals support this syntax ``re"[abc]"`` as
## a short form for ``re(r"[abc]")``.

View File

@@ -9,6 +9,9 @@
## This module provides an easy to use sockets-style
## nim interface to the OpenSSL library.
##
## **Warning:** This module is deprecated, use the SSL procedures defined in
## the ``net`` module instead.
{.deprecated.}

View File

@@ -402,7 +402,9 @@ proc routeEvent*(w: Window, event: Event)
proc scrollBy*(w: Window, x, y: int)
proc scrollTo*(w: Window, x, y: int)
proc setInterval*(w: Window, code: cstring, pause: int): ref TInterval
proc setInterval*(w: Window, function: proc (), pause: int): ref TInterval
proc setTimeout*(w: Window, code: cstring, pause: int): ref TTimeOut
proc setTimeout*(w: Window, function: proc (), pause: int): ref TInterval
proc stop*(w: Window)
# Node "methods"
@@ -481,6 +483,9 @@ proc getAttribute*(s: Style, attr: cstring, caseSensitive=false): cstring
proc removeAttribute*(s: Style, attr: cstring, caseSensitive=false)
proc setAttribute*(s: Style, attr, value: cstring, caseSensitive=false)
# Event "methods"
proc preventDefault*(ev: Event)
{.pop.}
var

View File

@@ -44,7 +44,7 @@ __clang__
#if defined(_MSC_VER)
# pragma warning(disable: 4005 4100 4101 4189 4191 4200 4244 4293 4296 4309)
# pragma warning(disable: 4310 4365 4456 4477 4514 4574 4611 4668 4702 4706)
# pragma warning(disable: 4710 4711 4774 4800 4820 4996)
# pragma warning(disable: 4710 4711 4774 4800 4820 4996 4090)
#endif
/* ------------------------------------------------------------------------- */
@@ -102,7 +102,7 @@ __clang__
defined __ICL || \
defined __DMC__ || \
defined __BORLANDC__ )
# define NIM_THREADVAR __declspec(thread)
# define NIM_THREADVAR __declspec(thread)
/* note that ICC (linux) and Clang are covered by __GNUC__ */
#elif defined __GNUC__ || \
defined __SUNPRO_C || \
@@ -222,6 +222,8 @@ __clang__
/* ----------------------------------------------------------------------- */
#define COMMA ,
#include <limits.h>
#include <stddef.h>
@@ -345,9 +347,6 @@ static N_INLINE(NI32, float32ToInt32)(float x) {
#define float64ToInt64(x) ((NI64) (x))
#define zeroMem(a, size) memset(a, 0, size)
#define equalMem(a, b, size) (memcmp(a, b, size) == 0)
#define STRING_LITERAL(name, str, length) \
static const struct { \
TGenericSeq Sup; \
@@ -450,8 +449,8 @@ static inline void GCGuard (void *ptr) { asm volatile ("" :: "X" (ptr)); }
/* Test to see if Nim and the C compiler agree on the size of a pointer.
On disagreement, your C compiler will say something like:
"error: 'assert_numbits' declared as an array with a negative size" */
typedef int assert_numbits[sizeof(NI) == sizeof(void*) && NIM_INTBITS == sizeof(NI)*8 ? 1 : -1];
"error: 'Nim_and_C_compiler_disagree_on_target_architecture' declared as an array with a negative size" */
typedef int Nim_and_C_compiler_disagree_on_target_architecture[sizeof(NI) == sizeof(void*) && NIM_INTBITS == sizeof(NI)*8 ? 1 : -1];
#endif
#ifdef __cplusplus

View File

@@ -31,13 +31,14 @@ type
state: TokenClass
SourceLanguage* = enum
langNone, langNim, langNimrod, langCpp, langCsharp, langC, langJava
langNone, langNim, langNimrod, langCpp, langCsharp, langC, langJava,
langYaml
{.deprecated: [TSourceLanguage: SourceLanguage, TTokenClass: TokenClass,
TGeneralTokenizer: GeneralTokenizer].}
const
sourceLanguageToStr*: array[SourceLanguage, string] = ["none",
"Nim", "Nimrod", "C++", "C#", "C", "Java"]
"Nim", "Nimrod", "C++", "C#", "C", "Java", "Yaml"]
tokenClassToStr*: array[TokenClass, string] = ["Eof", "None", "Whitespace",
"DecNumber", "BinNumber", "HexNumber", "OctNumber", "FloatNumber",
"Identifier", "Keyword", "StringLit", "LongStringLit", "CharLit",
@@ -578,6 +579,309 @@ proc javaNextToken(g: var GeneralTokenizer) =
"try", "void", "volatile", "while"]
clikeNextToken(g, keywords, {})
proc yamlPlainStrLit(g: var GeneralTokenizer, pos: var int) =
g.kind = gtStringLit
while g.buf[pos] notin {'\0', '\x09'..'\x0D', ',', ']', '}'}:
if g.buf[pos] == ':' and
g.buf[pos + 1] in {'\0', '\x09'..'\x0D', ' '}:
break
inc(pos)
proc yamlPossibleNumber(g: var GeneralTokenizer, pos: var int) =
g.kind = gtNone
if g.buf[pos] == '-': inc(pos)
if g.buf[pos] == '0': inc(pos)
elif g.buf[pos] in '1'..'9':
inc(pos)
while g.buf[pos] in {'0'..'9'}: inc(pos)
else: yamlPlainStrLit(g, pos)
if g.kind == gtNone:
if g.buf[pos] in {'\0', '\x09'..'\x0D', ' ', ',', ']', '}'}:
g.kind = gtDecNumber
elif g.buf[pos] == '.':
inc(pos)
if g.buf[pos] notin {'0'..'9'}: yamlPlainStrLit(g, pos)
else:
while g.buf[pos] in {'0'..'9'}: inc(pos)
if g.buf[pos] in {'\0', '\x09'..'\x0D', ' ', ',', ']', '}'}:
g.kind = gtFloatNumber
if g.kind == gtNone:
if g.buf[pos] in {'e', 'E'}:
inc(pos)
if g.buf[pos] in {'-', '+'}: inc(pos)
if g.buf[pos] notin {'0'..'9'}: yamlPlainStrLit(g, pos)
else:
while g.buf[pos] in {'0'..'9'}: inc(pos)
if g.buf[pos] in {'\0', '\x09'..'\x0D', ' ', ',', ']', '}'}:
g.kind = gtFloatNumber
else: yamlPlainStrLit(g, pos)
else: yamlPlainStrLit(g, pos)
while g.buf[pos] notin {'\0', ',', ']', '}', '\x0A', '\x0D'}:
inc(pos)
if g.buf[pos] notin {'\x09'..'\x0D', ' ', ',', ']', '}'}:
yamlPlainStrLit(g, pos)
break
# theoretically, we would need to parse indentation (like with block scalars)
# because of possible multiline flow scalars that start with number-like
# content, but that is far too troublesome. I think it is fine that the
# highlighter is sloppy here.
proc yamlNextToken(g: var GeneralTokenizer) =
const
hexChars = {'0'..'9', 'A'..'F', 'a'..'f'}
var pos = g.pos
g.start = g.pos
if g.state == gtStringLit:
g.kind = gtStringLit
while true:
case g.buf[pos]
of '\\':
if pos != g.pos: break
g.kind = gtEscapeSequence
inc(pos)
case g.buf[pos]
of 'x':
inc(pos)
for i in 1..2:
{.unroll.}
if g.buf[pos] in hexChars: inc(pos)
break
of 'u':
inc(pos)
for i in 1..4:
{.unroll.}
if g.buf[pos] in hexChars: inc(pos)
break
of 'U':
inc(pos)
for i in 1..8:
{.unroll.}
if g.buf[pos] in hexChars: inc(pos)
break
else: inc(pos)
break
of '\0':
g.state = gtOther
break
of '\"':
inc(pos)
g.state = gtOther
break
else: inc(pos)
elif g.state == gtCharLit:
# abusing gtCharLit as single-quoted string lit
g.kind = gtStringLit
inc(pos) # skip the starting '
while true:
case g.buf[pos]
of '\'':
inc(pos)
if g.buf[pos] == '\'':
inc(pos)
g.kind = gtEscapeSequence
else: g.state = gtOther
break
else: inc(pos)
elif g.state == gtCommand:
# gtCommand means 'block scalar header'
case g.buf[pos]
of ' ', '\t':
g.kind = gtWhitespace
while g.buf[pos] in {' ', '\t'}: inc(pos)
of '#':
g.kind = gtComment
while g.buf[pos] notin {'\0', '\x0A', '\x0D'}: inc(pos)
of '\x0A', '\x0D': discard
else:
# illegal here. just don't parse a block scalar
g.kind = gtNone
g.state = gtOther
if g.buf[pos] in {'\x0A', '\x0D'} and g.state == gtCommand:
g.state = gtLongStringLit
elif g.state == gtLongStringLit:
# beware, this is the only token where we actually have to parse
# indentation.
g.kind = gtLongStringLit
# first, we have to find the parent indentation of the block scalar, so that
# we know when to stop
assert g.buf[pos] in {'\x0A', '\x0D'}
var lookbehind = pos - 1
var headerStart = -1
while lookbehind >= 0 and g.buf[lookbehind] notin {'\x0A', '\x0D'}:
if headerStart == -1 and g.buf[lookbehind] in {'|', '>'}:
headerStart = lookbehind
dec(lookbehind)
assert headerStart != -1
var indentation = 1
while g.buf[lookbehind + indentation] == ' ': inc(indentation)
if g.buf[lookbehind + indentation] in {'|', '>'}:
# when the header is alone in a line, this line does not show the parent's
# indentation, so we must go further. search the first previous line with
# non-whitespace content.
while lookbehind >= 0 and g.buf[lookbehind] in {'\x0A', '\x0D'}:
dec(lookbehind)
while lookbehind >= 0 and
g.buf[lookbehind] in {' ', '\t'}: dec(lookbehind)
# now, find the beginning of the line...
while lookbehind >= 0 and g.buf[lookbehind] notin {'\x0A', '\x0D'}:
dec(lookbehind)
# ... and its indentation
indentation = 1
while g.buf[lookbehind + indentation] == ' ': inc(indentation)
if lookbehind == -1: indentation = 0 # top level
elif g.buf[lookbehind + 1] == '-' and g.buf[lookbehind + 2] == '-' and
g.buf[lookbehind + 3] == '-' and
g.buf[lookbehind + 4] in {'\x09'..'\x0D', ' '}:
# this is a document start, therefore, we are at top level
indentation = 0
# because lookbehind was at newline char when calculating indentation, we're
# off by one. fix that. top level's parent will have indentation of -1.
let parentIndentation = indentation - 1
# find first content
while g.buf[pos] in {' ', '\x0A', '\x0D'}:
if g.buf[pos] == ' ': inc(indentation)
else: indentation = 0
inc(pos)
var minIndentation = indentation
# for stupid edge cases, we must check whether an explicit indentation depth
# is given at the header.
while g.buf[headerStart] in {'>', '|', '+', '-'}: inc(headerStart)
if g.buf[headerStart] in {'0'..'9'}:
minIndentation = min(minIndentation, ord(g.buf[headerStart]) - ord('0'))
# process content lines
while indentation > parentIndentation and g.buf[pos] != '\0':
if (indentation < minIndentation and g.buf[pos] == '#') or
(indentation == 0 and g.buf[pos] == '.' and g.buf[pos + 1] == '.' and
g.buf[pos + 2] == '.' and
g.buf[pos + 3] in {'\0', '\x09'..'\x0D', ' '}):
# comment after end of block scalar, or end of document
break
minIndentation = min(indentation, minIndentation)
while g.buf[pos] notin {'\0', '\x0A', '\x0D'}: inc(pos)
while g.buf[pos] in {' ', '\x0A', '\x0D'}:
if g.buf[pos] == ' ': inc(indentation)
else: indentation = 0
inc(pos)
g.state = gtOther
elif g.state == gtOther:
# gtOther means 'inside YAML document'
case g.buf[pos]
of ' ', '\x09'..'\x0D':
g.kind = gtWhitespace
while g.buf[pos] in {' ', '\x09'..'\x0D'}: inc(pos)
of '#':
g.kind = gtComment
inc(pos)
while g.buf[pos] notin {'\0', '\x0A', '\x0D'}: inc(pos)
of '-':
inc(pos)
if g.buf[pos] in {'\0', ' ', '\x09'..'\x0D'}:
g.kind = gtPunctuation
elif g.buf[pos] == '-' and
(pos == 1 or g.buf[pos - 2] in {'\x0A', '\x0D'}): # start of line
inc(pos)
if g.buf[pos] == '-' and g.buf[pos + 1] in {'\0', '\x09'..'\x0D', ' '}:
inc(pos)
g.kind = gtKeyword
else: yamlPossibleNumber(g, pos)
else: yamlPossibleNumber(g, pos)
of '.':
if pos == 0 or g.buf[pos - 1] in {'\x0A', '\x0D'}:
inc(pos)
for i in 1..2:
{.unroll.}
if g.buf[pos] != '.': break
inc(pos)
if pos == g.start + 3:
g.kind = gtKeyword
g.state = gtNone
else: yamlPlainStrLit(g, pos)
else: yamlPlainStrLit(g, pos)
of '?':
inc(pos)
if g.buf[pos] in {'\0', ' ', '\x09'..'\x0D'}:
g.kind = gtPunctuation
else: yamlPlainStrLit(g, pos)
of ':':
inc(pos)
if g.buf[pos] in {'\0', '\x09'..'\x0D', ' ', '\'', '\"'} or
(pos > 0 and g.buf[pos - 2] in {'}', ']', '\"', '\''}):
g.kind = gtPunctuation
else: yamlPlainStrLit(g, pos)
of '[', ']', '{', '}', ',':
inc(pos)
g.kind = gtPunctuation
of '\"':
inc(pos)
g.state = gtStringLit
g.kind = gtStringLit
of '\'':
g.state = gtCharLit
g.kind = gtNone
of '!':
g.kind = gtTagStart
inc(pos)
if g.buf[pos] == '<':
# literal tag (e.g. `!<tag:yaml.org,2002:str>`)
while g.buf[pos] notin {'\0', '>', '\x09'..'\x0D', ' '}: inc(pos)
if g.buf[pos] == '>': inc(pos)
else:
while g.buf[pos] in {'A'..'Z', 'a'..'z', '0'..'9', '-'}: inc(pos)
case g.buf[pos]
of '!':
# prefixed tag (e.g. `!!str`)
inc(pos)
while g.buf[pos] notin
{'\0', '\x09'..'\x0D', ' ', ',', '[', ']', '{', '}'}: inc(pos)
of '\0', '\x09'..'\x0D', ' ': discard
else:
# local tag (e.g. `!nim:system:int`)
while g.buf[pos] notin {'\0', '\x09'..'\x0D', ' '}: inc(pos)
of '&':
g.kind = gtLabel
while g.buf[pos] notin {'\0', '\x09'..'\x0D', ' '}: inc(pos)
of '*':
g.kind = gtReference
while g.buf[pos] notin {'\0', '\x09'..'\x0D', ' '}: inc(pos)
of '|', '>':
# this can lead to incorrect tokenization when | or > appear inside flow
# content. checking whether we're inside flow content is not
# chomsky type-3, so we won't do that here.
g.kind = gtCommand
g.state = gtCommand
inc(pos)
while g.buf[pos] in {'0'..'9', '+', '-'}: inc(pos)
of '0'..'9': yamlPossibleNumber(g, pos)
of '\0': g.kind = gtEOF
else: yamlPlainStrLit(g, pos)
else:
# outside document
case g.buf[pos]
of '%':
if pos == 0 or g.buf[pos - 1] in {'\x0A', '\x0D'}:
g.kind = gtDirective
while g.buf[pos] notin {'\0', '\x0A', '\x0D'}: inc(pos)
else:
g.state = gtOther
yamlPlainStrLit(g, pos)
of ' ', '\x09'..'\x0D':
g.kind = gtWhitespace
while g.buf[pos] in {' ', '\x09'..'\x0D'}: inc(pos)
of '#':
g.kind = gtComment
while g.buf[pos] notin {'\0', '\x0A', '\x0D'}: inc(pos)
of '\0': g.kind = gtEOF
else:
g.kind = gtNone
g.state = gtOther
g.length = pos - g.pos
g.pos = pos
proc getNextToken*(g: var GeneralTokenizer, lang: SourceLanguage) =
case lang
of langNone: assert false
@@ -586,6 +890,7 @@ proc getNextToken*(g: var GeneralTokenizer, lang: SourceLanguage) =
of langCsharp: csharpNextToken(g)
of langC: cNextToken(g)
of langJava: javaNextToken(g)
of langYaml: yamlNextToken(g)
when isMainModule:
var keywords: seq[string]

View File

@@ -49,7 +49,7 @@ type
TMsgKind: MsgKind].}
const
messages: array [MsgKind, string] = [
messages: array[MsgKind, string] = [
meCannotOpenFile: "cannot open '$1'",
meExpected: "'$1' expected",
meGridTableNotImplemented: "grid table is not implemented",
@@ -323,6 +323,11 @@ proc newSharedState(options: RstParseOptions,
result.msgHandler = if not isNil(msgHandler): msgHandler else: defaultMsgHandler
result.findFile = if not isNil(findFile): findFile else: defaultFindFile
proc findRelativeFile(p: RstParser; filename: string): string =
result = p.filename.splitFile.dir / filename
if not existsFile(result):
result = p.s.findFile(filename)
proc rstMessage(p: RstParser, msgKind: MsgKind, arg: string) =
p.s.msgHandler(p.filename, p.line + p.tok[p.idx].line,
p.col + p.tok[p.idx].col, msgKind, arg)
@@ -1500,7 +1505,7 @@ proc dirInclude(p: var RstParser): PRstNode =
result = nil
var n = parseDirective(p, {hasArg, argIsFile, hasOptions}, nil)
var filename = strip(addNodes(n.sons[0]))
var path = p.s.findFile(filename)
var path = p.findRelativeFile(filename)
if path == "":
rstMessage(p, meCannotOpenFile, filename)
else:
@@ -1511,7 +1516,7 @@ proc dirInclude(p: var RstParser): PRstNode =
else:
var q: RstParser
initParser(q, p.s)
q.filename = filename
q.filename = path
q.col += getTokens(readFile(path), false, q.tok)
# workaround a GCC bug; more like the interior pointer bug?
#if find(q.tok[high(q.tok)].symbol, "\0\x01\x02") > 0:
@@ -1538,7 +1543,7 @@ proc dirCodeBlock(p: var RstParser, nimExtension = false): PRstNode =
result = parseDirective(p, {hasArg, hasOptions}, parseLiteralBlock)
var filename = strip(getFieldValue(result, "file"))
if filename != "":
var path = p.s.findFile(filename)
var path = p.findRelativeFile(filename)
if path == "": rstMessage(p, meCannotOpenFile, filename)
var n = newRstNode(rnLiteralBlock)
add(n, newRstNode(rnLeaf, readFile(path)))
@@ -1590,7 +1595,7 @@ proc dirRawAux(p: var RstParser, result: var PRstNode, kind: RstNodeKind,
contentParser: SectionParser) =
var filename = getFieldValue(result, "file")
if filename.len > 0:
var path = p.s.findFile(filename)
var path = p.findRelativeFile(filename)
if path.len == 0:
rstMessage(p, meCannotOpenFile, filename)
else:

View File

@@ -35,6 +35,15 @@ const
hasSpawnH = not defined(haiku) # should exist for every Posix system nowadays
hasAioH = defined(linux)
when defined(linux):
# On Linux:
# timer_{create,delete,settime,gettime},
# clock_{getcpuclockid, getres, gettime, nanosleep, settime} lives in librt
{.passL: "-lrt".}
when defined(solaris):
# On Solaris hstrerror lives in libresolv
{.passL: "-lresolv".}
when false:
const
C_IRUSR = 0c000400 ## Read by owner.
@@ -101,7 +110,7 @@ type
## (not POSIX)
when defined(linux) or defined(bsd):
d_off*: Off ## Not an offset. Value that ``telldir()`` would return.
d_name*: array [0..255, char] ## Name of entry.
d_name*: array[0..255, char] ## Name of entry.
Tflock* {.importc: "struct flock", final, pure,
header: "<fcntl.h>".} = object ## flock type
@@ -233,7 +242,7 @@ type
## network to which this node is attached, if any.
release*, ## Current release level of this implementation.
version*, ## Current version level of this release.
machine*: array [0..255, char] ## Name of the hardware type on which the
machine*: array[0..255, char] ## Name of the hardware type on which the
## system is running.
Sem* {.importc: "sem_t", header: "<semaphore.h>", final, pure.} = object
@@ -439,6 +448,14 @@ when hasSpawnH:
Tposix_spawn_file_actions* {.importc: "posix_spawn_file_actions_t",
header: "<spawn.h>", final, pure.} = object
when defined(linux):
# from sys/un.h
const Sockaddr_un_path_length* = 108
else:
# according to http://pubs.opengroup.org/onlinepubs/009604499/basedefs/sys/un.h.html
# this is >=92
const Sockaddr_un_path_length* = 92
type
Socklen* {.importc: "socklen_t", header: "<sys/socket.h>".} = cuint
TSa_Family* {.importc: "sa_family_t", header: "<sys/socket.h>".} = cint
@@ -446,7 +463,12 @@ type
SockAddr* {.importc: "struct sockaddr", header: "<sys/socket.h>",
pure, final.} = object ## struct sockaddr
sa_family*: TSa_Family ## Address family.
sa_data*: array [0..255, char] ## Socket address (variable-length data).
sa_data*: array[0..255, char] ## Socket address (variable-length data).
Sockaddr_un* {.importc: "struct sockaddr_un", header: "<sys/un.h>",
pure, final.} = object ## struct sockaddr_un
sun_family*: TSa_Family ## Address family.
sun_path*: array[0..Sockaddr_un_path_length-1, char] ## Socket path
Sockaddr_storage* {.importc: "struct sockaddr_storage",
header: "<sys/socket.h>",
@@ -504,7 +526,7 @@ type
In6Addr* {.importc: "struct in6_addr", pure, final,
header: "<netinet/in.h>".} = object ## struct in6_addr
s6_addr*: array [0..15, char]
s6_addr*: array[0..15, char]
Sockaddr_in6* {.importc: "struct sockaddr_in6", pure, final,
header: "<netinet/in.h>".} = object ## struct sockaddr_in6
@@ -1604,6 +1626,16 @@ else:
var
MAP_POPULATE*: cint = 0
when defined(linux) or defined(nimdoc):
when defined(alpha) or defined(mips) or defined(parisc) or
defined(sparc) or defined(nimdoc):
const SO_REUSEPORT* = cint(0x0200)
## Multiple binding: load balancing on incoming TCP connections
## or UDP packets. (Requires Linux kernel > 3.9)
else:
const SO_REUSEPORT* = cint(15)
else:
var SO_REUSEPORT* {.importc, header: "<sys/socket.h>".}: cint
when defined(macosx):
# We can't use the NOSIGNAL flag in the ``send`` function, it has no effect
@@ -1612,6 +1644,10 @@ when defined(macosx):
MSG_NOSIGNAL* = 0'i32
var
SO_NOSIGPIPE* {.importc, header: "<sys/socket.h>".}: cint
elif defined(solaris):
# Solaris dont have MSG_NOSIGNAL
const
MSG_NOSIGNAL* = 0'i32
else:
var
MSG_NOSIGNAL* {.importc, header: "<sys/socket.h>".}: cint
@@ -2309,9 +2345,9 @@ proc strftime*(a1: cstring, a2: int, a3: cstring,
a4: var Tm): int {.importc, header: "<time.h>".}
proc strptime*(a1, a2: cstring, a3: var Tm): cstring {.importc, header: "<time.h>".}
proc time*(a1: var Time): Time {.importc, header: "<time.h>".}
proc timer_create*(a1: var ClockId, a2: var SigEvent,
proc timer_create*(a1: ClockId, a2: var SigEvent,
a3: var Timer): cint {.importc, header: "<time.h>".}
proc timer_delete*(a1: var Timer): cint {.importc, header: "<time.h>".}
proc timer_delete*(a1: Timer): cint {.importc, header: "<time.h>".}
proc timer_gettime*(a1: Timer, a2: var Itimerspec): cint {.
importc, header: "<time.h>".}
proc timer_getoverrun*(a1: Timer): cint {.importc, header: "<time.h>".}
@@ -2357,8 +2393,14 @@ proc sigrelse*(a1: cint): cint {.importc, header: "<signal.h>".}
proc sigset*(a1: int, a2: proc (x: cint) {.noconv.}) {.
importc, header: "<signal.h>".}
proc sigsuspend*(a1: var Sigset): cint {.importc, header: "<signal.h>".}
proc sigtimedwait*(a1: var Sigset, a2: var SigInfo,
when defined(android):
proc sigtimedwait*(a1: var Sigset, a2: var SigInfo,
a3: var Timespec, sigsetsize: csize = sizeof(culong)*2): cint {.importc: "__rt_sigtimedwait", header:"<signal.h>".}
else:
proc sigtimedwait*(a1: var Sigset, a2: var SigInfo,
a3: var Timespec): cint {.importc, header: "<signal.h>".}
proc sigwait*(a1: var Sigset, a2: var cint): cint {.
importc, header: "<signal.h>".}
proc sigwaitinfo*(a1: var Sigset, a2: var SigInfo): cint {.
@@ -2574,8 +2616,12 @@ proc gai_strerror*(a1: cint): cstring {.importc:"(char *)$1", header: "<netdb.h>
proc getaddrinfo*(a1, a2: cstring, a3: ptr AddrInfo,
a4: var ptr AddrInfo): cint {.importc, header: "<netdb.h>".}
proc gethostbyaddr*(a1: pointer, a2: Socklen, a3: cint): ptr Hostent {.
importc, header: "<netdb.h>".}
when not defined(android4):
proc gethostbyaddr*(a1: pointer, a2: Socklen, a3: cint): ptr Hostent {.
importc, header: "<netdb.h>".}
else:
proc gethostbyaddr*(a1: cstring, a2: cint, a3: cint): ptr Hostent {.
importc, header: "<netdb.h>".}
proc gethostbyname*(a1: cstring): ptr Hostent {.importc, header: "<netdb.h>".}
proc gethostent*(): ptr Hostent {.importc, header: "<netdb.h>".}
@@ -2607,7 +2653,7 @@ proc poll*(a1: ptr TPollfd, a2: Tnfds, a3: int): cint {.
proc realpath*(name, resolved: cstring): cstring {.
importc: "realpath", header: "<stdlib.h>".}
proc utimes*(path: cstring, times: ptr array [2, Timeval]): int {.
proc utimes*(path: cstring, times: ptr array[2, Timeval]): int {.
importc: "utimes", header: "<sys/time.h>".}
## Sets file access and modification times.
##
@@ -2618,3 +2664,17 @@ proc utimes*(path: cstring, times: ptr array [2, Timeval]): int {.
## Returns zero on success.
##
## For more information read http://www.unix.com/man-page/posix/3/utimes/.
proc handle_signal(sig: cint, handler: proc (a: cint) {.noconv.}) {.importc: "signal", header: "<signal.h>".}
template onSignal*(signals: varargs[cint], body: untyped) =
## Setup code to be executed when Unix signals are received. Example:
## from posix import SIGINT, SIGTERM
## onSignal(SIGINT, SIGTERM):
## echo "bye"
for s in signals:
handle_signal(s,
proc (sig: cint) {.noconv.} =
body
)

View File

@@ -114,7 +114,7 @@ proc lowerBound*[T](a: openArray[T], key: T, cmp: proc(x,y: T): int {.closure.})
proc lowerBound*[T](a: openArray[T], key: T): int = lowerBound(a, key, cmp[T])
proc merge[T](a, b: var openArray[T], lo, m, hi: int,
cmp: proc (x, y: T): int {.closure.}, order: SortOrder) =
template `<-` (a, b: expr) =
template `<-` (a, b) =
when false:
a = b
elif onlySafeCode:
@@ -206,7 +206,7 @@ proc sorted*[T](a: openArray[T], cmp: proc(x, y: T): int {.closure.},
result[i] = a[i]
sort(result, cmp, order)
template sortedByIt*(seq1, op: expr): expr =
template sortedByIt*(seq1, op: untyped): untyped =
## Convenience template around the ``sorted`` proc to reduce typing.
##
## The template injects the ``it`` variable which you can use directly in an
@@ -231,7 +231,7 @@ template sortedByIt*(seq1, op: expr): expr =
##
## echo people.sortedByIt((it.age, it.name))
##
var result {.gensym.} = sorted(seq1, proc(x, y: type(seq1[0])): int =
var result = sorted(seq1, proc(x, y: type(seq1[0])): int =
var it {.inject.} = x
let a = op
it = y

View File

@@ -9,9 +9,9 @@
include "system/inclrtl"
import os, oids, tables, strutils, macros, times
import os, oids, tables, strutils, macros, times, heapqueue
import nativesockets, net
import nativesockets, net, queues
export Port, SocketFlag
@@ -155,6 +155,9 @@ type
when not defined(release):
var currentID = 0
proc callSoon*(cbproc: proc ()) {.gcsafe.}
proc newFuture*[T](fromProc: string = "unspecified"): Future[T] =
## Creates a new future.
##
@@ -257,7 +260,7 @@ proc `callback=`*(future: FutureBase, cb: proc () {.closure,gcsafe.}) =
## passes ``future`` as a param to the callback.
future.cb = cb
if future.finished:
future.cb()
callSoon(future.cb)
proc `callback=`*[T](future: Future[T],
cb: proc (future: Future[T]) {.closure,gcsafe.}) =
@@ -352,28 +355,88 @@ proc `or`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
fut2.callback = cb
return retFuture
proc all*[T](futs: varargs[Future[T]]): auto =
## Returns a future which will complete once
## all futures in ``futs`` complete.
##
## If the awaited futures are not ``Future[void]``, the returned future
## will hold the values of all awaited futures in a sequence.
##
## If the awaited futures *are* ``Future[void]``,
## this proc returns ``Future[void]``.
when T is void:
var
retFuture = newFuture[void]("asyncdispatch.all")
completedFutures = 0
let totalFutures = len(futs)
for fut in futs:
fut.callback = proc(f: Future[T]) =
inc(completedFutures)
if completedFutures == totalFutures:
retFuture.complete()
return retFuture
else:
var
retFuture = newFuture[seq[T]]("asyncdispatch.all")
retValues = newSeq[T](len(futs))
completedFutures = 0
for i, fut in futs:
proc setCallback(i: int) =
fut.callback = proc(f: Future[T]) =
retValues[i] = f.read()
inc(completedFutures)
if completedFutures == len(retValues):
retFuture.complete(retValues)
setCallback(i)
return retFuture
type
PDispatcherBase = ref object of RootRef
timers: seq[tuple[finishAt: float, fut: Future[void]]]
timers: HeapQueue[tuple[finishAt: float, fut: Future[void]]]
callbacks: Queue[proc ()]
proc processTimers(p: PDispatcherBase) =
var oldTimers = p.timers
p.timers = @[]
for t in oldTimers:
if epochTime() >= t.finishAt:
t.fut.complete()
else:
p.timers.add(t)
proc processTimers(p: PDispatcherBase) {.inline.} =
while p.timers.len > 0 and epochTime() >= p.timers[0].finishAt:
p.timers.pop().fut.complete()
proc processPendingCallbacks(p: PDispatcherBase) =
while p.callbacks.len > 0:
var cb = p.callbacks.dequeue()
cb()
proc adjustedTimeout(p: PDispatcherBase, timeout: int): int {.inline.} =
# If dispatcher has active timers this proc returns the timeout
# of the nearest timer. Returns `timeout` otherwise.
result = timeout
if p.timers.len > 0:
let timerTimeout = p.timers[0].finishAt
let curTime = epochTime()
if timeout == -1 or (curTime + (timeout / 1000)) > timerTimeout:
result = int((timerTimeout - curTime) * 1000)
if result < 0: result = 0
when defined(windows) or defined(nimdoc):
import winlean, sets, hashes
type
CompletionKey = Dword
CompletionKey = ULONG_PTR
CompletionData* = object
fd*: AsyncFD # TODO: Rename this.
cb*: proc (fd: AsyncFD, bytesTransferred: Dword,
errcode: OSErrorCode) {.closure,gcsafe.}
cell*: ForeignCell # we need this `cell` to protect our `cb` environment,
# when using RegisterWaitForSingleObject, because
# waiting is done in different thread.
PDispatcher* = ref object of PDispatcherBase
ioPort: Handle
@@ -385,6 +448,15 @@ when defined(windows) or defined(nimdoc):
PCustomOverlapped* = ref CustomOverlapped
AsyncFD* = distinct int
PostCallbackData = object
ioPort: Handle
handleFd: AsyncFD
waitFd: Handle
ovl: PCustomOverlapped
PostCallbackDataPtr = ptr PostCallbackData
Callback = proc (fd: AsyncFD): bool {.closure,gcsafe.}
{.deprecated: [TCompletionKey: CompletionKey, TAsyncFD: AsyncFD,
TCustomOverlapped: CustomOverlapped, TCompletionData: CompletionData].}
@@ -396,7 +468,8 @@ when defined(windows) or defined(nimdoc):
new result
result.ioPort = createIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1)
result.handles = initSet[AsyncFD]()
result.timers = @[]
result.timers.newHeapQueue()
result.callbacks = initQueue[proc ()](64)
var gDisp{.threadvar.}: PDispatcher ## Global dispatcher
proc getGlobalDispatcher*(): PDispatcher =
@@ -423,15 +496,17 @@ when defined(windows) or defined(nimdoc):
proc poll*(timeout = 500) =
## Waits for completion events and processes them.
let p = getGlobalDispatcher()
if p.handles.len == 0 and p.timers.len == 0:
if p.handles.len == 0 and p.timers.len == 0 and p.callbacks.len == 0:
raise newException(ValueError,
"No handles or timers registered in dispatcher.")
let llTimeout =
if timeout == -1: winlean.INFINITE
else: timeout.int32
let at = p.adjustedTimeout(timeout)
var llTimeout =
if at == -1: winlean.INFINITE
else: at.int32
var lpNumberOfBytesTransferred: Dword
var lpCompletionKey: ULONG
var lpCompletionKey: ULONG_PTR
var customOverlapped: PCustomOverlapped
let res = getQueuedCompletionStatus(p.ioPort,
addr lpNumberOfBytesTransferred, addr lpCompletionKey,
@@ -445,6 +520,13 @@ when defined(windows) or defined(nimdoc):
customOverlapped.data.cb(customOverlapped.data.fd,
lpNumberOfBytesTransferred, OSErrorCode(-1))
# If cell.data != nil, then system.protect(rawEnv(cb)) was called,
# so we need to dispose our `cb` environment, because it is not needed
# anymore.
if customOverlapped.data.cell.data != nil:
system.dispose(customOverlapped.data.cell)
GC_unref(customOverlapped)
else:
let errCode = osLastError()
@@ -452,6 +534,8 @@ when defined(windows) or defined(nimdoc):
assert customOverlapped.data.fd == lpCompletionKey.AsyncFD
customOverlapped.data.cb(customOverlapped.data.fd,
lpNumberOfBytesTransferred, errCode)
if customOverlapped.data.cell.data != nil:
system.dispose(customOverlapped.data.cell)
GC_unref(customOverlapped)
else:
if errCode.int32 == WAIT_TIMEOUT:
@@ -461,6 +545,8 @@ when defined(windows) or defined(nimdoc):
# Timer processing.
processTimers(p)
# Callback queue processing
processPendingCallbacks(p)
var connectExPtr: pointer = nil
var acceptExPtr: pointer = nil
@@ -651,34 +737,16 @@ when defined(windows) or defined(nimdoc):
retFuture.complete("")
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("")
# 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
var data = newString(realSize)
assert realSize <= size
copyMem(addr data[0], addr dataBuf.buf[0], realSize)
#dealloc dataBuf.buf
retFuture.complete($data)
# 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``.
elif ret == 0:
# Request completed immediately.
if bytesReceived != 0:
var data = newString(bytesReceived)
assert bytesReceived <= size
copyMem(addr data[0], addr dataBuf.buf[0], bytesReceived)
retFuture.complete($data)
else:
if hasOverlappedIoCompleted(cast[POVERLAPPED](ol)):
retFuture.complete("")
return retFuture
proc recvInto*(socket: AsyncFD, buf: cstring, size: int,
@@ -741,31 +809,14 @@ when defined(windows) or defined(nimdoc):
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``.
elif ret == 0:
# Request completed immediately.
if bytesReceived != 0:
assert bytesReceived <= size
retFuture.complete(bytesReceived)
else:
if hasOverlappedIoCompleted(cast[POVERLAPPED](ol)):
retFuture.complete(bytesReceived)
return retFuture
proc send*(socket: AsyncFD, data: string,
@@ -811,6 +862,101 @@ when defined(windows) or defined(nimdoc):
# free ``ol``.
return retFuture
proc sendTo*(socket: AsyncFD, data: pointer, size: int, saddr: ptr SockAddr,
saddrLen: Socklen,
flags = {SocketFlag.SafeDisconn}): Future[void] =
## Sends ``data`` to specified destination ``saddr``, using
## socket ``socket``. The returned future will complete once all data
## has been sent.
verifyPresence(socket)
var retFuture = newFuture[void]("sendTo")
var dataBuf: TWSABuf
dataBuf.buf = cast[cstring](data)
dataBuf.len = size.ULONG
var bytesSent = 0.Dword
var lowFlags = 0.Dword
# we will preserve address in our stack
var staddr: array[128, char] # SOCKADDR_STORAGE size is 128 bytes
var stalen: cint = cint(saddrLen)
zeroMem(addr(staddr[0]), 128)
copyMem(addr(staddr[0]), saddr, saddrLen)
var ol = PCustomOverlapped()
GC_ref(ol)
ol.data = CompletionData(fd: socket, cb:
proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished:
if errcode == OSErrorCode(-1):
retFuture.complete()
else:
retFuture.fail(newException(OSError, osErrorMsg(errcode)))
)
let ret = WSASendTo(socket.SocketHandle, addr dataBuf, 1, addr bytesSent,
lowFlags, cast[ptr SockAddr](addr(staddr[0])),
stalen, cast[POVERLAPPED](ol), nil)
if ret == -1:
let err = osLastError()
if err.int32 != ERROR_IO_PENDING:
GC_unref(ol)
retFuture.fail(newException(OSError, osErrorMsg(err)))
else:
retFuture.complete()
# 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 recvFromInto*(socket: AsyncFD, data: pointer, size: int,
saddr: ptr SockAddr, saddrLen: ptr SockLen,
flags = {SocketFlag.SafeDisconn}): Future[int] =
## Receives a datagram data from ``socket`` into ``buf``, which must
## be at least of size ``size``, address of datagram's sender will be
## stored into ``saddr`` and ``saddrLen``. Returned future will complete
## once one datagram has been received, and will return size of packet
## received.
verifyPresence(socket)
var retFuture = newFuture[int]("recvFromInto")
var dataBuf = TWSABuf(buf: cast[cstring](data), len: size.ULONG)
var bytesReceived = 0.Dword
var lowFlags = 0.Dword
var ol = PCustomOverlapped()
GC_ref(ol)
ol.data = CompletionData(fd: socket, cb:
proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished:
if errcode == OSErrorCode(-1):
assert bytesCount <= size
retFuture.complete(bytesCount)
else:
# datagram sockets don't have disconnection,
# so we can just raise an exception
retFuture.fail(newException(OSError, osErrorMsg(errcode)))
)
let res = WSARecvFrom(socket.SocketHandle, addr dataBuf, 1,
addr bytesReceived, addr lowFlags,
saddr, cast[ptr cint](saddrLen),
cast[POVERLAPPED](ol), nil)
if res == -1:
let err = osLastError()
if err.int32 != ERROR_IO_PENDING:
GC_unref(ol)
retFuture.fail(newException(OSError, osErrorMsg(err)))
else:
# Request completed immediately.
if bytesReceived != 0:
assert bytesReceived <= size
retFuture.complete(bytesReceived)
else:
if hasOverlappedIoCompleted(cast[POVERLAPPED](ol)):
retFuture.complete(bytesReceived)
return retFuture
proc acceptAddr*(socket: AsyncFD, flags = {SocketFlag.SafeDisconn}):
Future[tuple[address: string, client: AsyncFD]] =
## Accepts a new connection. Returns a future containing the client socket
@@ -837,7 +983,7 @@ when defined(windows) or defined(nimdoc):
let dwLocalAddressLength = Dword(sizeof (Sockaddr_in) + 16)
let dwRemoteAddressLength = Dword(sizeof(Sockaddr_in) + 16)
template completeAccept(): stmt {.immediate, dirty.} =
template completeAccept() {.dirty.} =
var listenSock = socket
let setoptRet = setsockopt(clientSock, SOL_SOCKET,
SO_UPDATE_ACCEPT_CONTEXT, addr listenSock,
@@ -857,7 +1003,7 @@ when defined(windows) or defined(nimdoc):
client: clientSock.AsyncFD)
)
template failAccept(errcode): stmt =
template failAccept(errcode) =
if flags.isDisconnectionError(errcode):
var newAcceptFut = acceptAddr(socket, flags)
newAcceptFut.callback =
@@ -923,6 +1069,126 @@ when defined(windows) or defined(nimdoc):
## Unregisters ``fd``.
getGlobalDispatcher().handles.excl(fd)
{.push stackTrace:off.}
proc waitableCallback(param: pointer,
timerOrWaitFired: WINBOOL): void {.stdcall.} =
var p = cast[PostCallbackDataPtr](param)
discard postQueuedCompletionStatus(p.ioPort, timerOrWaitFired.Dword,
ULONG_PTR(p.handleFd),
cast[pointer](p.ovl))
{.pop.}
template registerWaitableEvent(mask) =
let p = getGlobalDispatcher()
var flags = (WT_EXECUTEINWAITTHREAD or WT_EXECUTEONLYONCE).Dword
var hEvent = wsaCreateEvent()
if hEvent == 0:
raiseOSError(osLastError())
var pcd = cast[PostCallbackDataPtr](allocShared0(sizeof(PostCallbackData)))
pcd.ioPort = p.ioPort
pcd.handleFd = fd
var ol = PCustomOverlapped()
GC_ref(ol)
ol.data = CompletionData(fd: fd, cb:
proc(fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
# we excluding our `fd` because cb(fd) can register own handler
# for this `fd`
p.handles.excl(fd)
# unregisterWait() is called before callback, because appropriate
# winsockets function can re-enable event.
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms741576(v=vs.85).aspx
if unregisterWait(pcd.waitFd) == 0:
let err = osLastError()
if err.int32 != ERROR_IO_PENDING:
raiseOSError(osLastError())
if cb(fd):
# callback returned `true`, so we free all allocated resources
deallocShared(cast[pointer](pcd))
if not wsaCloseEvent(hEvent):
raiseOSError(osLastError())
# pcd.ovl will be unrefed in poll().
else:
# callback returned `false` we need to continue
if p.handles.contains(fd):
# new callback was already registered with `fd`, so we free all
# allocated resources. This happens because in callback `cb`
# addRead/addWrite was called with same `fd`.
deallocShared(cast[pointer](pcd))
if not wsaCloseEvent(hEvent):
raiseOSError(osLastError())
else:
# we need to include `fd` again
p.handles.incl(fd)
# and register WaitForSingleObject again
if not registerWaitForSingleObject(addr(pcd.waitFd), hEvent,
cast[WAITORTIMERCALLBACK](waitableCallback),
cast[pointer](pcd), INFINITE, flags):
# pcd.ovl will be unrefed in poll()
discard wsaCloseEvent(hEvent)
deallocShared(cast[pointer](pcd))
raiseOSError(osLastError())
else:
# we ref pcd.ovl one more time, because it will be unrefed in
# poll()
GC_ref(pcd.ovl)
)
# We need to protect our callback environment value, so GC will not free it
# accidentally.
ol.data.cell = system.protect(rawEnv(ol.data.cb))
# This is main part of `hacky way` is using WSAEventSelect, so `hEvent`
# will be signaled when appropriate `mask` events will be triggered.
if wsaEventSelect(fd.SocketHandle, hEvent, mask) != 0:
GC_unref(ol)
deallocShared(cast[pointer](pcd))
discard wsaCloseEvent(hEvent)
raiseOSError(osLastError())
pcd.ovl = ol
if not registerWaitForSingleObject(addr(pcd.waitFd), hEvent,
cast[WAITORTIMERCALLBACK](waitableCallback),
cast[pointer](pcd), INFINITE, flags):
GC_unref(ol)
deallocShared(cast[pointer](pcd))
discard wsaCloseEvent(hEvent)
raiseOSError(osLastError())
p.handles.incl(fd)
proc addRead*(fd: AsyncFD, cb: Callback) =
## Start watching the file descriptor for read availability and then call
## the callback ``cb``.
##
## This is not ``pure`` mechanism for Windows Completion Ports (IOCP),
## so if you can avoid it, please do it. Use `addRead` only if really
## need it (main usecase is adaptation of `unix like` libraries to be
## asynchronous on Windows).
## If you use this function, you dont need to use asyncdispatch.recv()
## or asyncdispatch.accept(), because they are using IOCP, please use
## nativesockets.recv() and nativesockets.accept() instead.
##
## Be sure your callback ``cb`` returns ``true``, if you want to remove
## watch of `read` notifications, and ``false``, if you want to continue
## receiving notifies.
registerWaitableEvent(FD_READ or FD_ACCEPT or FD_OOB or FD_CLOSE)
proc addWrite*(fd: AsyncFD, cb: Callback) =
## Start watching the file descriptor for write availability and then call
## the callback ``cb``.
##
## This is not ``pure`` mechanism for Windows Completion Ports (IOCP),
## so if you can avoid it, please do it. Use `addWrite` only if really
## need it (main usecase is adaptation of `unix like` libraries to be
## asynchronous on Windows).
## If you use this function, you dont need to use asyncdispatch.send()
## or asyncdispatch.connect(), because they are using IOCP, please use
## nativesockets.send() and nativesockets.connect() instead.
##
## Be sure your callback ``cb`` returns ``true``, if you want to remove
## watch of `write` notifications, and ``false``, if you want to continue
## receiving notifies.
registerWaitableEvent(FD_WRITE or FD_CONNECT or FD_CLOSE)
initAll()
else:
import selectors
@@ -956,7 +1222,8 @@ else:
proc newDispatcher*(): PDispatcher =
new result
result.selector = newSelector()
result.timers = @[]
result.timers.newHeapQueue()
result.callbacks = initQueue[proc ()](64)
var gDisp{.threadvar.}: PDispatcher ## Global dispatcher
proc getGlobalDispatcher*(): PDispatcher =
@@ -1014,7 +1281,7 @@ else:
proc poll*(timeout = 500) =
let p = getGlobalDispatcher()
for info in p.selector.select(timeout):
for info in p.selector.select(p.adjustedTimeout(timeout)):
let data = PData(info.key.data)
assert data.fd == info.key.fd.AsyncFD
#echo("In poll ", data.fd.cint)
@@ -1052,7 +1319,10 @@ else:
# (e.g. socket disconnected).
discard
# Timer processing.
processTimers(p)
# Callback queue processing
processPendingCallbacks(p)
proc connect*(socket: AsyncFD, address: string, port: Port,
domain = AF_INET): Future[void] =
@@ -1184,6 +1454,60 @@ else:
addWrite(socket, cb)
return retFuture
proc sendTo*(socket: AsyncFD, data: pointer, size: int, saddr: ptr SockAddr,
saddrLen: SockLen,
flags = {SocketFlag.SafeDisconn}): Future[void] =
## Sends ``data`` of size ``size`` in bytes to specified destination
## (``saddr`` of size ``saddrLen`` in bytes, using socket ``socket``.
## The returned future will complete once all data has been sent.
var retFuture = newFuture[void]("sendTo")
# we will preserve address in our stack
var staddr: array[128, char] # SOCKADDR_STORAGE size is 128 bytes
var stalen = saddrLen
zeroMem(addr(staddr[0]), 128)
copyMem(addr(staddr[0]), saddr, saddrLen)
proc cb(sock: AsyncFD): bool =
result = true
let res = sendto(sock.SocketHandle, data, size, MSG_NOSIGNAL,
cast[ptr SockAddr](addr(staddr[0])), stalen)
if res < 0:
let lastError = osLastError()
if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}:
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
else:
result = false # We still want this callback to be called.
else:
retFuture.complete()
addWrite(socket, cb)
return retFuture
proc recvFromInto*(socket: AsyncFD, data: pointer, size: int,
saddr: ptr SockAddr, saddrLen: ptr SockLen,
flags = {SocketFlag.SafeDisconn}): Future[int] =
## Receives a datagram data from ``socket`` into ``data``, which must
## be at least of size ``size`` in bytes, address of datagram's sender
## will be stored into ``saddr`` and ``saddrLen``. Returned future will
## complete once one datagram has been received, and will return size
## of packet received.
var retFuture = newFuture[int]("recvFromInto")
proc cb(sock: AsyncFD): bool =
result = true
let res = recvfrom(sock.SocketHandle, data, size.cint, flags.toOSFlags(),
saddr, saddrLen)
if res < 0:
let lastError = osLastError()
if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}:
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
else:
result = false
else:
retFuture.complete(res)
addRead(socket, cb)
return retFuture
proc acceptAddr*(socket: AsyncFD, flags = {SocketFlag.SafeDisconn}):
Future[tuple[address: string, client: AsyncFD]] =
var retFuture = newFuture[tuple[address: string,
@@ -1215,7 +1539,25 @@ proc sleepAsync*(ms: int): Future[void] =
## ``ms`` milliseconds.
var retFuture = newFuture[void]("sleepAsync")
let p = getGlobalDispatcher()
p.timers.add((epochTime() + (ms / 1000), retFuture))
p.timers.push((epochTime() + (ms / 1000), retFuture))
return retFuture
proc withTimeout*[T](fut: Future[T], timeout: int): Future[bool] =
## Returns a future which will complete once ``fut`` completes or after
## ``timeout`` milliseconds has elapsed.
##
## If ``fut`` completes first the returned future will hold true,
## otherwise, if ``timeout`` milliseconds has elapsed first, the returned
## future will hold false.
var retFuture = newFuture[bool]("asyncdispatch.`withTimeout`")
var timeoutFuture = sleepAsync(timeout)
fut.callback =
proc () =
if not retFuture.finished: retFuture.complete(true)
timeoutFuture.callback =
proc () =
if not retFuture.finished: retFuture.complete(false)
return retFuture
proc accept*(socket: AsyncFD,
@@ -1248,7 +1590,7 @@ proc skipStmtList(node: NimNode): NimNode {.compileTime.} =
result = node[0]
template createCb(retFutureSym, iteratorNameSym,
name: expr): stmt {.immediate.} =
name: untyped) =
var nameIterVar = iteratorNameSym
#{.push stackTrace: off.}
proc cb {.closure,gcsafe.} =
@@ -1316,6 +1658,23 @@ proc generateExceptionCheck(futSym,
)
result.add elseNode
template useVar(result: var NimNode, futureVarNode: NimNode, valueReceiver,
rootReceiver: expr, fromNode: NimNode) =
## Params:
## futureVarNode: The NimNode which is a symbol identifying the Future[T]
## variable to yield.
## fromNode: Used for better debug information (to give context).
## valueReceiver: The node which defines an expression that retrieves the
## future's value.
##
## rootReceiver: ??? TODO
# -> yield future<x>
result.add newNimNode(nnkYieldStmt, fromNode).add(futureVarNode)
# -> future<x>.read
valueReceiver = newDotExpr(futureVarNode, newIdentNode("read"))
result.add generateExceptionCheck(futureVarNode, tryStmt, rootReceiver,
fromNode)
template createVar(result: var NimNode, futSymName: string,
asyncProc: NimNode,
valueReceiver, rootReceiver: expr,
@@ -1323,9 +1682,7 @@ template createVar(result: var NimNode, futSymName: string,
result = newNimNode(nnkStmtList, fromNode)
var futSym = genSym(nskVar, "future")
result.add newVarStmt(futSym, asyncProc) # -> var future<x> = y
result.add newNimNode(nnkYieldStmt, fromNode).add(futSym) # -> yield future<x>
valueReceiver = newDotExpr(futSym, newIdentNode("read")) # -> future<x>.read
result.add generateExceptionCheck(futSym, tryStmt, rootReceiver, fromNode)
useVar(result, futSym, valueReceiver, rootReceiver, fromNode)
proc processBody(node, retFutureSym: NimNode,
subTypeIsVoid: bool,
@@ -1342,19 +1699,23 @@ proc processBody(node, retFutureSym: NimNode,
else:
result.add newCall(newIdentNode("complete"), retFutureSym)
else:
result.add newCall(newIdentNode("complete"), retFutureSym,
node[0].processBody(retFutureSym, subTypeIsVoid, tryStmt))
let x = node[0].processBody(retFutureSym, subTypeIsVoid, tryStmt)
if x.kind == nnkYieldStmt: result.add x
else:
result.add newCall(newIdentNode("complete"), retFutureSym, x)
result.add newNimNode(nnkReturnStmt, node).add(newNilLit())
return # Don't process the children of this return stmt
of nnkCommand, nnkCall:
if node[0].kind == nnkIdent and node[0].ident == !"await":
case node[1].kind
of nnkIdent, nnkInfix:
of nnkIdent, nnkInfix, nnkDotExpr:
# await x
# await x or y
result = newNimNode(nnkYieldStmt, node).add(node[1]) # -> yield x
of nnkCall, nnkCommand:
# await foo(p, x)
# await foo p, x
var futureValue: NimNode
result.createVar("future" & $node[1][0].toStrLit, node[1], futureValue,
futureValue, node)
@@ -1511,38 +1872,40 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
# -> complete(retFuture, result)
var iteratorNameSym = genSym(nskIterator, $prc[0].getName & "Iter")
var procBody = prc[6].processBody(retFutureSym, subtypeIsVoid, nil)
if not subtypeIsVoid:
procBody.insert(0, newNimNode(nnkPragma).add(newIdentNode("push"),
newNimNode(nnkExprColonExpr).add(newNimNode(nnkBracketExpr).add(
newIdentNode("warning"), newIdentNode("resultshadowed")),
newIdentNode("off")))) # -> {.push warning[resultshadowed]: off.}
# don't do anything with forward bodies (empty)
if procBody.kind != nnkEmpty:
if not subtypeIsVoid:
procBody.insert(0, newNimNode(nnkPragma).add(newIdentNode("push"),
newNimNode(nnkExprColonExpr).add(newNimNode(nnkBracketExpr).add(
newIdentNode("warning"), newIdentNode("resultshadowed")),
newIdentNode("off")))) # -> {.push warning[resultshadowed]: off.}
procBody.insert(1, newNimNode(nnkVarSection, prc[6]).add(
newIdentDefs(newIdentNode("result"), baseType))) # -> var result: T
procBody.insert(1, newNimNode(nnkVarSection, prc[6]).add(
newIdentDefs(newIdentNode("result"), baseType))) # -> var result: T
procBody.insert(2, newNimNode(nnkPragma).add(
newIdentNode("pop"))) # -> {.pop.})
procBody.insert(2, newNimNode(nnkPragma).add(
newIdentNode("pop"))) # -> {.pop.})
procBody.add(
newCall(newIdentNode("complete"),
retFutureSym, newIdentNode("result"))) # -> complete(retFuture, result)
else:
# -> complete(retFuture)
procBody.add(newCall(newIdentNode("complete"), retFutureSym))
procBody.add(
newCall(newIdentNode("complete"),
retFutureSym, newIdentNode("result"))) # -> complete(retFuture, result)
else:
# -> complete(retFuture)
procBody.add(newCall(newIdentNode("complete"), retFutureSym))
var closureIterator = newProc(iteratorNameSym, [newIdentNode("FutureBase")],
procBody, nnkIteratorDef)
closureIterator[4] = newNimNode(nnkPragma, prc[6]).add(newIdentNode("closure"))
outerProcBody.add(closureIterator)
var closureIterator = newProc(iteratorNameSym, [newIdentNode("FutureBase")],
procBody, nnkIteratorDef)
closureIterator[4] = newNimNode(nnkPragma, prc[6]).add(newIdentNode("closure"))
outerProcBody.add(closureIterator)
# -> createCb(retFuture)
#var cbName = newIdentNode("cb")
var procCb = newCall(bindSym"createCb", retFutureSym, iteratorNameSym,
newStrLitNode(prc[0].getName))
outerProcBody.add procCb
# -> createCb(retFuture)
#var cbName = newIdentNode("cb")
var procCb = getAst createCb(retFutureSym, iteratorNameSym,
newStrLitNode(prc[0].getName))
outerProcBody.add procCb
# -> return retFuture
outerProcBody.add newNimNode(nnkReturnStmt, prc[6][prc[6].len-1]).add(retFutureSym)
# -> return retFuture
outerProcBody.add newNimNode(nnkReturnStmt, prc[6][prc[6].len-1]).add(retFutureSym)
result = prc
@@ -1550,19 +1913,19 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
for i in 0 .. <result[4].len:
if result[4][i].kind == nnkIdent and result[4][i].ident == !"async":
result[4].del(i)
result[4] = newEmptyNode()
if subtypeIsVoid:
# Add discardable pragma.
if returnType.kind == nnkEmpty:
# Add Future[void]
result[3][0] = parseExpr("Future[void]")
result[6] = outerProcBody
if procBody.kind != nnkEmpty:
result[6] = outerProcBody
#echo(treeRepr(result))
#if prc[0].getName == "hubConnectionLoop":
#if prc[0].getName == "testInfix":
# echo(toStrLit(result))
macro async*(prc: stmt): stmt {.immediate.} =
macro async*(prc: untyped): untyped =
## Macro which processes async procedures into the appropriate
## iterators and yield statements.
if prc.kind == nnkStmtList:
@@ -1571,6 +1934,8 @@ macro async*(prc: stmt): stmt {.immediate.} =
result.add asyncSingleProc(oneProc)
else:
result = asyncSingleProc(prc)
when defined(nimDumpAsync):
echo repr result
proc recvLine*(socket: AsyncFD): Future[string] {.async.} =
## Reads a line of data from ``socket``. Returned future will complete once
@@ -1611,6 +1976,11 @@ proc recvLine*(socket: AsyncFD): Future[string] {.async.} =
return
add(result, c)
proc callSoon*(cbproc: proc ()) =
## Schedule `cbproc` to be called as soon as possible.
## The callback is called when control returns to the event loop.
getGlobalDispatcher().callbacks.enqueue(cbproc)
proc runForever*() =
## Begins a never ending global dispatcher poll loop.
while true:

View File

@@ -162,7 +162,7 @@ proc read*(f: AsyncFile, size: int): Future[string] =
# Request completed immediately.
var bytesRead: DWord
let overlappedRes = getOverlappedResult(f.fd.Handle,
cast[POverlapped](ol)[], bytesRead, false.WinBool)
cast[POverlapped](ol), bytesRead, false.WinBool)
if not overlappedRes.bool:
let err = osLastError()
if err.int32 == ERROR_HANDLE_EOF:
@@ -282,7 +282,7 @@ proc write*(f: AsyncFile, data: string): Future[void] =
# Request completed immediately.
var bytesWritten: DWord
let overlappedRes = getOverlappedResult(f.fd.Handle,
cast[POverlapped](ol)[], bytesWritten, false.WinBool)
cast[POverlapped](ol), bytesWritten, false.WinBool)
if not overlappedRes.bool:
retFuture.fail(newException(OSError, osErrorMsg(osLastError())))
else:
@@ -316,6 +316,7 @@ proc write*(f: AsyncFile, data: string): Future[void] =
proc close*(f: AsyncFile) =
## Closes the file specified.
unregister(f.fd)
when defined(windows) or defined(nimdoc):
if not closeHandle(f.fd.Handle).bool:
raiseOSError(osLastError())

View File

@@ -25,12 +25,21 @@
##
## waitFor server.serve(Port(8080), cb)
import strtabs, asyncnet, asyncdispatch, parseutils, uri, strutils
import tables, asyncnet, asyncdispatch, parseutils, uri, strutils
import httpcore
export httpcore except parseHeader
# TODO: If it turns out that the decisions that asynchttpserver makes
# explicitly, about whether to close the client sockets or upgrade them are
# wrong, then add a return value which determines what to do for the callback.
# Also, maybe move `client` out of `Request` object and into the args for
# the proc.
type
Request* = object
client*: AsyncSocket # TODO: Separate this into a Response object?
reqMethod*: string
headers*: StringTableRef
headers*: HttpHeaders
protocol*: tuple[orig: string, major, minor: int]
url*: Uri
hostname*: string ## The hostname of the client that made the request.
@@ -39,83 +48,29 @@ type
AsyncHttpServer* = ref object
socket: AsyncSocket
reuseAddr: bool
HttpCode* = enum
Http100 = "100 Continue",
Http101 = "101 Switching Protocols",
Http200 = "200 OK",
Http201 = "201 Created",
Http202 = "202 Accepted",
Http204 = "204 No Content",
Http205 = "205 Reset Content",
Http206 = "206 Partial Content",
Http300 = "300 Multiple Choices",
Http301 = "301 Moved Permanently",
Http302 = "302 Found",
Http303 = "303 See Other",
Http304 = "304 Not Modified",
Http305 = "305 Use Proxy",
Http307 = "307 Temporary Redirect",
Http400 = "400 Bad Request",
Http401 = "401 Unauthorized",
Http403 = "403 Forbidden",
Http404 = "404 Not Found",
Http405 = "405 Method Not Allowed",
Http406 = "406 Not Acceptable",
Http407 = "407 Proxy Authentication Required",
Http408 = "408 Request Timeout",
Http409 = "409 Conflict",
Http410 = "410 Gone",
Http411 = "411 Length Required",
Http412 = "412 Precondition Failed",
Http413 = "413 Request Entity Too Large",
Http414 = "414 Request-URI Too Long",
Http415 = "415 Unsupported Media Type",
Http416 = "416 Requested Range Not Satisfiable",
Http417 = "417 Expectation Failed",
Http418 = "418 I'm a teapot",
Http500 = "500 Internal Server Error",
Http501 = "501 Not Implemented",
Http502 = "502 Bad Gateway",
Http503 = "503 Service Unavailable",
Http504 = "504 Gateway Timeout",
Http505 = "505 HTTP Version Not Supported"
HttpVersion* = enum
HttpVer11,
HttpVer10
reusePort: bool
{.deprecated: [TRequest: Request, PAsyncHttpServer: AsyncHttpServer,
THttpCode: HttpCode, THttpVersion: HttpVersion].}
proc `==`*(protocol: tuple[orig: string, major, minor: int],
ver: HttpVersion): bool =
let major =
case ver
of HttpVer11, HttpVer10: 1
let minor =
case ver
of HttpVer11: 1
of HttpVer10: 0
result = protocol.major == major and protocol.minor == minor
proc newAsyncHttpServer*(reuseAddr = true): AsyncHttpServer =
proc newAsyncHttpServer*(reuseAddr = true, reusePort = false): AsyncHttpServer =
## Creates a new ``AsyncHttpServer`` instance.
new result
result.reuseAddr = reuseAddr
result.reusePort = reusePort
proc addHeaders(msg: var string, headers: StringTableRef) =
proc addHeaders(msg: var string, headers: HttpHeaders) =
for k, v in headers:
msg.add(k & ": " & v & "\c\L")
proc sendHeaders*(req: Request, headers: StringTableRef): Future[void] =
proc sendHeaders*(req: Request, headers: HttpHeaders): Future[void] =
## Sends the specified headers to the requesting client.
var msg = ""
addHeaders(msg, headers)
return req.client.send(msg)
proc respond*(req: Request, code: HttpCode, content: string,
headers: StringTableRef = nil): Future[void] =
headers: HttpHeaders = nil): Future[void] =
## Responds to the request with the specified ``HttpCode``, headers and
## content.
##
@@ -128,16 +83,6 @@ proc respond*(req: Request, code: HttpCode, content: string,
msg.add(content)
result = req.client.send(msg)
proc parseHeader(line: string): tuple[key, value: string] =
var i = 0
i = line.parseUntil(result.key, ':')
inc(i) # skip :
if i < len(line):
i += line.skipWhiteSpace(i)
i += line.parseUntil(result.value, {'\c', '\L'}, i)
else:
result.value = ""
proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] =
var i = protocol.skipIgnoreCase("HTTP/")
if i != 5:
@@ -156,7 +101,7 @@ proc processClient(client: AsyncSocket, address: string,
Future[void] {.closure, gcsafe.}) {.async.} =
var request: Request
request.url = initUri()
request.headers = newStringTable(modeCaseInsensitive)
request.headers = newHttpHeaders()
var lineFut = newFutureVar[string]("asynchttpserver.processClient")
lineFut.mget() = newStringOfCap(80)
var key, value = ""
@@ -165,7 +110,7 @@ proc processClient(client: AsyncSocket, address: string,
# GET /path HTTP/1.1
# Header: val
# \n
request.headers.clear(modeCaseInsensitive)
request.headers.clear()
request.body = ""
request.hostname.shallowCopy(address)
assert client != nil
@@ -208,29 +153,34 @@ proc processClient(client: AsyncSocket, address: string,
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 == "post":
# Check for Expect header
if request.headers.hasKey("Expect"):
if request.headers.getOrDefault("Expect").toLower == "100-continue":
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.getOrDefault("Content-Length"),
contentLength) == 0:
await request.respond(Http400, "Bad Request. Invalid Content-Length.")
continue
else:
request.body = await client.recv(contentLength)
assert request.body.len == contentLength
else:
await request.respond(Http400, "Bad Request. No Content-Length.")
# 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)
assert request.body.len == contentLength
elif request.reqMethod == "post":
await request.respond(Http400, "Bad Request. No Content-Length.")
continue
case request.reqMethod
of "get", "post", "head", "put", "delete", "trace", "options",
@@ -240,6 +190,9 @@ proc processClient(client: AsyncSocket, address: string,
await request.respond(Http400, "Invalid request method. Got: " &
request.reqMethod)
if "upgrade" in request.headers.getOrDefault("connection"):
return
# Persistent connections
if (request.protocol == HttpVer11 and
request.headers.getOrDefault("connection").normalize != "close") or
@@ -264,6 +217,8 @@ proc serve*(server: AsyncHttpServer, port: Port,
server.socket = newAsyncSocket()
if server.reuseAddr:
server.socket.setSockOpt(OptReuseAddr, true)
if server.reusePort:
server.socket.setSockOpt(OptReusePort, true)
server.socket.bindAddr(port, address)
server.socket.listen()
@@ -287,7 +242,7 @@ when not defined(testing) and isMainModule:
#echo(req.headers)
let headers = {"Date": "Tue, 29 Apr 2014 23:40:08 GMT",
"Content-type": "text/plain; charset=utf-8"}
await req.respond(Http200, "Hello World", headers.newStringTable())
await req.respond(Http200, "Hello World", headers.newHttpHeaders())
asyncCheck server.serve(Port(5555), cb)
runForever()

View File

@@ -11,7 +11,7 @@
## asynchronous dispatcher defined in the ``asyncdispatch`` module.
##
## SSL
## ---
## ----
##
## SSL can be enabled by compiling with the ``-d:ssl`` flag.
##
@@ -62,7 +62,9 @@ import os
export SOBool
when defined(ssl):
const defineSsl = defined(ssl) or defined(nimdoc)
when defineSsl:
import openssl
type
@@ -79,7 +81,7 @@ type
of false: nil
case isSsl: bool
of true:
when defined(ssl):
when defineSsl:
sslHandle: SslPtr
sslContext: SslContext
bioIn: BIO
@@ -125,7 +127,7 @@ proc newAsyncSocket*(domain, sockType, protocol: cint,
Domain(domain), SockType(sockType),
Protocol(protocol), buffered)
when defined(ssl):
when defineSsl:
proc getSslError(handle: SslPtr, err: cint): cint =
assert err < 0
var ret = SSLGetError(handle, err.cint)
@@ -186,7 +188,7 @@ proc connect*(socket: AsyncSocket, address: string, port: Port) {.async.} =
## or an error occurs.
await connect(socket.fd.AsyncFD, address, port, socket.domain)
if socket.isSsl:
when defined(ssl):
when defineSsl:
let flags = {SocketFlag.SafeDisconn}
sslSetConnectState(socket.sslHandle)
sslLoop(socket, flags, sslDoHandshake(socket.sslHandle))
@@ -197,7 +199,7 @@ template readInto(buf: cstring, size: int, socket: AsyncSocket,
## this is a template and not a proc.
var res = 0
if socket.isSsl:
when defined(ssl):
when defineSsl:
# SSL mode.
sslLoop(socket, flags,
sslRead(socket.sslHandle, buf, size.cint))
@@ -274,7 +276,7 @@ proc send*(socket: AsyncSocket, data: string,
## data has been sent.
assert socket != nil
if socket.isSsl:
when defined(ssl):
when defineSsl:
var copy = data
sslLoop(socket, flags,
sslWrite(socket.sslHandle, addr copy[0], copy.len.cint))
@@ -426,9 +428,6 @@ proc recvLine*(socket: AsyncSocket,
##
## **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:
# TODO: Optimise this
@@ -456,7 +455,8 @@ proc bindAddr*(socket: AsyncSocket, port = Port(0), address = "") {.
of AF_INET6: realaddr = "::"
of AF_INET: realaddr = "0.0.0.0"
else:
raiseOSError("Unknown socket address family and no address specified to bindAddr")
raise newException(ValueError,
"Unknown socket address family and no address specified to bindAddr")
var aiList = getAddrInfo(realaddr, port, socket.domain)
if bindAddr(socket.fd, aiList.ai_addr, aiList.ai_addrlen.Socklen) < 0'i32:
@@ -468,7 +468,7 @@ proc close*(socket: AsyncSocket) =
## Closes the socket.
defer:
socket.fd.AsyncFD.closeSocket()
when defined(ssl):
when defineSsl:
if socket.isSSL:
let res = SslShutdown(socket.sslHandle)
SSLFree(socket.sslHandle)
@@ -478,7 +478,7 @@ proc close*(socket: AsyncSocket) =
raiseSslError()
socket.closed = true # TODO: Add extra debugging checks for this.
when defined(ssl):
when defineSsl:
proc wrapSocket*(ctx: SslContext, socket: AsyncSocket) =
## Wraps a socket in an SSL context. This function effectively turns
## ``socket`` into an SSL socket.
@@ -487,7 +487,7 @@ when defined(ssl):
## prone to security vulnerabilities.
socket.isSsl = true
socket.sslContext = ctx
socket.sslHandle = SSLNew(SSLCTX(socket.sslContext))
socket.sslHandle = SSLNew(socket.sslContext.context)
if socket.sslHandle == nil:
raiseSslError()

View File

@@ -90,6 +90,8 @@ template encodeInternal(s: expr, lineLen: int, newLine: string): stmt {.immediat
if r+4 != result.len:
setLen(result, r+4)
else:
if r != result.len:
setLen(result, r)
#assert(r == result.len)
discard
@@ -162,4 +164,3 @@ when isMainModule:
"asure.", longText]
for t in items(tests):
assert decode(encode(t)) == t

View File

@@ -52,7 +52,7 @@ when sizeof(int) == 4: # 32bit
{.deprecated: [TRaw: Raw].}
elif sizeof(int) == 8: # 64bit
type
Raw = range[0..4611686018427387903]
Raw = range[0'i64..4611686018427387903'i64]
## The range of uint values that can be stored directly in a value slot
## when on a 64 bit platform
{.deprecated: [TRaw: Raw].}
@@ -74,7 +74,7 @@ type
copyDone: int
next: PConcTable[K,V]
data: EntryArr
{.deprecated: [TEntry: Entry, TEntryArr: EntryArr.}
{.deprecated: [TEntry: Entry, TEntryArr: EntryArr].}
proc setVal[K,V](table: var PConcTable[K,V], key: int, val: int,
expVal: int, match: bool): int
@@ -244,9 +244,9 @@ proc copySlot[K,V](idx: int, oldTbl: var PConcTable[K,V], newTbl: var PConcTable
echo("oldVal is Tomb!!!, should not happen")
if pop(oldVal) != 0:
result = setVal(newTbl, pop(oldKey), pop(oldVal), 0, true) == 0
if result:
#if result:
#echo("Copied a Slot! idx= " & $idx & " key= " & $oldKey & " val= " & $oldVal)
else:
#else:
#echo("copy slot failed")
# Our copy is done so we disable the old slot
while not ok:

View File

@@ -0,0 +1,44 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2016 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Template based implementation of singly and doubly linked lists.
## The involved types should have 'prev' or 'next' fields and the
## list header should have 'head' or 'tail' fields.
template prepend*(header, node) =
when compiles(header.head):
when compiles(node.prev):
if header.head != nil:
header.head.prev = node
node.next = header.head
header.head = node
when compiles(header.tail):
if header.tail == nil:
header.tail = node
template append*(header, node) =
when compiles(header.head):
if header.head == nil:
header.head = node
when compiles(header.tail):
when compiles(node.prev):
node.prev = header.tail
if header.tail != nil:
header.tail.next = node
header.tail = node
template unlink*(header, node) =
if node.next != nil:
node.next.prev = node.prev
if node.prev != nil:
node.prev.next = node.next
if header.head == node:
header.head = node.prev
if header.tail == node:
header.tail = node.next

View File

@@ -0,0 +1,107 @@
##[ Heap queue algorithm (a.k.a. priority queue). Ported from Python heapq.
Heaps are arrays for which a[k] <= a[2*k+1] and a[k] <= a[2*k+2] for
all k, counting elements from 0. For the sake of comparison,
non-existing elements are considered to be infinite. The interesting
property of a heap is that a[0] is always its smallest element.
]##
type HeapQueue*[T] = distinct seq[T]
proc newHeapQueue*[T](): HeapQueue[T] {.inline.} = HeapQueue[T](newSeq[T]())
proc newHeapQueue*[T](h: var HeapQueue[T]) {.inline.} = h = HeapQueue[T](newSeq[T]())
proc len*[T](h: HeapQueue[T]): int {.inline.} = seq[T](h).len
proc `[]`*[T](h: HeapQueue[T], i: int): T {.inline.} = seq[T](h)[i]
proc `[]=`[T](h: var HeapQueue[T], i: int, v: T) {.inline.} = seq[T](h)[i] = v
proc add[T](h: var HeapQueue[T], v: T) {.inline.} = seq[T](h).add(v)
proc heapCmp[T](x, y: T): bool {.inline.} =
return (x < y)
# 'heap' is a heap at all indices >= startpos, except possibly for pos. pos
# is the index of a leaf with a possibly out-of-order value. Restore the
# heap invariant.
proc siftdown[T](heap: var HeapQueue[T], startpos, p: int) =
var pos = p
var newitem = heap[pos]
# Follow the path to the root, moving parents down until finding a place
# newitem fits.
while pos > startpos:
let parentpos = (pos - 1) shr 1
let parent = heap[parentpos]
if heapCmp(newitem, parent):
heap[pos] = parent
pos = parentpos
else:
break
heap[pos] = newitem
proc siftup[T](heap: var HeapQueue[T], p: int) =
let endpos = len(heap)
var pos = p
let startpos = pos
let newitem = heap[pos]
# Bubble up the smaller child until hitting a leaf.
var childpos = 2*pos + 1 # leftmost child position
while childpos < endpos:
# Set childpos to index of smaller child.
let rightpos = childpos + 1
if rightpos < endpos and not heapCmp(heap[childpos], heap[rightpos]):
childpos = rightpos
# Move the smaller child up.
heap[pos] = heap[childpos]
pos = childpos
childpos = 2*pos + 1
# The leaf at pos is empty now. Put newitem there, and bubble it up
# to its final resting place (by sifting its parents down).
heap[pos] = newitem
siftdown(heap, startpos, pos)
proc push*[T](heap: var HeapQueue[T], item: T) =
## Push item onto heap, maintaining the heap invariant.
(seq[T](heap)).add(item)
siftdown(heap, 0, len(heap)-1)
proc pop*[T](heap: var HeapQueue[T]): T =
## Pop the smallest item off the heap, maintaining the heap invariant.
let lastelt = seq[T](heap).pop()
if heap.len > 0:
result = heap[0]
heap[0] = lastelt
siftup(heap, 0)
else:
result = lastelt
proc replace*[T](heap: var HeapQueue[T], item: T): T =
## Pop and return the current smallest value, and add the new item.
## This is more efficient than pop() followed by push(), and can be
## more appropriate when using a fixed-size heap. Note that the value
## returned may be larger than item! That constrains reasonable uses of
## this routine unless written as part of a conditional replacement:
## if item > heap[0]:
## item = replace(heap, item)
result = heap[0]
heap[0] = item
siftup(heap, 0)
proc pushpop*[T](heap: var HeapQueue[T], item: T): T =
## Fast version of a push followed by a pop.
if heap.len > 0 and heapCmp(heap[0], item):
swap(item, heap[0])
siftup(heap, 0)
return item
when isMainModule:
# Simple sanity test
var heap = newHeapQueue[int]()
let data = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
for item in data:
push(heap, item)
doAssert(heap[0] == 0)
var sort = newSeq[int]()
while heap.len > 0:
sort.add(pop(heap))
doAssert(sort == @[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

View File

@@ -8,56 +8,142 @@
#
## Implementation of a `queue`:idx:. The underlying implementation uses a ``seq``.
##
## None of the procs that get an individual value from the queue can be used
## on an empty queue.
## If compiled with `boundChecks` option, those procs will raise an `IndexError`
## on such access. This should not be relied upon, as `-d:release` will
## disable those checks and may return garbage or crash the program.
##
## As such, a check to see if the queue is empty is needed before any
## access, unless your program logic guarantees it indirectly.
##
## .. code-block:: Nim
## proc foo(a, b: Positive) = # assume random positive values for `a` and `b`
## var q = initQueue[int]() # initializes the object
## for i in 1 ..< a: q.add i # populates the queue
##
## if b < q.len: # checking before indexed access
## echo "The element at index position ", b, " is ", q[b]
##
## # The following two lines don't need any checking on access due to the
## # logic of the program, but that would not be the case if `a` could be 0.
## assert q.front == 1
## assert q.back == a
##
## while q.len > 0: # checking if the queue is empty
## echo q.pop()
##
## Note: For inter thread communication use
## a `Channel <channels.html>`_ instead.
import math
type
Queue*[T] = object ## a queue
Queue*[T] = object ## A queue.
data: seq[T]
rd, wr, count, mask: int
{.deprecated: [TQueue: Queue].}
proc initQueue*[T](initialSize=4): Queue[T] =
## creates a new queue. `initialSize` needs to be a power of 2.
proc initQueue*[T](initialSize: int = 4): Queue[T] =
## Create a new queue.
## Optionally, the initial capacity can be reserved via `initialSize` as a
## performance optimization. The length of a newly created queue will still
## be 0.
##
## `initialSize` needs to be a power of two. If you need to accept runtime
## values for this you could use the ``nextPowerOfTwo`` proc from the
## `math <math.html>`_ module.
assert isPowerOfTwo(initialSize)
result.mask = initialSize-1
newSeq(result.data, initialSize)
proc len*[T](q: Queue[T]): int =
## returns the number of elements of `q`.
proc len*[T](q: Queue[T]): int {.inline.}=
## Return the number of elements of `q`.
result = q.count
template emptyCheck(q) =
# Bounds check for the regular queue access.
when compileOption("boundChecks"):
if unlikely(q.count < 1):
raise newException(IndexError, "Empty queue.")
template xBoundsCheck(q, i) =
# Bounds check for the array like accesses.
when compileOption("boundChecks"): # d:release should disable this.
if unlikely(i >= q.count): # x < q.low is taken care by the Natural parameter
raise newException(IndexError,
"Out of bounds: " & $i & " > " & $(q.count - 1))
proc front*[T](q: Queue[T]): T {.inline.}=
## Return the oldest element of `q`. Equivalent to `q.pop()` but does not
## remove it from the queue.
emptyCheck(q)
result = q.data[q.rd]
proc back*[T](q: Queue[T]): T {.inline.} =
## Return the newest element of `q` but does not remove it from the queue.
emptyCheck(q)
result = q.data[q.wr - 1 and q.mask]
proc `[]`*[T](q: Queue[T], i: Natural) : T {.inline.} =
## Access the i-th element of `q` by order of insertion.
## q[0] is the oldest (the next one q.pop() will extract),
## q[^1] is the newest (last one added to the queue).
xBoundsCheck(q, i)
return q.data[q.rd + i and q.mask]
proc `[]`*[T](q: var Queue[T], i: Natural): var T {.inline.} =
## Access the i-th element of `q` and returns a mutable
## reference to it.
xBoundsCheck(q, i)
return q.data[q.rd + i and q.mask]
proc `[]=`* [T] (q: var Queue[T], i: Natural, val : T) {.inline.} =
## Change the i-th element of `q`.
xBoundsCheck(q, i)
q.data[q.rd + i and q.mask] = val
iterator items*[T](q: Queue[T]): T =
## yields every element of `q`.
## Yield every element of `q`.
var i = q.rd
var c = q.count
while c > 0:
dec c
for c in 0 ..< q.count:
yield q.data[i]
i = (i + 1) and q.mask
iterator mitems*[T](q: var Queue[T]): var T =
## yields every element of `q`.
## Yield every element of `q`.
var i = q.rd
var c = q.count
while c > 0:
dec c
for c in 0 ..< q.count:
yield q.data[i]
i = (i + 1) and q.mask
iterator pairs*[T](q: Queue[T]): tuple[key: int, val: T] =
## Yield every (position, value) of `q`.
var i = q.rd
for c in 0 ..< q.count:
yield (c, q.data[i])
i = (i + 1) and q.mask
proc contains*[T](q: Queue[T], item: T): bool {.inline.} =
## Return true if `item` is in `q` or false if not found. Usually used
## via the ``in`` operator. It is the equivalent of ``q.find(item) >= 0``.
##
## .. code-block:: Nim
## if x in q:
## assert q.contains x
for e in q:
if e == item: return true
return false
proc add*[T](q: var Queue[T], item: T) =
## adds an `item` to the end of the queue `q`.
## Add an `item` to the end of the queue `q`.
var cap = q.mask+1
if q.count >= cap:
var n: seq[T]
newSeq(n, cap*2)
var i = 0
for x in items(q):
if unlikely(q.count >= cap):
var n = newSeq[T](cap*2)
for i, x in q: # don't use copyMem because the GC and because it's slower.
shallowCopy(n[i], x)
inc i
shallowCopy(q.data, n)
q.mask = cap*2 - 1
q.wr = q.count
@@ -66,37 +152,104 @@ proc add*[T](q: var Queue[T], item: T) =
q.data[q.wr] = item
q.wr = (q.wr + 1) and q.mask
proc enqueue*[T](q: var Queue[T], item: T) =
## alias for the ``add`` operation.
add(q, item)
proc dequeue*[T](q: var Queue[T]): T =
## removes and returns the first element of the queue `q`.
assert q.count > 0
proc default[T](t: typedesc[T]): T {.inline.} = discard
proc pop*[T](q: var Queue[T]): T {.inline, discardable.} =
## Remove and returns the first (oldest) element of the queue `q`.
emptyCheck(q)
dec q.count
result = q.data[q.rd]
q.data[q.rd] = default(type(result))
q.rd = (q.rd + 1) and q.mask
proc enqueue*[T](q: var Queue[T], item: T) =
## Alias for the ``add`` operation.
q.add(item)
proc dequeue*[T](q: var Queue[T]): T =
## Alias for the ``pop`` operation.
q.pop()
proc `$`*[T](q: Queue[T]): string =
## turns a queue into its string representation.
## Turn a queue into its string representation.
result = "["
for x in items(q):
for x in items(q): # Don't remove the items here for reasons that don't fit in this margin.
if result.len > 1: result.add(", ")
result.add($x)
result.add("]")
when isMainModule:
var q = initQueue[int]()
var q = initQueue[int](1)
q.add(123)
q.add(9)
q.add(4)
var first = q.dequeue
q.enqueue(4)
var first = q.dequeue()
q.add(56)
q.add(6)
var second = q.dequeue
var second = q.pop()
q.add(789)
assert first == 123
assert second == 9
assert($q == "[4, 56, 6, 789]")
assert q[0] == q.front and q.front == 4
assert q[^1] == q.back and q.back == 789
q[0] = 42
q[^1] = 7
assert 6 in q and 789 notin q
assert q.find(6) >= 0
assert q.find(789) < 0
for i in -2 .. 10:
if i in q:
assert q.contains(i) and q.find(i) >= 0
else:
assert(not q.contains(i) and q.find(i) < 0)
when compileOption("boundChecks"):
try:
echo q[99]
assert false
except IndexError:
discard
try:
assert q.len == 4
for i in 0 ..< 5: q.pop()
assert false
except IndexError:
discard
# grabs some types of resize error.
q = initQueue[int]()
for i in 1 .. 4: q.add i
q.pop()
q.pop()
for i in 5 .. 8: q.add i
assert $q == "[3, 4, 5, 6, 7, 8]"
# Similar to proc from the documentation example
proc foo(a, b: Positive) = # assume random positive values for `a` and `b`.
var q = initQueue[int]()
assert q.len == 0
for i in 1 .. a: q.add i
if b < q.len: # checking before indexed access.
assert q[b] == b + 1
# The following two lines don't need any checking on access due to the logic
# of the program, but that would not be the case if `a` could be 0.
assert q.front == 1
assert q.back == a
while q.len > 0: # checking if the queue is empty
assert q.pop() > 0
#foo(0,0)
foo(8,5)
foo(10,9)
foo(1,1)
foo(2,1)
foo(1,5)
foo(3,2)

View File

@@ -18,7 +18,7 @@ type
RtArray*[T] = object ##
L: Natural
spart: seq[T]
apart: array [ArrayPartSize, T]
apart: array[ArrayPartSize, T]
UncheckedArray* {.unchecked.}[T] = array[0..100_000_000, T]
template usesSeqPart(x): expr = x.L > ArrayPartSize

View File

@@ -20,6 +20,8 @@
## **Note**: This interface will change as soon as the compiler supports
## closures and proper coroutines.
include "system/inclrtl"
when not defined(nimhygiene):
{.pragma: dirty.}
@@ -355,7 +357,7 @@ proc insert*[T](dest: var seq[T], src: openArray[T], pos=0) =
inc(j)
template filterIt*(seq1, pred: expr): expr =
template filterIt*(seq1, pred: untyped): untyped =
## Returns a new sequence with all the items that fulfilled the predicate.
##
## Unlike the `proc` version, the predicate needs to be an expression using
@@ -369,12 +371,12 @@ template filterIt*(seq1, pred: expr): expr =
## notAcceptable = filterIt(temperatures, it > 50 or it < -10)
## assert acceptable == @[-2.0, 24.5, 44.31]
## assert notAcceptable == @[-272.15, 99.9, -113.44]
var result {.gensym.} = newSeq[type(seq1[0])]()
var result = newSeq[type(seq1[0])]()
for it {.inject.} in items(seq1):
if pred: result.add(it)
result
template keepItIf*(varSeq: seq, pred: expr) =
template keepItIf*(varSeq: seq, pred: untyped) =
## Convenience template around the ``keepIf`` proc to reduce typing.
##
## Unlike the `proc` version, the predicate needs to be an expression using
@@ -409,7 +411,7 @@ proc all*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): bool =
return false
return true
template allIt*(seq1, pred: expr): bool {.immediate.} =
template allIt*(seq1, pred: untyped): bool =
## Checks if every item fulfills the predicate.
##
## Example:
@@ -418,7 +420,7 @@ template allIt*(seq1, pred: expr): bool {.immediate.} =
## let numbers = @[1, 4, 5, 8, 9, 7, 4]
## assert allIt(numbers, it < 10) == true
## assert allIt(numbers, it < 9) == false
var result {.gensym.} = true
var result = true
for it {.inject.} in items(seq1):
if not pred:
result = false
@@ -440,7 +442,7 @@ proc any*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): bool =
return true
return false
template anyIt*(seq1, pred: expr): bool {.immediate.} =
template anyIt*(seq1, pred: untyped): bool =
## Checks if some item fulfills the predicate.
##
## Example:
@@ -449,14 +451,14 @@ template anyIt*(seq1, pred: expr): bool {.immediate.} =
## let numbers = @[1, 4, 5, 8, 9, 7, 4]
## assert anyIt(numbers, it > 8) == true
## assert anyIt(numbers, it > 9) == false
var result {.gensym.} = false
var result = false
for it {.inject.} in items(seq1):
if pred:
result = true
break
result
template toSeq*(iter: expr): expr {.immediate.} =
template toSeq*(iter: untyped): untyped {.oldimmediate.} =
## Transforms any iterator into a sequence.
##
## Example:
@@ -482,7 +484,7 @@ template toSeq*(iter: expr): expr {.immediate.} =
result.add(x)
result
template foldl*(sequence, operation: expr): expr =
template foldl*(sequence, operation: untyped): untyped =
## Template to fold a sequence from left to right, returning the accumulation.
##
## The sequence is required to have at least a single element. Debug versions
@@ -510,7 +512,7 @@ template foldl*(sequence, operation: expr): expr =
## assert concatenation == "nimiscool"
let s = sequence
assert s.len > 0, "Can't fold empty sequences"
var result {.gensym.}: type(s[0])
var result: type(s[0])
result = s[0]
for i in 1..<s.len:
let
@@ -519,7 +521,7 @@ template foldl*(sequence, operation: expr): expr =
result = operation
result
template foldl*(sequence, operation: expr, first): expr =
template foldl*(sequence, operation, first): untyped =
## Template to fold a sequence from left to right, returning the accumulation.
##
## This version of ``foldl`` gets a starting parameter. This makes it possible
@@ -535,7 +537,7 @@ template foldl*(sequence, operation: expr, first): expr =
## numbers = @[0, 8, 1, 5]
## digits = foldl(numbers, a & (chr(b + ord('0'))), "")
## assert digits == "0815"
var result {.gensym.}: type(first)
var result: type(first)
result = first
for x in items(sequence):
let
@@ -544,7 +546,7 @@ template foldl*(sequence, operation: expr, first): expr =
result = operation
result
template foldr*(sequence, operation: expr): expr =
template foldr*(sequence, operation: untyped): untyped =
## Template to fold a sequence from right to left, returning the accumulation.
##
## The sequence is required to have at least a single element. Debug versions
@@ -572,7 +574,7 @@ template foldr*(sequence, operation: expr): expr =
## assert concatenation == "nimiscool"
let s = sequence
assert s.len > 0, "Can't fold empty sequences"
var result {.gensym.}: type(s[0])
var result: type(s[0])
result = sequence[s.len - 1]
for i in countdown(s.len - 2, 0):
let
@@ -581,7 +583,7 @@ template foldr*(sequence, operation: expr): expr =
result = operation
result
template mapIt*(seq1, typ, op: expr): expr {.deprecated.}=
template mapIt*(seq1, typ, op: untyped): untyped =
## Convenience template around the ``map`` proc to reduce typing.
##
## The template injects the ``it`` variable which you can use directly in an
@@ -596,13 +598,13 @@ template mapIt*(seq1, typ, op: expr): expr {.deprecated.}=
## assert strings == @["4", "8", "12", "16"]
## **Deprecated since version 0.12.0:** Use the ``mapIt(seq1, op)``
## template instead.
var result {.gensym.}: seq[typ] = @[]
var result: seq[typ] = @[]
for it {.inject.} in items(seq1):
result.add(op)
result
template mapIt*(seq1, op: expr): expr =
template mapIt*(seq1, op: untyped): untyped =
## Convenience template around the ``map`` proc to reduce typing.
##
## The template injects the ``it`` variable which you can use directly in an
@@ -631,7 +633,7 @@ template mapIt*(seq1, op: expr): expr =
result.add(op)
result
template applyIt*(varSeq, op: expr) =
template applyIt*(varSeq, op: untyped) =
## Convenience template around the mutable ``apply`` proc to reduce typing.
##
## The template injects the ``it`` variable which you can use directly in an
@@ -648,7 +650,7 @@ template applyIt*(varSeq, op: expr) =
template newSeqWith*(len: int, init: expr): expr =
template newSeqWith*(len: int, init: untyped): untyped =
## creates a new sequence, calling `init` to initialize each value. Example:
##
## .. code-block::
@@ -657,10 +659,10 @@ template newSeqWith*(len: int, init: expr): expr =
## seq2D[1][0] = true
## seq2D[0][1] = true
##
## import math
## import random
## var seqRand = newSeqWith(20, random(10))
## echo seqRand
var result {.gensym.} = newSeq[type(init)](len)
var result = newSeq[type(init)](len)
for i in 0 .. <len:
result[i] = init
result

View File

@@ -58,7 +58,7 @@ proc isValid*[A](s: HashSet[A]): bool =
## initialized. Example:
##
## .. code-block ::
## proc savePreferences(options: Set[string]) =
## proc savePreferences(options: HashSet[string]) =
## assert options.isValid, "Pass an initialized set!"
## # Do stuff here, may crash in release builds!
result = not s.data.isNil
@@ -72,7 +72,7 @@ proc len*[A](s: HashSet[A]): int =
##
## .. code-block::
##
## var values: Set[int]
## var values: HashSet[int]
## assert(not values.isValid)
## assert values.len == 0
result = s.counter
@@ -256,11 +256,13 @@ proc incl*[A](s: var HashSet[A], other: HashSet[A]) =
assert other.isValid, "The set `other` needs to be initialized."
for item in other: incl(s, item)
template doWhile(a: expr, b: stmt): stmt =
template doWhile(a, b) =
while true:
b
if not a: break
proc default[T](t: typedesc[T]): T {.inline.} = discard
proc excl*[A](s: var HashSet[A], key: A) =
## Excludes `key` from the set `s`.
##
@@ -277,12 +279,14 @@ proc excl*[A](s: var HashSet[A], key: A) =
var msk = high(s.data)
if i >= 0:
s.data[i].hcode = 0
s.data[i].key = default(type(s.data[i].key))
dec(s.counter)
while true: # KnuthV3 Algo6.4R adapted for i=i+1 instead of i=i-1
var j = i # The correctness of this depends on (h+1) in nextTry,
var r = j # though may be adaptable to other simple sequences.
s.data[i].hcode = 0 # mark current EMPTY
doWhile ((i >= r and r > j) or (r > j and j > i) or (j > i and i >= r)):
s.data[i].key = default(type(s.data[i].key))
doWhile((i >= r and r > j) or (r > j and j > i) or (j > i and i >= r)):
i = (i + 1) and msk # increment mod table size
if isEmpty(s.data[i].hcode): # end of collision cluster; So all done
return
@@ -334,7 +338,7 @@ proc init*[A](s: var HashSet[A], initialSize=64) =
## existing values and calling `excl() <#excl,TSet[A],A>`_ on them. Example:
##
## .. code-block ::
## var a: Set[int]
## var a: HashSet[int]
## a.init(4)
## a.incl(2)
## a.init
@@ -367,7 +371,7 @@ proc toSet*[A](keys: openArray[A]): HashSet[A] =
result = initSet[A](rightSize(keys.len))
for key in items(keys): result.incl(key)
template dollarImpl(): stmt {.dirty.} =
template dollarImpl() {.dirty.} =
result = "{"
for key in items(s):
if result.len > 1: result.add(", ")
@@ -611,7 +615,7 @@ proc card*[A](s: OrderedSet[A]): int {.inline.} =
## <http://en.wikipedia.org/wiki/Cardinality>`_ of a set.
result = s.counter
template forAllOrderedPairs(yieldStmt: stmt) {.dirty, immediate.} =
template forAllOrderedPairs(yieldStmt: untyped) {.dirty.} =
var h = s.first
while h >= 0:
var nxt = s.data[h].next

View File

@@ -45,6 +45,59 @@ template withLock(t, x: untyped) =
x
release(t.lock)
template withValue*[A, B](t: var SharedTable[A, B], key: A,
value, body: untyped) =
## retrieves the value at ``t[key]``.
## `value` can be modified in the scope of the ``withValue`` call.
##
## .. code-block:: nim
##
## sharedTable.withValue(key, value) do:
## # block is executed only if ``key`` in ``t``
## # value is threadsafe in block
## value.name = "username"
## value.uid = 1000
##
acquire(t.lock)
try:
var hc: Hash
var index = rawGet(t, key, hc)
let hasKey = index >= 0
if hasKey:
var value {.inject.} = addr(t.data[index].val)
body
finally:
release(t.lock)
template withValue*[A, B](t: var SharedTable[A, B], key: A,
value, body1, body2: untyped) =
## retrieves the value at ``t[key]``.
## `value` can be modified in the scope of the ``withValue`` call.
##
## .. code-block:: nim
##
## sharedTable.withValue(key, value) do:
## # block is executed only if ``key`` in ``t``
## # value is threadsafe in block
## value.name = "username"
## value.uid = 1000
## do:
## # block is executed when ``key`` not in ``t``
## raise newException(KeyError, "Key not found")
##
acquire(t.lock)
try:
var hc: Hash
var index = rawGet(t, key, hc)
let hasKey = index >= 0
if hasKey:
var value {.inject.} = addr(t.data[index].val)
body1
else:
body2
finally:
release(t.lock)
proc mget*[A, B](t: var SharedTable[A, B], key: A): var B =
## retrieves the value at ``t[key]``. The value can be modified.
## If `key` is not in `t`, the ``KeyError`` exception is raised.

View File

@@ -72,14 +72,14 @@ proc rawInsert[X, A, B](t: var X, data: var KeyValuePairSeq[A, B],
key: A, val: B, hc: Hash, h: Hash) =
rawInsertImpl()
template addImpl(enlarge) {.dirty, immediate.} =
template addImpl(enlarge) {.dirty.} =
if mustRehash(t.dataLen, t.counter): enlarge(t)
var hc: Hash
var j = rawGetDeep(t, key, hc)
rawInsert(t, t.data, key, val, hc, j)
inc(t.counter)
template maybeRehashPutImpl(enlarge) {.dirty, immediate.} =
template maybeRehashPutImpl(enlarge) {.oldimmediate, dirty.} =
if mustRehash(t.dataLen, t.counter):
enlarge(t)
index = rawGetKnownHC(t, key, hc)
@@ -87,13 +87,13 @@ template maybeRehashPutImpl(enlarge) {.dirty, immediate.} =
rawInsert(t, t.data, key, val, hc, index)
inc(t.counter)
template putImpl(enlarge) {.dirty, immediate.} =
template putImpl(enlarge) {.oldimmediate, dirty.} =
var hc: Hash
var index = rawGet(t, key, hc)
if index >= 0: t.data[index].val = val
else: maybeRehashPutImpl(enlarge)
template mgetOrPutImpl(enlarge) {.dirty, immediate.} =
template mgetOrPutImpl(enlarge) {.dirty.} =
var hc: Hash
var index = rawGet(t, key, hc)
if index < 0:
@@ -102,7 +102,7 @@ template mgetOrPutImpl(enlarge) {.dirty, immediate.} =
# either way return modifiable val
result = t.data[index].val
template hasKeyOrPutImpl(enlarge) {.dirty, immediate.} =
template hasKeyOrPutImpl(enlarge) {.dirty.} =
var hc: Hash
var index = rawGet(t, key, hc)
if index < 0:
@@ -110,18 +110,24 @@ template hasKeyOrPutImpl(enlarge) {.dirty, immediate.} =
maybeRehashPutImpl(enlarge)
else: result = true
template delImpl() {.dirty, immediate.} =
proc default[T](t: typedesc[T]): T {.inline.} = discard
template delImpl() {.dirty.} =
var hc: Hash
var i = rawGet(t, key, hc)
let msk = maxHash(t)
if i >= 0:
t.data[i].hcode = 0
t.data[i].key = default(type(t.data[i].key))
t.data[i].val = default(type(t.data[i].val))
dec(t.counter)
block outer:
while true: # KnuthV3 Algo6.4R adapted for i=i+1 instead of i=i-1
var j = i # The correctness of this depends on (h+1) in nextTry,
var r = j # though may be adaptable to other simple sequences.
t.data[i].hcode = 0 # mark current EMPTY
t.data[i].key = default(type(t.data[i].key))
t.data[i].val = default(type(t.data[i].val))
while true:
i = (i + 1) and msk # increment mod table size
if isEmpty(t.data[i].hcode): # end of collision cluster; So all done
@@ -133,3 +139,10 @@ template delImpl() {.dirty, immediate.} =
t.data[j] = t.data[i]
else:
shallowCopy(t.data[j], t.data[i]) # data[j] will be marked EMPTY next loop
template clearImpl() {.dirty.} =
for i in 0 .. <t.data.len:
t.data[i].hcode = 0
t.data[i].key = default(type(t.data[i].key))
t.data[i].val = default(type(t.data[i].val))
t.counter = 0

View File

@@ -16,6 +16,39 @@
## semantics, this means that ``=`` performs a copy of the hash table.
## For **reference** semantics use the ``Ref`` variant: ``TableRef``,
## ``OrderedTableRef``, ``CountTableRef``.
## To give an example, when `a` is a Table, then `var b = a` gives `b`
## as a new independent table. b is initialised with the contents of `a`.
## Changing `b` does not affect `a` and vice versa:
##
## .. code-block::
## import tables
##
## var
## a = {1: "one", 2: "two"}.toTable # creates a Table
## b = a
##
## echo a, b # output: {1: one, 2: two}{1: one, 2: two}
##
## b[3] = "three"
## echo a, b # output: {1: one, 2: two}{1: one, 2: two, 3: three}
## echo a == b # output: false
##
## On the other hand, when `a` is a TableRef instead, then changes to `b` also affect `a`.
## Both `a` and `b` reference the same data structure:
##
## .. code-block::
## import tables
##
## var
## a = {1: "one", 2: "two"}.newTable # creates a TableRef
## b = a
##
## echo a, b # output: {1: one, 2: two}{1: one, 2: two}
##
## b[3] = "three"
## echo a, b # output: {1: one, 2: two, 3: three}{1: one, 2: two, 3: three}
## echo a == b # output: true
##
##
## If you are using simple standard types like ``int`` or ``string`` for the
## keys of the table you won't have any problems, but as soon as you try to use
@@ -80,11 +113,15 @@ type
{.deprecated: [TTable: Table, PTable: TableRef].}
template maxHash(t): expr {.immediate.} = high(t.data)
template dataLen(t): expr = len(t.data)
template maxHash(t): untyped = high(t.data)
template dataLen(t): untyped = len(t.data)
include tableimpl
proc clear*[A, B](t: Table[A, B] | TableRef[A, B]) =
## Resets the table so that it is empty.
clearImpl()
proc rightSize*(count: Natural): int {.inline.} =
## Return the value of `initialSize` to support `count` items.
##
@@ -98,7 +135,7 @@ proc len*[A, B](t: Table[A, B]): int =
## returns the number of keys in `t`.
result = t.counter
template get(t, key): untyped {.immediate.} =
template get(t, key): untyped =
## retrieves the value at ``t[key]``. The value can be modified.
## If `key` is not in `t`, the ``KeyError`` exception is raised.
mixin rawGet
@@ -111,7 +148,7 @@ template get(t, key): untyped {.immediate.} =
else:
raise newException(KeyError, "key not found")
template getOrDefaultImpl(t, key): untyped {.immediate.} =
template getOrDefaultImpl(t, key): untyped =
mixin rawGet
var hc: Hash
var index = rawGet(t, key, hc)
@@ -136,6 +173,51 @@ proc mget*[A, B](t: var Table[A, B], key: A): var B {.deprecated.} =
proc getOrDefault*[A, B](t: Table[A, B], key: A): B = getOrDefaultImpl(t, key)
template withValue*[A, B](t: var Table[A, B], key: A,
value, body: untyped) =
## retrieves the value at ``t[key]``.
## `value` can be modified in the scope of the ``withValue`` call.
##
## .. code-block:: nim
##
## sharedTable.withValue(key, value) do:
## # block is executed only if ``key`` in ``t``
## value.name = "username"
## value.uid = 1000
##
mixin rawGet
var hc: Hash
var index = rawGet(t, key, hc)
let hasKey = index >= 0
if hasKey:
var value {.inject.} = addr(t.data[index].val)
body
template withValue*[A, B](t: var Table[A, B], key: A,
value, body1, body2: untyped) =
## retrieves the value at ``t[key]``.
## `value` can be modified in the scope of the ``withValue`` call.
##
## .. code-block:: nim
##
## table.withValue(key, value) do:
## # block is executed only if ``key`` in ``t``
## value.name = "username"
## value.uid = 1000
## do:
## # block is executed when ``key`` not in ``t``
## raise newException(KeyError, "Key not found")
##
mixin rawGet
var hc: Hash
var index = rawGet(t, key, hc)
let hasKey = index >= 0
if hasKey:
var value {.inject.} = addr(t.data[index].val)
body1
else:
body2
iterator allValues*[A, B](t: Table[A, B]; key: A): B =
## iterates over any value in the table `t` that belongs to the given `key`.
var h: Hash = hash(key) and high(t.data)
@@ -229,7 +311,7 @@ proc toTable*[A, B](pairs: openArray[(A,
result = initTable[A, B](rightSize(pairs.len))
for key, val in items(pairs): result[key] = val
template dollarImpl(): stmt {.dirty.} =
template dollarImpl(): untyped {.dirty.} =
if t.len == 0:
result = "{:}"
else:
@@ -249,18 +331,17 @@ proc hasKey*[A, B](t: TableRef[A, B], key: A): bool =
## returns true iff `key` is in the table `t`.
result = t[].hasKey(key)
template equalsImpl() =
template equalsImpl(t) =
if s.counter == t.counter:
# different insertion orders mean different 'data' seqs, so we have
# to use the slow route here:
for key, val in s:
# prefix notation leads to automatic dereference in case of PTable
if not t.hasKey(key): return false
if t[key] != val: return false
if t.getOrDefault(key) != val: return false
return true
proc `==`*[A, B](s, t: Table[A, B]): bool =
equalsImpl()
equalsImpl(t)
proc indexBy*[A, B, C](collection: A, index: proc(x: B): C): Table[C, B] =
## Index the collection with the proc provided.
@@ -350,7 +431,7 @@ proc `$`*[A, B](t: TableRef[A, B]): string =
proc `==`*[A, B](s, t: TableRef[A, B]): bool =
if isNil(s): result = isNil(t)
elif isNil(t): result = false
else: equalsImpl()
else: equalsImpl(t[])
proc newTableFrom*[A, B, C](collection: A, index: proc(x: B): C): TableRef[C, B] =
## Index the collection with the proc provided.
@@ -376,7 +457,13 @@ proc len*[A, B](t: OrderedTable[A, B]): int {.inline.} =
## returns the number of keys in `t`.
result = t.counter
template forAllOrderedPairs(yieldStmt: stmt) {.dirty, immediate.} =
proc clear*[A, B](t: OrderedTable[A, B] | OrderedTableRef[A, B]) =
## Resets the table so that it is empty.
clearImpl()
t.first = -1
t.last = -1
template forAllOrderedPairs(yieldStmt: untyped) {.oldimmediate, dirty.} =
var h = t.first
while h >= 0:
var nxt = t.data[h].next
@@ -562,7 +649,7 @@ proc len*[A, B](t: OrderedTableRef[A, B]): int {.inline.} =
## returns the number of keys in `t`.
result = t.counter
template forAllOrderedPairs(yieldStmt: stmt) {.dirty, immediate.} =
template forAllOrderedPairs(yieldStmt: untyped) {.oldimmediate, dirty.} =
var h = t.first
while h >= 0:
var nxt = t.data[h].next
@@ -663,6 +750,27 @@ proc sort*[A, B](t: OrderedTableRef[A, B],
## contrast to the `sort` for count tables).
t[].sort(cmp)
proc del*[A, B](t: var OrderedTable[A, B], key: A) =
## deletes `key` from ordered hash table `t`. O(n) comlexity.
var prev = -1
let hc = hash(key)
forAllOrderedPairs:
if t.data[h].hcode == hc:
if t.first == h:
t.first = t.data[h].next
else:
t.data[prev].next = t.data[h].next
var zeroValue : type(t.data[h])
t.data[h] = zeroValue
dec t.counter
break
else:
prev = h
proc del*[A, B](t: var OrderedTableRef[A, B], key: A) =
## deletes `key` from ordered hash table `t`. O(n) comlexity.
t[].del(key)
# ------------------------------ count tables -------------------------------
type
@@ -678,6 +786,11 @@ proc len*[A](t: CountTable[A]): int =
## returns the number of keys in `t`.
result = t.counter
proc clear*[A](t: CountTable[A] | CountTableRef[A]) =
## Resets the table so that it is empty.
clearImpl()
t.counter = 0
iterator pairs*[A](t: CountTable[A]): (A, int) =
## iterates over any (key, value) pair in the table `t`.
for h in 0..high(t.data):
@@ -711,7 +824,7 @@ proc rawGet[A](t: CountTable[A], key: A): int =
h = nextTry(h, high(t.data))
result = -1 - h # < 0 => MISSING; insert idx = -1 - result
template ctget(t, key: untyped): untyped {.immediate.} =
template ctget(t, key: untyped): untyped =
var index = rawGet(t, key)
if index >= 0: result = t.data[index].val
else:
@@ -984,6 +1097,26 @@ when isMainModule:
s3[p1] = 30_000
s3[p2] = 45_000
block: # Ordered table should preserve order after deletion
var
s4 = initOrderedTable[int, int]()
s4[1] = 1
s4[2] = 2
s4[3] = 3
var prev = 0
for i in s4.values:
doAssert(prev < i)
prev = i
s4.del(2)
doAssert(2 notin s4)
doAssert(s4.len == 2)
prev = 0
for i in s4.values:
doAssert(prev < i)
prev = i
var
t1 = initCountTable[string]()
t2 = initCountTable[string]()
@@ -1040,3 +1173,12 @@ when isMainModule:
doAssert 0 == t.getOrDefault(testKey)
t.inc(testKey,3)
doAssert 3 == t.getOrDefault(testKey)
# Clear tests
var clearTable = newTable[int, string]()
clearTable[42] = "asd"
clearTable[123123] = "piuyqwb "
doAssert clearTable[42] == "asd"
clearTable.clear()
doAssert(not clearTable.hasKey(123123))
doAssert clearTable.getOrDefault(42) == nil

View File

@@ -79,6 +79,8 @@ proc advice*(s: var ThreadPoolState): ThreadPoolAdvice =
inc s.calls
when not defined(testing) and isMainModule:
import random
proc busyLoop() =
while true:
discard random(80)

View File

@@ -29,28 +29,24 @@ proc createProcType(p, b: NimNode): NimNode {.compileTime.} =
of nnkExprColonExpr:
identDefs.add ident[0]
identDefs.add ident[1]
of nnkIdent:
else:
identDefs.add newIdentNode("i" & $i)
identDefs.add(ident)
else:
error("Incorrect type list in proc type declaration.")
identDefs.add newEmptyNode()
formalParams.add identDefs
of nnkIdent:
else:
var identDefs = newNimNode(nnkIdentDefs)
identDefs.add newIdentNode("i0")
identDefs.add(p)
identDefs.add newEmptyNode()
formalParams.add identDefs
else:
error("Incorrect type list in proc type declaration.")
result.add formalParams
result.add newEmptyNode()
#echo(treeRepr(result))
#echo(result.toStrLit())
macro `=>`*(p, b: expr): expr {.immediate.} =
macro `=>`*(p, b: untyped): untyped =
## Syntax sugar for anonymous procedures.
##
## .. code-block:: nim
@@ -111,7 +107,7 @@ macro `=>`*(p, b: expr): expr {.immediate.} =
#echo(result.toStrLit())
#return result # TODO: Bug?
macro `->`*(p, b: expr): expr {.immediate.} =
macro `->`*(p, b: untyped): untyped =
## Syntax sugar for procedure types.
##
## .. code-block:: nim
@@ -129,7 +125,7 @@ macro `->`*(p, b: expr): expr {.immediate.} =
type ListComprehension = object
var lc*: ListComprehension
macro `[]`*(lc: ListComprehension, comp, typ: expr): expr =
macro `[]`*(lc: ListComprehension, comp, typ: untyped): untyped =
## List comprehension, returns a sequence. `comp` is the actual list
## comprehension, for example ``x | (x <- 1..10, x mod 2 == 0)``. `typ` is
## the type that will be stored inside the result seq.

View File

@@ -433,7 +433,7 @@ proc htmlTag*(n: XmlNode): HtmlTag =
proc htmlTag*(s: string): HtmlTag =
## converts `s` to a ``HtmlTag``. If `s` is no HTML tag, ``tagUnknown`` is
## returned.
let s = if allLower(s): s else: s.toLower
let s = if allLower(s): s else: toLowerAscii(s)
result = toHtmlTag(s)
proc entityToUtf8*(entity: string): string =
@@ -464,12 +464,18 @@ proc untilElementEnd(x: var XmlParser, result: XmlNode,
case x.kind
of xmlElementStart, xmlElementOpen:
case result.htmlTag
of tagLi, tagP, tagDt, tagDd, tagInput, tagOption:
# some tags are common to have no ``</end>``, like ``<li>``:
of tagP, tagInput, tagOption:
# some tags are common to have no ``</end>``, like ``<li>`` but
# allow ``<p>`` in `<dd>`, `<dt>` and ``<li>`` in next case
if htmlTag(x.elemName) in {tagLi, tagP, tagDt, tagDd, tagInput,
tagOption}:
errors.add(expected(x, result))
break
of tagDd, tagDt, tagLi:
if htmlTag(x.elemName) in {tagLi, tagDt, tagDd, tagInput,
tagOption}:
errors.add(expected(x, result))
break
of tagTd, tagTh:
if htmlTag(x.elemName) in {tagTr, tagTd, tagTh, tagTfoot, tagThead}:
errors.add(expected(x, result))
@@ -513,13 +519,13 @@ proc parse(x: var XmlParser, errors: var seq[string]): XmlNode =
errors.add(errorMsg(x))
next(x)
of xmlElementStart:
result = newElement(x.elemName.toLower)
result = newElement(toLowerAscii(x.elemName))
next(x)
untilElementEnd(x, result, errors)
of xmlElementEnd:
errors.add(errorMsg(x, "unexpected ending tag: " & x.elemName))
of xmlElementOpen:
result = newElement(x.elemName.toLower)
result = newElement(toLowerAscii(x.elemName))
next(x)
result.attrs = newStringTable()
while true:

View File

@@ -79,15 +79,18 @@
## constructor should be used for this purpose. However,
## currently only basic authentication is supported.
import net, strutils, uri, parseutils, strtabs, base64, os, mimetypes, math
import net, strutils, uri, parseutils, strtabs, base64, os, mimetypes,
math, random, httpcore
import asyncnet, asyncdispatch
import nativesockets
export httpcore except parseHeader # TODO: The ``except`` doesn't work
type
Response* = tuple[
version: string,
status: string,
headers: StringTableRef,
headers: HttpHeaders,
body: string]
Proxy* = ref object
@@ -164,7 +167,7 @@ proc parseChunks(s: Socket, timeout: int): string =
# Trailer headers will only be sent if the request specifies that we want
# them: http://tools.ietf.org/html/rfc2616#section-3.6.1
proc parseBody(s: Socket, headers: StringTableRef, timeout: int): string =
proc parseBody(s: Socket, headers: HttpHeaders, httpVersion: string, timeout: int): string =
result = ""
if headers.getOrDefault"Transfer-Encoding" == "chunked":
result = parseChunks(s, timeout)
@@ -190,7 +193,7 @@ proc parseBody(s: Socket, headers: StringTableRef, timeout: int): string =
# -REGION- Connection: Close
# (http://tools.ietf.org/html/rfc2616#section-4.4) NR.5
if headers.getOrDefault"Connection" == "close":
if headers.getOrDefault"Connection" == "close" or httpVersion == "1.0":
var buf = ""
while true:
buf = newString(4000)
@@ -204,7 +207,7 @@ proc parseResponse(s: Socket, getBody: bool, timeout: int): Response =
var linei = 0
var fullyRead = false
var line = ""
result.headers = newStringTable(modeCaseInsensitive)
result.headers = newHttpHeaders()
while true:
line = ""
linei = 0
@@ -239,10 +242,14 @@ proc parseResponse(s: Socket, getBody: bool, timeout: int): Response =
inc(linei) # Skip :
result.headers[name] = line[linei.. ^1].strip()
# Ensure the server isn't trying to DoS us.
if result.headers.len > headerLimit:
httpError("too many headers")
if not fullyRead:
httpError("Connection was closed before full request has been made")
if getBody:
result.body = parseBody(s, result.headers, timeout)
result.body = parseBody(s, result.headers, result.version, timeout)
else:
result.body = ""
@@ -335,7 +342,7 @@ proc addFiles*(p: var MultipartData, xs: openarray[tuple[name, file: string]]):
var m = newMimetypes()
for name, file in xs.items:
var contentType: string
let (dir, fName, ext) = splitFile(file)
let (_, fName, ext) = splitFile(file)
if ext.len > 0:
contentType = m.getMimetype(ext[1..ext.high], nil)
p.add(name, readFile(file), fName & ext, contentType)
@@ -392,6 +399,59 @@ proc request*(url: string, httpMethod: string, extraHeaders = "",
var hostUrl = if proxy == nil: r else: parseUri(url)
var headers = substr(httpMethod, len("http"))
# TODO: Use generateHeaders further down once it supports proxies.
var s = newSocket()
defer: s.close()
if s == nil: raiseOSError(osLastError())
var port = net.Port(80)
if r.scheme == "https":
when defined(ssl):
sslContext.wrapSocket(s)
port = net.Port(443)
else:
raise newException(HttpRequestError,
"SSL support is not available. Cannot connect over SSL.")
if r.port != "":
port = net.Port(r.port.parseInt)
# get the socket ready. If we are connecting through a proxy to SSL,
# send the appropiate CONNECT header. If not, simply connect to the proper
# host (which may still be the proxy, for normal HTTP)
if proxy != nil and hostUrl.scheme == "https":
when defined(ssl):
var connectHeaders = "CONNECT "
let targetPort = if hostUrl.port == "": 443 else: hostUrl.port.parseInt
connectHeaders.add(hostUrl.hostname)
connectHeaders.add(":" & $targetPort)
connectHeaders.add(" HTTP/1.1\c\L")
connectHeaders.add("Host: " & hostUrl.hostname & ":" & $targetPort & "\c\L")
if proxy.auth != "":
let auth = base64.encode(proxy.auth, newline = "")
connectHeaders.add("Proxy-Authorization: basic " & auth & "\c\L")
connectHeaders.add("\c\L")
if timeout == -1:
s.connect(r.hostname, port)
else:
s.connect(r.hostname, port, timeout)
s.send(connectHeaders)
let connectResult = parseResponse(s, false, timeout)
if not connectResult.status.startsWith("200"):
raise newException(HttpRequestError,
"The proxy server rejected a CONNECT request, " &
"so a secure connection could not be established.")
sslContext.wrapConnectedSocket(s, handshakeAsClient)
else:
raise newException(HttpRequestError, "SSL support not available. Cannot connect via proxy over SSL")
else:
if timeout == -1:
s.connect(r.hostname, port)
else:
s.connect(r.hostname, port, timeout)
# now that the socket is ready, prepare the headers
if proxy == nil:
headers.add ' '
if r.path[0] != '/': headers.add '/'
@@ -415,29 +475,13 @@ proc request*(url: string, httpMethod: string, extraHeaders = "",
add(headers, "Proxy-Authorization: basic " & auth & "\c\L")
add(headers, extraHeaders)
add(headers, "\c\L")
var s = newSocket()
if s == nil: raiseOSError(osLastError())
var port = net.Port(80)
if r.scheme == "https":
when defined(ssl):
sslContext.wrapSocket(s)
port = net.Port(443)
else:
raise newException(HttpRequestError,
"SSL support is not available. Cannot connect over SSL.")
if r.port != "":
port = net.Port(r.port.parseInt)
if timeout == -1:
s.connect(r.hostname, port)
else:
s.connect(r.hostname, port, timeout)
# headers are ready. send them, await the result, and close the socket.
s.send(headers)
if body != "":
s.send(body)
result = parseResponse(s, httpMethod != "httpHEAD", timeout)
s.close()
proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
body = "", sslContext = defaultSSLContext, timeout = -1,
@@ -455,7 +499,7 @@ proc redirection(status: string): bool =
if status.startsWith(i):
return true
proc getNewLocation(lastURL: string, headers: StringTableRef): string =
proc getNewLocation(lastURL: string, headers: HttpHeaders): string =
result = headers.getOrDefault"Location"
if result == "": httpError("location header expected")
# Relative URLs. (Not part of the spec, but soon will be.)
@@ -624,10 +668,10 @@ proc newAsyncHttpClient*(userAgent = defUserAgent,
## ``sslContext`` specifies the SSL context to use for HTTPS requests.
new result
result.headers = newStringTable(modeCaseInsensitive)
result.userAgent = defUserAgent
result.userAgent = userAgent
result.maxRedirects = maxRedirects
when defined(ssl):
result.sslContext = net.SslContext(sslContext)
result.sslContext = sslContext
proc close*(client: AsyncHttpClient) =
## Closes any connections held by the HTTP client.
@@ -678,7 +722,8 @@ proc parseChunks(client: AsyncHttpClient): Future[string] {.async.} =
# them: http://tools.ietf.org/html/rfc2616#section-3.6.1
proc parseBody(client: AsyncHttpClient,
headers: StringTableRef): Future[string] {.async.} =
headers: HttpHeaders,
httpVersion: string): Future[string] {.async.} =
result = ""
if headers.getOrDefault"Transfer-Encoding" == "chunked":
result = await parseChunks(client)
@@ -700,7 +745,7 @@ proc parseBody(client: AsyncHttpClient,
# -REGION- Connection: Close
# (http://tools.ietf.org/html/rfc2616#section-4.4) NR.5
if headers.getOrDefault"Connection" == "close":
if headers.getOrDefault"Connection" == "close" or httpVersion == "1.0":
var buf = ""
while true:
buf = await client.socket.recvFull(4000)
@@ -713,7 +758,7 @@ proc parseResponse(client: AsyncHttpClient,
var linei = 0
var fullyRead = false
var line = ""
result.headers = newStringTable(modeCaseInsensitive)
result.headers = newHttpHeaders()
while true:
linei = 0
line = await client.socket.recvLine()
@@ -748,10 +793,13 @@ proc parseResponse(client: AsyncHttpClient,
inc(linei) # Skip :
result.headers[name] = line[linei.. ^1].strip()
if result.headers.len > headerLimit:
httpError("too many headers")
if not fullyRead:
httpError("Connection was closed before full request has been made")
if getBody:
result.body = await parseBody(client, result.headers)
result.body = await parseBody(client, result.headers, result.version)
else:
result.body = ""

198
lib/pure/httpcore.nim Normal file
View File

@@ -0,0 +1,198 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2016 Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Contains functionality shared between the ``httpclient`` and
## ``asynchttpserver`` modules.
import tables, strutils, parseutils
type
HttpHeaders* = ref object
table*: TableRef[string, seq[string]]
HttpHeaderValues* = distinct seq[string]
HttpCode* = enum
Http100 = "100 Continue",
Http101 = "101 Switching Protocols",
Http200 = "200 OK",
Http201 = "201 Created",
Http202 = "202 Accepted",
Http203 = "203 Non-Authoritative Information",
Http204 = "204 No Content",
Http205 = "205 Reset Content",
Http206 = "206 Partial Content",
Http300 = "300 Multiple Choices",
Http301 = "301 Moved Permanently",
Http302 = "302 Found",
Http303 = "303 See Other",
Http304 = "304 Not Modified",
Http305 = "305 Use Proxy",
Http307 = "307 Temporary Redirect",
Http400 = "400 Bad Request",
Http401 = "401 Unauthorized",
Http403 = "403 Forbidden",
Http404 = "404 Not Found",
Http405 = "405 Method Not Allowed",
Http406 = "406 Not Acceptable",
Http407 = "407 Proxy Authentication Required",
Http408 = "408 Request Timeout",
Http409 = "409 Conflict",
Http410 = "410 Gone",
Http411 = "411 Length Required",
Http412 = "412 Precondition Failed",
Http413 = "413 Request Entity Too Large",
Http414 = "414 Request-URI Too Long",
Http415 = "415 Unsupported Media Type",
Http416 = "416 Requested Range Not Satisfiable",
Http417 = "417 Expectation Failed",
Http418 = "418 I'm a teapot",
Http421 = "421 Misdirected Request",
Http422 = "422 Unprocessable Entity",
Http426 = "426 Upgrade Required",
Http428 = "428 Precondition Required",
Http429 = "429 Too Many Requests",
Http431 = "431 Request Header Fields Too Large",
Http451 = "451 Unavailable For Legal Reasons",
Http500 = "500 Internal Server Error",
Http501 = "501 Not Implemented",
Http502 = "502 Bad Gateway",
Http503 = "503 Service Unavailable",
Http504 = "504 Gateway Timeout",
Http505 = "505 HTTP Version Not Supported"
HttpVersion* = enum
HttpVer11,
HttpVer10
const headerLimit* = 10_000
proc newHttpHeaders*(): HttpHeaders =
new result
result.table = newTable[string, seq[string]]()
proc newHttpHeaders*(keyValuePairs:
openarray[tuple[key: string, val: string]]): HttpHeaders =
var pairs: seq[tuple[key: string, val: seq[string]]] = @[]
for pair in keyValuePairs:
pairs.add((pair.key.toLower(), @[pair.val]))
new result
result.table = newTable[string, seq[string]](pairs)
proc clear*(headers: HttpHeaders) =
headers.table.clear()
proc `[]`*(headers: HttpHeaders, key: string): HttpHeaderValues =
## Returns the values associated with the given ``key``. If the returned
## values are passed to a procedure expecting a ``string``, the first
## value is automatically picked. If there are
## no values associated with the key, an exception is raised.
##
## To access multiple values of a key, use the overloaded ``[]`` below or
## to get all of them access the ``table`` field directly.
return headers.table[key.toLower].HttpHeaderValues
converter toString*(values: HttpHeaderValues): string =
return seq[string](values)[0]
proc `[]`*(headers: HttpHeaders, key: string, i: int): string =
## Returns the ``i``'th value associated with the given key. If there are
## no values associated with the key or the ``i``'th value doesn't exist,
## an exception is raised.
return headers.table[key.toLower][i]
proc `[]=`*(headers: HttpHeaders, key, value: string) =
## Sets the header entries associated with ``key`` to the specified value.
## Replaces any existing values.
headers.table[key.toLower] = @[value]
proc `[]=`*(headers: HttpHeaders, key: string, value: seq[string]) =
## Sets the header entries associated with ``key`` to the specified list of
## values.
## Replaces any existing values.
headers.table[key.toLower] = value
proc add*(headers: HttpHeaders, key, value: string) =
## Adds the specified value to the specified key. Appends to any existing
## values associated with the key.
if not headers.table.hasKey(key.toLower):
headers.table[key.toLower] = @[value]
else:
headers.table[key.toLower].add(value)
iterator pairs*(headers: HttpHeaders): tuple[key, value: string] =
## Yields each key, value pair.
for k, v in headers.table:
for value in v:
yield (k, value)
proc contains*(values: HttpHeaderValues, value: string): bool =
## Determines if ``value`` is one of the values inside ``values``. Comparison
## is performed without case sensitivity.
for val in seq[string](values):
if val.toLower == value.toLower: return true
proc hasKey*(headers: HttpHeaders, key: string): bool =
return headers.table.hasKey(key.toLower())
proc getOrDefault*(headers: HttpHeaders, key: string,
default = @[""].HttpHeaderValues): HttpHeaderValues =
## Returns the values associated with the given ``key``. If there are no
## values associated with the key, then ``default`` is returned.
if headers.hasKey(key):
return headers[key]
else:
return default
proc len*(headers: HttpHeaders): int = return headers.table.len
proc parseList(line: string, list: var seq[string], start: int): int =
var i = 0
var current = ""
while line[start + i] notin {'\c', '\l', '\0'}:
i += line.skipWhitespace(start + i)
i += line.parseUntil(current, {'\c', '\l', ','}, start + i)
list.add(current)
if line[start + i] == ',':
i.inc # Skip ,
current.setLen(0)
proc parseHeader*(line: string): tuple[key: string, value: seq[string]] =
## Parses a single raw header HTTP line into key value pairs.
##
## Used by ``asynchttpserver`` and ``httpclient`` internally and should not
## be used by you.
result.value = @[]
var i = 0
i = line.parseUntil(result.key, ':')
inc(i) # skip :
if i < len(line):
i += parseList(line, result.value, i)
else:
result.value = @[]
proc `==`*(protocol: tuple[orig: string, major, minor: int],
ver: HttpVersion): bool =
let major =
case ver
of HttpVer11, HttpVer10: 1
let minor =
case ver
of HttpVer11: 1
of HttpVer10: 0
result = protocol.major == major and protocol.minor == minor
when isMainModule:
var test = newHttpHeaders()
test["Connection"] = @["Upgrade", "Close"]
doAssert test["Connection", 0] == "Upgrade"
doAssert test["Connection", 1] == "Close"
test.add("Connection", "Test")
doAssert test["Connection", 2] == "Test"
doAssert "upgrade" in test["Connection"]

255
lib/pure/ioselectors.nim Normal file
View File

@@ -0,0 +1,255 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2016 Eugene Kabanov
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module allows high-level and efficient I/O multiplexing.
##
## Supported OS primitives: ``epoll``, ``kqueue``, ``poll`` and
## Windows ``select``.
##
## To use threadsafe version of this module, it needs to be compiled
## with both ``-d:threadsafe`` and ``--threads:on`` options.
##
## Supported features: files, sockets, pipes, timers, processes, signals
## and user events.
##
## Fully supported OS: MacOSX, FreeBSD, OpenBSD, NetBSD, Linux.
##
## Partially supported OS: Windows (only sockets and user events),
## Solaris (files, sockets, handles and user events).
##
## TODO: ``/dev/poll``, ``event ports`` and filesystem events.
import os
const hasThreadSupport = compileOption("threads") and defined(threadsafe)
const supportedPlatform = defined(macosx) or defined(freebsd) or
defined(netbsd) or defined(openbsd) or
defined(linux)
const bsdPlatform = defined(macosx) or defined(freebsd) or
defined(netbsd) or defined(openbsd)
when defined(nimdoc):
type
Selector*[T] = ref object
## An object which holds descriptors to be checked for read/write status
Event* {.pure.} = enum
## An enum which hold event types
Read, ## Descriptor is available for read
Write, ## Descriptor is available for write
Timer, ## Timer descriptor is completed
Signal, ## Signal is raised
Process, ## Process is finished
Vnode, ## Currently not supported
User, ## User event is raised
Error ## Error happens while waiting, for descriptor
ReadyKey*[T] = object
## An object which holds result for descriptor
fd* : int ## file/socket descriptor
events*: set[Event] ## set of events
data*: T ## application-defined data
SelectEvent* = object
## An object which holds user defined event
proc newSelector*[T](): Selector[T] =
## Creates a new selector
proc close*[T](s: Selector[T]) =
## Closes selector
proc registerHandle*[T](s: Selector[T], fd: SocketHandle, events: set[Event],
data: T) =
## Registers file/socket descriptor ``fd`` to selector ``s``
## with events set in ``events``. The ``data`` is application-defined
## data, which to be passed when event happens.
proc updateHandle*[T](s: Selector[T], fd: SocketHandle, events: set[Event]) =
## Update file/socket descriptor ``fd``, registered in selector
## ``s`` with new events set ``event``.
proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool,
data: T): int {.discardable.} =
## Registers timer notification with ``timeout`` in milliseconds
## to selector ``s``.
## If ``oneshot`` is ``true`` timer will be notified only once.
## Set ``oneshot`` to ``false`` if your want periodic notifications.
## The ``data`` is application-defined data, which to be passed, when
## time limit expired.
proc registerSignal*[T](s: Selector[T], signal: int,
data: T): int {.discardable.} =
## Registers Unix signal notification with ``signal`` to selector
## ``s``. The ``data`` is application-defined data, which to be
## passed, when signal raises.
##
## This function is not supported for ``Windows``.
proc registerProcess*[T](s: Selector[T], pid: int,
data: T): int {.discardable.} =
## Registers process id (pid) notification when process has
## exited to selector ``s``.
## The ``data`` is application-defined data, which to be passed, when
## process with ``pid`` has exited.
proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
## Registers selector event ``ev`` to selector ``s``.
## ``data`` application-defined data, which to be passed, when
## ``ev`` happens.
proc newSelectEvent*(): SelectEvent =
## Creates new event ``SelectEvent``.
proc setEvent*(ev: SelectEvent) =
## Trigger event ``ev``.
proc close*(ev: SelectEvent) =
## Closes selector event ``ev``.
proc unregister*[T](s: Selector[T], ev: SelectEvent) =
## Unregisters event ``ev`` from selector ``s``.
proc unregister*[T](s: Selector[T], fd: int|SocketHandle|cint) =
## Unregisters file/socket descriptor ``fd`` from selector ``s``.
proc flush*[T](s: Selector[T]) =
## Flushes all changes was made to kernel pool/queue.
## This function is usefull only for BSD and MacOS, because
## kqueue supports bulk changes to be made.
## On Linux/Windows and other Posix compatible operation systems,
## ``flush`` is alias for `discard`.
proc selectInto*[T](s: Selector[T], timeout: int,
results: var openarray[ReadyKey[T]]): int =
## Process call waiting for events registered in selector ``s``.
## The ``timeout`` argument specifies the minimum number of milliseconds
## the function will be blocked, if no events are not ready. Specifying a
## timeout of ``-1`` causes function to block indefinitely.
## All available events will be stored in ``results`` array.
##
## Function returns number of triggered events.
proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] =
## Process call waiting for events registered in selector ``s``.
## The ``timeout`` argument specifies the minimum number of milliseconds
## the function will be blocked, if no events are not ready. Specifying a
## timeout of -1 causes function to block indefinitely.
##
## Function returns sequence of triggered events.
template isEmpty*[T](s: Selector[T]): bool =
## Returns ``true``, if there no registered events or descriptors
## in selector.
template withData*[T](s: Selector[T], fd: SocketHandle, value,
body: untyped) =
## retrieves the application-data assigned with descriptor ``fd``
## to ``value``. This ``value`` can be modified in the scope of
## the ``withData`` call.
##
## .. code-block:: nim
##
## s.withData(fd, value) do:
## # block is executed only if ``fd`` registered in selector ``s``
## value.uid = 1000
##
template withData*[T](s: Selector[T], fd: SocketHandle, value,
body1, body2: untyped) =
## retrieves the application-data assigned with descriptor ``fd``
## to ``value``. This ``value`` can be modified in the scope of
## the ``withData`` call.
##
## .. code-block:: nim
##
## s.withData(fd, value) do:
## # block is executed only if ``fd`` registered in selector ``s``.
## value.uid = 1000
## do:
## # block is executed if ``fd`` not registered in selector ``s``.
## raise
##
else:
when hasThreadSupport:
import locks
type
SharedArray {.unchecked.}[T] = array[0..100, T]
proc allocSharedArray[T](nsize: int): ptr SharedArray[T] =
result = cast[ptr SharedArray[T]](allocShared0(sizeof(T) * nsize))
proc deallocSharedArray[T](sa: ptr SharedArray[T]) =
deallocShared(cast[pointer](sa))
type
Event* {.pure.} = enum
Read, Write, Timer, Signal, Process, Vnode, User, Error, Oneshot
ReadyKey*[T] = object
fd* : int
events*: set[Event]
data*: T
SelectorKey[T] = object
ident: int
events: set[Event]
param: int
key: ReadyKey[T]
when not defined(windows):
import posix
proc setNonBlocking(fd: cint) {.inline.} =
var x = fcntl(fd, F_GETFL, 0)
if x == -1:
raiseOSError(osLastError())
else:
var mode = x or O_NONBLOCK
if fcntl(fd, F_SETFL, mode) == -1:
raiseOSError(osLastError())
template setKey(s, pident, pkeyfd, pevents, pparam, pdata) =
var skey = addr(s.fds[pident])
skey.ident = pident
skey.events = pevents
skey.param = pparam
skey.key.fd = pkeyfd
skey.key.data = pdata
when supportedPlatform:
template blockSignals(newmask: var Sigset, oldmask: var Sigset) =
when hasThreadSupport:
if posix.pthread_sigmask(SIG_BLOCK, newmask, oldmask) == -1:
raiseOSError(osLastError())
else:
if posix.sigprocmask(SIG_BLOCK, newmask, oldmask) == -1:
raiseOSError(osLastError())
template unblockSignals(newmask: var Sigset, oldmask: var Sigset) =
when hasThreadSupport:
if posix.pthread_sigmask(SIG_UNBLOCK, newmask, oldmask) == -1:
raiseOSError(osLastError())
else:
if posix.sigprocmask(SIG_UNBLOCK, newmask, oldmask) == -1:
raiseOSError(osLastError())
when defined(linux):
include ioselects/ioselectors_epoll
elif bsdPlatform:
include ioselects/ioselectors_kqueue
elif defined(windows):
include ioselects/ioselectors_select
elif defined(solaris):
include ioselects/ioselectors_poll # need to replace it with event ports
else:
include ioselects/ioselectors_poll

View File

@@ -0,0 +1,461 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2016 Eugene Kabanov
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# This module implements Linux epoll().
import posix, times
# Maximum number of events that can be returned
const MAX_EPOLL_RESULT_EVENTS = 64
type
SignalFdInfo* {.importc: "struct signalfd_siginfo",
header: "<sys/signalfd.h>", pure, final.} = object
ssi_signo*: uint32
ssi_errno*: int32
ssi_code*: int32
ssi_pid*: uint32
ssi_uid*: uint32
ssi_fd*: int32
ssi_tid*: uint32
ssi_band*: uint32
ssi_overrun*: uint32
ssi_trapno*: uint32
ssi_status*: int32
ssi_int*: int32
ssi_ptr*: uint64
ssi_utime*: uint64
ssi_stime*: uint64
ssi_addr*: uint64
pad* {.importc: "__pad".}: array[0..47, uint8]
eventFdData {.importc: "eventfd_t",
header: "<sys/eventfd.h>", pure, final.} = uint64
epoll_data {.importc: "union epoll_data", header: "<sys/epoll.h>",
pure, final.} = object
u64 {.importc: "u64".}: uint64
epoll_event {.importc: "struct epoll_event",
header: "<sys/epoll.h>", pure, final.} = object
events: uint32 # Epoll events
data: epoll_data # User data variable
const
EPOLL_CTL_ADD = 1 # Add a file descriptor to the interface.
EPOLL_CTL_DEL = 2 # Remove a file descriptor from the interface.
EPOLL_CTL_MOD = 3 # Change file descriptor epoll_event structure.
EPOLLIN = 0x00000001
EPOLLOUT = 0x00000004
EPOLLERR = 0x00000008
EPOLLHUP = 0x00000010
EPOLLRDHUP = 0x00002000
EPOLLONESHOT = 1 shl 30
proc epoll_create(size: cint): cint
{.importc: "epoll_create", header: "<sys/epoll.h>".}
proc epoll_ctl(epfd: cint; op: cint; fd: cint; event: ptr epoll_event): cint
{.importc: "epoll_ctl", header: "<sys/epoll.h>".}
proc epoll_wait(epfd: cint; events: ptr epoll_event; maxevents: cint;
timeout: cint): cint
{.importc: "epoll_wait", header: "<sys/epoll.h>".}
proc timerfd_create(clock_id: ClockId, flags: cint): cint
{.cdecl, importc: "timerfd_create", header: "<sys/timerfd.h>".}
proc timerfd_settime(ufd: cint, flags: cint,
utmr: var Itimerspec, otmr: var Itimerspec): cint
{.cdecl, importc: "timerfd_settime", header: "<sys/timerfd.h>".}
proc signalfd(fd: cint, mask: var Sigset, flags: cint): cint
{.cdecl, importc: "signalfd", header: "<sys/signalfd.h>".}
proc eventfd(count: cuint, flags: cint): cint
{.cdecl, importc: "eventfd", header: "<sys/eventfd.h>".}
proc ulimit(cmd: cint): clong
{.importc: "ulimit", header: "<ulimit.h>", varargs.}
when hasThreadSupport:
type
SelectorImpl[T] = object
epollFD : cint
maxFD : int
fds: ptr SharedArray[SelectorKey[T]]
count: int
Selector*[T] = ptr SelectorImpl[T]
else:
type
SelectorImpl[T] = object
epollFD : cint
maxFD : int
fds: seq[SelectorKey[T]]
count: int
Selector*[T] = ref SelectorImpl[T]
type
SelectEventImpl = object
efd: cint
SelectEvent* = ptr SelectEventImpl
proc newSelector*[T](): Selector[T] =
var maxFD = int(ulimit(4, 0))
doAssert(maxFD > 0)
var epollFD = epoll_create(MAX_EPOLL_RESULT_EVENTS)
if epollFD < 0:
raiseOsError(osLastError())
when hasThreadSupport:
result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T])))
result.epollFD = epollFD
result.maxFD = maxFD
result.fds = allocSharedArray[SelectorKey[T]](maxFD)
else:
result = Selector[T]()
result.epollFD = epollFD
result.maxFD = maxFD
result.fds = newSeq[SelectorKey[T]](maxFD)
proc close*[T](s: Selector[T]) =
if posix.close(s.epollFD) != 0:
raiseOSError(osLastError())
when hasThreadSupport:
deallocSharedArray(s.fds)
deallocShared(cast[pointer](s))
proc newSelectEvent*(): SelectEvent =
let fdci = eventfd(0, 0)
if fdci == -1:
raiseOSError(osLastError())
setNonBlocking(fdci)
result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl)))
result.efd = fdci
proc setEvent*(ev: SelectEvent) =
var data : uint64 = 1
if posix.write(ev.efd, addr data, sizeof(uint64)) == -1:
raiseOSError(osLastError())
proc close*(ev: SelectEvent) =
discard posix.close(ev.efd)
deallocShared(cast[pointer](ev))
template checkFd(s, f) =
if f >= s.maxFD:
raise newException(ValueError, "Maximum file descriptors exceeded")
proc registerHandle*[T](s: Selector[T], fd: SocketHandle,
events: set[Event], data: T) =
let fdi = int(fd)
s.checkFd(fdi)
doAssert(s.fds[fdi].ident == 0)
s.setKey(fdi, fdi, events, 0, data)
if events != {}:
var epv = epoll_event(events: EPOLLRDHUP)
epv.data.u64 = fdi.uint
if Event.Read in events: epv.events = epv.events or EPOLLIN
if Event.Write in events: epv.events = epv.events or EPOLLOUT
if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1:
raiseOSError(osLastError())
inc(s.count)
proc updateHandle*[T](s: Selector[T], fd: SocketHandle, events: set[Event]) =
let maskEvents = {Event.Timer, Event.Signal, Event.Process, Event.Vnode,
Event.User, Event.Oneshot, Event.Error}
let fdi = int(fd)
s.checkFd(fdi)
var pkey = addr(s.fds[fdi])
doAssert(pkey.ident != 0)
doAssert(pkey.events * maskEvents == {})
if pkey.events != events:
var epv = epoll_event(events: EPOLLRDHUP)
epv.data.u64 = fdi.uint
if Event.Read in events: epv.events = epv.events or EPOLLIN
if Event.Write in events: epv.events = epv.events or EPOLLOUT
if pkey.events == {}:
if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1:
raiseOSError(osLastError())
inc(s.count)
else:
if events != {}:
if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fdi.cint, addr epv) == -1:
raiseOSError(osLastError())
else:
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
raiseOSError(osLastError())
dec(s.count)
pkey.events = events
proc unregister*[T](s: Selector[T], fd: int|SocketHandle) =
let fdi = int(fd)
s.checkFd(fdi)
var pkey = addr(s.fds[fdi])
doAssert(pkey.ident != 0)
if pkey.events != {}:
if pkey.events * {Event.Read, Event.Write} != {}:
var epv = epoll_event()
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
raiseOSError(osLastError())
dec(s.count)
elif Event.Timer in pkey.events:
var epv = epoll_event()
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
raiseOSError(osLastError())
discard posix.close(fdi.cint)
dec(s.count)
elif Event.Signal in pkey.events:
var epv = epoll_event()
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
raiseOSError(osLastError())
var nmask, omask: Sigset
discard sigemptyset(nmask)
discard sigemptyset(omask)
discard sigaddset(nmask, cint(s.fds[fdi].param))
unblockSignals(nmask, omask)
discard posix.close(fdi.cint)
dec(s.count)
elif Event.Process in pkey.events:
var epv = epoll_event()
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
raiseOSError(osLastError())
var nmask, omask: Sigset
discard sigemptyset(nmask)
discard sigemptyset(omask)
discard sigaddset(nmask, SIGCHLD)
unblockSignals(nmask, omask)
discard posix.close(fdi.cint)
dec(s.count)
pkey.ident = 0
pkey.events = {}
proc unregister*[T](s: Selector[T], ev: SelectEvent) =
let fdi = int(ev.efd)
s.checkFd(fdi)
var pkey = addr(s.fds[fdi])
doAssert(pkey.ident != 0)
doAssert(Event.User in pkey.events)
pkey.ident = 0
pkey.events = {}
var epv = epoll_event()
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
raiseOSError(osLastError())
dec(s.count)
proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool,
data: T): int {.discardable.} =
var
new_ts: Itimerspec
old_ts: Itimerspec
let fdi = timerfd_create(CLOCK_MONOTONIC, 0).int
if fdi == -1:
raiseOSError(osLastError())
setNonBlocking(fdi.cint)
s.checkFd(fdi)
doAssert(s.fds[fdi].ident == 0)
var events = {Event.Timer}
var epv = epoll_event(events: EPOLLIN or EPOLLRDHUP)
epv.data.u64 = fdi.uint
if oneshot:
new_ts.it_interval.tv_sec = 0.Time
new_ts.it_interval.tv_nsec = 0
new_ts.it_value.tv_sec = (timeout div 1_000).Time
new_ts.it_value.tv_nsec = (timeout %% 1_000) * 1_000_000
incl(events, Event.Oneshot)
epv.events = epv.events or EPOLLONESHOT
else:
new_ts.it_interval.tv_sec = (timeout div 1000).Time
new_ts.it_interval.tv_nsec = (timeout %% 1_000) * 1_000_000
new_ts.it_value.tv_sec = new_ts.it_interval.tv_sec
new_ts.it_value.tv_nsec = new_ts.it_interval.tv_nsec
if timerfd_settime(fdi.cint, cint(0), new_ts, old_ts) == -1:
raiseOSError(osLastError())
if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1:
raiseOSError(osLastError())
s.setKey(fdi, fdi, events, 0, data)
inc(s.count)
result = fdi
proc registerSignal*[T](s: Selector[T], signal: int,
data: T): int {.discardable.} =
var
nmask: Sigset
omask: Sigset
discard sigemptyset(nmask)
discard sigemptyset(omask)
discard sigaddset(nmask, cint(signal))
blockSignals(nmask, omask)
let fdi = signalfd(-1, nmask, 0).int
if fdi == -1:
raiseOSError(osLastError())
setNonBlocking(fdi.cint)
s.checkFd(fdi)
doAssert(s.fds[fdi].ident == 0)
var epv = epoll_event(events: EPOLLIN or EPOLLRDHUP)
epv.data.u64 = fdi.uint
if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1:
raiseOSError(osLastError())
s.setKey(fdi, signal, {Event.Signal}, signal, data)
inc(s.count)
result = fdi
proc registerProcess*[T](s: Selector, pid: int,
data: T): int {.discardable.} =
var
nmask: Sigset
omask: Sigset
discard sigemptyset(nmask)
discard sigemptyset(omask)
discard sigaddset(nmask, posix.SIGCHLD)
blockSignals(nmask, omask)
let fdi = signalfd(-1, nmask, 0).int
if fdi == -1:
raiseOSError(osLastError())
setNonBlocking(fdi.cint)
s.checkFd(fdi)
doAssert(s.fds[fdi].ident == 0)
var epv = epoll_event(events: EPOLLIN or EPOLLRDHUP)
epv.data.u64 = fdi.uint
epv.events = EPOLLIN or EPOLLRDHUP
if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1:
raiseOSError(osLastError())
s.setKey(fdi, pid, {Event.Process, Event.Oneshot}, pid, data)
inc(s.count)
result = fdi
proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
let fdi = int(ev.efd)
doAssert(s.fds[fdi].ident == 0)
s.setKey(fdi, fdi, {Event.User}, 0, data)
var epv = epoll_event(events: EPOLLIN or EPOLLRDHUP)
epv.data.u64 = ev.efd.uint
if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, ev.efd, addr epv) == -1:
raiseOSError(osLastError())
inc(s.count)
proc flush*[T](s: Selector[T]) =
discard
proc selectInto*[T](s: Selector[T], timeout: int,
results: var openarray[ReadyKey[T]]): int =
var
resTable: array[MAX_EPOLL_RESULT_EVENTS, epoll_event]
maxres = MAX_EPOLL_RESULT_EVENTS
events: set[Event] = {}
i, k: int
if maxres > len(results):
maxres = len(results)
let count = epoll_wait(s.epollFD, addr(resTable[0]), maxres.cint,
timeout.cint)
if count < 0:
result = 0
let err = osLastError()
if cint(err) != EINTR:
raiseOSError(err)
elif count == 0:
result = 0
else:
i = 0
k = 0
while i < count:
let fdi = int(resTable[i].data.u64)
let pevents = resTable[i].events
var skey = addr(s.fds[fdi])
doAssert(skey.ident != 0)
events = {}
if (pevents and EPOLLERR) != 0 or (pevents and EPOLLHUP) != 0:
events.incl(Event.Error)
if (pevents and EPOLLOUT) != 0:
events.incl(Event.Write)
if (pevents and EPOLLIN) != 0:
if Event.Read in skey.events:
events.incl(Event.Read)
elif Event.Timer in skey.events:
var data: uint64 = 0
if posix.read(fdi.cint, addr data, sizeof(uint64)) != sizeof(uint64):
raiseOSError(osLastError())
events = {Event.Timer}
elif Event.Signal in skey.events:
var data = SignalFdInfo()
if posix.read(fdi.cint, addr data,
sizeof(SignalFdInfo)) != sizeof(SignalFdInfo):
raiseOsError(osLastError())
events = {Event.Signal}
elif Event.Process in skey.events:
var data = SignalFdInfo()
if posix.read(fdi.cint, addr data,
sizeof(SignalFdInfo)) != sizeof(SignalFdInfo):
raiseOsError(osLastError())
if cast[int](data.ssi_pid) == skey.param:
events = {Event.Process}
else:
inc(i)
continue
elif Event.User in skey.events:
var data: uint = 0
if posix.read(fdi.cint, addr data, sizeof(uint)) != sizeof(uint):
let err = osLastError()
if err == OSErrorCode(EAGAIN):
inc(i)
continue
else:
raiseOSError(err)
events = {Event.User}
skey.key.events = events
results[k] = skey.key
inc(k)
if Event.Oneshot in skey.events:
var epv = epoll_event()
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
raiseOSError(osLastError())
discard posix.close(fdi.cint)
skey.ident = 0
skey.events = {}
dec(s.count)
inc(i)
result = k
proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] =
result = newSeq[ReadyKey[T]](MAX_EPOLL_RESULT_EVENTS)
let count = selectInto(s, timeout, result)
result.setLen(count)
template isEmpty*[T](s: Selector[T]): bool =
(s.count == 0)
template withData*[T](s: Selector[T], fd: SocketHandle, value,
body: untyped) =
mixin checkFd
let fdi = int(fd)
s.checkFd(fdi)
if s.fds[fdi].ident != 0:
var value = addr(s.fds[fdi].key.data)
body
template withData*[T](s: Selector[T], fd: SocketHandle, value, body1,
body2: untyped) =
mixin checkFd
let fdi = int(fd)
s.checkFd(fdi)
if s.fds[fdi].ident != 0:
var value = addr(s.fds[fdi].key.data)
body1
else:
body2

View File

@@ -0,0 +1,443 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2016 Eugene Kabanov
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# This module implements BSD kqueue().
import posix, times, kqueue
const
# Maximum number of cached changes.
MAX_KQUEUE_CHANGE_EVENTS = 64
# Maximum number of events that can be returned.
MAX_KQUEUE_RESULT_EVENTS = 64
# SIG_IGN and SIG_DFL declared in posix.nim as variables, but we need them
# to be constants and GC-safe.
SIG_DFL = cast[proc(x: cint) {.noconv,gcsafe.}](0)
SIG_IGN = cast[proc(x: cint) {.noconv,gcsafe.}](1)
when defined(macosx) or defined(freebsd):
when defined(macosx):
const MAX_DESCRIPTORS_ID = 29 # KERN_MAXFILESPERPROC (MacOS)
else:
const MAX_DESCRIPTORS_ID = 27 # KERN_MAXFILESPERPROC (FreeBSD)
proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr int,
newp: pointer, newplen: int): cint
{.importc: "sysctl",header: """#include <sys/types.h>
#include <sys/sysctl.h>"""}
elif defined(netbsd) or defined(openbsd):
# OpenBSD and NetBSD don't have KERN_MAXFILESPERPROC, so we are using
# KERN_MAXFILES, because KERN_MAXFILES is always bigger,
# than KERN_MAXFILESPERPROC.
const MAX_DESCRIPTORS_ID = 7 # KERN_MAXFILES
proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr int,
newp: pointer, newplen: int): cint
{.importc: "sysctl",header: """#include <sys/param.h>
#include <sys/sysctl.h>"""}
when hasThreadSupport:
type
SelectorImpl[T] = object
kqFD : cint
maxFD : int
changesTable: array[MAX_KQUEUE_CHANGE_EVENTS, KEvent]
changesCount: int
fds: ptr SharedArray[SelectorKey[T]]
count: int
changesLock: Lock
Selector*[T] = ptr SelectorImpl[T]
else:
type
SelectorImpl[T] = object
kqFD : cint
maxFD : int
changesTable: array[MAX_KQUEUE_CHANGE_EVENTS, KEvent]
changesCount: int
fds: seq[SelectorKey[T]]
count: int
Selector*[T] = ref SelectorImpl[T]
type
SelectEventImpl = object
rfd: cint
wfd: cint
# SelectEvent is declared as `ptr` to be placed in `shared memory`,
# so you can share one SelectEvent handle between threads.
type SelectEvent* = ptr SelectEventImpl
proc newSelector*[T](): Selector[T] =
var maxFD = 0.cint
var size = sizeof(cint)
var namearr = [1.cint, MAX_DESCRIPTORS_ID.cint]
# Obtain maximum number of file descriptors for process
if sysctl(addr(namearr[0]), 2, cast[pointer](addr maxFD), addr size,
nil, 0) != 0:
raiseOsError(osLastError())
var kqFD = kqueue()
if kqFD < 0:
raiseOsError(osLastError())
when hasThreadSupport:
result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T])))
result.kqFD = kqFD
result.maxFD = maxFD.int
result.fds = allocSharedArray[SelectorKey[T]](maxFD)
initLock(result.changesLock)
else:
result = Selector[T]()
result.kqFD = kqFD
result.maxFD = maxFD.int
result.fds = newSeq[SelectorKey[T]](maxFD)
proc close*[T](s: Selector[T]) =
if posix.close(s.kqFD) != 0:
raiseOSError(osLastError())
when hasThreadSupport:
deinitLock(s.changesLock)
deallocSharedArray(s.fds)
deallocShared(cast[pointer](s))
proc newSelectEvent*(): SelectEvent =
var fds: array[2, cint]
if posix.pipe(fds) == -1:
raiseOSError(osLastError())
setNonBlocking(fds[0])
setNonBlocking(fds[1])
result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl)))
result.rfd = fds[0]
result.wfd = fds[1]
proc setEvent*(ev: SelectEvent) =
var data: uint64 = 1
if posix.write(ev.wfd, addr data, sizeof(uint64)) != sizeof(uint64):
raiseOSError(osLastError())
proc close*(ev: SelectEvent) =
discard posix.close(cint(ev.rfd))
discard posix.close(cint(ev.wfd))
deallocShared(cast[pointer](ev))
template checkFd(s, f) =
if f >= s.maxFD:
raise newException(ValueError, "Maximum file descriptors exceeded")
when hasThreadSupport:
template withChangeLock[T](s: Selector[T], body: untyped) =
acquire(s.changesLock)
{.locks: [s.changesLock].}:
try:
body
finally:
release(s.changesLock)
else:
template withChangeLock(s, body: untyped) =
body
template modifyKQueue[T](s: Selector[T], nident: uint, nfilter: cshort,
nflags: cushort, nfflags: cuint, ndata: int,
nudata: pointer) =
mixin withChangeLock
s.withChangeLock():
s.changesTable[s.changesCount] = KEvent(ident: nident,
filter: nfilter, flags: nflags,
fflags: nfflags, data: ndata,
udata: nudata)
inc(s.changesCount)
if s.changesCount == MAX_KQUEUE_CHANGE_EVENTS:
if kevent(s.kqFD, addr(s.changesTable[0]), cint(s.changesCount),
nil, 0, nil) == -1:
raiseOSError(osLastError())
s.changesCount = 0
proc registerHandle*[T](s: Selector[T], fd: SocketHandle,
events: set[Event], data: T) =
let fdi = int(fd)
s.checkFd(fdi)
doAssert(s.fds[fdi].ident == 0)
s.setKey(fdi, fdi, events, 0, data)
if events != {}:
if Event.Read in events:
modifyKQueue(s, fdi.uint, EVFILT_READ, EV_ADD, 0, 0, nil)
inc(s.count)
if Event.Write in events:
modifyKQueue(s, fdi.uint, EVFILT_WRITE, EV_ADD, 0, 0, nil)
inc(s.count)
proc updateHandle*[T](s: Selector[T], fd: SocketHandle,
events: set[Event]) =
let maskEvents = {Event.Timer, Event.Signal, Event.Process, Event.Vnode,
Event.User, Event.Oneshot, Event.Error}
let fdi = int(fd)
s.checkFd(fdi)
var pkey = addr(s.fds[fdi])
doAssert(pkey.ident != 0)
doAssert(pkey.events * maskEvents == {})
if pkey.events != events:
if (Event.Read in pkey.events) and (Event.Read notin events):
modifyKQueue(s, fdi.uint, EVFILT_READ, EV_DELETE, 0, 0, nil)
dec(s.count)
if (Event.Write in pkey.events) and (Event.Write notin events):
modifyKQueue(s, fdi.uint, EVFILT_WRITE, EV_DELETE, 0, 0, nil)
dec(s.count)
if (Event.Read notin pkey.events) and (Event.Read in events):
modifyKQueue(s, fdi.uint, EVFILT_READ, EV_ADD, 0, 0, nil)
inc(s.count)
if (Event.Write notin pkey.events) and (Event.Write in events):
modifyKQueue(s, fdi.uint, EVFILT_WRITE, EV_ADD, 0, 0, nil)
inc(s.count)
pkey.events = events
proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool,
data: T): int {.discardable.} =
var fdi = posix.socket(posix.AF_INET, posix.SOCK_STREAM,
posix.IPPROTO_TCP).int
if fdi == -1:
raiseOsError(osLastError())
s.checkFd(fdi)
doAssert(s.fds[fdi].ident == 0)
let events = if oneshot: {Event.Timer, Event.Oneshot} else: {Event.Timer}
let flags: cushort = if oneshot: EV_ONESHOT or EV_ADD else: EV_ADD
s.setKey(fdi, fdi, events, 0, data)
# EVFILT_TIMER on Open/Net(BSD) has granularity of only milliseconds,
# but MacOS and FreeBSD allow use `0` as `fflags` to use milliseconds
# too
modifyKQueue(s, fdi.uint, EVFILT_TIMER, flags, 0, cint(timeout), nil)
inc(s.count)
result = fdi
proc registerSignal*[T](s: Selector[T], signal: int,
data: T): int {.discardable.} =
var fdi = posix.socket(posix.AF_INET, posix.SOCK_STREAM,
posix.IPPROTO_TCP).int
if fdi == -1:
raiseOsError(osLastError())
s.checkFd(fdi)
doAssert(s.fds[fdi].ident == 0)
s.setKey(fdi, signal, {Event.Signal}, signal, data)
var nmask, omask: Sigset
discard sigemptyset(nmask)
discard sigemptyset(omask)
discard sigaddset(nmask, cint(signal))
blockSignals(nmask, omask)
# to be compatible with linux semantic we need to "eat" signals
posix.signal(cint(signal), SIG_IGN)
modifyKQueue(s, signal.uint, EVFILT_SIGNAL, EV_ADD, 0, 0,
cast[pointer](fdi))
inc(s.count)
result = fdi
proc registerProcess*[T](s: Selector[T], pid: int,
data: T): int {.discardable.} =
var fdi = posix.socket(posix.AF_INET, posix.SOCK_STREAM,
posix.IPPROTO_TCP).int
if fdi == -1:
raiseOsError(osLastError())
s.checkFd(fdi)
doAssert(s.fds[fdi].ident == 0)
var kflags: cushort = EV_ONESHOT or EV_ADD
setKey(s, fdi, pid, {Event.Process, Event.Oneshot}, pid, data)
modifyKQueue(s, pid.uint, EVFILT_PROC, kflags, NOTE_EXIT, 0,
cast[pointer](fdi))
inc(s.count)
result = fdi
proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
let fdi = ev.rfd.int
doAssert(s.fds[fdi].ident == 0)
setKey(s, fdi, fdi, {Event.User}, 0, data)
modifyKQueue(s, fdi.uint, EVFILT_READ, EV_ADD, 0, 0, nil)
inc(s.count)
proc unregister*[T](s: Selector[T], fd: int|SocketHandle) =
let fdi = int(fd)
s.checkFd(fdi)
var pkey = addr(s.fds[fdi])
doAssert(pkey.ident != 0)
if pkey.events != {}:
if pkey.events * {Event.Read, Event.Write} != {}:
if Event.Read in pkey.events:
modifyKQueue(s, fdi.uint, EVFILT_READ, EV_DELETE, 0, 0, nil)
dec(s.count)
if Event.Write in pkey.events:
modifyKQueue(s, fdi.uint, EVFILT_WRITE, EV_DELETE, 0, 0, nil)
dec(s.count)
elif Event.Timer in pkey.events:
discard posix.close(cint(pkey.key.fd))
modifyKQueue(s, fdi.uint, EVFILT_TIMER, EV_DELETE, 0, 0, nil)
dec(s.count)
elif Event.Signal in pkey.events:
var nmask, omask: Sigset
var signal = cint(pkey.param)
discard sigemptyset(nmask)
discard sigemptyset(omask)
discard sigaddset(nmask, signal)
unblockSignals(nmask, omask)
posix.signal(signal, SIG_DFL)
discard posix.close(cint(pkey.key.fd))
modifyKQueue(s, fdi.uint, EVFILT_SIGNAL, EV_DELETE, 0, 0, nil)
dec(s.count)
elif Event.Process in pkey.events:
discard posix.close(cint(pkey.key.fd))
modifyKQueue(s, fdi.uint, EVFILT_PROC, EV_DELETE, 0, 0, nil)
dec(s.count)
elif Event.User in pkey.events:
modifyKQueue(s, fdi.uint, EVFILT_READ, EV_DELETE, 0, 0, nil)
dec(s.count)
pkey.ident = 0
pkey.events = {}
proc unregister*[T](s: Selector[T], ev: SelectEvent) =
let fdi = int(ev.rfd)
s.checkFd(fdi)
var pkey = addr(s.fds[fdi])
doAssert(pkey.ident != 0)
doAssert(Event.User in pkey.events)
pkey.ident = 0
pkey.events = {}
modifyKQueue(s, fdi.uint, EVFILT_READ, EV_DELETE, 0, 0, nil)
dec(s.count)
proc flush*[T](s: Selector[T]) =
s.withChangeLock():
var tv = Timespec()
if kevent(s.kqFD, addr(s.changesTable[0]), cint(s.changesCount),
nil, 0, addr tv) == -1:
raiseOSError(osLastError())
s.changesCount = 0
proc selectInto*[T](s: Selector[T], timeout: int,
results: var openarray[ReadyKey[T]]): int =
var
tv: Timespec
resTable: array[MAX_KQUEUE_RESULT_EVENTS, KEvent]
ptv = addr tv
maxres = MAX_KQUEUE_RESULT_EVENTS
if timeout != -1:
if timeout >= 1000:
tv.tv_sec = (timeout div 1_000).Time
tv.tv_nsec = (timeout %% 1_000) * 1_000_000
else:
tv.tv_sec = 0.Time
tv.tv_nsec = timeout * 1_000_000
else:
ptv = nil
if maxres > len(results):
maxres = len(results)
var count = 0
s.withChangeLock():
count = kevent(s.kqFD, addr(s.changesTable[0]), cint(s.changesCount),
addr(resTable[0]), cint(maxres), ptv)
s.changesCount = 0
if count < 0:
result = 0
let err = osLastError()
if cint(err) != EINTR:
raiseOSError(err)
elif count == 0:
result = 0
else:
var i = 0
var k = 0
var pkey: ptr SelectorKey[T]
while i < count:
let kevent = addr(resTable[i])
if (kevent.flags and EV_ERROR) == 0:
case kevent.filter:
of EVFILT_READ:
pkey = addr(s.fds[kevent.ident.int])
pkey.key.events = {Event.Read}
if Event.User in pkey.events:
var data: uint64 = 0
if posix.read(kevent.ident.cint, addr data,
sizeof(uint64)) != sizeof(uint64):
let err = osLastError()
if err == OSErrorCode(EAGAIN):
# someone already consumed event data
inc(i)
continue
else:
raiseOSError(osLastError())
pkey.key.events = {Event.User}
of EVFILT_WRITE:
pkey = addr(s.fds[kevent.ident.int])
pkey.key.events = {Event.Write}
of EVFILT_TIMER:
pkey = addr(s.fds[kevent.ident.int])
if Event.Oneshot in pkey.events:
if posix.close(cint(pkey.ident)) == -1:
raiseOSError(osLastError())
pkey.ident = 0
pkey.events = {}
dec(s.count)
pkey.key.events = {Event.Timer}
of EVFILT_VNODE:
pkey = addr(s.fds[kevent.ident.int])
pkey.key.events = {Event.Vnode}
of EVFILT_SIGNAL:
pkey = addr(s.fds[cast[int](kevent.udata)])
pkey.key.events = {Event.Signal}
of EVFILT_PROC:
pkey = addr(s.fds[cast[int](kevent.udata)])
if posix.close(cint(pkey.ident)) == -1:
raiseOSError(osLastError())
pkey.ident = 0
pkey.events = {}
dec(s.count)
pkey.key.events = {Event.Process}
else:
raise newException(ValueError, "Unsupported kqueue filter in queue")
if (kevent.flags and EV_EOF) != 0:
pkey.key.events.incl(Event.Error)
results[k] = pkey.key
inc(k)
inc(i)
result = k
proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] =
result = newSeq[ReadyKey[T]](MAX_KQUEUE_RESULT_EVENTS)
let count = selectInto(s, timeout, result)
result.setLen(count)
template isEmpty*[T](s: Selector[T]): bool =
(s.count == 0)
template withData*[T](s: Selector[T], fd: SocketHandle, value,
body: untyped) =
mixin checkFd
let fdi = int(fd)
s.checkFd(fdi)
if s.fds[fdi].ident != 0:
var value = addr(s.fds[fdi].key.data)
body
template withData*[T](s: Selector[T], fd: SocketHandle, value, body1,
body2: untyped) =
mixin checkFd
let fdi = int(fd)
s.checkFd(fdi)
if s.fds[fdi].ident != 0:
var value = addr(s.fds[fdi].key.data)
body1
else:
body2

View File

@@ -0,0 +1,295 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2016 Eugene Kabanov
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# This module implements Posix poll().
import posix, times
# Maximum number of events that can be returned
const MAX_POLL_RESULT_EVENTS = 64
when hasThreadSupport:
type
SelectorImpl[T] = object
maxFD : int
pollcnt: int
fds: ptr SharedArray[SelectorKey[T]]
pollfds: ptr SharedArray[TPollFd]
count: int
lock: Lock
Selector*[T] = ptr SelectorImpl[T]
else:
type
SelectorImpl[T] = object
maxFD : int
pollcnt: int
fds: seq[SelectorKey[T]]
pollfds: seq[TPollFd]
count: int
Selector*[T] = ref SelectorImpl[T]
type
SelectEventImpl = object
rfd: cint
wfd: cint
SelectEvent* = ptr SelectEventImpl
var RLIMIT_NOFILE {.importc: "RLIMIT_NOFILE",
header: "<sys/resource.h>".}: cint
type
rlimit {.importc: "struct rlimit",
header: "<sys/resource.h>", pure, final.} = object
rlim_cur: int
rlim_max: int
proc getrlimit(resource: cint, rlp: var rlimit): cint
{.importc: "getrlimit",header: "<sys/resource.h>".}
when hasThreadSupport:
template withPollLock[T](s: Selector[T], body: untyped) =
acquire(s.lock)
{.locks: [s.lock].}:
try:
body
finally:
release(s.lock)
else:
template withPollLock(s, body: untyped) =
body
proc newSelector*[T](): Selector[T] =
var a = rlimit()
if getrlimit(RLIMIT_NOFILE, a) != 0:
raiseOsError(osLastError())
var maxFD = int(a.rlim_max)
when hasThreadSupport:
result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T])))
result.maxFD = maxFD
result.fds = allocSharedArray[SelectorKey[T]](maxFD)
result.pollfds = allocSharedArray[TPollFd](maxFD)
initLock(result.lock)
else:
result = Selector[T]()
result.maxFD = maxFD
result.fds = newSeq[SelectorKey[T]](maxFD)
result.pollfds = newSeq[TPollFd](maxFD)
proc close*[T](s: Selector[T]) =
when hasThreadSupport:
deinitLock(s.lock)
deallocSharedArray(s.fds)
deallocSharedArray(s.pollfds)
deallocShared(cast[pointer](s))
template pollAdd[T](s: Selector[T], sock: cint, events: set[Event]) =
withPollLock(s):
var pollev: cshort = 0
if Event.Read in events: pollev = pollev or POLLIN
if Event.Write in events: pollev = pollev or POLLOUT
s.pollfds[s.pollcnt].fd = cint(sock)
s.pollfds[s.pollcnt].events = pollev
inc(s.count)
inc(s.pollcnt)
template pollUpdate[T](s: Selector[T], sock: cint, events: set[Event]) =
withPollLock(s):
var i = 0
var pollev: cshort = 0
if Event.Read in events: pollev = pollev or POLLIN
if Event.Write in events: pollev = pollev or POLLOUT
while i < s.pollcnt:
if s.pollfds[i].fd == sock:
s.pollfds[i].events = pollev
break
inc(i)
if i == s.pollcnt:
raise newException(ValueError, "Descriptor is not registered in queue")
template pollRemove[T](s: Selector[T], sock: cint) =
withPollLock(s):
var i = 0
while i < s.pollcnt:
if s.pollfds[i].fd == sock:
if i == s.pollcnt - 1:
s.pollfds[i].fd = 0
s.pollfds[i].events = 0
s.pollfds[i].revents = 0
else:
while i < (s.pollcnt - 1):
s.pollfds[i].fd = s.pollfds[i + 1].fd
s.pollfds[i].events = s.pollfds[i + 1].events
inc(i)
break
inc(i)
dec(s.pollcnt)
dec(s.count)
template checkFd(s, f) =
if f >= s.maxFD:
raise newException(ValueError, "Maximum file descriptors exceeded")
proc registerHandle*[T](s: Selector[T], fd: SocketHandle,
events: set[Event], data: T) =
var fdi = int(fd)
s.checkFd(fdi)
doAssert(s.fds[fdi].ident == 0)
s.setKey(fdi, fdi, events, 0, data)
if events != {}: s.pollAdd(fdi.cint, events)
proc updateHandle*[T](s: Selector[T], fd: SocketHandle,
events: set[Event]) =
let maskEvents = {Event.Timer, Event.Signal, Event.Process, Event.Vnode,
Event.User, Event.Oneshot, Event.Error}
let fdi = int(fd)
s.checkFd(fdi)
var pkey = addr(s.fds[fdi])
doAssert(pkey.ident != 0)
doAssert(pkey.events * maskEvents == {})
if pkey.events != events:
if pkey.events == {}:
s.pollAdd(fd.cint, events)
else:
if events != {}:
s.pollUpdate(fd.cint, events)
else:
s.pollRemove(fd.cint)
pkey.events = events
proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
var fdi = int(ev.rfd)
doAssert(s.fds[fdi].ident == 0)
var events = {Event.User}
setKey(s, fdi, fdi, events, 0, data)
events.incl(Event.Read)
s.pollAdd(fdi.cint, events)
proc flush*[T](s: Selector[T]) = discard
proc unregister*[T](s: Selector[T], fd: int|SocketHandle) =
let fdi = int(fd)
s.checkFd(fdi)
var pkey = addr(s.fds[fdi])
doAssert(pkey.ident != 0)
pkey.ident = 0
pkey.events = {}
s.pollRemove(fdi.cint)
proc unregister*[T](s: Selector[T], ev: SelectEvent) =
let fdi = int(ev.rfd)
s.checkFd(fdi)
var pkey = addr(s.fds[fdi])
doAssert(pkey.ident != 0)
doAssert(Event.User in pkey.events)
pkey.ident = 0
pkey.events = {}
s.pollRemove(fdi.cint)
proc newSelectEvent*(): SelectEvent =
var fds: array[2, cint]
if posix.pipe(fds) == -1:
raiseOSError(osLastError())
setNonBlocking(fds[0])
setNonBlocking(fds[1])
result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl)))
result.rfd = fds[0]
result.wfd = fds[1]
proc setEvent*(ev: SelectEvent) =
var data: uint64 = 1
if posix.write(ev.wfd, addr data, sizeof(uint64)) != sizeof(uint64):
raiseOSError(osLastError())
proc close*(ev: SelectEvent) =
discard posix.close(cint(ev.rfd))
discard posix.close(cint(ev.wfd))
deallocShared(cast[pointer](ev))
proc selectInto*[T](s: Selector[T], timeout: int,
results: var openarray[ReadyKey[T]]): int =
var maxres = MAX_POLL_RESULT_EVENTS
if maxres > len(results):
maxres = len(results)
s.withPollLock():
let count = posix.poll(addr(s.pollfds[0]), Tnfds(s.pollcnt), timeout)
if count < 0:
result = 0
let err = osLastError()
if err.cint == EINTR:
discard
else:
raiseOSError(osLastError())
elif count == 0:
result = 0
else:
var i = 0
var k = 0
var rindex = 0
while (i < s.pollcnt) and (k < count) and (rindex < maxres):
let revents = s.pollfds[i].revents
if revents != 0:
let fd = s.pollfds[i].fd
var skey = addr(s.fds[fd])
skey.key.events = {}
if (revents and POLLIN) != 0:
skey.key.events.incl(Event.Read)
if Event.User in skey.events:
var data: uint64 = 0
if posix.read(fd, addr data, sizeof(int)) != sizeof(int):
let err = osLastError()
if err != OSErrorCode(EAGAIN):
raiseOSError(osLastError())
else:
# someone already consumed event data
inc(i)
continue
skey.key.events = {Event.User}
if (revents and POLLOUT) != 0:
skey.key.events.incl(Event.Write)
if (revents and POLLERR) != 0 or (revents and POLLHUP) != 0 or
(revents and POLLNVAL) != 0:
skey.key.events.incl(Event.Error)
results[rindex] = skey.key
s.pollfds[i].revents = 0
inc(rindex)
inc(k)
inc(i)
result = k
proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] =
result = newSeq[ReadyKey[T]](MAX_POLL_RESULT_EVENTS)
let count = selectInto(s, timeout, result)
result.setLen(count)
template isEmpty*[T](s: Selector[T]): bool =
(s.count == 0)
template withData*[T](s: Selector[T], fd: SocketHandle, value,
body: untyped) =
mixin checkFd
let fdi = int(fd)
s.checkFd(fdi)
if s.fds[fdi].ident != 0:
var value = addr(s.fds[fdi].key.data)
body
template withData*[T](s: Selector[T], fd: SocketHandle, value, body1,
body2: untyped) =
mixin checkFd
let fdi = int(fd)
s.checkFd(fdi)
if s.fds[fdi].ident != 0:
var value = addr(s.fds[fdi].key.data)
body1
else:
body2

View File

@@ -0,0 +1,416 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2016 Eugene Kabanov
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# This module implements Posix and Windows select().
import times, nativesockets
when defined(windows):
import winlean
when defined(gcc):
{.passL: "-lws2_32".}
elif defined(vcc):
{.passL: "ws2_32.lib".}
const platformHeaders = """#include <winsock2.h>
#include <windows.h>"""
const EAGAIN = WSAEWOULDBLOCK
else:
const platformHeaders = """#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>"""
type
Fdset {.importc: "fd_set", header: platformHeaders, pure, final.} = object
var
FD_SETSIZE {.importc: "FD_SETSIZE", header: platformHeaders.}: cint
proc IOFD_SET(fd: SocketHandle, fdset: ptr Fdset)
{.cdecl, importc: "FD_SET", header: platformHeaders, inline.}
proc IOFD_CLR(fd: SocketHandle, fdset: ptr Fdset)
{.cdecl, importc: "FD_CLR", header: platformHeaders, inline.}
proc IOFD_ZERO(fdset: ptr Fdset)
{.cdecl, importc: "FD_ZERO", header: platformHeaders, inline.}
when defined(windows):
proc IOFD_ISSET(fd: SocketHandle, fdset: ptr Fdset): cint
{.stdcall, importc: "FD_ISSET", header: platformHeaders, inline.}
proc ioselect(nfds: cint, readFds, writeFds, exceptFds: ptr Fdset,
timeout: ptr Timeval): cint
{.stdcall, importc: "select", header: platformHeaders.}
else:
proc IOFD_ISSET(fd: SocketHandle, fdset: ptr Fdset): cint
{.cdecl, importc: "FD_ISSET", header: platformHeaders, inline.}
proc ioselect(nfds: cint, readFds, writeFds, exceptFds: ptr Fdset,
timeout: ptr Timeval): cint
{.cdecl, importc: "select", header: platformHeaders.}
when hasThreadSupport:
type
SelectorImpl[T] = object
rSet: FdSet
wSet: FdSet
eSet: FdSet
maxFD: int
fds: ptr SharedArray[SelectorKey[T]]
count: int
lock: Lock
Selector*[T] = ptr SelectorImpl[T]
else:
type
SelectorImpl[T] = object
rSet: FdSet
wSet: FdSet
eSet: FdSet
maxFD: int
fds: seq[SelectorKey[T]]
count: int
Selector*[T] = ref SelectorImpl[T]
type
SelectEventImpl = object
rsock: SocketHandle
wsock: SocketHandle
SelectEvent* = ptr SelectEventImpl
when hasThreadSupport:
template withSelectLock[T](s: Selector[T], body: untyped) =
acquire(s.lock)
{.locks: [s.lock].}:
try:
body
finally:
release(s.lock)
else:
template withSelectLock[T](s: Selector[T], body: untyped) =
body
proc newSelector*[T](): Selector[T] =
when hasThreadSupport:
result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T])))
result.fds = allocSharedArray[SelectorKey[T]](FD_SETSIZE)
initLock result.lock
else:
result = Selector[T]()
result.fds = newSeq[SelectorKey[T]](FD_SETSIZE)
IOFD_ZERO(addr result.rSet)
IOFD_ZERO(addr result.wSet)
IOFD_ZERO(addr result.eSet)
proc close*[T](s: Selector[T]) =
when hasThreadSupport:
deallocSharedArray(s.fds)
deallocShared(cast[pointer](s))
when defined(windows):
proc newSelectEvent*(): SelectEvent =
var ssock = newNativeSocket()
var wsock = newNativeSocket()
var rsock: SocketHandle = INVALID_SOCKET
var saddr = Sockaddr_in()
saddr.sin_family = winlean.AF_INET
saddr.sin_port = 0
saddr.sin_addr.s_addr = INADDR_ANY
if bindAddr(ssock, cast[ptr SockAddr](addr(saddr)),
sizeof(saddr).SockLen) < 0'i32:
raiseOSError(osLastError())
if winlean.listen(ssock, 1) == -1:
raiseOSError(osLastError())
var namelen = sizeof(saddr).SockLen
if getsockname(ssock, cast[ptr SockAddr](addr(saddr)),
addr(namelen)) == -1'i32:
raiseOSError(osLastError())
saddr.sin_addr.s_addr = 0x0100007F
if winlean.connect(wsock, cast[ptr SockAddr](addr(saddr)),
sizeof(saddr).SockLen) == -1:
raiseOSError(osLastError())
namelen = sizeof(saddr).SockLen
rsock = winlean.accept(ssock, cast[ptr SockAddr](addr(saddr)),
cast[ptr SockLen](addr(namelen)))
if rsock == SocketHandle(-1):
raiseOSError(osLastError())
if winlean.closesocket(ssock) == -1:
raiseOSError(osLastError())
var mode = clong(1)
if ioctlsocket(rsock, FIONBIO, addr(mode)) == -1:
raiseOSError(osLastError())
mode = clong(1)
if ioctlsocket(wsock, FIONBIO, addr(mode)) == -1:
raiseOSError(osLastError())
result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl)))
result.rsock = rsock
result.wsock = wsock
proc setEvent*(ev: SelectEvent) =
var data: int = 1
if winlean.send(ev.wsock, cast[pointer](addr data),
cint(sizeof(int)), 0) != sizeof(int):
raiseOSError(osLastError())
proc close*(ev: SelectEvent) =
discard winlean.closesocket(ev.rsock)
discard winlean.closesocket(ev.wsock)
deallocShared(cast[pointer](ev))
else:
proc newSelectEvent*(): SelectEvent =
var fds: array[2, cint]
if posix.pipe(fds) == -1:
raiseOSError(osLastError())
setNonBlocking(fds[0])
setNonBlocking(fds[1])
result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl)))
result.rsock = SocketHandle(fds[0])
result.wsock = SocketHandle(fds[1])
proc setEvent*(ev: SelectEvent) =
var data: uint64 = 1
if posix.write(cint(ev.wsock), addr data, sizeof(uint64)) != sizeof(uint64):
raiseOSError(osLastError())
proc close*(ev: SelectEvent) =
discard posix.close(cint(ev.rsock))
discard posix.close(cint(ev.wsock))
deallocShared(cast[pointer](ev))
proc setKey[T](s: Selector[T], fd: SocketHandle, events: set[Event], data: T) =
var i = 0
let fdi = int(fd)
while i < FD_SETSIZE:
if s.fds[i].ident == 0:
var pkey = addr(s.fds[i])
pkey.ident = fdi
pkey.events = events
pkey.key.fd = fd.int
pkey.key.events = {}
pkey.key.data = data
break
inc(i)
if i == FD_SETSIZE:
raise newException(ValueError, "Maximum numbers of fds exceeded")
proc getKey[T](s: Selector[T], fd: SocketHandle): ptr SelectorKey[T] =
var i = 0
let fdi = int(fd)
while i < FD_SETSIZE:
if s.fds[i].ident == fdi:
result = addr(s.fds[i])
break
inc(i)
doAssert(i < FD_SETSIZE, "Descriptor not registered in queue")
proc delKey[T](s: Selector[T], fd: SocketHandle) =
var i = 0
while i < FD_SETSIZE:
if s.fds[i].ident == fd.int:
s.fds[i].ident = 0
s.fds[i].events = {}
break
inc(i)
doAssert(i < FD_SETSIZE, "Descriptor not registered in queue")
proc registerHandle*[T](s: Selector[T], fd: SocketHandle,
events: set[Event], data: T) =
when not defined(windows):
let fdi = int(fd)
s.withSelectLock():
s.setKey(fd, events, data)
when not defined(windows):
if fdi > s.maxFD: s.maxFD = fdi
if Event.Read in events:
IOFD_SET(fd, addr s.rSet)
inc(s.count)
if Event.Write in events:
IOFD_SET(fd, addr s.wSet)
IOFD_SET(fd, addr s.eSet)
inc(s.count)
proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
when not defined(windows):
let fdi = int(ev.rsock)
s.withSelectLock():
s.setKey(ev.rsock, {Event.User}, data)
when not defined(windows):
if fdi > s.maxFD: s.maxFD = fdi
IOFD_SET(ev.rsock, addr s.rSet)
inc(s.count)
proc updateHandle*[T](s: Selector[T], fd: SocketHandle,
events: set[Event]) =
let maskEvents = {Event.Timer, Event.Signal, Event.Process, Event.Vnode,
Event.User, Event.Oneshot, Event.Error}
s.withSelectLock():
var pkey = s.getKey(fd)
doAssert(pkey.events * maskEvents == {})
if pkey.events != events:
if (Event.Read in pkey.events) and (Event.Read notin events):
IOFD_CLR(fd, addr s.rSet)
dec(s.count)
if (Event.Write in pkey.events) and (Event.Write notin events):
IOFD_CLR(fd, addr s.wSet)
IOFD_CLR(fd, addr s.eSet)
dec(s.count)
if (Event.Read notin pkey.events) and (Event.Read in events):
IOFD_SET(fd, addr s.rSet)
inc(s.count)
if (Event.Write notin pkey.events) and (Event.Write in events):
IOFD_SET(fd, addr s.wSet)
IOFD_SET(fd, addr s.eSet)
inc(s.count)
pkey.events = events
proc unregister*[T](s: Selector[T], fd: SocketHandle) =
s.withSelectLock():
var pkey = s.getKey(fd)
if Event.Read in pkey.events:
IOFD_CLR(fd, addr s.rSet)
dec(s.count)
if Event.Write in pkey.events:
IOFD_CLR(fd, addr s.wSet)
IOFD_CLR(fd, addr s.eSet)
dec(s.count)
s.delKey(fd)
proc unregister*[T](s: Selector[T], ev: SelectEvent) =
let fd = ev.rsock
s.withSelectLock():
IOFD_CLR(fd, addr s.rSet)
dec(s.count)
s.delKey(fd)
proc selectInto*[T](s: Selector[T], timeout: int,
results: var openarray[ReadyKey[T]]): int =
var tv = Timeval()
var ptv = addr tv
var rset, wset, eset: FdSet
if timeout != -1:
tv.tv_sec = timeout.int32 div 1_000
tv.tv_usec = (timeout.int32 %% 1_000) * 1_000
else:
ptv = nil
s.withSelectLock():
rset = s.rSet
wset = s.wSet
eset = s.eSet
var count = ioselect(cint(s.maxFD) + 1, addr(rset), addr(wset),
addr(eset), ptv)
if count < 0:
result = 0
when defined(windows):
raiseOSError(osLastError())
else:
let err = osLastError()
if cint(err) != EINTR:
raiseOSError(err)
elif count == 0:
result = 0
else:
var rindex = 0
var i = 0
var k = 0
while (i < FD_SETSIZE) and (k < count):
if s.fds[i].ident != 0:
var flag = false
var pkey = addr(s.fds[i])
pkey.key.events = {}
let fd = SocketHandle(pkey.ident)
if IOFD_ISSET(fd, addr rset) != 0:
if Event.User in pkey.events:
var data: uint64 = 0
if recv(fd, cast[pointer](addr(data)),
sizeof(uint64).cint, 0) != sizeof(uint64):
let err = osLastError()
if cint(err) != EAGAIN:
raiseOSError(err)
else:
inc(i)
inc(k)
continue
else:
flag = true
pkey.key.events = {Event.User}
else:
flag = true
pkey.key.events = {Event.Read}
if IOFD_ISSET(fd, addr wset) != 0:
pkey.key.events.incl(Event.Write)
if IOFD_ISSET(fd, addr eset) != 0:
pkey.key.events.incl(Event.Error)
flag = true
if flag:
results[rindex] = pkey.key
inc(rindex)
inc(k)
inc(i)
result = rindex
proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] =
result = newSeq[ReadyKey[T]](FD_SETSIZE)
var count = selectInto(s, timeout, result)
result.setLen(count)
proc flush*[T](s: Selector[T]) = discard
template isEmpty*[T](s: Selector[T]): bool =
(s.count == 0)
when hasThreadSupport:
template withSelectLock[T](s: Selector[T], body: untyped) =
acquire(s.lock)
{.locks: [s.lock].}:
try:
body
finally:
release(s.lock)
else:
template withSelectLock[T](s: Selector[T], body: untyped) =
body
template withData*[T](s: Selector[T], fd: SocketHandle, value,
body: untyped) =
mixin withSelectLock
s.withSelectLock():
var value: ptr T
let fdi = int(fd)
var i = 0
while i < FD_SETSIZE:
if s.fds[i].ident == fdi:
value = addr(s.fds[i].key.data)
break
inc(i)
if i != FD_SETSIZE:
body
template withData*[T](s: Selector[T], fd: SocketHandle, value,
body1, body2: untyped) =
mixin withSelectLock
s.withSelectLock():
var value: ptr T
let fdi = int(fd)
var i = 0
while i < FD_SETSIZE:
if s.fds[i].ident == fdi:
value = addr(s.fds[i].key.data)
break
inc(i)
if i != FD_SETSIZE:
body1
else:
body2

View File

@@ -20,7 +20,8 @@
## let
## small_json = """{"test": 1.3, "key2": true}"""
## jobj = parseJson(small_json)
## assert (jobj.kind == JObject)
## assert (jobj.kind == JObject)\
## jobj["test"] = newJFloat(0.7) # create or update
## echo($jobj["test"].fnum)
## echo($jobj["key2"].bval)
##
@@ -49,6 +50,10 @@
## "age": herAge
## }
## ]
##
## var j2 = %* {"name": "Isaac", "books": ["Robot Dreams"]}
## j2["details"] = %* {"age":35, "pi":3.1415}
## echo j2
import
hashes, tables, strutils, lexbase, streams, unicode, macros
@@ -56,6 +61,11 @@ import
export
tables.`$`
when defined(nimJsonGet):
{.pragma: deprecatedGet, deprecated.}
else:
{.pragma: deprecatedGet.}
type
JsonEventKind* = enum ## enumeration of all events that may occur when parsing
jsonError, ## an error occurred during parsing
@@ -116,7 +126,7 @@ type
TJsonParser: JsonParser, TTokKind: TokKind].}
const
errorMessages: array [JsonError, string] = [
errorMessages: array[JsonError, string] = [
"no error",
"invalid token",
"string expected",
@@ -129,7 +139,7 @@ const
"EOF expected",
"expression expected"
]
tokToStr: array [TokKind, string] = [
tokToStr: array[TokKind, string] = [
"invalid token",
"EOF",
"string literal",
@@ -702,17 +712,28 @@ proc `%`*(b: bool): JsonNode =
proc `%`*(keyVals: openArray[tuple[key: string, val: JsonNode]]): JsonNode =
## Generic constructor for JSON data. Creates a new `JObject JsonNode`
new(result)
result.kind = JObject
result.fields = initTable[string, JsonNode](4)
if keyvals.len == 0: return newJArray()
result = newJObject()
for key, val in items(keyVals): result.fields[key] = val
proc `%`*(elements: openArray[JsonNode]): JsonNode =
template `%`*(j: JsonNode): JsonNode = j
proc `%`*[T](elements: openArray[T]): JsonNode =
## Generic constructor for JSON data. Creates a new `JArray JsonNode`
new(result)
result.kind = JArray
newSeq(result.elems, elements.len)
for i, p in pairs(elements): result.elems[i] = p
result = newJArray()
for elem in elements: result.add(%elem)
proc `%`*(o: object): JsonNode =
## Generic constructor for JSON data. Creates a new `JObject JsonNode`
result = newJObject()
for k, v in o.fieldPairs: result[k] = %v
proc `%`*(o: ref object): JsonNode =
## Generic constructor for JSON data. Creates a new `JObject JsonNode`
if o.isNil:
result = newJNull()
else:
result = %(o[])
proc toJson(x: NimNode): NimNode {.compiletime.} =
case x.kind
@@ -731,6 +752,9 @@ proc toJson(x: NimNode): NimNode {.compiletime.} =
result = newNimNode(nnkTableConstr)
x.expectLen(0)
of nnkNilLit:
result = newCall("newJNull")
else:
result = x
@@ -799,16 +823,23 @@ proc len*(n: JsonNode): int =
of JObject: result = n.fields.len
else: discard
proc `[]`*(node: JsonNode, name: string): JsonNode {.inline.} =
proc `[]`*(node: JsonNode, name: string): JsonNode {.inline, deprecatedGet.} =
## Gets a field from a `JObject`, which must not be nil.
## If the value at `name` does not exist, returns nil
## If the value at `name` does not exist, raises KeyError.
##
## **Note:** The behaviour of this procedure changed in version 0.14.0. To
## get a list of usages and to restore the old behaviour of this procedure,
## compile with the ``-d:nimJsonGet`` flag.
assert(not isNil(node))
assert(node.kind == JObject)
result = node.fields.getOrDefault(name)
when defined(nimJsonGet):
if not node.fields.hasKey(name): return nil
result = node.fields[name]
proc `[]`*(node: JsonNode, index: int): JsonNode {.inline.} =
## Gets the node at `index` in an Array. Result is undefined if `index`
## is out of bounds
## is out of bounds, but as long as array bound checks are enabled it will
## result in an exception.
assert(not isNil(node))
assert(node.kind == JArray)
return node.elems[index]
@@ -818,6 +849,16 @@ proc hasKey*(node: JsonNode, key: string): bool =
assert(node.kind == JObject)
result = node.fields.hasKey(key)
proc contains*(node: JsonNode, key: string): bool =
## Checks if `key` exists in `node`.
assert(node.kind == JObject)
node.fields.hasKey(key)
proc contains*(node: JsonNode, val: JsonNode): bool =
## Checks if `val` exists in array `node`.
assert(node.kind == JArray)
find(node.elems, val) >= 0
proc existsKey*(node: JsonNode, key: string): bool {.deprecated.} = node.hasKey(key)
## Deprecated for `hasKey`
@@ -838,20 +879,28 @@ proc `[]=`*(obj: JsonNode, key: string, val: JsonNode) {.inline.} =
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
## keys do not exist, returns ``nil``. Also returns ``nil`` if one of the
## intermediate data structures is not an object.
result = node
for key in keys:
if isNil(result) or result.kind!=JObject:
if isNil(result) or result.kind != JObject:
return nil
result=result[key]
result = result.fields.getOrDefault(key)
proc getOrDefault*(node: JsonNode, key: string): JsonNode =
## Gets a field from a `node`. If `node` is nil or not an object or
## value at `key` does not exist, returns nil
if not isNil(node) and node.kind == JObject:
result = node.fields.getOrDefault(key)
template simpleGetOrDefault*{`{}`(node, [key])}(node: JsonNode, key: string): JsonNode = node.getOrDefault(key)
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
## to ``value``. If any of the keys are missing, they are added.
var node = node
for i in 0..(keys.len-2):
if isNil(node[keys[i]]):
if not node.hasKey(keys[i]):
node[keys[i]] = newJObject()
node = node[keys[i]]
node[keys[keys.len-1]] = value
@@ -972,7 +1021,7 @@ proc toPretty(result: var string, node: JsonNode, indent = 2, ml = true,
result.add("null")
proc pretty*(node: JsonNode, indent = 2): string =
## Converts `node` to its JSON Representation, with indentation and
## Returns a JSON Representation of `node`, with indentation and
## on multiple lines.
result = ""
toPretty(result, node, indent)
@@ -982,7 +1031,9 @@ proc toUgly*(result: var string, node: JsonNode) =
## regard for human readability. Meant to improve ``$`` string
## conversion performance.
##
## This provides higher efficiency than the ``toPretty`` procedure as it
## JSON representation is stored in the passed `result`
##
## This provides higher efficiency than the ``pretty`` procedure as it
## does **not** attempt to format the resulting JSON to make it human readable.
var comma = false
case node.kind:
@@ -1076,7 +1127,7 @@ proc parseJson(p: var JsonParser): JsonNode =
discard getTok(p)
while p.tok != tkCurlyRi:
if p.tok != tkString:
raiseParseErr(p, "string literal as key expected")
raiseParseErr(p, "string literal as key")
var key = p.a
discard getTok(p)
eat(p, tkColon)
@@ -1217,16 +1268,6 @@ when false:
# To get that we shall use, obj["json"]
when isMainModule:
when not defined(js):
var parsed = parseFile("tests/testdata/jsontest.json")
try:
discard parsed["key2"][12123]
doAssert(false)
except IndexError: doAssert(true)
var parsed2 = parseFile("tests/testdata/jsontest2.json")
doAssert(parsed2{"repository", "description"}.str=="IRC Library for Haskell", "Couldn't fetch via multiply nested key using {}")
let testJson = parseJson"""{ "a": [1, 2, 3, 4], "b": "asd", "c": "\ud83c\udf83", "d": "\u00E6"}"""
# nil passthrough
@@ -1314,3 +1355,35 @@ when isMainModule:
var j4 = %*{"test": nil}
doAssert j4 == %{"test": newJNull()}
let seqOfNodes = @[%1, %2]
let jSeqOfNodes = %seqOfNodes
doAssert(jSeqOfNodes[1].num == 2)
type MyObj = object
a, b: int
s: string
f32: float32
f64: float64
next: ref MyObj
var m: MyObj
m.s = "hi"
m.a = 5
let jMyObj = %m
doAssert(jMyObj["a"].num == 5)
doAssert(jMyObj["s"].str == "hi")
# Test loading of file.
when not defined(js):
echo("99% of tests finished. Going to try loading file.")
var parsed = parseFile("tests/testdata/jsontest.json")
try:
discard parsed["key2"][12123]
doAssert(false)
except IndexError: doAssert(true)
var parsed2 = parseFile("tests/testdata/jsontest2.json")
doAssert(parsed2{"repository", "description"}.str=="IRC Library for Haskell", "Couldn't fetch via multiply nested key using {}")
echo("Tests succeeded!")

View File

@@ -54,14 +54,15 @@ type
lvlAll, ## all levels active
lvlDebug, ## debug level (and any above) active
lvlInfo, ## info level (and any above) active
lvlNotice, ## info notice (and any above) active
lvlWarn, ## warn level (and any above) active
lvlError, ## error level (and any above) active
lvlFatal, ## fatal level (and any above) active
lvlNone ## no levels active
const
LevelNames*: array [Level, string] = [
"DEBUG", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "NONE"
LevelNames*: array[Level, string] = [
"DEBUG", "DEBUG", "INFO", "NOTICE", "WARN", "ERROR", "FATAL", "NONE"
]
defaultFmtStr* = "$levelname " ## default format string
@@ -258,22 +259,47 @@ template log*(level: Level, args: varargs[string, `$`]) =
template debug*(args: varargs[string, `$`]) =
## Logs a debug message to all registered handlers.
##
## Messages that are useful to the application developer only and are usually
## turned off in release.
log(lvlDebug, args)
template info*(args: varargs[string, `$`]) =
## Logs an info message to all registered handlers.
##
## Messages that are generated during the normal operation of an application
## and are of no particular importance. Useful to aggregate for potential
## later analysis.
log(lvlInfo, args)
template notice*(args: varargs[string, `$`]) =
## Logs an notice message to all registered handlers.
##
## Semantically very similar to `info`, but meant to be messages you want to
## be actively notified about (depending on your application).
## These could be, for example, grouped by hour and mailed out.
log(lvlNotice, args)
template warn*(args: varargs[string, `$`]) =
## Logs a warning message to all registered handlers.
##
## A non-error message that may indicate a potential problem rising or
## impacted performance.
log(lvlWarn, args)
template error*(args: varargs[string, `$`]) =
## Logs an error message to all registered handlers.
##
## A application-level error condition. For example, some user input generated
## an exception. The application will continue to run, but functionality or
## data was impacted, possibly visible to users.
log(lvlError, args)
template fatal*(args: varargs[string, `$`]) =
## Logs a fatal error message to all registered handlers.
##
## A application-level fatal condition. FATAL usually means that the application
## cannot go on and will exit (but this logging event will not do that for you).
log(lvlFatal, args)
proc addHandler*(handler: Logger) =

View File

@@ -8,6 +8,10 @@
#
## This module contains various string matchers for email addresses, etc.
##
## **Warning:** This module is deprecated since version 0.14.0.
{.deprecated.}
{.deadCodeElim: on.}
{.push debugger:off .} # the user does not want to trace a part

View File

@@ -39,8 +39,6 @@ proc fac*(n: int): int {.noSideEffect.} =
when defined(Posix) and not defined(haiku):
{.passl: "-lm".}
when not defined(js) and not defined(nimscript):
import times
const
PI* = 3.1415926535897932384626433 ## the circle constant PI (Ludolph's number)
@@ -119,192 +117,247 @@ proc sum*[T](x: openArray[T]): T {.noSideEffect.} =
## If `x` is empty, 0 is returned.
for i in items(x): result = result + i
proc random*(max: int): int {.benign.}
## Returns a random number in the range 0..max-1. The sequence of
## random number is always the same, unless `randomize` is called
## which initializes the random number generator with a "random"
## number, i.e. a tickcount.
proc random*(max: float): float {.benign.}
## Returns a random number in the range 0..<max. The sequence of
## random number is always the same, unless `randomize` is called
## which initializes the random number generator with a "random"
## number, i.e. a tickcount. This has a 16-bit resolution on windows
## and a 48-bit resolution on other platforms.
when not defined(nimscript):
proc randomize*() {.benign.}
## Initializes the random number generator with a "random"
## number, i.e. a tickcount. Note: Does nothing for the JavaScript target,
## as JavaScript does not support this. Nor does it work for NimScript.
proc randomize*(seed: int) {.benign.}
## Initializes the random number generator with a specific seed.
## Note: Does nothing for the JavaScript target,
## as JavaScript does not support this.
{.push noSideEffect.}
when not defined(JS):
proc sqrt*(x: float): float {.importc: "sqrt", header: "<math.h>".}
proc sqrt*(x: float32): float32 {.importc: "sqrtf", header: "<math.h>".}
proc sqrt*(x: float64): float64 {.importc: "sqrt", header: "<math.h>".}
## Computes the square root of `x`.
proc cbrt*(x: float): float {.importc: "cbrt", header: "<math.h>".}
proc cbrt*(x: float32): float32 {.importc: "cbrtf", header: "<math.h>".}
proc cbrt*(x: float64): float64 {.importc: "cbrt", header: "<math.h>".}
## Computes the cubic root of `x`
proc ln*(x: float): float {.importc: "log", header: "<math.h>".}
proc ln*(x: float32): float32 {.importc: "logf", header: "<math.h>".}
proc ln*(x: float64): float64 {.importc: "log", header: "<math.h>".}
## Computes the natural log of `x`
proc log10*(x: float): float {.importc: "log10", header: "<math.h>".}
proc log10*(x: float32): float32 {.importc: "log10f", header: "<math.h>".}
proc log10*(x: float64): float64 {.importc: "log10", header: "<math.h>".}
## Computes the common logarithm (base 10) of `x`
proc log2*(x: float): float = return ln(x) / ln(2.0)
proc log2*[T: float32|float64](x: T): T = return ln(x) / ln(2.0)
## Computes the binary logarithm (base 2) of `x`
proc exp*(x: float): float {.importc: "exp", header: "<math.h>".}
proc exp*(x: float32): float32 {.importc: "expf", header: "<math.h>".}
proc exp*(x: float64): float64 {.importc: "exp", header: "<math.h>".}
## Computes the exponential function of `x` (pow(E, x))
proc frexp*(x: float, exponent: var int): float {.
importc: "frexp", header: "<math.h>".}
## Split a number into mantissa and exponent.
## `frexp` calculates the mantissa m (a float greater than or equal to 0.5
## and less than 1) and the integer value n such that `x` (the original
## float value) equals m * 2**n. frexp stores n in `exponent` and returns
## m.
proc round*(x: float): int {.importc: "lrint", header: "<math.h>".}
## Converts a float to an int by rounding.
proc arccos*(x: float): float {.importc: "acos", header: "<math.h>".}
proc arccos*(x: float32): float32 {.importc: "acosf", header: "<math.h>".}
proc arccos*(x: float64): float64 {.importc: "acos", header: "<math.h>".}
## Computes the arc cosine of `x`
proc arcsin*(x: float): float {.importc: "asin", header: "<math.h>".}
proc arcsin*(x: float32): float32 {.importc: "asinf", header: "<math.h>".}
proc arcsin*(x: float64): float64 {.importc: "asin", header: "<math.h>".}
## Computes the arc sine of `x`
proc arctan*(x: float): float {.importc: "atan", header: "<math.h>".}
proc arctan*(x: float32): float32 {.importc: "atanf", header: "<math.h>".}
proc arctan*(x: float64): float64 {.importc: "atan", header: "<math.h>".}
## Calculate the arc tangent of `y` / `x`
proc arctan2*(y, x: float): float {.importc: "atan2", header: "<math.h>".}
proc arctan2*(y, x: float32): float32 {.importc: "atan2f", header: "<math.h>".}
proc arctan2*(y, x: float64): float64 {.importc: "atan2", header: "<math.h>".}
## Calculate the arc tangent of `y` / `x`.
## `atan2` returns the arc tangent of `y` / `x`; it produces correct
## results even when the resulting angle is near pi/2 or -pi/2
## (`x` near 0).
proc cos*(x: float): float {.importc: "cos", header: "<math.h>".}
proc cos*(x: float32): float32 {.importc: "cosf", header: "<math.h>".}
proc cos*(x: float64): float64 {.importc: "cos", header: "<math.h>".}
## Computes the cosine of `x`
proc cosh*(x: float): float {.importc: "cosh", header: "<math.h>".}
proc cosh*(x: float32): float32 {.importc: "coshf", header: "<math.h>".}
proc cosh*(x: float64): float64 {.importc: "cosh", header: "<math.h>".}
## Computes the hyperbolic cosine of `x`
proc hypot*(x, y: float): float {.importc: "hypot", header: "<math.h>".}
proc hypot*(x, y: float32): float32 {.importc: "hypotf", header: "<math.h>".}
proc hypot*(x, y: float64): float64 {.importc: "hypot", header: "<math.h>".}
## Computes the hypotenuse of a right-angle triangle with `x` and
## `y` as its base and height. Equivalent to ``sqrt(x*x + y*y)``.
proc sinh*(x: float): float {.importc: "sinh", header: "<math.h>".}
proc sinh*(x: float32): float32 {.importc: "sinhf", header: "<math.h>".}
proc sinh*(x: float64): float64 {.importc: "sinh", header: "<math.h>".}
## Computes the hyperbolic sine of `x`
proc sin*(x: float): float {.importc: "sin", header: "<math.h>".}
proc sin*(x: float32): float32 {.importc: "sinf", header: "<math.h>".}
proc sin*(x: float64): float64 {.importc: "sin", header: "<math.h>".}
## Computes the sine of `x`
proc tan*(x: float): float {.importc: "tan", header: "<math.h>".}
## Computes the tangent of `x`
proc tanh*(x: float): float {.importc: "tanh", header: "<math.h>".}
## Computes the hyperbolic tangent of `x`
proc pow*(x, y: float): float {.importc: "pow", header: "<math.h>".}
## Computes `x` to power of `y`.
proc erf*(x: float): float {.importc: "erf", header: "<math.h>".}
proc tan*(x: float32): float32 {.importc: "tanf", header: "<math.h>".}
proc tan*(x: float64): float64 {.importc: "tan", header: "<math.h>".}
## Computes the tangent of `x`
proc tanh*(x: float32): float32 {.importc: "tanhf", header: "<math.h>".}
proc tanh*(x: float64): float64 {.importc: "tanh", header: "<math.h>".}
## Computes the hyperbolic tangent of `x`
proc pow*(x, y: float32): float32 {.importc: "powf", header: "<math.h>".}
proc pow*(x, y: float64): float64 {.importc: "pow", header: "<math.h>".}
## computes x to power raised of y.
proc erf*(x: float32): float32 {.importc: "erff", header: "<math.h>".}
proc erf*(x: float64): float64 {.importc: "erf", header: "<math.h>".}
## The error function
proc erfc*(x: float): float {.importc: "erfc", header: "<math.h>".}
proc erfc*(x: float32): float32 {.importc: "erfcf", header: "<math.h>".}
proc erfc*(x: float64): float64 {.importc: "erfc", header: "<math.h>".}
## The complementary error function
proc lgamma*(x: float): float {.importc: "lgamma", header: "<math.h>".}
proc lgamma*(x: float32): float32 {.importc: "lgammaf", header: "<math.h>".}
proc lgamma*(x: float64): float64 {.importc: "lgamma", header: "<math.h>".}
## Natural log of the gamma function
proc tgamma*(x: float): float {.importc: "tgamma", header: "<math.h>".}
proc tgamma*(x: float32): float32 {.importc: "tgammaf", header: "<math.h>".}
proc tgamma*(x: float64): float64 {.importc: "tgamma", header: "<math.h>".}
## The gamma function
# C procs:
when defined(vcc) and false:
# The "secure" random, available from Windows XP
# https://msdn.microsoft.com/en-us/library/sxtz2fa8.aspx
# Present in some variants of MinGW but not enough to justify
# `when defined(windows)` yet
proc rand_s(val: var cuint) {.importc: "rand_s", header: "<stdlib.h>".}
# To behave like the normal version
proc rand(): cuint = rand_s(result)
else:
proc srand(seed: cint) {.importc: "srand", header: "<stdlib.h>".}
proc rand(): cint {.importc: "rand", header: "<stdlib.h>".}
when not defined(windows):
proc srand48(seed: clong) {.importc: "srand48", header: "<stdlib.h>".}
proc drand48(): float {.importc: "drand48", header: "<stdlib.h>".}
proc random(max: float): float =
result = drand48() * max
else:
when defined(vcc): # Windows with Visual C
proc random(max: float): float =
# we are hardcoding this because
# importc-ing macros is extremely problematic
# and because the value is publicly documented
# on MSDN and very unlikely to change
# See https://msdn.microsoft.com/en-us/library/296az74e.aspx
const rand_max = 4294967295 # UINT_MAX
result = (float(rand()) / float(rand_max)) * max
proc randomize() = discard
proc randomize(seed: int) = discard
else: # Windows with another compiler
proc random(max: float): float =
# we are hardcoding this because
# importc-ing macros is extremely problematic
# and because the value is publicly documented
# on MSDN and very unlikely to change
const rand_max = 32767
result = (float(rand()) / float(rand_max)) * max
when not defined(vcc): # the above code for vcc uses `discard` instead
# this is either not Windows or is Windows without vcc
when not defined(nimscript):
proc randomize() =
randomize(cast[int](epochTime()))
proc randomize(seed: int) =
srand(cint(seed)) # rand_s doesn't use srand
when declared(srand48): srand48(seed)
proc random(max: int): int =
result = int(rand()) mod max
proc trunc*(x: float): float {.importc: "trunc", header: "<math.h>".}
## Truncates `x` to the decimal point
##
## .. code-block:: nim
## echo trunc(PI) # 3.0
proc floor*(x: float): float {.importc: "floor", header: "<math.h>".}
proc floor*(x: float32): float32 {.importc: "floorf", header: "<math.h>".}
proc floor*(x: float64): float64 {.importc: "floor", header: "<math.h>".}
## Computes the floor function (i.e., the largest integer not greater than `x`)
##
## .. code-block:: nim
## echo floor(-3.5) ## -4.0
proc ceil*(x: float): float {.importc: "ceil", header: "<math.h>".}
proc ceil*(x: float32): float32 {.importc: "ceilf", header: "<math.h>".}
proc ceil*(x: float64): float64 {.importc: "ceil", header: "<math.h>".}
## Computes the ceiling function (i.e., the smallest integer not less than `x`)
##
## .. code-block:: nim
## echo ceil(-2.1) ## -2.0
proc fmod*(x, y: float): float {.importc: "fmod", header: "<math.h>".}
when defined(windows) and defined(vcc):
# MSVC 2010 don't have trunc/truncf
# this implementation was inspired by Go-lang Math.Trunc
proc truncImpl(f: float64): float64 =
const
mask : uint64 = 0x7FF
shift: uint64 = 64 - 12
bias : uint64 = 0x3FF
if f < 1:
if f < 0: return -truncImpl(-f)
elif f == 0: return f # Return -0 when f == -0
else: return 0
var x = cast[uint64](f)
let e = (x shr shift) and mask - bias
# Keep the top 12+e bits, the integer part; clear the rest.
if e < 64-12:
x = x and (not (1'u64 shl (64'u64-12'u64-e) - 1'u64))
result = cast[float64](x)
proc truncImpl(f: float32): float32 =
const
mask : uint32 = 0xFF
shift: uint32 = 32 - 9
bias : uint32 = 0x7F
if f < 1:
if f < 0: return -truncImpl(-f)
elif f == 0: return f # Return -0 when f == -0
else: return 0
var x = cast[uint32](f)
let e = (x shr shift) and mask - bias
# Keep the top 9+e bits, the integer part; clear the rest.
if e < 32-9:
x = x and (not (1'u32 shl (32'u32-9'u32-e) - 1'u32))
result = cast[float32](x)
proc trunc*(x: float64): float64 =
if classify(x) in {fcZero, fcNegZero, fcNan, fcInf, fcNegInf}: return x
result = truncImpl(x)
proc trunc*(x: float32): float32 =
if classify(x) in {fcZero, fcNegZero, fcNan, fcInf, fcNegInf}: return x
result = truncImpl(x)
proc round0[T: float32|float64](x: T): T =
## Windows compilers prior to MSVC 2012 do not implement 'round',
## 'roundl' or 'roundf'.
result = if x < 0.0: ceil(x - T(0.5)) else: floor(x + T(0.5))
else:
proc round0(x: float32): float32 {.importc: "roundf", header: "<math.h>".}
proc round0(x: float64): float64 {.importc: "round", header: "<math.h>".}
## Rounds a float to zero decimal places. Used internally by the round
## function when the specified number of places is 0.
proc trunc*(x: float32): float32 {.importc: "truncf", header: "<math.h>".}
proc trunc*(x: float64): float64 {.importc: "trunc", header: "<math.h>".}
## Truncates `x` to the decimal point
##
## .. code-block:: nim
## echo trunc(PI) # 3.0
proc fmod*(x, y: float32): float32 {.importc: "fmodf", header: "<math.h>".}
proc fmod*(x, y: float64): float64 {.importc: "fmod", header: "<math.h>".}
## Computes the remainder of `x` divided by `y`
##
## .. code-block:: nim
## echo fmod(-2.5, 0.3) ## -0.1
else:
proc mathrandom(): float {.importc: "Math.random", nodecl.}
proc floor*(x: float): float {.importc: "Math.floor", nodecl.}
proc ceil*(x: float): float {.importc: "Math.ceil", nodecl.}
proc random(max: int): int =
result = int(floor(mathrandom() * float(max)))
proc random(max: float): float =
result = float(mathrandom() * float(max))
proc randomize() = discard
proc randomize(seed: int) = discard
proc floor*(x: float32): float32 {.importc: "Math.floor", nodecl.}
proc floor*(x: float64): float64 {.importc: "Math.floor", nodecl.}
proc ceil*(x: float32): float32 {.importc: "Math.ceil", nodecl.}
proc ceil*(x: float64): float64 {.importc: "Math.ceil", nodecl.}
proc sqrt*(x: float): float {.importc: "Math.sqrt", nodecl.}
proc ln*(x: float): float {.importc: "Math.log", nodecl.}
proc log10*(x: float): float = return ln(x) / ln(10.0)
proc log2*(x: float): float = return ln(x) / ln(2.0)
proc sqrt*(x: float32): float32 {.importc: "Math.sqrt", nodecl.}
proc sqrt*(x: float64): float64 {.importc: "Math.sqrt", nodecl.}
proc ln*(x: float32): float32 {.importc: "Math.log", nodecl.}
proc ln*(x: float64): float64 {.importc: "Math.log", nodecl.}
proc log10*[T: float32|float64](x: T): T = return ln(x) / ln(10.0)
proc log2*[T: float32|float64](x: T): T = return ln(x) / ln(2.0)
proc exp*(x: float): float {.importc: "Math.exp", nodecl.}
proc round*(x: float): int {.importc: "Math.round", nodecl.}
proc pow*(x, y: float): float {.importc: "Math.pow", nodecl.}
proc exp*(x: float32): float32 {.importc: "Math.exp", nodecl.}
proc exp*(x: float64): float64 {.importc: "Math.exp", nodecl.}
proc round0(x: float): float {.importc: "Math.round", nodecl.}
proc frexp*(x: float, exponent: var int): float =
proc pow*(x, y: float32): float32 {.importC: "Math.pow", nodecl.}
proc pow*(x, y: float64): float64 {.importc: "Math.pow", nodecl.}
proc arccos*(x: float32): float32 {.importc: "Math.acos", nodecl.}
proc arccos*(x: float64): float64 {.importc: "Math.acos", nodecl.}
proc arcsin*(x: float32): float32 {.importc: "Math.asin", nodecl.}
proc arcsin*(x: float64): float64 {.importc: "Math.asin", nodecl.}
proc arctan*(x: float32): float32 {.importc: "Math.atan", nodecl.}
proc arctan*(x: float64): float64 {.importc: "Math.atan", nodecl.}
proc arctan2*(y, x: float32): float32 {.importC: "Math.atan2", nodecl.}
proc arctan2*(y, x: float64): float64 {.importc: "Math.atan2", nodecl.}
proc cos*(x: float32): float32 {.importc: "Math.cos", nodecl.}
proc cos*(x: float64): float64 {.importc: "Math.cos", nodecl.}
proc cosh*(x: float32): float32 = return (exp(x)+exp(-x))*0.5
proc cosh*(x: float64): float64 = return (exp(x)+exp(-x))*0.5
proc hypot*[T: float32|float64](x, y: T): T = return sqrt(x*x + y*y)
proc sinh*[T: float32|float64](x: T): T = return (exp(x)-exp(-x))*0.5
proc sin*(x: float32): float32 {.importc: "Math.sin", nodecl.}
proc sin*(x: float64): float64 {.importc: "Math.sin", nodecl.}
proc tan*(x: float32): float32 {.importc: "Math.tan", nodecl.}
proc tan*(x: float64): float64 {.importc: "Math.tan", nodecl.}
proc tanh*[T: float32|float64](x: T): T =
var y = exp(2.0*x)
return (y-1.0)/(y+1.0)
proc round*[T: float32|float64](x: T, places: int = 0): T =
## Round a floating point number.
##
## If `places` is 0 (or omitted), round to the nearest integral value
## following normal mathematical rounding rules (e.g. `round(54.5) -> 55.0`).
## If `places` is greater than 0, round to the given number of decimal
## places, e.g. `round(54.346, 2) -> 54.35`.
## If `places` is negative, round to the left of the decimal place, e.g.
## `round(537.345, -1) -> 540.0`
if places == 0:
result = round0(x)
else:
var mult = pow(10.0, places.T)
result = round0(x*mult)/mult
when not defined(JS):
proc frexp*(x: float32, exponent: var int): float32 {.
importc: "frexp", header: "<math.h>".}
proc frexp*(x: float64, exponent: var int): float64 {.
importc: "frexp", header: "<math.h>".}
## Split a number into mantissa and exponent.
## `frexp` calculates the mantissa m (a float greater than or equal to 0.5
## and less than 1) and the integer value n such that `x` (the original
## float value) equals m * 2**n. frexp stores n in `exponent` and returns
## m.
else:
proc frexp*[T: float32|float64](x: T, exponent: var int): T =
if x == 0.0:
exponent = 0
result = 0.0
@@ -315,20 +368,22 @@ else:
exponent = round(ex)
result = x / pow(2.0, ex)
proc arccos*(x: float): float {.importc: "Math.acos", nodecl.}
proc arcsin*(x: float): float {.importc: "Math.asin", nodecl.}
proc arctan*(x: float): float {.importc: "Math.atan", nodecl.}
proc arctan2*(y, x: float): float {.importc: "Math.atan2", nodecl.}
proc cos*(x: float): float {.importc: "Math.cos", nodecl.}
proc cosh*(x: float): float = return (exp(x)+exp(-x))*0.5
proc hypot*(x, y: float): float = return sqrt(x*x + y*y)
proc sinh*(x: float): float = return (exp(x)-exp(-x))*0.5
proc sin*(x: float): float {.importc: "Math.sin", nodecl.}
proc tan*(x: float): float {.importc: "Math.tan", nodecl.}
proc tanh*(x: float): float =
var y = exp(2.0*x)
return (y-1.0)/(y+1.0)
proc splitDecimal*[T: float32|float64](x: T): tuple[intpart: T, floatpart: T] =
## Breaks `x` into an integral and a fractional part.
##
## Returns a tuple containing intpart and floatpart representing
## the integer part and the fractional part respectively.
##
## Both parts have the same sign as `x`. Analogous to the `modf`
## function in C.
var
absolute: T
absolute = abs(x)
result.intpart = floor(absolute)
result.floatpart = absolute - result.intpart
if x < 0:
result.intpart = -result.intpart
result.floatpart = -result.floatpart
{.pop.}
@@ -340,7 +395,7 @@ proc radToDeg*[T: float32|float64](d: T): T {.inline.} =
## Convert from radians to degrees
result = T(d) / RadPerDeg
proc `mod`*(x, y: float): float =
proc `mod`*[T: float32|float64](x, y: T): T =
## Computes the modulo operation for float operators. Equivalent
## to ``x - y * floor(x/y)``. Note that the remainder will always
## have the same sign as the divisor.
@@ -349,21 +404,13 @@ proc `mod`*(x, y: float): float =
## echo (4.0 mod -3.1) # -2.2
result = if y == 0.0: x else: x - y * (x/y).floor
proc random*[T](x: Slice[T]): T =
## For a slice `a .. b` returns a value in the range `a .. b-1`.
result = random(x.b - x.a) + x.a
proc random*[T](a: openArray[T]): T =
## returns a random element from the openarray `a`.
result = a[random(a.low..a.len)]
{.pop.}
{.pop.}
proc `^`*[T](x, y: T): T =
## Computes ``x`` to the power ``y`. ``x`` must be non-negative, use
## `pow <#pow,float,float>` for negative exponents.
assert y >= 0
assert y >= T(0)
var (x, y) = (x, y)
result = 1
@@ -391,24 +438,6 @@ proc lcm*[T](x, y: T): T =
x div gcd(x, y) * y
when isMainModule and not defined(JS):
proc gettime(dummy: ptr cint): cint {.importc: "time", header: "<time.h>".}
# Verifies random seed initialization.
let seed = gettime(nil)
randomize(seed)
const SIZE = 10
var buf : array[0..SIZE, int]
# Fill the buffer with random values
for i in 0..SIZE-1:
buf[i] = random(high(int))
# Check that the second random calls are the same for each position.
randomize(seed)
for i in 0..SIZE-1:
assert buf[i] == random(high(int)), "non deterministic random seeding"
when not defined(testing):
echo "random values equal after reseeding"
# Check for no side effect annotation
proc mySqrt(num: float): float {.noSideEffect.} =
return sqrt(num)
@@ -418,3 +447,65 @@ when isMainModule and not defined(JS):
assert(lgamma(1.0) == 0.0) # ln(1.0) == 0.0
assert(erf(6.0) > erf(5.0))
assert(erfc(6.0) < erfc(5.0))
when isMainModule:
# Function for approximate comparison of floats
proc `==~`(x, y: float): bool = (abs(x-y) < 1e-9)
block: # round() tests
# Round to 0 decimal places
doAssert round(54.652) ==~ 55.0
doAssert round(54.352) ==~ 54.0
doAssert round(-54.652) ==~ -55.0
doAssert round(-54.352) ==~ -54.0
doAssert round(0.0) ==~ 0.0
# Round to positive decimal places
doAssert round(-547.652, 1) ==~ -547.7
doAssert round(547.652, 1) ==~ 547.7
doAssert round(-547.652, 2) ==~ -547.65
doAssert round(547.652, 2) ==~ 547.65
# Round to negative decimal places
doAssert round(547.652, -1) ==~ 550.0
doAssert round(547.652, -2) ==~ 500.0
doAssert round(547.652, -3) ==~ 1000.0
doAssert round(547.652, -4) ==~ 0.0
doAssert round(-547.652, -1) ==~ -550.0
doAssert round(-547.652, -2) ==~ -500.0
doAssert round(-547.652, -3) ==~ -1000.0
doAssert round(-547.652, -4) ==~ 0.0
block: # splitDecimal() tests
doAssert splitDecimal(54.674).intpart ==~ 54.0
doAssert splitDecimal(54.674).floatpart ==~ 0.674
doAssert splitDecimal(-693.4356).intpart ==~ -693.0
doAssert splitDecimal(-693.4356).floatpart ==~ -0.4356
doAssert splitDecimal(0.0).intpart ==~ 0.0
doAssert splitDecimal(0.0).floatpart ==~ 0.0
block: # trunc tests for vcc
doAssert(trunc(-1.1) == -1)
doAssert(trunc(1.1) == 1)
doAssert(trunc(-0.1) == -0)
doAssert(trunc(0.1) == 0)
#special case
doAssert(classify(trunc(1e1000000)) == fcInf)
doAssert(classify(trunc(-1e1000000)) == fcNegInf)
doAssert(classify(trunc(0.0/0.0)) == fcNan)
doAssert(classify(trunc(0.0)) == fcZero)
#trick the compiler to produce signed zero
let
f_neg_one = -1.0
f_zero = 0.0
f_nan = f_zero / f_zero
doAssert(classify(trunc(f_neg_one*f_zero)) == fcNegZero)
doAssert(trunc(-1.1'f32) == -1)
doAssert(trunc(1.1'f32) == 1)
doAssert(trunc(-0.1'f32) == -0)
doAssert(trunc(0.1'f32) == 0)
doAssert(classify(trunc(1e1000000'f32)) == fcInf)
doAssert(classify(trunc(-1e1000000'f32)) == fcNegInf)
doAssert(classify(trunc(f_nan.float32)) == fcNan)
doAssert(classify(trunc(0.0'f32)) == fcZero)

View File

@@ -42,6 +42,10 @@ type
proc mapMem*(m: var MemFile, mode: FileMode = fmRead,
mappedSize = -1, offset = 0): pointer =
## returns a pointer to a mapped portion of MemFile `m`
##
## ``mappedSize`` of ``-1`` maps to the whole file, and
## ``offset`` must be multiples of the PAGE SIZE of your OS
var readonly = mode == fmRead
when defined(windows):
result = mapViewOfFileEx(
@@ -68,7 +72,9 @@ proc mapMem*(m: var MemFile, mode: FileMode = fmRead,
proc unmapMem*(f: var MemFile, p: pointer, size: int) =
## unmaps the memory region ``(p, <p+size)`` of the mapped file `f`.
## All changes are written back to the file system, if `f` was opened
## with write access. ``size`` must be of exactly the size that was requested
## with write access.
##
## ``size`` must be of exactly the size that was requested
## via ``mapMem``.
when defined(windows):
if unmapViewOfFile(p) == 0: raiseOSError(osLastError())
@@ -79,9 +85,17 @@ proc unmapMem*(f: var MemFile, p: pointer, size: int) =
proc open*(filename: string, mode: FileMode = fmRead,
mappedSize = -1, offset = 0, newFileSize = -1): MemFile =
## opens a memory mapped file. If this fails, ``EOS`` is raised.
## `newFileSize` can only be set if the file does not exist and is opened
## with write access (e.g., with fmReadWrite). `mappedSize` and `offset`
## can be used to map only a slice of the file. Example:
##
## ``newFileSize`` can only be set if the file does not exist and is opened
## with write access (e.g., with fmReadWrite).
##
##``mappedSize`` and ``offset``
## can be used to map only a slice of the file.
##
## ``offset`` must be multiples of the PAGE SIZE of your OS
## (usually 4K or 8K but is unique to your OS)
##
## Example:
##
## .. code-block:: nim
## var
@@ -257,12 +271,10 @@ type MemSlice* = object ## represent slice of a MemFile for iteration over deli
data*: pointer
size*: int
proc c_memcpy(a, b: pointer, n: int) {.importc: "memcpy", header: "<string.h>".}
proc `$`*(ms: MemSlice): string {.inline.} =
## Return a Nim string built from a MemSlice.
var buf = newString(ms.size)
c_memcpy(addr(buf[0]), ms.data, ms.size)
copyMem(addr(buf[0]), ms.data, ms.size)
buf[ms.size] = '\0'
result = buf
@@ -287,7 +299,9 @@ iterator memSlices*(mfile: MemFile, delim='\l', eat='\r'): MemSlice {.inline.} =
## iterate over line-like records in a file. However, returned (data,size)
## objects are not Nim strings, bounds checked Nim arrays, or even terminated
## C strings. So, care is required to access the data (e.g., think C mem*
## functions, not str* functions). Example:
## functions, not str* functions).
##
## Example:
##
## .. code-block:: nim
## var count = 0
@@ -320,7 +334,9 @@ iterator lines*(mfile: MemFile, buf: var TaintedString, delim='\l', eat='\r'): T
## Replace contents of passed buffer with each new line, like
## `readLine(File) <system.html#readLine,File,TaintedString>`_.
## `delim`, `eat`, and delimiting logic is exactly as for
## `memSlices <#memSlices>`_, but Nim strings are returned. Example:
## `memSlices <#memSlices>`_, but Nim strings are returned.
##
## Example:
##
## .. code-block:: nim
## var buffer: TaintedString = ""
@@ -329,7 +345,7 @@ iterator lines*(mfile: MemFile, buf: var TaintedString, delim='\l', eat='\r'): T
for ms in memSlices(mfile, delim, eat):
buf.setLen(ms.size)
c_memcpy(addr(buf[0]), ms.data, ms.size)
copyMem(addr(buf[0]), ms.data, ms.size)
buf[ms.size] = '\0'
yield buf
@@ -337,7 +353,9 @@ iterator lines*(mfile: MemFile, delim='\l', eat='\r'): TaintedString {.inline.}
## Return each line in a file as a Nim string, like
## `lines(File) <system.html#lines.i,File>`_.
## `delim`, `eat`, and delimiting logic is exactly as for
## `memSlices <#memSlices>`_, but Nim strings are returned. Example:
## `memSlices <#memSlices>`_, but Nim strings are returned.
##
## Example:
##
## .. code-block:: nim
## for line in lines(memfiles.open("foo")):

View File

@@ -1,3 +1,12 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2015 Nim Contributors
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
type
MersenneTwister* = object
mt: array[0..623, uint32]
@@ -5,29 +14,31 @@ type
{.deprecated: [TMersenneTwister: MersenneTwister].}
proc newMersenneTwister*(seed: int): MersenneTwister =
proc newMersenneTwister*(seed: uint32): MersenneTwister =
result.index = 0
result.mt[0]= uint32(seed)
result.mt[0] = seed
for i in 1..623'u32:
result.mt[i]= (0x6c078965'u32 * (result.mt[i-1] xor (result.mt[i-1] shr 30'u32)) + i)
result.mt[i] = (0x6c078965'u32 * (result.mt[i-1] xor (result.mt[i-1] shr 30'u32)) + i)
proc generateNumbers(m: var MersenneTwister) =
for i in 0..623:
var y = (m.mt[i] and 0x80000000'u32) + (m.mt[(i+1) mod 624] and 0x7fffffff'u32)
var y = (m.mt[i] and 0x80000000'u32) +
(m.mt[(i+1) mod 624] and 0x7fffffff'u32)
m.mt[i] = m.mt[(i+397) mod 624] xor uint32(y shr 1'u32)
if (y mod 2'u32) != 0:
m.mt[i] = m.mt[i] xor 0x9908b0df'u32
m.mt[i] = m.mt[i] xor 0x9908b0df'u32
proc getNum*(m: var MersenneTwister): int =
proc getNum*(m: var MersenneTwister): uint32 =
## Returns the next pseudo random number ranging from 0 to high(uint32)
if m.index == 0:
generateNumbers(m)
var y = m.mt[m.index]
y = y xor (y shr 11'u32)
y = y xor ((7'u32 shl y) and 0x9d2c5680'u32)
y = y xor ((15'u32 shl y) and 0xefc60000'u32)
y = y xor (y shr 18'u32)
m.index = (m.index+1) mod 624
return int(y)
result = m.mt[m.index]
m.index = (m.index + 1) mod m.mt.len
result = result xor (result shr 11'u32)
result = result xor ((7'u32 shl result) and 0x9d2c5680'u32)
result = result xor ((15'u32 shl result) and 0xefc60000'u32)
result = result xor (result shr 18'u32)
# Test
when not defined(testing) and isMainModule:

View File

@@ -27,7 +27,7 @@ else:
import posix
export fcntl, F_GETFL, O_NONBLOCK, F_SETFL, EAGAIN, EWOULDBLOCK, MSG_NOSIGNAL,
EINTR, EINPROGRESS, ECONNRESET, EPIPE, ENETRESET
export Sockaddr_storage
export Sockaddr_storage, Sockaddr_un, Sockaddr_un_path_length
export SocketHandle, Sockaddr_in, Addrinfo, INADDR_ANY, SockAddr, SockLen,
Sockaddr_in6,
@@ -38,7 +38,7 @@ export
SOL_SOCKET,
SOMAXCONN,
SO_ACCEPTCONN, SO_BROADCAST, SO_DEBUG, SO_DONTROUTE,
SO_KEEPALIVE, SO_OOBINLINE, SO_REUSEADDR,
SO_KEEPALIVE, SO_OOBINLINE, SO_REUSEADDR, SO_REUSEPORT,
MSG_PEEK
when defined(macosx) and not defined(nimdoc):
@@ -326,8 +326,13 @@ proc getHostByAddr*(ip: string): Hostent {.tags: [ReadIOEffect].} =
cint(AF_INET))
if s == nil: raiseOSError(osLastError())
else:
var s = posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).Socklen,
cint(posix.AF_INET))
var s =
when defined(android4):
posix.gethostbyaddr(cast[cstring](addr(myaddr)), sizeof(myaddr).cint,
cint(posix.AF_INET))
else:
posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).Socklen,
cint(posix.AF_INET))
if s == nil:
raiseOSError(osLastError(), $hstrerror(h_errno))

View File

@@ -8,19 +8,77 @@
#
## This module implements a high-level cross-platform sockets interface.
## The procedures implemented in this module are primarily for blocking sockets.
## For asynchronous non-blocking sockets use the ``asyncnet`` module together
## with the ``asyncdispatch`` module.
##
## The first thing you will always need to do in order to start using sockets,
## is to create a new instance of the ``Socket`` type using the ``newSocket``
## procedure.
##
## SSL
## ====
##
## In order to use the SSL procedures defined in this module, you will need to
## compile your application with the ``-d:ssl`` flag.
##
## Examples
## ========
##
## Connecting to a server
## ----------------------
##
## After you create a socket with the ``newSocket`` procedure, you can easily
## connect it to a server running at a known hostname (or IP address) and port.
## To do so over TCP, use the example below.
##
## .. code-block:: Nim
## var socket = newSocket()
## socket.connect("google.com", Port(80))
##
## UDP is a connectionless protocol, so UDP sockets don't have to explicitly
## call the ``connect`` procedure. They can simply start sending data
## immediately.
##
## .. code-block:: Nim
## var socket = newSocket()
## socket.sendTo("192.168.0.1", Port(27960), "status\n")
##
## Creating a server
## -----------------
##
## After you create a socket with the ``newSocket`` procedure, you can create a
## TCP server by calling the ``bindAddr`` and ``listen`` procedures.
##
## .. code-block:: Nim
## var socket = newSocket()
## socket.bindAddr(Port(1234))
## socket.listen()
##
## You can then begin accepting connections using the ``accept`` procedure.
##
## .. code-block:: Nim
## var client = newSocket()
## var address = ""
## while true:
## socket.acceptAddr(client, address)
## echo("Client connected from: ", address)
##
{.deadCodeElim: on.}
import nativesockets, os, strutils, parseutils, times
import nativesockets, os, strutils, parseutils, times, sets
export Port, `$`, `==`
export Domain, SockType, Protocol
const useWinVersion = defined(Windows) or defined(nimdoc)
const defineSsl = defined(ssl) or defined(nimdoc)
when defined(ssl):
when defineSsl:
import openssl
# Note: The enumerations are mapped to Window's constants.
when defined(ssl):
when defineSsl:
type
SslError* = object of Exception
@@ -30,7 +88,10 @@ when defined(ssl):
SslProtVersion* = enum
protSSLv2, protSSLv3, protTLSv1, protSSLv23
SslContext* = distinct SslCtx
SslContext* = ref object
context*: SslCtx
extraInternalIndex: int
referencedData: HashSet[int]
SslAcceptResult* = enum
AcceptNoClient = 0, AcceptNoHandshake, AcceptSuccess
@@ -38,6 +99,10 @@ when defined(ssl):
SslHandshakeType* = enum
handshakeAsClient, handshakeAsServer
SslClientGetPskFunc* = proc(hint: string): tuple[identity: string, psk: string]
SslServerGetPskFunc* = proc(identity: string): string
{.deprecated: [ESSL: SSLError, TSSLCVerifyMode: SSLCVerifyMode,
TSSLProtVersion: SSLProtVersion, PSSLContext: SSLContext,
TSSLAcceptResult: SSLAcceptResult].}
@@ -54,7 +119,7 @@ type
currPos: int # current index in buffer
bufLen: int # current length of buffer
of false: nil
when defined(ssl):
when defineSsl:
case isSsl: bool
of true:
sslHandle: SSLPtr
@@ -72,7 +137,7 @@ type
SOBool* = enum ## Boolean socket options.
OptAcceptConn, OptBroadcast, OptDebug, OptDontRoute, OptKeepAlive,
OptOOBInline, OptReuseAddr
OptOOBInline, OptReuseAddr, OptReusePort
ReadLineResult* = enum ## result for readLineAsync
ReadFullLine, ReadPartialLine, ReadDisconnected, ReadNone
@@ -140,6 +205,10 @@ proc newSocket*(fd: SocketHandle, domain: Domain = AF_INET,
if buffered:
result.currPos = 0
# Set SO_NOSIGPIPE on OS X.
when defined(macosx):
setSockOptInt(fd, SOL_SOCKET, SO_NOSIGPIPE, 1)
proc newSocket*(domain, sockType, protocol: cint, buffered = true): Socket =
## Creates a new socket.
##
@@ -160,13 +229,18 @@ proc newSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM,
raiseOSError(osLastError())
result = newSocket(fd, domain, sockType, protocol, buffered)
when defined(ssl):
when defineSsl:
CRYPTO_malloc_init()
SslLibraryInit()
SslLoadErrorStrings()
ErrLoadBioStrings()
OpenSSL_add_all_algorithms()
type
SslContextExtraInternal = ref object of RootRef
serverGetPskFunc: SslServerGetPskFunc
clientGetPskFunc: SslClientGetPskFunc
proc raiseSSLError*(s = "") =
## Raises a new SSL error.
if s != "":
@@ -179,6 +253,34 @@ when defined(ssl):
var errStr = ErrErrorString(err, nil)
raise newException(SSLError, $errStr)
proc getExtraDataIndex*(ctx: SSLContext): int =
## Retrieves unique index for storing extra data in SSLContext.
result = SSL_CTX_get_ex_new_index(0, nil, nil, nil, nil).int
if result < 0:
raiseSSLError()
proc getExtraData*(ctx: SSLContext, index: int): RootRef =
## Retrieves arbitrary data stored inside SSLContext.
if index notin ctx.referencedData:
raise newException(IndexError, "No data with that index.")
let res = ctx.context.SSL_CTX_get_ex_data(index.cint)
if cast[int](res) == 0:
raiseSSLError()
return cast[RootRef](res)
proc setExtraData*(ctx: SSLContext, index: int, data: RootRef) =
## Stores arbitrary data inside SSLContext. The unique `index`
## should be retrieved using getSslContextExtraDataIndex.
if index in ctx.referencedData:
GC_unref(getExtraData(ctx, index))
if ctx.context.SSL_CTX_set_ex_data(index.cint, cast[pointer](data)) == -1:
raiseSSLError()
if index notin ctx.referencedData:
ctx.referencedData.incl(index)
GC_ref(data)
# http://simplestcodings.blogspot.co.uk/2010/08/secure-server-client-using-openssl-in-c.html
proc loadCertificates(ctx: SSL_CTX, certFile, keyFile: string) =
if certFile != "" and not existsFile(certFile):
@@ -201,7 +303,7 @@ when defined(ssl):
raiseSSLError("Verification of private key file failed.")
proc newContext*(protVersion = protSSLv23, verifyMode = CVerifyPeer,
certFile = "", keyFile = ""): SSLContext =
certFile = "", keyFile = "", cipherList = "ALL"): SSLContext =
## Creates an SSL context.
##
## Protocol version specifies the protocol to use. SSLv2, SSLv3, TLSv1
@@ -222,13 +324,13 @@ when defined(ssl):
of protSSLv23:
newCTX = SSL_CTX_new(SSLv23_method()) # SSlv2,3 and TLS1 support.
of protSSLv2:
raiseSslError("SSLv2 is no longer secure and has been deprecated, use protSSLv3")
raiseSslError("SSLv2 is no longer secure and has been deprecated, use protSSLv23")
of protSSLv3:
newCTX = SSL_CTX_new(SSLv3_method())
raiseSslError("SSLv3 is no longer secure and has been deprecated, use protSSLv23")
of protTLSv1:
newCTX = SSL_CTX_new(TLSv1_method())
if newCTX.SSLCTXSetCipherList("ALL") != 1:
if newCTX.SSLCTXSetCipherList(cipherList) != 1:
raiseSSLError()
case verifyMode
of CVerifyPeer:
@@ -240,7 +342,87 @@ when defined(ssl):
discard newCTX.SSLCTXSetMode(SSL_MODE_AUTO_RETRY)
newCTX.loadCertificates(certFile, keyFile)
return SSLContext(newCTX)
result = SSLContext(context: newCTX, extraInternalIndex: 0,
referencedData: initSet[int]())
result.extraInternalIndex = getExtraDataIndex(result)
let extraInternal = new(SslContextExtraInternal)
result.setExtraData(result.extraInternalIndex, extraInternal)
proc getExtraInternal(ctx: SSLContext): SslContextExtraInternal =
return SslContextExtraInternal(ctx.getExtraData(ctx.extraInternalIndex))
proc destroyContext*(ctx: SSLContext) =
## Free memory referenced by SSLContext.
# We assume here that OpenSSL's internal indexes increase by 1 each time.
# That means we can assume that the next internal index is the length of
# extra data indexes.
for i in ctx.referencedData:
GC_unref(getExtraData(ctx, i).RootRef)
ctx.context.SSL_CTX_free()
proc `pskIdentityHint=`*(ctx: SSLContext, hint: string) =
## Sets the identity hint passed to server.
##
## Only used in PSK ciphersuites.
if ctx.context.SSL_CTX_use_psk_identity_hint(hint) <= 0:
raiseSSLError()
proc clientGetPskFunc*(ctx: SSLContext): SslClientGetPskFunc =
return ctx.getExtraInternal().clientGetPskFunc
proc pskClientCallback(ssl: SslPtr; hint: cstring; identity: cstring; max_identity_len: cuint; psk: ptr cuchar;
max_psk_len: cuint): cuint {.cdecl.} =
let ctx = SSLContext(context: ssl.SSL_get_SSL_CTX, extraInternalIndex: 0)
let hintString = if hint == nil: nil else: $hint
let (identityString, pskString) = (ctx.clientGetPskFunc)(hintString)
if psk.len.cuint > max_psk_len:
return 0
if identityString.len.cuint >= max_identity_len:
return 0
copyMem(identity, identityString.cstring, pskString.len + 1) # with the last zero byte
copyMem(psk, pskString.cstring, pskString.len)
return pskString.len.cuint
proc `clientGetPskFunc=`*(ctx: SSLContext, fun: SslClientGetPskFunc) =
## Sets function that returns the client identity and the PSK based on identity
## hint from the server.
##
## Only used in PSK ciphersuites.
ctx.getExtraInternal().clientGetPskFunc = fun
assert ctx.extraInternalIndex == 0,
"The pskClientCallback assumes the extraInternalIndex is 0"
ctx.context.SSL_CTX_set_psk_client_callback(
if fun == nil: nil else: pskClientCallback)
proc serverGetPskFunc*(ctx: SSLContext): SslServerGetPskFunc =
return ctx.getExtraInternal().serverGetPskFunc
proc pskServerCallback(ssl: SslCtx; identity: cstring; psk: ptr cuchar; max_psk_len: cint): cuint {.cdecl.} =
let ctx = SSLContext(context: ssl.SSL_get_SSL_CTX, extraInternalIndex: 0)
let pskString = (ctx.serverGetPskFunc)($identity)
if psk.len.cint > max_psk_len:
return 0
copyMem(psk, pskString.cstring, pskString.len)
return pskString.len.cuint
proc `serverGetPskFunc=`*(ctx: SSLContext, fun: SslServerGetPskFunc) =
## Sets function that returns PSK based on the client identity.
##
## Only used in PSK ciphersuites.
ctx.getExtraInternal().serverGetPskFunc = fun
ctx.context.SSL_CTX_set_psk_server_callback(if fun == nil: nil
else: pskServerCallback)
proc getPskIdentity*(socket: Socket): string =
## Gets the PSK identity provided by the client.
assert socket.isSSL
return $(socket.sslHandle.SSL_get_psk_identity)
proc wrapSocket*(ctx: SSLContext, socket: Socket) =
## Wraps a socket in an SSL context. This function effectively turns
@@ -255,7 +437,7 @@ when defined(ssl):
assert (not socket.isSSL)
socket.isSSL = true
socket.sslContext = ctx
socket.sslHandle = SSLNew(SSLCTX(socket.sslContext))
socket.sslHandle = SSLNew(socket.sslContext.context)
socket.sslNoHandshake = false
socket.sslHasPeekChar = false
if socket.sslHandle == nil:
@@ -301,7 +483,7 @@ proc socketError*(socket: Socket, err: int = -1, async = false,
## error was caused by no data being available to be read.
##
## If ``err`` is not lower than 0 no exception will be raised.
when defined(ssl):
when defineSsl:
if socket.isSSL:
if err <= 0:
var ret = SSLGetError(socket.sslHandle, err.cint)
@@ -334,7 +516,7 @@ proc socketError*(socket: Socket, err: int = -1, async = false,
raiseSSLError()
else: raiseSSLError("Unknown Error")
if err == -1 and not (when defined(ssl): socket.isSSL else: false):
if err == -1 and not (when defineSsl: socket.isSSL else: false):
var lastE = if lastError.int == -1: getSocketError(socket) else: lastError
if async:
when useWinVersion:
@@ -414,7 +596,7 @@ proc acceptAddr*(server: Socket, client: var Socket, address: var string,
client.isBuffered = server.isBuffered
# Handle SSL.
when defined(ssl):
when defineSsl:
if server.isSSL:
# We must wrap the client sock in a ssl context.
@@ -425,7 +607,7 @@ proc acceptAddr*(server: Socket, client: var Socket, address: var string,
# Client socket is set above.
address = $inet_ntoa(sockAddress.sin_addr)
when false: #defined(ssl):
when false: #defineSsl:
proc acceptAddrSSL*(server: Socket, client: var Socket,
address: var string): SSLAcceptResult {.
tags: [ReadIOEffect].} =
@@ -444,7 +626,7 @@ when false: #defined(ssl):
## ``AcceptNoClient`` will be returned when no client is currently attempting
## to connect.
template doHandshake(): stmt =
when defined(ssl):
when defineSsl:
if server.isSSL:
client.setBlocking(false)
# We must wrap the client sock in a ssl context.
@@ -495,7 +677,7 @@ proc accept*(server: Socket, client: var Socket,
proc close*(socket: Socket) =
## Closes a socket.
try:
when defined(ssl):
when defineSsl:
if socket.isSSL:
ErrClearError()
# As we are closing the underlying socket immediately afterwards,
@@ -522,6 +704,7 @@ proc toCInt*(opt: SOBool): cint =
of OptKeepAlive: SO_KEEPALIVE
of OptOOBInline: SO_OOBINLINE
of OptReuseAddr: SO_REUSEADDR
of OptReusePort: SO_REUSEPORT
proc getSockOpt*(socket: Socket, opt: SOBool, level = SOL_SOCKET): bool {.
tags: [ReadIOEffect].} =
@@ -547,8 +730,35 @@ proc setSockOpt*(socket: Socket, opt: SOBool, value: bool, level = SOL_SOCKET) {
var valuei = cint(if value: 1 else: 0)
setSockOptInt(socket.fd, cint(level), toCInt(opt), valuei)
when defined(posix) and not defined(nimdoc):
proc makeUnixAddr(path: string): Sockaddr_un =
result.sun_family = AF_UNIX.toInt
if path.len >= Sockaddr_un_path_length:
raise newException(ValueError, "socket path too long")
copyMem(addr result.sun_path, path.cstring, path.len + 1)
when defined(posix):
proc connectUnix*(socket: Socket, path: string) =
## Connects to Unix socket on `path`.
## This only works on Unix-style systems: Mac OS X, BSD and Linux
when not defined(nimdoc):
var socketAddr = makeUnixAddr(path)
if socket.fd.connect(cast[ptr SockAddr](addr socketAddr),
sizeof(socketAddr).Socklen) != 0'i32:
raiseOSError(osLastError())
proc bindUnix*(socket: Socket, path: string) =
## Binds Unix socket to `path`.
## This only works on Unix-style systems: Mac OS X, BSD and Linux
when not defined(nimdoc):
var socketAddr = makeUnixAddr(path)
if socket.fd.bindAddr(cast[ptr SockAddr](addr socketAddr),
sizeof(socketAddr).Socklen) != 0'i32:
raiseOSError(osLastError())
when defined(ssl):
proc handshake*(socket: Socket): bool {.tags: [ReadIOEffect, WriteIOEffect].} =
proc handshake*(socket: Socket): bool
{.tags: [ReadIOEffect, WriteIOEffect], deprecated.} =
## This proc needs to be called on a socket after it connects. This is
## only applicable when using ``connectAsync``.
## This proc performs the SSL handshake.
@@ -557,6 +767,8 @@ when defined(ssl):
## ``True`` whenever handshake completed successfully.
##
## A ESSL error is raised on any other errors.
##
## **Note:** This procedure is deprecated since version 0.14.0.
result = true
if socket.isSSL:
var ret = SSLConnect(socket.sslHandle)
@@ -594,7 +806,7 @@ proc hasDataBuffered*(s: Socket): bool =
if s.isBuffered:
result = s.bufLen > 0 and s.currPos != s.bufLen
when defined(ssl):
when defineSsl:
if s.isSSL and not result:
result = s.sslHasPeekChar
@@ -608,7 +820,7 @@ proc select(readfd: Socket, timeout = 500): int =
proc readIntoBuf(socket: Socket, flags: int32): int =
result = 0
when defined(ssl):
when defineSsl:
if socket.isSSL:
result = SSLRead(socket.sslHandle, addr(socket.buffer), int(socket.buffer.high))
else:
@@ -658,7 +870,7 @@ proc recv*(socket: Socket, data: pointer, size: int): int {.tags: [ReadIOEffect]
result = read
else:
when defined(ssl):
when defineSsl:
if socket.isSSL:
if socket.sslHasPeekChar:
copyMem(data, addr(socket.sslPeekChar), 1)
@@ -696,7 +908,7 @@ proc waitFor(socket: Socket, waited: var float, timeout, size: int,
if timeout - int(waited * 1000.0) < 1:
raise newException(TimeoutError, "Call to '" & funcName & "' timed out.")
when defined(ssl):
when defineSsl:
if socket.isSSL:
if socket.hasDataBuffered:
# sslPeekChar is present.
@@ -764,7 +976,7 @@ proc peekChar(socket: Socket, c: var char): int {.tags: [ReadIOEffect].} =
c = socket.buffer[socket.currPos]
else:
when defined(ssl):
when defineSsl:
if socket.isSSL:
if not socket.sslHasPeekChar:
result = SSLRead(socket.sslHandle, addr(socket.sslPeekChar), 1)
@@ -792,11 +1004,11 @@ proc readLine*(socket: Socket, line: var TaintedString, timeout = -1,
##
## **Warning**: Only the ``SafeDisconn`` flag is currently supported.
template addNLIfEmpty(): stmt =
template addNLIfEmpty() =
if line.len == 0:
line.string.add("\c\L")
template raiseSockError(): stmt {.dirty, immediate.} =
template raiseSockError() {.dirty.} =
let lastError = getSocketError(socket)
if flags.isDisconnectionError(lastError): setLen(line.string, 0); return
socket.socketError(n, lastError = lastError)
@@ -872,7 +1084,7 @@ proc send*(socket: Socket, data: pointer, size: int): int {.
##
## **Note**: This is a low-level version of ``send``. You likely should use
## the version below.
when defined(ssl):
when defineSsl:
if socket.isSSL:
return SSLWrite(socket.sslHandle, cast[cstring](data), size)
@@ -943,7 +1155,7 @@ proc sendTo*(socket: Socket, address: string, port: Port,
proc isSsl*(socket: Socket): bool =
## Determines whether ``socket`` is a SSL socket.
when defined(ssl):
when defineSsl:
result = socket.isSSL
else:
result = false
@@ -1253,7 +1465,7 @@ proc connect*(socket: Socket, address: string,
dealloc(aiList)
if not success: raiseOSError(lastError)
when defined(ssl):
when defineSsl:
if socket.isSSL:
# RFC3546 for SNI specifies that IP addresses are not allowed.
if not isIpAddress(address):
@@ -1314,8 +1526,10 @@ proc connect*(socket: Socket, address: string, port = Port(0),
if selectWrite(s, timeout) != 1:
raise newException(TimeoutError, "Call to 'connect' timed out.")
else:
when defined(ssl):
when defineSsl and not defined(nimdoc):
if socket.isSSL:
socket.fd.setBlocking(true)
{.warning[Deprecated]: off.}
doAssert socket.handshake()
{.warning[Deprecated]: on.}
socket.fd.setBlocking(true)

View File

@@ -27,19 +27,22 @@ const
tickCountCorrection = 50_000
when not declared(system.StackTrace):
type StackTrace = array [0..20, cstring]
type StackTrace = object
lines: array[0..20, cstring]
files: array[0..20, cstring]
{.deprecated: [TStackTrace: StackTrace].}
proc `[]`*(st: StackTrace, i: int): cstring = st.lines[i]
# We use a simple hash table of bounded size to keep track of the stack traces:
type
ProfileEntry = object
total: int
st: StackTrace
ProfileData = array [0..64*1024-1, ptr ProfileEntry]
ProfileData = array[0..64*1024-1, ptr ProfileEntry]
{.deprecated: [TProfileEntry: ProfileEntry, TProfileData: ProfileData].}
proc `==`(a, b: StackTrace): bool =
for i in 0 .. high(a):
for i in 0 .. high(a.lines):
if a[i] != b[i]: return false
result = true
@@ -72,7 +75,7 @@ proc hookAux(st: StackTrace, costs: int) =
# this is quite performance sensitive!
when withThreads: acquire profilingLock
inc totalCalls
var last = high(st)
var last = high(st.lines)
while last > 0 and isNil(st[last]): dec last
var h = hash(pointer(st[last])) and high(profileData)
@@ -178,7 +181,7 @@ proc writeProfile() {.noconv.} =
var perProc = initCountTable[string]()
for i in 0..entries-1:
var dups = initSet[string]()
for ii in 0..high(StackTrace):
for ii in 0..high(StackTrace.lines):
let procname = profileData[i].st[ii]
if isNil(procname): break
let p = $procname
@@ -193,10 +196,11 @@ proc writeProfile() {.noconv.} =
writeLine(f, "Entry: ", i+1, "/", entries, " Calls: ",
profileData[i].total // totalCalls, " [sum: ", sum, "; ",
sum // totalCalls, "]")
for ii in 0..high(StackTrace):
for ii in 0..high(StackTrace.lines):
let procname = profileData[i].st[ii]
let filename = profileData[i].st.files[ii]
if isNil(procname): break
writeLine(f, " ", procname, " ", perProc[$procname] // totalCalls)
writeLine(f, " ", $filename & ": " & $procname, " ", perProc[$procname] // totalCalls)
close(f)
echo "... done"
else:

View File

@@ -74,8 +74,7 @@ proc genOid*(): Oid =
var t = gettime(nil)
var i = int32(incr)
atomicInc(incr)
var i = int32(atomicInc(incr))
if fuzz == 0:
# racy, but fine semantically:

View File

@@ -26,7 +26,6 @@ elif defined(posix):
else:
{.error: "OS module not ported to your operating system!".}
include "system/ansi_c"
include ospaths
when defined(posix):
@@ -37,6 +36,23 @@ when defined(posix):
var
pathMax {.importc: "PATH_MAX", header: "<stdlib.h>".}: cint
proc c_remove(filename: cstring): cint {.
importc: "remove", header: "<stdio.h>".}
proc c_rename(oldname, newname: cstring): cint {.
importc: "rename", header: "<stdio.h>".}
proc c_system(cmd: cstring): cint {.
importc: "system", header: "<stdlib.h>".}
proc c_strerror(errnum: cint): cstring {.
importc: "strerror", header: "<string.h>".}
proc c_strlen(a: cstring): cint {.
importc: "strlen", header: "<string.h>", noSideEffect.}
proc c_getenv(env: cstring): cstring {.
importc: "getenv", header: "<stdlib.h>".}
proc c_putenv(env: cstring): cint {.
importc: "putenv", header: "<stdlib.h>".}
var errno {.importc, header: "<errno.h>".}: cint
proc osErrorMsg*(): string {.rtl, extern: "nos$1", deprecated.} =
## Retrieves the operating system's error flag, ``errno``.
## On Windows ``GetLastError`` is checked before ``errno``.
@@ -50,18 +66,18 @@ proc osErrorMsg*(): string {.rtl, extern: "nos$1", deprecated.} =
if err != 0'i32:
when useWinUnicode:
var msgbuf: WideCString
if formatMessageW(0x00000100 or 0x00001000 or 0x00000200,
if formatMessageW(0x00000100 or 0x00001000 or 0x00000200 or 0x000000FF,
nil, err, 0, addr(msgbuf), 0, nil) != 0'i32:
result = $msgbuf
if msgbuf != nil: localFree(cast[pointer](msgbuf))
else:
var msgbuf: cstring
if formatMessageA(0x00000100 or 0x00001000 or 0x00000200,
if formatMessageA(0x00000100 or 0x00001000 or 0x00000200 or 0x000000FF,
nil, err, 0, addr(msgbuf), 0, nil) != 0'i32:
result = $msgbuf
if msgbuf != nil: localFree(msgbuf)
if errno != 0'i32:
result = $os.strerror(errno)
result = $os.c_strerror(errno)
{.push warning[deprecated]: off.}
proc raiseOSError*(msg: string = "") {.noinline, rtl, extern: "nos$1",
@@ -114,7 +130,7 @@ proc osErrorMsg*(errorCode: OSErrorCode): string =
if msgbuf != nil: localFree(msgbuf)
else:
if errorCode != OSErrorCode(0'i32):
result = $os.strerror(errorCode.int32)
result = $os.c_strerror(errorCode.int32)
proc raiseOSError*(errorCode: OSErrorCode; additionalInfo = "") {.noinline.} =
## Raises an ``OSError`` exception. The ``errorCode`` will determine the
@@ -129,7 +145,7 @@ proc raiseOSError*(errorCode: OSErrorCode; additionalInfo = "") {.noinline.} =
if additionalInfo.len == 0:
e.msg = osErrorMsg(errorCode)
else:
e.msg = additionalInfo & " " & osErrorMsg(errorCode)
e.msg = osErrorMsg(errorCode) & "\nAdditional info: " & additionalInfo
if e.msg == "":
e.msg = "unknown OS error"
raise e
@@ -157,24 +173,24 @@ proc osLastError*(): OSErrorCode =
when defined(windows):
when useWinUnicode:
template wrapUnary(varname, winApiProc, arg: expr) {.immediate.} =
template wrapUnary(varname, winApiProc, arg: untyped) =
var varname = winApiProc(newWideCString(arg))
template wrapBinary(varname, winApiProc, arg, arg2: expr) {.immediate.} =
template wrapBinary(varname, winApiProc, arg, arg2: untyped) =
var varname = winApiProc(newWideCString(arg), arg2)
proc findFirstFile(a: string, b: var WIN32_FIND_DATA): Handle =
result = findFirstFileW(newWideCString(a), b)
template findNextFile(a, b: expr): expr = findNextFileW(a, b)
template getCommandLine(): expr = getCommandLineW()
template findNextFile(a, b: untyped): untyped = findNextFileW(a, b)
template getCommandLine(): untyped = getCommandLineW()
template getFilename(f: expr): expr =
template getFilename(f: untyped): untyped =
$cast[WideCString](addr(f.cFilename[0]))
else:
template findFirstFile(a, b: expr): expr = findFirstFileA(a, b)
template findNextFile(a, b: expr): expr = findNextFileA(a, b)
template getCommandLine(): expr = getCommandLineA()
template findFirstFile(a, b: untyped): untyped = findFirstFileA(a, b)
template findNextFile(a, b: untyped): untyped = findNextFileA(a, b)
template getCommandLine(): untyped = getCommandLineA()
template getFilename(f: expr): expr = $f.cFilename
template getFilename(f: untyped): untyped = $f.cFilename
proc skipFindData(f: WIN32_FIND_DATA): bool {.inline.} =
# Note - takes advantage of null delimiter in the cstring
@@ -320,7 +336,7 @@ proc setCurrentDir*(newDir: string) {.inline, tags: [].} =
proc expandFilename*(filename: string): string {.rtl, extern: "nos$1",
tags: [ReadDirEffect].} =
## Returns the full path of `filename`, raises OSError in case of an error.
## Returns the full (`absolute`:idx:) path of the file `filename`, raises OSError in case of an error.
when defined(windows):
const bufsize = 3072'i32
when useWinUnicode:
@@ -762,12 +778,26 @@ iterator envPairs*(): tuple[key, value: TaintedString] {.tags: [ReadEnvEffect].}
yield (TaintedString(substr(environment[i], 0, p-1)),
TaintedString(substr(environment[i], p+1)))
iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect].} =
## Iterate over all the files that match the `pattern`. On POSIX this uses
## the `glob`:idx: call.
##
## `pattern` is OS dependent, but at least the "\*.ext"
## notation is supported.
# Templates for filtering directories and files
when defined(windows):
template isDir(f: WIN32_FIND_DATA): bool =
(f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32
template isFile(f: WIN32_FIND_DATA): bool =
not isDir(f)
else:
template isDir(f: string): bool =
dirExists(f)
template isFile(f: string): bool =
fileExists(f)
template defaultWalkFilter(item): bool =
## Walk filter used to return true on both
## files and directories
true
template walkCommon(pattern: string, filter) =
## Common code for getting the files and directories with the
## specified `pattern`
when defined(windows):
var
f: WIN32_FIND_DATA
@@ -776,8 +806,7 @@ iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect].} =
if res != -1:
defer: findClose(res)
while true:
if not skipFindData(f) and
(f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) == 0'i32:
if not skipFindData(f) and filter(f):
# Windows bug/gotcha: 't*.nim' matches 'tfoo.nims' -.- so we check
# that the file extensions have the same length ...
let ff = getFilename(f)
@@ -799,7 +828,33 @@ iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect].} =
if res == 0:
for i in 0.. f.gl_pathc - 1:
assert(f.gl_pathv[i] != nil)
yield $f.gl_pathv[i]
let path = $f.gl_pathv[i]
if filter(path):
yield path
iterator walkPattern*(pattern: string): string {.tags: [ReadDirEffect].} =
## Iterate over all the files and directories that match the `pattern`.
## On POSIX this uses the `glob`:idx: call.
##
## `pattern` is OS dependent, but at least the "\*.ext"
## notation is supported.
walkCommon(pattern, defaultWalkFilter)
iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect].} =
## Iterate over all the files that match the `pattern`. On POSIX this uses
## the `glob`:idx: call.
##
## `pattern` is OS dependent, but at least the "\*.ext"
## notation is supported.
walkCommon(pattern, isFile)
iterator walkDirs*(pattern: string): string {.tags: [ReadDirEffect].} =
## Iterate over all the directories that match the `pattern`.
## On POSIX this uses the `glob`:idx: call.
##
## `pattern` is OS dependent, but at least the "\*.ext"
## notation is supported.
walkCommon(pattern, isDir)
type
PathComponent* = enum ## Enumeration specifying a path component.
@@ -1067,7 +1122,7 @@ proc parseCmdLine*(c: string): seq[string] {.
while true:
setLen(a, 0)
# eat all delimiting whitespace
while c[i] == ' ' or c[i] == '\t' or c [i] == '\l' or c [i] == '\r' : inc(i)
while c[i] == ' ' or c[i] == '\t' or c[i] == '\l' or c[i] == '\r' : inc(i)
when defined(windows):
# parse a single argument according to the above rules:
if c[i] == '\0': break
@@ -1447,13 +1502,13 @@ type
lastWriteTime*: Time # Time file was last modified/written to.
creationTime*: Time # Time file was created. Not supported on all systems!
template rawToFormalFileInfo(rawInfo, formalInfo): expr =
template rawToFormalFileInfo(rawInfo, formalInfo): untyped =
## Transforms the native file info structure into the one nim uses.
## 'rawInfo' is either a 'TBY_HANDLE_FILE_INFORMATION' structure on Windows,
## or a 'Stat' structure on posix
when defined(Windows):
template toTime(e): expr = winTimeToUnixTime(rdFileTime(e))
template merge(a, b): expr = a or (b shl 32)
template toTime(e: FILETIME): untyped {.gensym.} = winTimeToUnixTime(rdFileTime(e)) # local templates default to bind semantics
template merge(a, b): untyped = a or (b shl 32)
formalInfo.id.device = rawInfo.dwVolumeSerialNumber
formalInfo.id.file = merge(rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh)
formalInfo.size = merge(rawInfo.nFileSizeLow, rawInfo.nFileSizeHigh)
@@ -1478,7 +1533,7 @@ template rawToFormalFileInfo(rawInfo, formalInfo): expr =
else:
template checkAndIncludeMode(rawMode, formalMode: expr) =
template checkAndIncludeMode(rawMode, formalMode: untyped) =
if (rawInfo.st_mode and rawMode) != 0'i32:
formalInfo.permissions.incl(formalMode)
formalInfo.id = (rawInfo.st_dev, rawInfo.st_ino)

View File

@@ -10,7 +10,7 @@
# Included by the ``os`` module but a module in its own right for NimScript
# support.
when isMainModule:
when not declared(os):
{.pragma: rtl.}
import strutils
@@ -556,12 +556,20 @@ when declared(getEnv) or defined(nimscript):
yield substr(s, first, last-1)
inc(last)
proc findExe*(exe: string): string {.
when not defined(windows) and declared(os):
proc checkSymlink(path: string): bool =
var rawInfo: Stat
if lstat(path, rawInfo) < 0'i32: result = false
else: result = S_ISLNK(rawInfo.st_mode)
proc findExe*(exe: string, followSymlinks: bool = true): string {.
tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect].} =
## Searches for `exe` in the current working directory and then
## in directories listed in the ``PATH`` environment variable.
## Returns "" if the `exe` cannot be found. On DOS-like platforms, `exe`
## is added the `ExeExt <#ExeExt>`_ file extension if it has none.
## If the system supports symlinks it also resolves them until it
## meets the actual file. This behavior can be disabled if desired.
result = addFileExt(exe, ExeExt)
if existsFile(result): return
var path = string(getEnv("PATH"))
@@ -572,7 +580,25 @@ when declared(getEnv) or defined(nimscript):
result
else:
var x = expandTilde(candidate) / result
if existsFile(x): return x
if existsFile(x):
when not defined(windows) and declared(os):
while followSymlinks: # doubles as if here
if x.checkSymlink:
var r = newString(256)
var len = readlink(x, r, 256)
if len < 0:
raiseOSError(osLastError())
if len > 256:
r = newString(len+1)
len = readlink(x, r, len)
setLen(r, len)
if isAbsolute(r):
x = r
else:
x = parentDir(x) / r
else:
break
return x
result = ""
when defined(nimscript) or (defined(nimdoc) and not declared(os)):

View File

@@ -89,7 +89,7 @@ proc quoteShellWindows*(s: string): string {.noSideEffect, rtl, extern: "nosp$1"
result.add("\"")
proc quoteShellPosix*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
## Quote s, so it can be safely passed to POSIX shell.
## Quote ``s``, so it can be safely passed to POSIX shell.
## Based on Python's pipes.quote
const safeUnixChars = {'%', '+', '-', '.', '/', '_', ':', '=', '@',
'0'..'9', 'A'..'Z', 'a'..'z'}
@@ -104,7 +104,7 @@ proc quoteShellPosix*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".}
return "'" & s.replace("'", "'\"'\"'") & "'"
proc quoteShell*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
## Quote s, so it can be safely passed to shell.
## Quote ``s``, so it can be safely passed to shell.
when defined(Windows):
return quoteShellWindows(s)
elif defined(posix):
@@ -175,7 +175,11 @@ proc startCmd*(command: string, options: set[ProcessOption] = {
result = startProcess(command=command, options=options + {poEvalCommand})
proc close*(p: Process) {.rtl, extern: "nosp$1", tags: [].}
## When the process has finished executing, cleanup related handles
## When the process has finished executing, cleanup related handles.
##
## **Warning:** If the process has not finished executing, this will forcibly
## terminate the process. Doing so may result in zombie processes and
## `pty leaks <http://stackoverflow.com/questions/27021641/how-to-fix-request-failed-on-channel-0>`_.
proc suspend*(p: Process) {.rtl, extern: "nosp$1", tags: [].}
## Suspends the process `p`.
@@ -400,15 +404,16 @@ when defined(Windows) and not defined(useNimRtl):
result = cast[cstring](alloc0(res.len+1))
copyMem(result, cstring(res), res.len)
proc buildEnv(env: StringTableRef): cstring =
proc buildEnv(env: StringTableRef): tuple[str: cstring, len: int] =
var L = 0
for key, val in pairs(env): inc(L, key.len + val.len + 2)
result = cast[cstring](alloc0(L+2))
var str = cast[cstring](alloc0(L+2))
L = 0
for key, val in pairs(env):
var x = key & "=" & val
copyMem(addr(result[L]), cstring(x), x.len+1) # copy \0
copyMem(addr(str[L]), cstring(x), x.len+1) # copy \0
inc(L, x.len+1)
(str, L)
#proc open_osfhandle(osh: Handle, mode: int): int {.
# importc: "_open_osfhandle", header: "<fcntl.h>".}
@@ -526,13 +531,15 @@ when defined(Windows) and not defined(useNimRtl):
else:
cmdl = buildCommandLine(command, args)
var wd: cstring = nil
var e: cstring = nil
var e = (str: nil.cstring, len: -1)
if len(workingDir) > 0: wd = workingDir
if env != nil: e = buildEnv(env)
if poEchoCmd in options: echo($cmdl)
when useWinUnicode:
var tmp = newWideCString(cmdl)
var ee = newWideCString(e)
var ee =
if e.str.isNil: nil
else: newWideCString(e.str, e.len)
var wwd = newWideCString(wd)
var flags = NORMAL_PRIORITY_CLASS or CREATE_UNICODE_ENVIRONMENT
if poDemon in options: flags = flags or CREATE_NO_WINDOW
@@ -549,7 +556,7 @@ when defined(Windows) and not defined(useNimRtl):
if poStdErrToStdOut notin options:
fileClose(si.hStdError)
if e != nil: dealloc(e)
if e.str != nil: dealloc(e.str)
if success == 0:
if poInteractive in result.options: close(result)
const errInvalidParameter = 87.int
@@ -721,7 +728,7 @@ elif not defined(useNimRtl):
env: StringTableRef = nil,
options: set[ProcessOption] = {poStdErrToStdOut}): Process =
var
pStdin, pStdout, pStderr: array [0..1, cint]
pStdin, pStdout, pStderr: array[0..1, cint]
new(result)
result.options = options
result.exitCode = -3 # for ``waitForExit``
@@ -875,8 +882,9 @@ elif not defined(useNimRtl):
var error: cint
let sizeRead = read(data.pErrorPipe[readIdx], addr error, sizeof(error))
if sizeRead == sizeof(error):
raiseOSError("Could not find command: '$1'. OS error: $2" %
[$data.sysCommand, $strerror(error)])
raiseOSError(osLastError(),
"Could not find command: '$1'. OS error: $2" %
[$data.sysCommand, $strerror(error)])
return pid
@@ -967,16 +975,168 @@ elif not defined(useNimRtl):
if kill(p.id, SIGKILL) != 0'i32:
raiseOsError(osLastError())
proc waitForExit(p: Process, timeout: int = -1): int =
#if waitPid(p.id, p.exitCode, 0) == int(p.id):
# ``waitPid`` fails if the process is not running anymore. But then
# ``running`` probably set ``p.exitCode`` for us. Since ``p.exitCode`` is
# initialized with -3, wrong success exit codes are prevented.
if p.exitCode != -3: return p.exitCode
if waitpid(p.id, p.exitCode, 0) < 0:
p.exitCode = -3
raiseOSError(osLastError())
result = int(p.exitCode) shr 8
when defined(macosx) or defined(freebsd) or defined(netbsd) or
defined(openbsd):
import kqueue, times
proc waitForExit(p: Process, timeout: int = -1): int =
if p.exitCode != -3: return p.exitCode
if timeout == -1:
if waitpid(p.id, p.exitCode, 0) < 0:
p.exitCode = -3
raiseOSError(osLastError())
else:
var kqFD = kqueue()
if kqFD == -1:
raiseOSError(osLastError())
var kevIn = KEvent(ident: p.id.uint, filter: EVFILT_PROC,
flags: EV_ADD, fflags: NOTE_EXIT)
var kevOut: KEvent
var tmspec: Timespec
if timeout >= 1000:
tmspec.tv_sec = (timeout div 1_000).Time
tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000
else:
tmspec.tv_sec = 0.Time
tmspec.tv_nsec = (timeout * 1_000_000)
try:
while true:
var count = kevent(kqFD, addr(kevIn), 1, addr(kevOut), 1,
addr(tmspec))
if count < 0:
let err = osLastError()
if err.cint != EINTR:
raiseOSError(osLastError())
elif count == 0:
# timeout expired, so we trying to kill process
if posix.kill(p.id, SIGKILL) == -1:
raiseOSError(osLastError())
if waitpid(p.id, p.exitCode, 0) < 0:
p.exitCode = -3
raiseOSError(osLastError())
break
else:
if kevOut.ident == p.id.uint and kevOut.filter == EVFILT_PROC:
if waitpid(p.id, p.exitCode, 0) < 0:
p.exitCode = -3
raiseOSError(osLastError())
break
else:
raiseOSError(osLastError())
finally:
discard posix.close(kqFD)
result = int(p.exitCode) shr 8
else:
import times
const
hasThreadSupport = compileOption("threads") and not defined(nimscript)
proc waitForExit(p: Process, timeout: int = -1): int =
template adjustTimeout(t, s, e: Timespec) =
var diff: int
var b: Timespec
b.tv_sec = e.tv_sec
b.tv_nsec = e.tv_nsec
e.tv_sec = (e.tv_sec - s.tv_sec).Time
if e.tv_nsec >= s.tv_nsec:
e.tv_nsec -= s.tv_nsec
else:
if e.tv_sec == 0.Time:
raise newException(ValueError, "System time was modified")
else:
diff = s.tv_nsec - e.tv_nsec
e.tv_nsec = 1_000_000_000 - diff
t.tv_sec = (t.tv_sec - e.tv_sec).Time
if t.tv_nsec >= e.tv_nsec:
t.tv_nsec -= e.tv_nsec
else:
t.tv_sec = (int(t.tv_sec) - 1).Time
diff = e.tv_nsec - t.tv_nsec
t.tv_nsec = 1_000_000_000 - diff
s.tv_sec = b.tv_sec
s.tv_nsec = b.tv_nsec
#if waitPid(p.id, p.exitCode, 0) == int(p.id):
# ``waitPid`` fails if the process is not running anymore. But then
# ``running`` probably set ``p.exitCode`` for us. Since ``p.exitCode`` is
# initialized with -3, wrong success exit codes are prevented.
if p.exitCode != -3: return p.exitCode
if timeout == -1:
if waitpid(p.id, p.exitCode, 0) < 0:
p.exitCode = -3
raiseOSError(osLastError())
else:
var nmask, omask: Sigset
var sinfo: SigInfo
var stspec, enspec, tmspec: Timespec
discard sigemptyset(nmask)
discard sigemptyset(omask)
discard sigaddset(nmask, SIGCHLD)
when hasThreadSupport:
if pthread_sigmask(SIG_BLOCK, nmask, omask) == -1:
raiseOSError(osLastError())
else:
if sigprocmask(SIG_BLOCK, nmask, omask) == -1:
raiseOSError(osLastError())
if timeout >= 1000:
tmspec.tv_sec = (timeout div 1_000).Time
tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000
else:
tmspec.tv_sec = 0.Time
tmspec.tv_nsec = (timeout * 1_000_000)
try:
if clock_gettime(CLOCK_REALTIME, stspec) == -1:
raiseOSError(osLastError())
while true:
let res = sigtimedwait(nmask, sinfo, tmspec)
if res == SIGCHLD:
if sinfo.si_pid == p.id:
if waitpid(p.id, p.exitCode, 0) < 0:
p.exitCode = -3
raiseOSError(osLastError())
break
else:
# we have SIGCHLD, but not for process we are waiting,
# so we need to adjust timeout value and continue
if clock_gettime(CLOCK_REALTIME, enspec) == -1:
raiseOSError(osLastError())
adjustTimeout(tmspec, stspec, enspec)
elif res < 0:
let err = osLastError()
if err.cint == EINTR:
# we have received another signal, so we need to
# adjust timeout and continue
if clock_gettime(CLOCK_REALTIME, enspec) == -1:
raiseOSError(osLastError())
adjustTimeout(tmspec, stspec, enspec)
elif err.cint == EAGAIN:
# timeout expired, so we trying to kill process
if posix.kill(p.id, SIGKILL) == -1:
raiseOSError(osLastError())
if waitpid(p.id, p.exitCode, 0) < 0:
p.exitCode = -3
raiseOSError(osLastError())
break
else:
raiseOSError(err)
finally:
when hasThreadSupport:
if pthread_sigmask(SIG_UNBLOCK, nmask, omask) == -1:
raiseOSError(osLastError())
else:
if sigprocmask(SIG_UNBLOCK, nmask, omask) == -1:
raiseOSError(osLastError())
result = int(p.exitCode) shr 8
proc peekExitCode(p: Process): int =
if p.exitCode != -3: return p.exitCode

View File

@@ -1,3 +1,11 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Compile-time only version for walkDir if you need it at compile-time
## for JavaScript.

View File

@@ -15,17 +15,78 @@
## This is an example of how a configuration file may look like:
##
## .. include:: doc/mytest.cfg
## .. include:: ../../doc/mytest.cfg
## :literal:
## The file ``examples/parsecfgex.nim`` demonstrates how to use the
## configuration file parser:
##
## .. code-block:: nim
## :file: examples/parsecfgex.nim
## :file: ../../examples/parsecfgex.nim
##
## Examples
## --------
##
## This is an example of a configuration file.
##
## ::
##
## charset = "utf-8"
## [Package]
## name = "hello"
## --threads:on
## [Author]
## name = "lihf8515"
## qq = "10214028"
## email = "lihaifeng@wxm.com"
##
## Creating a configuration file.
## ==============================
## .. code-block:: nim
##
## import parsecfg
## var dict=newConfig()
## dict.setSectionKey("","charset","utf-8")
## dict.setSectionKey("Package","name","hello")
## dict.setSectionKey("Package","--threads","on")
## dict.setSectionKey("Author","name","lihf8515")
## dict.setSectionKey("Author","qq","10214028")
## dict.setSectionKey("Author","email","lihaifeng@wxm.com")
## dict.writeConfig("config.ini")
##
## Reading a configuration file.
## =============================
## .. code-block:: nim
##
## import parsecfg
## var dict = loadConfig("config.ini")
## var charset = dict.getSectionValue("","charset")
## var threads = dict.getSectionValue("Package","--threads")
## var pname = dict.getSectionValue("Package","name")
## var name = dict.getSectionValue("Author","name")
## var qq = dict.getSectionValue("Author","qq")
## var email = dict.getSectionValue("Author","email")
## echo pname & "\n" & name & "\n" & qq & "\n" & email
##
## Modifying a configuration file.
## ===============================
## .. code-block:: nim
##
## import parsecfg
## var dict = loadConfig("config.ini")
## dict.setSectionKey("Author","name","lhf")
## dict.writeConfig("config.ini")
##
## Deleting a section key in a configuration file.
## ===============================================
## .. code-block:: nim
##
## import parsecfg
## var dict = loadConfig("config.ini")
## dict.delSectionKey("Author","email")
## dict.writeConfig("config.ini")
import
hashes, strutils, lexbase, streams
hashes, strutils, lexbase, streams, tables
include "system/inclrtl"
@@ -70,7 +131,7 @@ type
# implementation
const
SymChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF', '.', '/', '\\'}
SymChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF', '.', '/', '\\', '-'}
proc rawGetTok(c: var CfgParser, tok: var Token) {.gcsafe.}
@@ -359,3 +420,138 @@ proc next*(c: var CfgParser): CfgEvent {.rtl, extern: "npc$1".} =
result.kind = cfgError
result.msg = errorStr(c, "invalid token: " & c.tok.literal)
rawGetTok(c, c.tok)
# ---------------- Configuration file related operations ----------------
type
Config* = OrderedTableRef[string, OrderedTableRef[string, string]]
proc newConfig*(): Config =
## Create a new configuration table.
## Useful when wanting to create a configuration file.
result = newOrderedTable[string, OrderedTableRef[string, string]]()
proc loadConfig*(filename: string): Config =
## Load the specified configuration file into a new Config instance.
var dict = newOrderedTable[string, OrderedTableRef[string, string]]()
var curSection = "" ## Current section,
## the default value of the current section is "",
## which means that the current section is a common
var p: CfgParser
var fileStream = newFileStream(filename, fmRead)
if fileStream != nil:
open(p, fileStream, filename)
while true:
var e = next(p)
case e.kind
of cfgEof:
break
of cfgSectionStart: # Only look for the first time the Section
curSection = e.section
of cfgKeyValuePair:
var t = newOrderedTable[string, string]()
if dict.hasKey(curSection):
t = dict[curSection]
t[e.key] = e.value
dict[curSection] = t
of cfgOption:
var c = newOrderedTable[string, string]()
if dict.hasKey(curSection):
c = dict[curSection]
c["--" & e.key] = e.value
dict[curSection] = c
of cfgError:
break
close(p)
result = dict
proc replace(s: string): string =
var d = ""
var i = 0
while i < s.len():
if s[i] == '\\':
d.add(r"\\")
elif s[i] == '\c' and s[i+1] == '\L':
d.add(r"\n")
inc(i)
elif s[i] == '\c':
d.add(r"\n")
elif s[i] == '\L':
d.add(r"\n")
else:
d.add(s[i])
inc(i)
result = d
proc writeConfig*(dict: Config, filename: string) =
## Writes the contents of the table to the specified configuration file.
## Note: Comment statement will be ignored.
var file: File
if file.open(filename, fmWrite):
try:
var section, key, value, kv, segmentChar:string
for pair in dict.pairs():
section = pair[0]
if section != "": ## Not general section
if not allCharsInSet(section, SymChars): ## Non system character
file.writeLine("[\"" & section & "\"]")
else:
file.writeLine("[" & section & "]")
for pair2 in pair[1].pairs():
key = pair2[0]
value = pair2[1]
if key[0] == '-' and key[1] == '-': ## If it is a command key
segmentChar = ":"
if not allCharsInSet(key[2..key.len()-1], SymChars):
kv.add("--\"")
kv.add(key[2..key.len()-1])
kv.add("\"")
else:
kv = key
else:
segmentChar = "="
kv = key
if value != "": ## If the key is not empty
if not allCharsInSet(value, SymChars):
kv.add(segmentChar)
kv.add("\"")
kv.add(replace(value))
kv.add("\"")
else:
kv.add(segmentChar)
kv.add(value)
file.writeLine(kv)
except:
raise
finally:
file.close()
proc getSectionValue*(dict: Config, section, key: string): string =
## Gets the Key value of the specified Section.
if dict.haskey(section):
if dict[section].hasKey(key):
result = dict[section][key]
else:
result = ""
else:
result = ""
proc setSectionKey*(dict: var Config, section, key, value: string) =
## Sets the Key value of the specified Section.
var t = newOrderedTable[string, string]()
if dict.hasKey(section):
t = dict[section]
t[key] = value
dict[section] = t
proc delSection*(dict: var Config, section: string) =
## Deletes the specified section and all of its sub keys.
dict.del(section)
proc delSectionKey*(dict: var Config, section, key: string) =
## Delete the key of the specified section.
if dict.haskey(section):
if dict[section].hasKey(key):
if dict[section].len() == 1:
dict.del(section)
else:
dict[section].del(key)

View File

@@ -25,6 +25,28 @@
## echo "##", val, "##"
## close(x)
##
## For CSV files with a header row, the header can be read and then used as a
## reference for item access with `rowEntry <#rowEntry.CsvParser.string>`_:
##
## .. code-block:: nim
## import parsecsv
## import os
## # Prepare a file
## var csv_content = """One,Two,Three,Four
## 1,2,3,4
## 10,20,30,40
## 100,200,300,400
## """
## writeFile("temp.csv", content)
##
## var p: CsvParser
## p.open("temp.csv")
## p.readHeaderRow()
## while p.readRow():
## echo "new row: "
## for col in items(p.headers):
## echo "##", col, ":", p.rowEntry(col), "##"
## p.close()
import
lexbase, streams
@@ -37,6 +59,9 @@ type
sep, quote, esc: char
skipWhite: bool
currRow: int
headers*: seq[string] ## The columns that are defined in the csv file
## (read using `readHeaderRow <#readHeaderRow.CsvParser>`_).
## Used with `rowEntry <#rowEntry.CsvParser.string>`_).
CsvError* = object of IOError ## exception that is raised if
## a parsing error occurs
@@ -77,6 +102,15 @@ proc open*(my: var CsvParser, input: Stream, filename: string,
my.row = @[]
my.currRow = 0
proc open*(my: var CsvParser, filename: string,
separator = ',', quote = '"', escape = '\0',
skipInitialSpace = false) =
## same as the other `open` but creates the file stream for you.
var s = newFileStream(filename, fmRead)
if s == nil: my.error(0, "cannot open: " & filename)
open(my, s, filename, separator,
quote, escape, skipInitialSpace)
proc parseField(my: var CsvParser, a: var string) =
var pos = my.bufpos
var buf = my.buf
@@ -131,6 +165,8 @@ proc readRow*(my: var CsvParser, columns = 0): bool =
## reads the next row; if `columns` > 0, it expects the row to have
## exactly this many columns. Returns false if the end of the file
## has been encountered else true.
##
## Blank lines are skipped.
var col = 0 # current column
var oldpos = my.bufpos
while my.buf[my.bufpos] != '\0':
@@ -166,6 +202,22 @@ proc close*(my: var CsvParser) {.inline.} =
## closes the parser `my` and its associated input stream.
lexbase.close(my)
proc readHeaderRow*(my: var CsvParser) =
## Reads the first row and creates a look-up table for column numbers
## See also `rowEntry <#rowEntry.CsvParser.string>`_.
var present = my.readRow()
if present:
my.headers = my.row
proc rowEntry*(my: var CsvParser, entry: string): string =
## Reads a specified `entry` from the current row.
##
## Assumes that `readHeaderRow <#readHeaderRow.CsvParser>`_ has already been
## called.
var index = my.headers.find(entry)
if index >= 0:
result = my.row[index]
when not defined(testing) and isMainModule:
import os
var s = newFileStream(paramStr(1), fmRead)
@@ -178,3 +230,35 @@ when not defined(testing) and isMainModule:
echo "##", val, "##"
close(x)
when isMainModule:
import os
import strutils
block: # Tests for reading the header row
var content = "One,Two,Three,Four\n1,2,3,4\n10,20,30,40,\n100,200,300,400\n"
writeFile("temp.csv", content)
var p: CsvParser
p.open("temp.csv")
p.readHeaderRow()
while p.readRow():
var zeros = repeat('0', p.currRow-2)
doAssert p.rowEntry("One") == "1" & zeros
doAssert p.rowEntry("Two") == "2" & zeros
doAssert p.rowEntry("Three") == "3" & zeros
doAssert p.rowEntry("Four") == "4" & zeros
p.close()
when not defined(testing):
var parser: CsvParser
parser.open("temp.csv")
parser.readHeaderRow()
while parser.readRow():
echo "new row: "
for col in items(parser.headers):
echo "##", col, ":", parser.rowEntry(col), "##"
parser.close()
removeFile("temp.csv")
# Tidy up
removeFile("temp.csv")

View File

@@ -173,6 +173,22 @@ proc parseUntil*(s: string, token: var string, until: char,
result = i-start
token = substr(s, start, i-1)
proc parseUntil*(s: string, token: var string, until: string,
start = 0): int {.inline.} =
## parses a token and stores it in ``token``. Returns
## the number of the parsed characters or 0 in case of an error. A token
## consists of any character that comes before the `until` token.
var i = start
while i < s.len:
if s[i] == until[0]:
var u = 1
while i+u < s.len and u < until.len and s[i+u] == until[u]:
inc u
if u >= until.len: break
inc(i)
result = i-start
token = substr(s, start, i-1)
proc parseWhile*(s: string, token: var string, validChars: set[char],
start = 0): int {.inline.} =
## parses a token and stores it in ``token``. Returns
@@ -234,6 +250,51 @@ proc parseInt*(s: string, number: var int, start = 0): int {.
elif result != 0:
number = int(res)
# overflowChecks doesn't work with uint64
proc rawParseUInt(s: string, b: var uint64, start = 0): int =
var
res = 0'u64
prev = 0'u64
i = start
if s[i] == '+': inc(i) # Allow
if s[i] in {'0'..'9'}:
b = 0
while s[i] in {'0'..'9'}:
prev = res
res = res * 10 + (ord(s[i]) - ord('0')).uint64
if prev > res:
return 0 # overflowChecks emulation
inc(i)
while s[i] == '_': inc(i) # underscores are allowed and ignored
b = res
result = i - start
proc parseBiggestUInt*(s: string, number: var uint64, start = 0): int {.
rtl, extern: "npuParseBiggestUInt", noSideEffect.} =
## parses an unsigned integer starting at `start` and stores the value
## into `number`.
## Result is the number of processed chars or 0 if there is no integer
## or overflow detected.
var res: uint64
# use 'res' for exception safety (don't write to 'number' in case of an
# overflow exception):
result = rawParseUInt(s, res, start)
number = res
proc parseUInt*(s: string, number: var uint, start = 0): int {.
rtl, extern: "npuParseUInt", noSideEffect.} =
## parses an unsigned integer starting at `start` and stores the value
## into `number`.
## Result is the number of processed chars or 0 if there is no integer or
## overflow detected.
var res: uint64
result = parseBiggestUInt(s, res, start)
if (sizeof(uint) <= 4) and
(res > 0xFFFF_FFFF'u64):
raise newException(OverflowError, "overflow")
elif result != 0:
number = uint(res)
proc parseBiggestFloat*(s: string, number: var BiggestFloat, start = 0): int {.
magic: "ParseBiggestFloat", importc: "nimParseBiggestFloat", noSideEffect.}
## parses a float starting at `start` and stores the value into `number`.

View File

@@ -34,7 +34,7 @@
## document.
##
## .. code-block:: nim
## :file: examples/htmltitle.nim
## :file: ../../examples/htmltitle.nim
##
##
## Example 2: Retrieve all HTML links
@@ -45,7 +45,7 @@
## an HTML document contains.
##
## .. code-block:: nim
## :file: examples/htmlrefs.nim
## :file: ../../examples/htmlrefs.nim
##
import
@@ -142,6 +142,9 @@ proc kind*(my: XmlParser): XmlEventKind {.inline.} =
template charData*(my: XmlParser): string =
## returns the character data for the events: ``xmlCharData``,
## ``xmlWhitespace``, ``xmlComment``, ``xmlCData``, ``xmlSpecial``
## Raises an assertion in debug mode if ``my.kind`` is not one
## of those events. In release mode, this will not trigger an error
## but the value returned will not be valid.
assert(my.kind in {xmlCharData, xmlWhitespace, xmlComment, xmlCData,
xmlSpecial})
my.a
@@ -149,31 +152,49 @@ template charData*(my: XmlParser): string =
template elementName*(my: XmlParser): string =
## returns the element name for the events: ``xmlElementStart``,
## ``xmlElementEnd``, ``xmlElementOpen``
## Raises an assertion in debug mode if ``my.kind`` is not one
## of those events. In release mode, this will not trigger an error
## but the value returned will not be valid.
assert(my.kind in {xmlElementStart, xmlElementEnd, xmlElementOpen})
my.a
template entityName*(my: XmlParser): string =
## returns the entity name for the event: ``xmlEntity``
## Raises an assertion in debug mode if ``my.kind`` is not
## ``xmlEntity``. In release mode, this will not trigger an error
## but the value returned will not be valid.
assert(my.kind == xmlEntity)
my.a
template attrKey*(my: XmlParser): string =
## returns the attribute key for the event ``xmlAttribute``
## Raises an assertion in debug mode if ``my.kind`` is not
## ``xmlAttribute``. In release mode, this will not trigger an error
## but the value returned will not be valid.
assert(my.kind == xmlAttribute)
my.a
template attrValue*(my: XmlParser): string =
## returns the attribute value for the event ``xmlAttribute``
## Raises an assertion in debug mode if ``my.kind`` is not
## ``xmlAttribute``. In release mode, this will not trigger an error
## but the value returned will not be valid.
assert(my.kind == xmlAttribute)
my.b
template piName*(my: XmlParser): string =
## returns the processing instruction name for the event ``xmlPI``
## Raises an assertion in debug mode if ``my.kind`` is not
## ``xmlPI``. In release mode, this will not trigger an error
## but the value returned will not be valid.
assert(my.kind == xmlPI)
my.a
template piRest*(my: XmlParser): string =
## returns the rest of the processing instruction for the event ``xmlPI``
## Raises an assertion in debug mode if ``my.kind`` is not
## ``xmlPI``. In release mode, this will not trigger an error
## but the value returned will not be valid.
assert(my.kind == xmlPI)
my.b

View File

@@ -12,7 +12,7 @@
## Matching performance is hopefully competitive with optimized regular
## expression engines.
##
## .. include:: ../doc/pegdocs.txt
## .. include:: ../../doc/pegdocs.txt
##
include "system/inclrtl"
@@ -659,7 +659,7 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
of pkSearch:
var oldMl = c.ml
result = 0
while start+result < s.len:
while start+result <= s.len:
var x = rawMatch(s, p.sons[0], start+result, c)
if x >= 0:
inc(result, x)
@@ -671,7 +671,7 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
var idx = c.ml # reserve a slot for the subpattern
inc(c.ml)
result = 0
while start+result < s.len:
while start+result <= s.len:
var x = rawMatch(s, p.sons[0], start+result, c)
if x >= 0:
if idx < MaxSubpatterns:
@@ -962,6 +962,50 @@ proc parallelReplace*(s: string, subs: varargs[
# copy the rest:
add(result, substr(s, i))
proc replace*(s: string, sub: Peg, cb: proc(
match: int, cnt: int, caps: openArray[string]): string): string {.
rtl, extern: "npegs$1cb".}=
## Replaces `sub` in `s` by the resulting strings from the callback.
## The callback proc receives the index of the current match (starting with 0),
## the count of captures and an open array with the captures of each match. Examples:
##
## .. code-block:: nim
##
## proc handleMatches*(m: int, n: int, c: openArray[string]): string =
## result = ""
## if m > 0:
## result.add ", "
## result.add case n:
## of 2: c[0].toLower & ": '" & c[1] & "'"
## of 1: c[0].toLower & ": ''"
## else: ""
##
## let s = "Var1=key1;var2=Key2; VAR3"
## echo s.replace(peg"{\ident}('='{\ident})* ';'* \s*", handleMatches)
##
## Results in:
##
## .. code-block:: nim
##
## "var1: 'key1', var2: 'Key2', var3: ''"
result = ""
var i = 0
var caps: array[0..MaxSubpatterns-1, string]
var c: Captures
var m = 0
while i < s.len:
c.ml = 0
var x = rawMatch(s, sub, i, c)
if x <= 0:
add(result, s[i])
inc(i)
else:
fillMatches(s, caps, c)
add(result, cb(m, c.ml, caps))
inc(i, x)
inc(m)
add(result, substr(s, i))
proc transformFile*(infile, outfile: string,
subs: varargs[tuple[pattern: Peg, repl: string]]) {.
rtl, extern: "npegs$1".} =
@@ -1789,3 +1833,22 @@ when isMainModule:
assert(str.find(empty_test) == 0)
assert(str.match(empty_test))
proc handleMatches*(m: int, n: int, c: openArray[string]): string =
result = ""
if m > 0:
result.add ", "
result.add case n:
of 2: toLowerAscii(c[0]) & ": '" & c[1] & "'"
of 1: toLowerAscii(c[0]) & ": ''"
else: ""
assert("Var1=key1;var2=Key2; VAR3".
replace(peg"{\ident}('='{\ident})* ';'* \s*",
handleMatches)=="var1: 'key1', var2: 'Key2', var3: ''")
doAssert "test1".match(peg"""{@}$""")
doAssert "test2".match(peg"""{(!$ .)*} $""")

174
lib/pure/punycode.nim Normal file
View File

@@ -0,0 +1,174 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2016 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
import strutils
import unicode
# issue #3045
const
Base = 36
TMin = 1
TMax = 26
Skew = 38
Damp = 700
InitialBias = 72
InitialN = 128
Delimiter = '-'
type
PunyError* = object of Exception
proc decodeDigit(x: char): int {.raises: [PunyError].} =
if '0' <= x and x <= '9':
result = ord(x) - (ord('0') - 26)
elif 'A' <= x and x <= 'Z':
result = ord(x) - ord('A')
elif 'a' <= x and x <= 'z':
result = ord(x) - ord('a')
else:
raise newException(PunyError, "Bad input")
proc encodeDigit(digit: int): Rune {.raises: [PunyError].} =
if 0 <= digit and digit < 26:
result = Rune(digit + ord('a'))
elif 26 <= digit and digit < 36:
result = Rune(digit + (ord('0') - 26))
else:
raise newException(PunyError, "internal error in punycode encoding")
proc isBasic(c: char): bool = ord(c) < 0x80
proc isBasic(r: Rune): bool = int(r) < 0x80
proc adapt(delta, numPoints: int, first: bool): int =
var d = if first: delta div Damp else: delta div 2
d += d div numPoints
var k = 0
while d > ((Base-TMin)*TMax) div 2:
d = d div (Base - TMin)
k += Base
result = k + (Base - TMin + 1) * d div (d + Skew)
proc encode*(prefix, s: string): string {.raises: [PunyError].} =
## Encode a string that may contain Unicode.
## Prepend `prefix` to the result
result = prefix
var (d, n, bias) = (0, InitialN, InitialBias)
var (b, remaining) = (0, 0)
for r in s.runes:
if r.isBasic:
# basic Ascii character
inc b
result.add($r)
else:
# special character
inc remaining
var h = b
if b > 0:
result.add(Delimiter) # we have some Ascii chars
while remaining != 0:
var m: int = high(int32)
for r in s.runes:
if m > int(r) and int(r) >= n:
m = int(r)
d += (m - n) * (h + 1)
if d < 0:
raise newException(PunyError, "invalid label " & s)
n = m
for r in s.runes:
if int(r) < n:
inc d
if d < 0:
raise newException(PunyError, "invalid label " & s)
continue
if int(r) > n:
continue
var q = d
var k = Base
while true:
var t = k - bias
if t < TMin:
t = TMin
elif t > TMax:
t = TMax
if q < t:
break
result.add($encodeDigit(t + (q - t) mod (Base - t)))
q = (q - t) div (Base - t)
k += Base
result.add($encodeDigit(q))
bias = adapt(d, h + 1, h == b)
d = 0
inc h
dec remaining
inc d
inc n
proc encode*(s: string): string {.raises: [PunyError].} =
## Encode a string that may contain Unicode. Prefix is empty.
result = encode("", s)
proc decode*(encoded: string): string {.raises: [PunyError].} =
## Decode a Punycode-encoded string
var
n = InitialN
i = 0
bias = InitialBias
var d = rfind(encoded, Delimiter)
result = ""
if d > 0:
# found Delimiter
for j in 0..<d:
var c = encoded[j] # char
if not c.isBasic:
raise newException(PunyError, "Encoded contains a non-basic char")
result.add(c) # add the character
inc d
else:
d = 0 # set to first index
while (d < len(encoded)):
var oldi = i
var w = 1
var k = Base
while true:
if d == len(encoded):
raise newException(PunyError, "Bad input: " & encoded)
var c = encoded[d]; inc d
var digit = int(decodeDigit(c))
if digit > (high(int32) - i) div w:
raise newException(PunyError, "Too large a value: " & $digit)
i += digit * w
var t: int
if k <= bias:
t = TMin
elif k >= bias + TMax:
t = TMax
else:
t = k - bias
if digit < t:
break
w *= Base - t
k += Base
bias = adapt(i - oldi, runelen(result) + 1, oldi == 0)
if i div (runelen(result) + 1) > high(int32) - n:
raise newException(PunyError, "Value too large")
n += i div (runelen(result) + 1)
i = i mod (runelen(result) + 1)
insert(result, $Rune(n), i)
inc i
when isMainModule:
assert(decode(encode("", "bücher")) == "bücher")
assert(decode(encode("münchen")) == "münchen")
assert encode("xn--", "münchen") == "xn--mnchen-3ya"

128
lib/pure/random.nim Normal file
View File

@@ -0,0 +1,128 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2016 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Nim's standard random number generator. Based on the ``xoroshiro128+`` (xor/rotate/shift/rotate) library.
## * More information: http://xoroshiro.di.unimi.it/
## * C implementation: http://xoroshiro.di.unimi.it/xoroshiro128plus.c
##
include "system/inclrtl"
{.push debugger:off.}
# XXX Expose RandomGenState
when defined(JS):
type ui = uint32
else:
type ui = uint64
type
RandomGenState = object
a0, a1: ui
when defined(JS):
var state = RandomGenState(
a0: 0x69B4C98Cu32,
a1: 0xFED1DD30u32) # global for backwards compatibility
else:
# racy for multi-threading but good enough for now:
var state = RandomGenState(
a0: 0x69B4C98CB8530805u64,
a1: 0xFED1DD3004688D67CAu64) # global for backwards compatibility
proc rotl(x, k: ui): ui =
result = (x shl k) or (x shr (ui(64) - k))
proc next(s: var RandomGenState): uint64 =
let s0 = s.a0
var s1 = s.a1
result = s0 + s1
s1 = s1 xor s0
s.a0 = rotl(s0, 55) xor s1 xor (s1 shl 14) # a, b
s.a1 = rotl(s1, 36) # c
proc skipRandomNumbers(s: var RandomGenState) =
## This is the jump function for the generator. It is equivalent
## to 2^64 calls to next(); it can be used to generate 2^64
## non-overlapping subsequences for parallel computations.
when defined(JS):
const helper = [0xbeac0467u32, 0xd86b048bu32]
else:
const helper = [0xbeac0467eba5facbu64, 0xd86b048b86aa9922u64]
var
s0 = ui 0
s1 = ui 0
for i in 0..high(helper):
for b in 0..< 64:
if (helper[i] and (ui(1) shl ui(b))) != 0:
s0 = s0 xor s.a0
s1 = s1 xor s.a1
discard next(s)
s.a0 = s0
s.a1 = s1
proc random*(max: int): int {.benign.} =
## Returns a random number in the range 0..max-1. The sequence of
## random number is always the same, unless `randomize` is called
## which initializes the random number generator with a "random"
## number, i.e. a tickcount.
result = int(next(state) mod uint64(max))
proc random*(max: float): float {.benign.} =
## Returns a random number in the range 0..<max. The sequence of
## random number is always the same, unless `randomize` is called
## which initializes the random number generator with a "random"
## number, i.e. a tickcount.
let x = next(state)
when defined(JS):
result = (float(x) / float(high(uint32))) * max
else:
let u = (0x3FFu64 shl 52u64) or (x shr 12u64)
result = (cast[float](u) - 1.0) * max
proc random*[T](x: Slice[T]): T =
## For a slice `a .. b` returns a value in the range `a .. b-1`.
result = random(x.b - x.a) + x.a
proc random*[T](a: openArray[T]): T =
## returns a random element from the openarray `a`.
result = a[random(a.low..a.len)]
proc randomize*(seed: int) {.benign.} =
## Initializes the random number generator with a specific seed.
state.a0 = ui(seed shr 16)
state.a1 = ui(seed and 0xffff)
when not defined(nimscript):
import times
proc randomize*() {.benign.} =
## Initializes the random number generator with a "random"
## number, i.e. a tickcount. Note: Does not work for NimScript.
when defined(JS):
proc getMil(t: Time): int {.importcpp: "getTime", nodecl.}
randomize(getMil times.getTime())
else:
randomize(int times.getTime())
{.pop.}
when isMainModule:
proc main =
var occur: array[1000, int]
var x = 8234
for i in 0..100_000:
x = random(len(occur)) # myrand(x)
inc occur[x]
for i, oc in occur:
if oc < 69:
doAssert false, "too few occurances of " & $i
elif oc > 130:
doAssert false, "too many occurances of " & $i
main()

View File

@@ -41,26 +41,26 @@ proc toRational*[T:SomeInteger](x: T): Rational[T] =
proc toRationalSub(x: float, n: int): Rational[int] =
var
a = 0
b, c, d = 1
a = 0'i64
b, c, d = 1'i64
result = 0 // 1 # rational 0
while b <= n and d <= n:
let ac = (a+c)
let bd = (b+d)
# scale by 1000 so not overflow for high precision
let mediant = (ac/1000) / (bd/1000)
let mediant = (ac.float/1000) / (bd.float/1000)
if x == mediant:
if bd <= n:
result.num = ac
result.den = bd
result.num = ac.int
result.den = bd.int
return result
elif d > b:
result.num = c
result.den = d
result.num = c.int
result.den = d.int
return result
else:
result.num = a
result.den = b
result.num = a.int
result.den = b.int
return result
elif x > mediant:
a = ac
@@ -69,8 +69,8 @@ proc toRationalSub(x: float, n: int): Rational[int] =
c = ac
d = bd
if (b > n):
return initRational(c, d)
return initRational(a, b)
return initRational(c.int, d.int)
return initRational(a.int, b.int)
proc toRational*(x: float, n: int = high(int)): Rational[int] =
## Calculate the best rational numerator and denominator

View File

@@ -132,11 +132,12 @@ elif defined(linux):
s.fds[fd].events = events
proc unregister*(s: var Selector, fd: SocketHandle) =
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fd, nil) != 0:
let err = osLastError()
if err.cint notin {ENOENT, EBADF}:
# TODO: Why do we sometimes get an EBADF? Is this normal?
raiseOSError(err)
if s.fds[fd].events != {}:
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fd, nil) != 0:
let err = osLastError()
if err.cint notin {ENOENT, EBADF}:
# TODO: Why do we sometimes get an EBADF? Is this normal?
raiseOSError(err)
s.fds.del(fd)
proc close*(s: var Selector) =

View File

@@ -20,9 +20,9 @@
## var msg = createMessage("Hello from Nim's SMTP",
## "Hello!.\n Is this awesome or what?",
## @["foo@gmail.com"])
## var smtp = connect("smtp.gmail.com", 465, true, true)
## smtp.auth("username", "password")
## smtp.sendmail("username@gmail.com", @["foo@gmail.com"], $msg)
## var smtpConn = connect("smtp.gmail.com", Port 465, true, true)
## smtpConn.auth("username", "password")
## smtpConn.sendmail("username@gmail.com", @["foo@gmail.com"], $msg)
##
##
## For SSL support this module relies on OpenSSL. If you want to
@@ -31,6 +31,8 @@
import net, strutils, strtabs, base64, os
import asyncnet, asyncdispatch
export Port
type
Smtp* = object
sock: Socket
@@ -258,8 +260,8 @@ when not defined(testing) and isMainModule:
# "Hello, my name is dom96.\n What\'s yours?", @["dominik@localhost"])
#echo(msg)
#var smtp = connect("localhost", 25, False, True)
#smtp.sendmail("root@localhost", @["dominik@localhost"], $msg)
#var smtpConn = connect("localhost", Port 25, false, true)
#smtpConn.sendmail("root@localhost", @["dominik@localhost"], $msg)
#echo(decode("a17sm3701420wbe.12"))
proc main() {.async.} =

View File

@@ -306,67 +306,67 @@ proc peekLine*(s: Stream): TaintedString =
defer: setPosition(s, pos)
result = readLine(s)
type
StringStream* = ref StringStreamObj ## a stream that encapsulates a string
StringStreamObj* = object of StreamObj
data*: string
pos: int
{.deprecated: [PStringStream: StringStream, TStringStream: StringStreamObj].}
proc ssAtEnd(s: Stream): bool =
var s = StringStream(s)
return s.pos >= s.data.len
proc ssSetPosition(s: Stream, pos: int) =
var s = StringStream(s)
s.pos = clamp(pos, 0, s.data.len)
proc ssGetPosition(s: Stream): int =
var s = StringStream(s)
return s.pos
proc ssReadData(s: Stream, buffer: pointer, bufLen: int): int =
var s = StringStream(s)
result = min(bufLen, s.data.len - s.pos)
if result > 0:
copyMem(buffer, addr(s.data[s.pos]), result)
inc(s.pos, result)
proc ssPeekData(s: Stream, buffer: pointer, bufLen: int): int =
var s = StringStream(s)
result = min(bufLen, s.data.len - s.pos)
if result > 0:
copyMem(buffer, addr(s.data[s.pos]), result)
proc ssWriteData(s: Stream, buffer: pointer, bufLen: int) =
var s = StringStream(s)
if bufLen <= 0:
return
if s.pos + bufLen > s.data.len:
setLen(s.data, s.pos + bufLen)
copyMem(addr(s.data[s.pos]), buffer, bufLen)
inc(s.pos, bufLen)
proc ssClose(s: Stream) =
var s = StringStream(s)
s.data = nil
proc newStringStream*(s: string = ""): StringStream =
## creates a new stream from the string `s`.
new(result)
result.data = s
result.pos = 0
result.closeImpl = ssClose
result.atEndImpl = ssAtEnd
result.setPositionImpl = ssSetPosition
result.getPositionImpl = ssGetPosition
result.readDataImpl = ssReadData
result.peekDataImpl = ssPeekData
result.writeDataImpl = ssWriteData
when not defined(js):
type
StringStream* = ref StringStreamObj ## a stream that encapsulates a string
StringStreamObj* = object of StreamObj
data*: string
pos: int
{.deprecated: [PStringStream: StringStream, TStringStream: StringStreamObj].}
proc ssAtEnd(s: Stream): bool =
var s = StringStream(s)
return s.pos >= s.data.len
proc ssSetPosition(s: Stream, pos: int) =
var s = StringStream(s)
s.pos = clamp(pos, 0, s.data.len)
proc ssGetPosition(s: Stream): int =
var s = StringStream(s)
return s.pos
proc ssReadData(s: Stream, buffer: pointer, bufLen: int): int =
var s = StringStream(s)
result = min(bufLen, s.data.len - s.pos)
if result > 0:
copyMem(buffer, addr(s.data[s.pos]), result)
inc(s.pos, result)
proc ssPeekData(s: Stream, buffer: pointer, bufLen: int): int =
var s = StringStream(s)
result = min(bufLen, s.data.len - s.pos)
if result > 0:
copyMem(buffer, addr(s.data[s.pos]), result)
proc ssWriteData(s: Stream, buffer: pointer, bufLen: int) =
var s = StringStream(s)
if bufLen <= 0:
return
if s.pos + bufLen > s.data.len:
setLen(s.data, s.pos + bufLen)
copyMem(addr(s.data[s.pos]), buffer, bufLen)
inc(s.pos, bufLen)
proc ssClose(s: Stream) =
var s = StringStream(s)
s.data = nil
proc newStringStream*(s: string = ""): StringStream =
## creates a new stream from the string `s`.
new(result)
result.data = s
result.pos = 0
result.closeImpl = ssClose
result.atEndImpl = ssAtEnd
result.setPositionImpl = ssSetPosition
result.getPositionImpl = ssGetPosition
result.readDataImpl = ssReadData
result.peekDataImpl = ssPeekData
result.writeDataImpl = ssWriteData
type
FileStream* = ref FileStreamObj ## a stream that encapsulates a `File`
FileStreamObj* = object of Stream

83
lib/pure/strmisc.nim Normal file
View File

@@ -0,0 +1,83 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2016 Joey Payne
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module contains various string utility routines that are uncommonly
## used in comparison to `strutils <strutils.html>`_.
import strutils
{.deadCodeElim: on.}
proc expandTabs*(s: string, tabSize: int = 8): string {.noSideEffect,
procvar.} =
## Expand tab characters in `s` by `tabSize` spaces
result = newStringOfCap(s.len + s.len shr 2)
var pos = 0
template addSpaces(n) =
for j in 0 ..< n:
result.add(' ')
pos += 1
for i in 0 ..< len(s):
let c = s[i]
if c == '\t':
let
denominator = if tabSize > 0: tabSize else: 1
numSpaces = tabSize - pos mod denominator
addSpaces(numSpaces)
else:
result.add(c)
pos += 1
if c == '\l':
pos = 0
proc partition*(s: string, sep: string,
right: bool = false): (string, string, string)
{.noSideEffect, procvar.} =
## Split the string at the first or last occurrence of `sep` into a 3-tuple
##
## Returns a 3 string tuple of (beforeSep, `sep`, afterSep) or
## (`s`, "", "") if `sep` is not found and `right` is false or
## ("", "", `s`) if `sep` is not found and `right` is true
let position = if right: s.rfind(sep) else: s.find(sep)
if position != -1:
return (s[0 ..< position], sep, s[position + sep.len ..< s.len])
return if right: ("", "", s) else: (s, "", "")
proc rpartition*(s: string, sep: string): (string, string, string)
{.noSideEffect, procvar.} =
## Split the string at the last occurrence of `sep` into a 3-tuple
##
## Returns a 3 string tuple of (beforeSep, `sep`, afterSep) or
## ("", "", `s`) if `sep` is not found
return partition(s, sep, right = true)
when isMainModule:
doAssert expandTabs("\t", 4) == " "
doAssert expandTabs("\tfoo\t", 4) == " foo "
doAssert expandTabs("\tfoo\tbar", 4) == " foo bar"
doAssert expandTabs("\tfoo\tbar\t", 4) == " foo bar "
doAssert expandTabs("", 4) == ""
doAssert expandTabs("", 0) == ""
doAssert expandTabs("\t\t\t", 0) == ""
doAssert partition("foo:bar", ":") == ("foo", ":", "bar")
doAssert partition("foobarbar", "bar") == ("foo", "bar", "bar")
doAssert partition("foobarbar", "bank") == ("foobarbar", "", "")
doAssert partition("foobarbar", "foo") == ("", "foo", "barbar")
doAssert partition("foofoobar", "bar") == ("foofoo", "bar", "")
doAssert rpartition("foo:bar", ":") == ("foo", ":", "bar")
doAssert rpartition("foobarbar", "bar") == ("foobar", "bar", "")
doAssert rpartition("foobarbar", "bank") == ("", "", "foobarbar")
doAssert rpartition("foobarbar", "foo") == ("", "foo", "barbar")
doAssert rpartition("foofoobar", "bar") == ("foofoo", "bar", "")

522
lib/pure/strscans.nim Normal file
View File

@@ -0,0 +1,522 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2016 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
##[
This module contains a `scanf`:idx: macro that can be used for extracting
substrings from an input string. This is often easier than regular expressions.
Some examples as an apetizer:
.. code-block:: nim
# check if input string matches a triple of integers:
const input = "(1,2,4)"
var x, y, z: int
if scanf(input, "($i,$i,$i)", x, y, z):
echo "matches and x is ", x, " y is ", y, " z is ", z
# check if input string matches an ISO date followed by an identifier followed
# by whitespace and a floating point number:
var year, month, day: int
var identifier: string
var myfloat: float
if scanf(input, "$i-$i-$i $w$s$f", year, month, day, identifier, myfloat):
echo "yes, we have a match!"
As can be seen from the examples, strings are matched verbatim except for
substrings starting with ``$``. These constructions are available:
================= ========================================================
``$i`` Matches an integer. This uses ``parseutils.parseInt``.
``$f`` Matches a floating pointer number. Uses ``parseFloat``.
``$w`` Matches an ASCII identifier: ``[A-Z-a-z_][A-Za-z_0-9]*``.
``$s`` Skips optional whitespace.
``$$`` Matches a single dollar sign.
``$.`` Matches if the end of the input string has been reached.
``$*`` Matches until the token following the ``$*`` was found.
The match is allowed to be of 0 length.
``$+`` Matches until the token following the ``$+`` was found.
The match must consist of at least one char.
``${foo}`` User defined matcher. Uses the proc ``foo`` to perform
the match. See below for more details.
``$[foo]`` Call user defined proc ``foo`` to **skip** some optional
parts in the input string. See below for more details.
================= ========================================================
Even though ``$*`` and ``$+`` look similar to the regular expressions ``.*``
and ``.+`` they work quite differently, there is no non-deterministic
state machine involved and the matches are non-greedy. ``[$*]``
matches ``[xyz]`` via ``parseutils.parseUntil``.
Furthermore no backtracking is performed, if parsing fails after a value
has already been bound to a matched subexpression this value is not restored
to its original value. This rarely causes problems in practice and if it does
for you, it's easy enough to bind to a temporary variable first.
Startswith vs full match
========================
``scanf`` returns true if the input string **starts with** the specified
pattern. If instead it should only return true if theres is also nothing
left in the input, append ``$.`` to your pattern.
User definable matchers
=======================
One very nice advantage over regular expressions is that ``scanf`` is
extensible with ordinary Nim procs. The proc is either enclosed in ``${}``
or in ``$[]``. ``${}`` matches and binds the result
to a variable (that was passed to the ``scanf`` macro) while ``$[]`` merely
optional tokens.
In this example, we define a helper proc ``skipSep`` that skips some separators
which we then use in our scanf pattern to help us in the matching process:
.. code-block:: nim
proc someSep(input: string; start: int; seps: set[char] = {':','-','.'}): int =
# Note: The parameters and return value must match to what ``scanf`` requires
result = 0
while input[start+result] in seps: inc result
if scanf(input, "$w${someSep}$w", key, value):
...
It also possible to pass arguments to a user definable matcher:
.. code-block:: nim
proc ndigits(input: string; start: int; intVal: var int; n: int): int =
# matches exactly ``n`` digits. Matchers need to return 0 if nothing
# matched or otherwise the number of processed chars.
var x = 0
var i = 0
while i < n and i+start < input.len and input[i+start] in {'0'..'9'}:
x = x * 10 + input[i+start].ord - '0'.ord
inc i
# only overwrite if we had a match
if i == n:
result = n
intVal = x
# match an ISO date extracting year, month, day at the same time.
# Also ensure the input ends after the ISO date:
var year, month, day: int
if scanf("2013-01-03", "${ndigits(4)}-${ndigits(2)}-${ndigits(2)}$.", year, month, day):
...
]##
import macros, parseutils
proc conditionsToIfChain(n, idx, res: NimNode; start: int): NimNode =
assert n.kind == nnkStmtList
if start >= n.len: return newAssignment(res, newLit true)
var ifs: NimNode = nil
if n[start+1].kind == nnkEmpty:
ifs = conditionsToIfChain(n, idx, res, start+3)
else:
ifs = newIfStmt((n[start+1],
newTree(nnkStmtList, newCall(bindSym"inc", idx, n[start+2]),
conditionsToIfChain(n, idx, res, start+3))))
result = newTree(nnkStmtList, n[start], ifs)
proc notZero(x: NimNode): NimNode = newCall(bindSym"!=", x, newLit 0)
proc buildUserCall(x: string; args: varargs[NimNode]): NimNode =
let y = parseExpr(x)
result = newTree(nnkCall)
if y.kind in nnkCallKinds: result.add y[0]
else: result.add y
for a in args: result.add a
if y.kind in nnkCallKinds:
for i in 1..<y.len: result.add y[i]
macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): bool =
## See top level documentation of his module of how ``scanf`` works.
template matchBind(parser) {.dirty.} =
var resLen = genSym(nskLet, "resLen")
conds.add newLetStmt(resLen, newCall(bindSym(parser), input, results[i], idx))
conds.add resLen.notZero
conds.add resLen
var i = 0
var p = 0
var idx = genSym(nskVar, "idx")
var res = genSym(nskVar, "res")
result = newTree(nnkStmtListExpr, newVarStmt(idx, newLit 0), newVarStmt(res, newLit false))
var conds = newTree(nnkStmtList)
var fullMatch = false
while p < pattern.len:
if pattern[p] == '$':
inc p
case pattern[p]
of '$':
var resLen = genSym(nskLet, "resLen")
conds.add newLetStmt(resLen, newCall(bindSym"skip", input, newLit($pattern[p]), idx))
conds.add resLen.notZero
conds.add resLen
of 'w':
if i < results.len or getType(results[i]).typeKind != ntyString:
matchBind "parseIdent"
else:
error("no string var given for $w")
inc i
of 'i':
if i < results.len or getType(results[i]).typeKind != ntyInt:
matchBind "parseInt"
else:
error("no int var given for $d")
inc i
of 'f':
if i < results.len or getType(results[i]).typeKind != ntyFloat:
matchBind "parseFloat"
else:
error("no float var given for $f")
inc i
of 's':
conds.add newCall(bindSym"inc", idx, newCall(bindSym"skipWhitespace", input, idx))
conds.add newEmptyNode()
conds.add newEmptyNode()
of '.':
if p == pattern.len-1:
fullMatch = true
else:
error("invalid format string")
of '*', '+':
if i < results.len or getType(results[i]).typeKind != ntyString:
var min = ord(pattern[p] == '+')
var q=p+1
var token = ""
while q < pattern.len and pattern[q] != '$':
token.add pattern[q]
inc q
var resLen = genSym(nskLet, "resLen")
conds.add newLetStmt(resLen, newCall(bindSym"parseUntil", input, results[i], newLit(token), idx))
conds.add newCall(bindSym"!=", resLen, newLit min)
conds.add resLen
else:
error("no string var given for $" & pattern[p])
inc i
of '{':
inc p
var nesting = 0
let start = p
while true:
case pattern[p]
of '{': inc nesting
of '}':
if nesting == 0: break
dec nesting
of '\0': error("expected closing '}'")
else: discard
inc p
let expr = pattern.substr(start, p-1)
if i < results.len:
var resLen = genSym(nskLet, "resLen")
conds.add newLetStmt(resLen, buildUserCall(expr, input, results[i], idx))
conds.add newCall(bindSym"!=", resLen, newLit 0)
conds.add resLen
else:
error("no var given for $" & expr)
inc i
of '[':
inc p
var nesting = 0
let start = p
while true:
case pattern[p]
of '[': inc nesting
of ']':
if nesting == 0: break
dec nesting
of '\0': error("expected closing ']'")
else: discard
inc p
let expr = pattern.substr(start, p-1)
conds.add newCall(bindSym"inc", idx, buildUserCall(expr, input, idx))
conds.add newEmptyNode()
conds.add newEmptyNode()
else: error("invalid format string")
inc p
else:
var token = ""
while p < pattern.len and pattern[p] != '$':
token.add pattern[p]
inc p
var resLen = genSym(nskLet, "resLen")
conds.add newLetStmt(resLen, newCall(bindSym"skip", input, newLit(token), idx))
conds.add resLen.notZero
conds.add resLen
result.add conditionsToIfChain(conds, idx, res, 0)
if fullMatch:
result.add newCall(bindSym"and", res,
newCall(bindSym">=", idx, newCall(bindSym"len", input)))
else:
result.add res
template atom*(input: string; idx: int; c: char): bool =
## Used in scanp for the matching of atoms (usually chars).
input[idx] == c
template atom*(input: string; idx: int; s: set[char]): bool =
input[idx] in s
#template prepare*(input: string): int = 0
template success*(x: int): bool = x != 0
template nxt*(input: string; idx, step: int = 1) = inc(idx, step)
macro scanp*(input, idx: typed; pattern: varargs[untyped]): bool =
## See top level documentation of his module of how ``scanp`` works.
type StmtTriple = tuple[init, cond, action: NimNode]
template interf(x): untyped = bindSym(x, brForceOpen)
proc toIfChain(n: seq[StmtTriple]; idx, res: NimNode; start: int): NimNode =
if start >= n.len: return newAssignment(res, newLit true)
var ifs: NimNode = nil
if n[start].cond.kind == nnkEmpty:
ifs = toIfChain(n, idx, res, start+1)
else:
ifs = newIfStmt((n[start].cond,
newTree(nnkStmtList, n[start].action,
toIfChain(n, idx, res, start+1))))
result = newTree(nnkStmtList, n[start].init, ifs)
proc attach(x, attached: NimNode): NimNode =
if attached == nil: x
else: newStmtList(attached, x)
proc placeholder(n, x, j: NimNode): NimNode =
if n.kind == nnkPrefix and n[0].eqIdent("$"):
let n1 = n[1]
if n1.eqIdent"_" or n1.eqIdent"current":
result = newTree(nnkBracketExpr, x, j)
elif n1.eqIdent"input":
result = x
elif n1.eqIdent"i" or n1.eqIdent"index":
result = j
else:
error("unknown pattern " & repr(n))
else:
result = copyNimNode(n)
for i in 0 ..< n.len:
result.add placeholder(n[i], x, j)
proc atm(it, input, idx, attached: NimNode): StmtTriple =
template `!!`(x): untyped = attach(x, attached)
case it.kind
of nnkIdent:
var resLen = genSym(nskLet, "resLen")
result = (newLetStmt(resLen, newCall(it, input, idx)),
newCall(interf"success", resLen),
!!newCall(interf"nxt", input, idx, resLen))
of nnkCallKinds:
# *{'A'..'Z'} !! s.add(!_)
template buildWhile(init, cond, action): untyped =
while true:
init
if not cond: break
action
# (x) a # bind action a to (x)
if it[0].kind == nnkPar and it.len == 2:
result = atm(it[0], input, idx, placeholder(it[1], input, idx))
elif it.kind == nnkInfix and it[0].eqIdent"->":
# bind matching to some action:
result = atm(it[1], input, idx, placeholder(it[2], input, idx))
elif it.kind == nnkInfix and it[0].eqIdent"as":
let cond = if it[1].kind in nnkCallKinds: placeholder(it[1], input, idx)
else: newCall(it[1], input, idx)
result = (newLetStmt(it[2], cond),
newCall(interf"success", it[2]),
!!newCall(interf"nxt", input, idx, it[2]))
elif it.kind == nnkPrefix and it[0].eqIdent"*":
let (init, cond, action) = atm(it[1], input, idx, attached)
result = (getAst(buildWhile(init, cond, action)),
newEmptyNode(), newEmptyNode())
elif it.kind == nnkPrefix and it[0].eqIdent"+":
# x+ is the same as xx*
result = atm(newTree(nnkPar, it[1], newTree(nnkPrefix, ident"*", it[1])),
input, idx, attached)
elif it.kind == nnkPrefix and it[0].eqIdent"?":
# optional.
let (init, cond, action) = atm(it[1], input, idx, attached)
if cond.kind == nnkEmpty:
error("'?' operator applied to a non-condition")
else:
result = (newTree(nnkStmtList, init, newIfStmt((cond, action))),
newEmptyNode(), newEmptyNode())
elif it.kind == nnkPrefix and it[0].eqIdent"~":
# not operator
let (init, cond, action) = atm(it[1], input, idx, attached)
if cond.kind == nnkEmpty:
error("'~' operator applied to a non-condition")
else:
result = (init, newCall(bindSym"not", cond), action)
elif it.kind == nnkInfix and it[0].eqIdent"|":
let a = atm(it[1], input, idx, attached)
let b = atm(it[2], input, idx, attached)
if a.cond.kind == nnkEmpty or b.cond.kind == nnkEmpty:
error("'|' operator applied to a non-condition")
else:
result = (newStmtList(a.init,
newIfStmt((a.cond, a.action), (newTree(nnkStmtListExpr, b.init, b.cond), b.action))),
newEmptyNode(), newEmptyNode())
elif it.kind == nnkInfix and it[0].eqIdent"^*":
# a ^* b is rewritten to: (a *(b a))?
#exprList = expr ^+ comma
template tmp(a, b): untyped = ?(a, *(b, a))
result = atm(getAst(tmp(it[1], it[2])), input, idx, attached)
elif it.kind == nnkInfix and it[0].eqIdent"^+":
# a ^* b is rewritten to: (a +(b a))?
template tmp(a, b): untyped = (a, *(b, a))
result = atm(getAst(tmp(it[1], it[2])), input, idx, attached)
elif it.kind == nnkCommand and it.len == 2 and it[0].eqIdent"pred":
# enforce that the wrapped call is interpreted as a predicate, not a non-terminal:
result = (newEmptyNode(), placeholder(it[1], input, idx), newEmptyNode())
else:
var resLen = genSym(nskLet, "resLen")
result = (newLetStmt(resLen, placeholder(it, input, idx)),
newCall(interf"success", resLen), !!newCall(interf"nxt", input, idx, resLen))
of nnkStrLit..nnkTripleStrLit:
var resLen = genSym(nskLet, "resLen")
result = (newLetStmt(resLen, newCall(interf"skip", input, it, idx)),
newCall(interf"success", resLen), !!newCall(interf"nxt", input, idx, resLen))
of nnkCurly, nnkAccQuoted, nnkCharLit:
result = (newEmptyNode(), newCall(interf"atom", input, idx, it), !!newCall(interf"nxt", input, idx))
of nnkCurlyExpr:
if it.len == 3 and it[1].kind == nnkIntLit and it[2].kind == nnkIntLit:
var h = newTree(nnkPar, it[0])
for count in 2..it[1].intVal: h.add(it[0])
for count in it[1].intVal .. it[2].intVal-1: h.add(newTree(nnkPrefix, ident"?", it[0]))
result = atm(h, input, idx, attached)
elif it.len == 2 and it[1].kind == nnkIntLit:
var h = newTree(nnkPar, it[0])
for count in 2..it[1].intVal: h.add(it[0])
result = atm(h, input, idx, attached)
else:
error("invalid pattern")
of nnkPar:
if it.len == 1:
result = atm(it[0], input, idx, attached)
else:
# concatenation:
var conds: seq[StmtTriple] = @[]
for x in it: conds.add atm(x, input, idx, attached)
var res = genSym(nskVar, "res")
result = (newStmtList(newVarStmt(res, newLit false),
toIfChain(conds, idx, res, 0)), res, newEmptyNode())
else:
error("invalid pattern")
#var idx = genSym(nskVar, "idx")
var res = genSym(nskVar, "res")
result = newTree(nnkStmtListExpr, #newVarStmt(idx, newCall(interf"prepare", input)),
newVarStmt(res, newLit false))
var conds: seq[StmtTriple] = @[]
for it in pattern:
conds.add atm(it, input, idx, nil)
result.add toIfChain(conds, idx, res, 0)
result.add res
when defined(debugScanp):
echo repr result
when isMainModule:
proc twoDigits(input: string; x: var int; start: int): int =
if input[start] == '0' and input[start+1] == '0':
result = 2
x = 13
else:
result = 0
proc someSep(input: string; start: int; seps: set[char] = {';',',','-','.'}): int =
result = 0
while input[start+result] in seps: inc result
proc demangle(s: string; res: var string; start: int): int =
while s[result+start] in {'_', '@'}: inc result
res = ""
while result+start < s.len and s[result+start] > ' ' and s[result+start] != '_':
res.add s[result+start]
inc result
while result+start < s.len and s[result+start] > ' ':
inc result
proc parseGDB(resp: string): seq[string] =
const
digits = {'0'..'9'}
hexdigits = digits + {'a'..'f', 'A'..'F'}
whites = {' ', '\t', '\C', '\L'}
result = @[]
var idx = 0
while true:
var prc = ""
var info = ""
if scanp(resp, idx, *`whites`, '#', *`digits`, +`whites`, ?("0x", *`hexdigits`, " in "),
demangle($input, prc, $index), *`whites`, '(', * ~ ')', ')',
*`whites`, "at ", +(~{'\C', '\L', '\0'} -> info.add($_)) ):
result.add prc & " " & info
else:
break
var key, val: string
var intval: int
var floatval: float
doAssert scanf("abc:: xyz 89 33.25", "$w$s::$s$w$s$i $f", key, val, intval, floatVal)
doAssert key == "abc"
doAssert val == "xyz"
doAssert intval == 89
doAssert floatVal == 33.25
let xx = scanf("$abc", "$$$i", intval)
doAssert xx == false
let xx2 = scanf("$1234", "$$$i", intval)
doAssert xx2
let yy = scanf(";.--Breakpoint00 [output]", "$[someSep]Breakpoint${twoDigits}$[someSep({';','.','-'})] [$+]$.", intVal, key)
doAssert yy
doAssert key == "output"
doAssert intVal == 13
var ident = ""
var idx = 0
let zz = scanp("foobar x x x xWZ", idx, +{'a'..'z'} -> add(ident, $_), *(*{' ', '\t'}, "x"), ~'U', "Z")
doAssert zz
doAssert ident == "foobar"
const digits = {'0'..'9'}
var year = 0
var idx2 = 0
if scanp("201655-8-9", idx2, `digits`{4,6} -> (year = year * 10 + ord($_) - ord('0')), "-8", "-9"):
doAssert year == 201655
const gdbOut = """
#0 @foo_96013_1208911747@8 (x0=...)
at c:/users/anwender/projects/nim/temp.nim:11
#1 0x00417754 in tempInit000 () at c:/users/anwender/projects/nim/temp.nim:13
#2 0x0041768d in NimMainInner ()
at c:/users/anwender/projects/nim/lib/system.nim:2605
#3 0x004176b1 in NimMain ()
at c:/users/anwender/projects/nim/lib/system.nim:2613
#4 0x004176db in main (argc=1, args=0x712cc8, env=0x711ca8)
at c:/users/anwender/projects/nim/lib/system.nim:2620"""
const result = @["foo c:/users/anwender/projects/nim/temp.nim:11",
"tempInit000 c:/users/anwender/projects/nim/temp.nim:13",
"NimMainInner c:/users/anwender/projects/nim/lib/system.nim:2605",
"NimMain c:/users/anwender/projects/nim/lib/system.nim:2613",
"main c:/users/anwender/projects/nim/lib/system.nim:2620"]
doAssert parseGDB(gdbOut) == result

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@
## Nim support for `substitution expressions`:idx: (`subex`:idx:).
##
## .. include:: ../doc/subexes.txt
## .. include:: ../../doc/subexes.txt
##
{.push debugger:off .} # the user does not want to trace a part
@@ -390,11 +390,11 @@ when isMainModule:
doAssert(("type MyEnum* = enum\n $', '2i'\n '{..}" % ["fieldA",
"fieldB", "FiledClkad", "fieldD", "fieldE", "longishFieldName"]).replace(" \n", "\n") ==
strutils.unindent """
strutils.unindent("""
type MyEnum* = enum
fieldA, fieldB,
FiledClkad, fieldD,
fieldE, longishFieldName""")
fieldE, longishFieldName""", 6))
doAssert subex"$1($', '{2..})" % ["f", "a", "b", "c"] == "f(a, b, c)"
@@ -404,8 +404,8 @@ when isMainModule:
doAssert((subex("type\n Enum = enum\n $', '40c'\n '{..}") % [
"fieldNameA", "fieldNameB", "fieldNameC", "fieldNameD"]).replace(" \n", "\n") ==
strutils.unindent """
strutils.unindent("""
type
Enum = enum
fieldNameA, fieldNameB, fieldNameC,
fieldNameD""")
fieldNameD""", 6))

View File

@@ -384,7 +384,7 @@ proc setForegroundColor*(f: File, fg: ForegroundColor, bright=false) =
var old = getAttributes(h) and not 0x0007
if bright:
old = old or FOREGROUND_INTENSITY
const lookup: array [ForegroundColor, int] = [
const lookup: array[ForegroundColor, int] = [
0,
(FOREGROUND_RED),
(FOREGROUND_GREEN),
@@ -406,7 +406,7 @@ proc setBackgroundColor*(f: File, bg: BackgroundColor, bright=false) =
var old = getAttributes(h) and not 0x0070
if bright:
old = old or BACKGROUND_INTENSITY
const lookup: array [BackgroundColor, int] = [
const lookup: array[BackgroundColor, int] = [
0,
(BACKGROUND_RED),
(BACKGROUND_GREEN),
@@ -493,17 +493,21 @@ template styledEcho*(args: varargs[expr]): expr =
## Echoes styles arguments to stdout using ``styledWriteLine``.
callStyledEcho(args)
when defined(nimdoc):
proc getch*(): char =
## Read a single character from the terminal, blocking until it is entered.
## The character is not printed to the terminal. This is not available for
## Windows.
discard
elif not defined(windows):
proc getch*(): char =
## Read a single character from the terminal, blocking until it is entered.
## The character is not printed to the terminal. This is not available for
## Windows.
proc getch*(): char =
## Read a single character from the terminal, blocking until it is entered.
## The character is not printed to the terminal.
when defined(windows):
let fd = getStdHandle(STD_INPUT_HANDLE)
var keyEvent = KEY_EVENT_RECORD()
var numRead: cint
while true:
# Block until character is entered
doAssert(waitForSingleObject(fd, INFINITE) == WAIT_OBJECT_0)
doAssert(readConsoleInput(fd, addr(keyEvent), 1, addr(numRead)) != 0)
if numRead == 0 or keyEvent.eventType != 1 or keyEvent.bKeyDown == 0:
continue
return char(keyEvent.uChar)
else:
let fd = getFileHandle(stdin)
var oldMode: Termios
discard fd.tcgetattr(addr oldMode)

View File

@@ -64,8 +64,9 @@ when defined(posix) and not defined(JS):
proc posix_gettimeofday(tp: var Timeval, unused: pointer = nil) {.
importc: "gettimeofday", header: "<sys/time.h>".}
when not defined(freebsd) and not defined(netbsd) and not defined(openbsd):
var timezone {.importc, header: "<time.h>".}: int
var
timezone {.importc, header: "<time.h>".}: int
tzname {.importc, header: "<time.h>" .}: array[0..1, cstring]
# we also need tzset() to make sure that tzname is initialized
proc tzset() {.importc, header: "<time.h>".}
@@ -94,7 +95,8 @@ elif defined(windows):
elif defined(JS):
type
Time* {.importc.} = object
Time* = ref TimeObj
TimeObj {.importc.} = object
getDay: proc (): int {.tags: [], raises: [], benign.}
getFullYear: proc (): int {.tags: [], raises: [], benign.}
getHours: proc (): int {.tags: [], raises: [], benign.}
@@ -182,7 +184,16 @@ proc getGMTime*(t: Time): TimeInfo {.tags: [TimeEffect], raises: [], benign.}
## converts the calendar time `t` to broken-down time representation,
## expressed in Coordinated Universal Time (UTC).
proc timeInfoToTime*(timeInfo: TimeInfo): Time {.tags: [], benign.}
proc timeInfoToTime*(timeInfo: TimeInfo): Time {.tags: [], benign, deprecated.}
## converts a broken-down time structure to
## calendar time representation. The function ignores the specified
## contents of the structure members `weekday` and `yearday` and recomputes
## them from the other information in the broken-down time structure.
##
## **Warning:** This procedure is deprecated since version 0.14.0.
## Use ``toTime`` instead.
proc toTime*(timeInfo: TimeInfo): Time {.tags: [], benign.}
## converts a broken-down time structure to
## calendar time representation. The function ignores the specified
## contents of the structure members `weekday` and `yearday` and recomputes
@@ -356,16 +367,19 @@ proc `+`*(a: TimeInfo, interval: TimeInterval): TimeInfo =
##
## **Note:** This has been only briefly tested and it may not be
## very accurate.
let t = toSeconds(timeInfoToTime(a))
let t = toSeconds(toTime(a))
let secs = toSeconds(a, interval)
result = getLocalTime(fromSeconds(t + secs))
if a.tzname == "UTC":
result = getGMTime(fromSeconds(t + secs))
else:
result = getLocalTime(fromSeconds(t + secs))
proc `-`*(a: TimeInfo, interval: TimeInterval): TimeInfo =
## subtracts ``interval`` time from TimeInfo ``a``.
##
## **Note:** This has been only briefly tested, it is inaccurate especially
## when you subtract so much that you reach the Julian calendar.
let t = toSeconds(timeInfoToTime(a))
let t = toSeconds(toTime(a))
var intval: TimeInterval
intval.milliseconds = - interval.milliseconds
intval.seconds = - interval.seconds
@@ -375,7 +389,10 @@ proc `-`*(a: TimeInfo, interval: TimeInterval): TimeInfo =
intval.months = - interval.months
intval.years = - interval.years
let secs = toSeconds(a, intval)
result = getLocalTime(fromSeconds(t + secs))
if a.tzname == "UTC":
result = getGMTime(fromSeconds(t + secs))
else:
result = getLocalTime(fromSeconds(t + secs))
proc miliseconds*(t: TimeInterval): int {.deprecated.} = t.milliseconds
@@ -407,18 +424,32 @@ when not defined(JS):
when not defined(JS):
# C wrapper:
when defined(freebsd) or defined(netbsd) or defined(openbsd):
type
StructTM {.importc: "struct tm", final.} = object
second {.importc: "tm_sec".},
minute {.importc: "tm_min".},
hour {.importc: "tm_hour".},
monthday {.importc: "tm_mday".},
month {.importc: "tm_mon".},
year {.importc: "tm_year".},
weekday {.importc: "tm_wday".},
yearday {.importc: "tm_yday".},
isdst {.importc: "tm_isdst".}: cint
gmtoff {.importc: "tm_gmtoff".}: clong
else:
type
StructTM {.importc: "struct tm", final.} = object
second {.importc: "tm_sec".},
minute {.importc: "tm_min".},
hour {.importc: "tm_hour".},
monthday {.importc: "tm_mday".},
month {.importc: "tm_mon".},
year {.importc: "tm_year".},
weekday {.importc: "tm_wday".},
yearday {.importc: "tm_yday".},
isdst {.importc: "tm_isdst".}: cint
type
StructTM {.importc: "struct tm", final.} = object
second {.importc: "tm_sec".},
minute {.importc: "tm_min".},
hour {.importc: "tm_hour".},
monthday {.importc: "tm_mday".},
month {.importc: "tm_mon".},
year {.importc: "tm_year".},
weekday {.importc: "tm_wday".},
yearday {.importc: "tm_yday".},
isdst {.importc: "tm_isdst".}: cint
TimeInfoPtr = ptr StructTM
Clock {.importc: "clock_t".} = distinct int
@@ -446,30 +477,53 @@ when not defined(JS):
# our own procs on top of that:
proc tmToTimeInfo(tm: StructTM, local: bool): TimeInfo =
const
weekDays: array [0..6, WeekDay] = [
weekDays: array[0..6, WeekDay] = [
dSun, dMon, dTue, dWed, dThu, dFri, dSat]
TimeInfo(second: int(tm.second),
minute: int(tm.minute),
hour: int(tm.hour),
monthday: int(tm.monthday),
month: Month(tm.month),
year: tm.year + 1900'i32,
weekday: weekDays[int(tm.weekday)],
yearday: int(tm.yearday),
isDST: tm.isdst > 0,
tzname: if local:
if tm.isdst > 0:
getTzname().DST
when defined(freebsd) or defined(netbsd) or defined(openbsd):
TimeInfo(second: int(tm.second),
minute: int(tm.minute),
hour: int(tm.hour),
monthday: int(tm.monthday),
month: Month(tm.month),
year: tm.year + 1900'i32,
weekday: weekDays[int(tm.weekday)],
yearday: int(tm.yearday),
isDST: tm.isdst > 0,
tzname: if local:
if tm.isdst > 0:
getTzname().DST
else:
getTzname().nonDST
else:
getTzname().nonDST
else:
"UTC",
timezone: if local: getTimezone() else: 0
)
"UTC",
# BSD stores in `gmtoff` offset east of UTC in seconds,
# but posix systems using west of UTC in seconds
timezone: if local: -(tm.gmtoff) else: 0
)
else:
TimeInfo(second: int(tm.second),
minute: int(tm.minute),
hour: int(tm.hour),
monthday: int(tm.monthday),
month: Month(tm.month),
year: tm.year + 1900'i32,
weekday: weekDays[int(tm.weekday)],
yearday: int(tm.yearday),
isDST: tm.isdst > 0,
tzname: if local:
if tm.isdst > 0:
getTzname().DST
else:
getTzname().nonDST
else:
"UTC",
timezone: if local: getTimezone() else: 0
)
proc timeInfoToTM(t: TimeInfo): StructTM =
const
weekDays: array [WeekDay, int8] = [1'i8,2'i8,3'i8,4'i8,5'i8,6'i8,0'i8]
weekDays: array[WeekDay, int8] = [1'i8,2'i8,3'i8,4'i8,5'i8,6'i8,0'i8]
result.second = t.second
result.minute = t.minute
result.hour = t.hour
@@ -517,6 +571,11 @@ when not defined(JS):
# because the header of mktime is broken in my version of libc
return mktime(timeInfoToTM(cTimeInfo))
proc toTime(timeInfo: TimeInfo): Time =
var cTimeInfo = timeInfo # for C++ we have to make a copy,
# because the header of mktime is broken in my version of libc
return mktime(timeInfoToTM(cTimeInfo))
proc toStringTillNL(p: cstring): string =
result = ""
var i = 0
@@ -550,7 +609,14 @@ when not defined(JS):
return ($tzname[0], $tzname[1])
proc getTimezone(): int =
return timezone
when defined(freebsd) or defined(netbsd) or defined(openbsd):
var a = timec(nil)
let lt = localtime(addr(a))
# BSD stores in `gmtoff` offset east of UTC in seconds,
# but posix systems using west of UTC in seconds
return -(lt.gmtoff)
else:
return timezone
proc fromSeconds(since1970: float): Time = Time(since1970)
@@ -586,7 +652,7 @@ elif defined(JS):
return newDate()
const
weekDays: array [0..6, WeekDay] = [
weekDays: array[0..6, WeekDay] = [
dSun, dMon, dTue, dWed, dThu, dFri, dSat]
proc getLocalTime(t: Time): TimeInfo =
@@ -618,7 +684,16 @@ elif defined(JS):
result.setFullYear(timeInfo.year)
result.setDate(timeInfo.monthday)
proc `$`(timeInfo: TimeInfo): string = return $(timeInfoToTime(timeInfo))
proc toTime*(timeInfo: TimeInfo): Time =
result = internGetTime()
result.setSeconds(timeInfo.second)
result.setMinutes(timeInfo.minute)
result.setHours(timeInfo.hour)
result.setMonth(ord(timeInfo.month))
result.setFullYear(timeInfo.year)
result.setDate(timeInfo.monthday)
proc `$`(timeInfo: TimeInfo): string = return $(toTime(timeInfo))
proc `$`(time: Time): string = return $time.toLocaleString()
proc `-` (a, b: Time): int64 =
@@ -708,24 +783,24 @@ proc years*(y: int): TimeInterval {.inline.} =
proc `+=`*(t: var Time, ti: TimeInterval) =
## modifies `t` by adding the interval `ti`
t = timeInfoToTime(getLocalTime(t) + ti)
t = toTime(getLocalTime(t) + ti)
proc `+`*(t: Time, ti: TimeInterval): Time =
## adds the interval `ti` to Time `t`
## by converting to localTime, adding the interval, and converting back
##
## ``echo getTime() + 1.day``
result = timeInfoToTime(getLocalTime(t) + ti)
result = toTime(getLocalTime(t) + ti)
proc `-=`*(t: var Time, ti: TimeInterval) =
## modifies `t` by subtracting the interval `ti`
t = timeInfoToTime(getLocalTime(t) - ti)
t = toTime(getLocalTime(t) - ti)
proc `-`*(t: Time, ti: TimeInterval): Time =
## adds the interval `ti` to Time `t`
##
## ``echo getTime() - 1.day``
result = timeInfoToTime(getLocalTime(t) - ti)
result = toTime(getLocalTime(t) - ti)
proc formatToken(info: TimeInfo, token: string, buf: var string) =
## Helper of the format proc to parse individual tokens.
@@ -923,21 +998,14 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) =
info.monthday = value[j..j+1].parseInt()
j += 2
of "ddd":
case value[j..j+2].toLower()
of "sun":
info.weekday = dSun
of "mon":
info.weekday = dMon
of "tue":
info.weekday = dTue
of "wed":
info.weekday = dWed
of "thu":
info.weekday = dThu
of "fri":
info.weekday = dFri
of "sat":
info.weekday = dSat
case value[j..j+2].toLowerAscii()
of "sun": info.weekday = dSun
of "mon": info.weekday = dMon
of "tue": info.weekday = dTue
of "wed": info.weekday = dWed
of "thu": info.weekday = dThu
of "fri": info.weekday = dFri
of "sat": info.weekday = dSat
else:
raise newException(ValueError,
"Couldn't parse day of week (ddd), got: " & value[j..j+2])
@@ -991,31 +1059,19 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) =
j += 2
info.month = Month(month-1)
of "MMM":
case value[j..j+2].toLower():
of "jan":
info.month = mJan
of "feb":
info.month = mFeb
of "mar":
info.month = mMar
of "apr":
info.month = mApr
of "may":
info.month = mMay
of "jun":
info.month = mJun
of "jul":
info.month = mJul
of "aug":
info.month = mAug
of "sep":
info.month = mSep
of "oct":
info.month = mOct
of "nov":
info.month = mNov
of "dec":
info.month = mDec
case value[j..j+2].toLowerAscii():
of "jan": info.month = mJan
of "feb": info.month = mFeb
of "mar": info.month = mMar
of "apr": info.month = mApr
of "may": info.month = mMay
of "jun": info.month = mJun
of "jul": info.month = mJul
of "aug": info.month = mAug
of "sep": info.month = mSep
of "oct": info.month = mOct
of "nov": info.month = mNov
of "dec": info.month = mDec
else:
raise newException(ValueError,
"Couldn't parse month (MMM), got: " & value)
@@ -1112,7 +1168,7 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) =
"Couldn't parse timezone offset (zzz), got: " & value[j])
j += 6
of "ZZZ":
info.tzname = value[j..j+2].toUpper()
info.tzname = value[j..j+2].toUpperAscii()
j += 3
else:
# Ignore the token and move forward in the value string by the same length
@@ -1193,7 +1249,7 @@ proc parse*(value, layout: string): TimeInfo =
parseToken(info, token, value, j)
token = ""
# Reset weekday as it might not have been provided and the default may be wrong
info.weekday = getLocalTime(timeInfoToTime(info)).weekday
info.weekday = getLocalTime(toTime(info)).weekday
return info
# Leap year calculations are adapted from:
@@ -1204,7 +1260,7 @@ proc parse*(value, layout: string): TimeInfo =
proc countLeapYears*(yearSpan: int): int =
## Returns the number of leap years spanned by a given number of years.
##
## Note: for leap years, start date is assumed to be 1 AD.
## **Note:** For leap years, start date is assumed to be 1 AD.
## counts the number of leap years up to January 1st of a given year.
## Keep in mind that if specified year is a leap year, the leap day
## has not happened before January 1st of that year.
@@ -1239,13 +1295,14 @@ proc getDayOfWeek*(day, month, year: int): WeekDay =
y = year - a
m = month + (12*a) - 2
d = (day + y + (y div 4) - (y div 100) + (y div 400) + (31*m) div 12) mod 7
# The value of d is 0 for a Sunday, 1 for a Monday, 2 for a Tuesday, etc. so we must correct
# for the WeekDay type.
# The value of d is 0 for a Sunday, 1 for a Monday, 2 for a Tuesday, etc.
# so we must correct for the WeekDay type.
if d == 0: return dSun
result = (d-1).WeekDay
proc getDayOfWeekJulian*(day, month, year: int): WeekDay =
## Returns the day of the week enum from day, month and year, according to the Julian calendar.
## Returns the day of the week enum from day, month and year,
## according to the Julian calendar.
# Day & month start from one.
let
a = (14 - month) div 12
@@ -1254,8 +1311,11 @@ proc getDayOfWeekJulian*(day, month, year: int): WeekDay =
d = (5 + day + y + (y div 4) + (31*m) div 12) mod 7
result = d.WeekDay
proc timeToTimeInfo*(t: Time): TimeInfo =
proc timeToTimeInfo*(t: Time): TimeInfo {.deprecated.} =
## Converts a Time to TimeInfo.
##
## **Warning:** This procedure is deprecated since version 0.14.0.
## Use ``getLocalTime`` or ``getGMTime`` instead.
let
secs = t.toSeconds().int
daysSinceEpoch = secs div secondsInDay
@@ -1286,34 +1346,21 @@ proc timeToTimeInfo*(t: Time): TimeInfo =
s = daySeconds mod secondsInMin
result = TimeInfo(year: y, yearday: yd, month: m, monthday: md, weekday: wd, hour: h, minute: mi, second: s)
proc timeToTimeInterval*(t: Time): TimeInterval =
proc timeToTimeInterval*(t: Time): TimeInterval {.deprecated.} =
## Converts a Time to a TimeInterval.
let
secs = t.toSeconds().int
daysSinceEpoch = secs div secondsInDay
(yearsSinceEpoch, daysRemaining) = countYearsAndDays(daysSinceEpoch)
daySeconds = secs mod secondsInDay
result.years = yearsSinceEpoch + epochStartYear
var
mon = mJan
days = daysRemaining
daysInMonth = getDaysInMonth(mon, result.years)
# calculate month and day remainder
while days > daysInMonth and mon <= mDec:
days -= daysInMonth
mon.inc
daysInMonth = getDaysInMonth(mon, result.years)
result.months = mon.int + 1 # month is 1 indexed int
result.days = days
result.hours = daySeconds div secondsInHour + 1
result.minutes = (daySeconds mod secondsInHour) div secondsInMin
result.seconds = daySeconds mod secondsInMin
##
## **Warning:** This procedure is deprecated since version 0.14.0.
## Use ``toTimeInterval`` instead.
# Milliseconds not available from Time
var tInfo = t.getLocalTime()
initInterval(0, tInfo.second, tInfo.minute, tInfo.hour, tInfo.weekday.ord, tInfo.month.ord, tInfo.year)
proc toTimeInterval*(t: Time): TimeInterval =
## Converts a Time to a TimeInterval.
# Milliseconds not available from Time
var tInfo = t.getLocalTime()
initInterval(0, tInfo.second, tInfo.minute, tInfo.hour, tInfo.weekday.ord, tInfo.month.ord, tInfo.year)
when isMainModule:
# this is testing non-exported function

View File

@@ -14,7 +14,7 @@
include "system/inclrtl"
type
RuneImpl = int # underlying type of Rune
RuneImpl = int32 # underlying type of Rune
Rune* = distinct RuneImpl ## type that can hold any Unicode character
Rune16* = distinct int16 ## 16 bit Unicode character
@@ -135,45 +135,62 @@ proc runeAt*(s: string, i: Natural): Rune =
## Returns the unicode character in ``s`` at byte index ``i``
fastRuneAt(s, i, result, false)
proc toUTF8*(c: Rune): string {.rtl, extern: "nuc$1".} =
## Converts a rune into its UTF-8 representation
template fastToUTF8Copy*(c: Rune, s: var string, pos: int, doInc = true) =
## Copies UTF-8 representation of `c` into the preallocated string `s`
## starting at position `pos`. If `doInc == true`, `pos` is incremented
## by the number of bytes that have been processed.
##
## To be the most efficient, make sure `s` is preallocated
## with an additional amount equal to the byte length of
## `c`.
var i = RuneImpl(c)
if i <=% 127:
result = newString(1)
result[0] = chr(i)
s.setLen(pos+1)
s[pos+0] = chr(i)
when doInc: inc(pos)
elif i <=% 0x07FF:
result = newString(2)
result[0] = chr((i shr 6) or 0b110_00000)
result[1] = chr((i and ones(6)) or 0b10_0000_00)
s.setLen(pos+2)
s[pos+0] = chr((i shr 6) or 0b110_00000)
s[pos+1] = chr((i and ones(6)) or 0b10_0000_00)
when doInc: inc(pos, 2)
elif i <=% 0xFFFF:
result = newString(3)
result[0] = chr(i shr 12 or 0b1110_0000)
result[1] = chr(i shr 6 and ones(6) or 0b10_0000_00)
result[2] = chr(i and ones(6) or 0b10_0000_00)
s.setLen(pos+3)
s[pos+0] = chr(i shr 12 or 0b1110_0000)
s[pos+1] = chr(i shr 6 and ones(6) or 0b10_0000_00)
s[pos+2] = chr(i and ones(6) or 0b10_0000_00)
when doInc: inc(pos, 3)
elif i <=% 0x001FFFFF:
result = newString(4)
result[0] = chr(i shr 18 or 0b1111_0000)
result[1] = chr(i shr 12 and ones(6) or 0b10_0000_00)
result[2] = chr(i shr 6 and ones(6) or 0b10_0000_00)
result[3] = chr(i and ones(6) or 0b10_0000_00)
s.setLen(pos+4)
s[pos+0] = chr(i shr 18 or 0b1111_0000)
s[pos+1] = chr(i shr 12 and ones(6) or 0b10_0000_00)
s[pos+2] = chr(i shr 6 and ones(6) or 0b10_0000_00)
s[pos+3] = chr(i and ones(6) or 0b10_0000_00)
when doInc: inc(pos, 4)
elif i <=% 0x03FFFFFF:
result = newString(5)
result[0] = chr(i shr 24 or 0b111110_00)
result[1] = chr(i shr 18 and ones(6) or 0b10_0000_00)
result[2] = chr(i shr 12 and ones(6) or 0b10_0000_00)
result[3] = chr(i shr 6 and ones(6) or 0b10_0000_00)
result[4] = chr(i and ones(6) or 0b10_0000_00)
s.setLen(pos+5)
s[pos+0] = chr(i shr 24 or 0b111110_00)
s[pos+1] = chr(i shr 18 and ones(6) or 0b10_0000_00)
s[pos+2] = chr(i shr 12 and ones(6) or 0b10_0000_00)
s[pos+3] = chr(i shr 6 and ones(6) or 0b10_0000_00)
s[pos+4] = chr(i and ones(6) or 0b10_0000_00)
when doInc: inc(pos, 5)
elif i <=% 0x7FFFFFFF:
result = newString(6)
result[0] = chr(i shr 30 or 0b1111110_0)
result[1] = chr(i shr 24 and ones(6) or 0b10_0000_00)
result[2] = chr(i shr 18 and ones(6) or 0b10_0000_00)
result[3] = chr(i shr 12 and ones(6) or 0b10_0000_00)
result[4] = chr(i shr 6 and ones(6) or 0b10_0000_00)
result[5] = chr(i and ones(6) or 0b10_0000_00)
s.setLen(pos+6)
s[pos+0] = chr(i shr 30 or 0b1111110_0)
s[pos+1] = chr(i shr 24 and ones(6) or 0b10_0000_00)
s[pos+2] = chr(i shr 18 and ones(6) or 0b10_0000_00)
s[pos+3] = chr(i shr 12 and ones(6) or 0b10_0000_00)
s[pos+4] = chr(i shr 6 and ones(6) or 0b10_0000_00)
s[pos+5] = chr(i and ones(6) or 0b10_0000_00)
when doInc: inc(pos, 6)
else:
discard # error, exception?
proc toUTF8*(c: Rune): string {.rtl, extern: "nuc$1".} =
## Converts a rune into its UTF-8 representation
result = ""
fastToUTF8Copy(c, result, 0, false)
proc `$`*(rune: Rune): string =
## Converts a Rune to a string
rune.toUTF8
@@ -183,6 +200,105 @@ proc `$`*(runes: seq[Rune]): string =
result = ""
for rune in runes: result.add(rune.toUTF8)
proc runeOffset*(s: string, pos:Natural, start: Natural = 0): int =
## Returns the byte position of unicode character
## at position pos in s with an optional start byte position.
## returns the special value -1 if it runs out of the string
##
## Beware: This can lead to unoptimized code and slow execution!
## Most problems are solve more efficient by using an iterator
## or conversion to a seq of Rune.
var
i = 0
o = start
while i < pos:
o += runeLenAt(s, o)
if o >= s.len:
return -1
inc i
return o
proc runeAtPos*(s: string, pos: int): Rune =
## Returns the unicode character at position pos
##
## Beware: This can lead to unoptimized code and slow execution!
## Most problems are solve more efficient by using an iterator
## or conversion to a seq of Rune.
fastRuneAt(s, runeOffset(s, pos), result, false)
proc runeStrAtPos*(s: string, pos: Natural): string =
## Returns the unicode character at position pos as UTF8 String
##
## Beware: This can lead to unoptimized code and slow execution!
## Most problems are solve more efficient by using an iterator
## or conversion to a seq of Rune.
let o = runeOffset(s, pos)
s[o.. (o+runeLenAt(s, o)-1)]
proc runeReverseOffset*(s: string, rev:Positive): (int, int) =
## Returns a tuple with the the byte offset of the
## unicode character at position ``rev`` in s counting
## from the end (starting with 1) and the total
## number of runes in the string. Returns a negative value
## for offset if there are to few runes in the string to
## satisfy the request.
##
## Beware: This can lead to unoptimized code and slow execution!
## Most problems are solve more efficient by using an iterator
## or conversion to a seq of Rune.
var
a = rev.int
o = 0
x = 0
while o < s.len:
let r = runeLenAt(s, o)
o += r
if a < 0:
x += r
dec a
if a > 0:
return (-a, rev.int-a)
return (x, -a+rev.int)
proc runeSubStr*(s: string, pos:int, len:int = int.high): string =
## Returns the UTF-8 substring starting at codepoint pos
## with len codepoints. If pos or len is negativ they count from
## the end of the string. If len is not given it means the longest
## possible string.
##
## (Needs some examples)
if pos < 0:
let (o, rl) = runeReverseOffset(s, -pos)
if len >= rl:
result = s[o.. s.len-1]
elif len < 0:
let e = rl + len
if e < 0:
result = ""
else:
result = s[o.. runeOffset(s, e-(rl+pos) , o)-1]
else:
result = s[o.. runeOffset(s, len, o)-1]
else:
let o = runeOffset(s, pos)
if o < 0:
result = ""
elif len == int.high:
result = s[o.. s.len-1]
elif len < 0:
let (e, rl) = runeReverseOffset(s, -len)
discard rl
if e <= 0:
result = ""
else:
result = s[o.. e-1]
else:
var e = runeOffset(s, len, o)
if e < 0:
e = s.len
result = s[o.. e-1]
const
alphaRanges = [
0x00d8, 0x00f6, # -
@@ -1148,7 +1264,7 @@ const
0x01f1, 501, #
0x01f3, 499] #
proc binarySearch(c: RuneImpl, tab: openArray[RuneImpl], len, stride: int): int =
proc binarySearch(c: RuneImpl, tab: openArray[int], len, stride: int): int =
var n = len
var t = 0
while n > 1:
@@ -1253,8 +1369,210 @@ proc isCombining*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} =
(c >= 0x20d0 and c <= 0x20ff) or
(c >= 0xfe20 and c <= 0xfe2f))
template runeCheck(s, runeProc) =
## Common code for rune.isLower, rune.isUpper, etc
result = if len(s) == 0: false else: true
var
i = 0
rune: Rune
while i < len(s) and result:
fastRuneAt(s, i, rune, doInc=true)
result = runeProc(rune) and result
proc isUpper*(s: string): bool {.noSideEffect, procvar,
rtl, extern: "nuc$1Str".} =
## Returns true iff `s` contains all upper case unicode characters.
runeCheck(s, isUpper)
proc isLower*(s: string): bool {.noSideEffect, procvar,
rtl, extern: "nuc$1Str".} =
## Returns true iff `s` contains all lower case unicode characters.
runeCheck(s, isLower)
proc isAlpha*(s: string): bool {.noSideEffect, procvar,
rtl, extern: "nuc$1Str".} =
## Returns true iff `s` contains all alphabetic unicode characters.
runeCheck(s, isAlpha)
proc isSpace*(s: string): bool {.noSideEffect, procvar,
rtl, extern: "nuc$1Str".} =
## Returns true iff `s` contains all whitespace unicode characters.
runeCheck(s, isWhiteSpace)
template convertRune(s, runeProc) =
## Convert runes in `s` using `runeProc` as the converter.
result = newString(len(s))
var
i = 0
lastIndex = 0
rune: Rune
while i < len(s):
lastIndex = i
fastRuneAt(s, i, rune, doInc=true)
rune = runeProc(rune)
rune.fastToUTF8Copy(result, lastIndex)
proc toUpper*(s: string): string {.noSideEffect, procvar,
rtl, extern: "nuc$1Str".} =
## Converts `s` into upper-case unicode characters.
convertRune(s, toUpper)
proc toLower*(s: string): string {.noSideEffect, procvar,
rtl, extern: "nuc$1Str".} =
## Converts `s` into lower-case unicode characters.
convertRune(s, toLower)
proc swapCase*(s: string): string {.noSideEffect, procvar,
rtl, extern: "nuc$1".} =
## Swaps the case of unicode characters in `s`
##
## Returns a new string such that the cases of all unicode characters
## are swapped if possible
var
i = 0
lastIndex = 0
rune: Rune
result = newString(len(s))
while i < len(s):
lastIndex = i
fastRuneAt(s, i, rune)
if rune.isUpper():
rune = rune.toLower()
elif rune.isLower():
rune = rune.toUpper()
rune.fastToUTF8Copy(result, lastIndex)
proc capitalize*(s: string): string {.noSideEffect, procvar,
rtl, extern: "nuc$1".} =
## Converts the first character of `s` into an upper-case unicode character.
if len(s) == 0:
return s
var
rune: Rune
i = 0
fastRuneAt(s, i, rune, doInc=true)
result = $toUpper(rune) & substr(s, i)
proc translate*(s: string, replacements: proc(key: string): string): string {.
rtl, extern: "nuc$1".} =
## Translates words in a string using the `replacements` proc to substitute
## words inside `s` with their replacements
##
## `replacements` is any proc that takes a word and returns
## a new word to fill it's place.
# Allocate memory for the new string based on the old one.
# If the new string length is less than the old, no allocations
# will be needed. If the new string length is greater than the
# old, then maybe only one allocation is needed
result = newStringOfCap(s.len)
var
index = 0
lastIndex = 0
wordStart = 0
inWord = false
rune: Rune
while index < len(s):
lastIndex = index
fastRuneAt(s, index, rune)
let whiteSpace = rune.isWhiteSpace()
if whiteSpace and inWord:
# If we've reached the end of a word
let word = s[wordStart ..< lastIndex]
result.add(replacements(word))
result.add($rune)
inWord = false
elif not whiteSpace and not inWord:
# If we've hit a non space character and
# are not currently in a word, track
# the starting index of the word
inWord = true
wordStart = lastIndex
elif whiteSpace:
result.add($rune)
if wordStart < len(s) and inWord:
# Get the trailing word at the end
let word = s[wordStart .. ^1]
result.add(replacements(word))
proc title*(s: string): string {.noSideEffect, procvar,
rtl, extern: "nuc$1".} =
## Converts `s` to a unicode title.
##
## Returns a new string such that the first character
## in each word inside `s` is capitalized
var
i = 0
lastIndex = 0
rune: Rune
result = newString(len(s))
var firstRune = true
while i < len(s):
lastIndex = i
fastRuneAt(s, i, rune)
if not rune.isWhiteSpace() and firstRune:
rune = rune.toUpper()
firstRune = false
elif rune.isWhiteSpace():
firstRune = true
rune.fastToUTF8Copy(result, lastIndex)
proc isTitle*(s: string): bool {.noSideEffect, procvar,
rtl, extern: "nuc$1Str".}=
## Checks whether or not `s` is a unicode title.
##
## Returns true if the first character in each word inside `s`
## are upper case and there is at least one character in `s`.
if s.len() == 0:
return false
result = true
var
i = 0
rune: Rune
var firstRune = true
while i < len(s) and result:
fastRuneAt(s, i, rune, doInc=true)
if not rune.isWhiteSpace() and firstRune:
result = rune.isUpper() and result
firstRune = false
elif rune.isWhiteSpace():
firstRune = true
iterator runes*(s: string): Rune =
## Iterates over any unicode character of the string ``s``
## Iterates over any unicode character of the string ``s`` returning runes
var
i = 0
result: Rune
@@ -1262,6 +1580,14 @@ iterator runes*(s: string): Rune =
fastRuneAt(s, i, result, true)
yield result
iterator utf8*(s: string): string =
## Iterates over any unicode character of the string ``s`` returning utf8 values
var o = 0
while o < s.len:
let n = runeLenAt(s, o)
yield s[o.. (o+n-1)]
o += n
proc toRunes*(s: string): seq[Rune] =
## Obtains a sequence containing the Runes in ``s``
result = newSeq[Rune]()
@@ -1352,6 +1678,101 @@ when isMainModule:
compared = (someString == $someRunes)
doAssert compared == true
proc test_replacements(word: string): string =
case word
of "two":
return "2"
of "foo":
return "BAR"
of "βeta":
return "beta"
of "alpha":
return "αlpha"
else:
return "12345"
doAssert translate("two not alpha foo βeta", test_replacements) == "2 12345 αlpha BAR beta"
doAssert translate(" two not foo βeta ", test_replacements) == " 2 12345 BAR beta "
doAssert title("foo bar") == "Foo Bar"
doAssert title("αlpha βeta γamma") == "Αlpha Βeta Γamma"
doAssert title("") == ""
doAssert capitalize("βeta") == "Βeta"
doAssert capitalize("foo") == "Foo"
doAssert capitalize("") == ""
doAssert isTitle("Foo")
doAssert(not isTitle("Foo bar"))
doAssert(not isTitle("αlpha Βeta"))
doAssert(isTitle("Αlpha Βeta Γamma"))
doAssert(not isTitle("fFoo"))
doAssert swapCase("FooBar") == "fOObAR"
doAssert swapCase(" ") == " "
doAssert swapCase("Αlpha Βeta Γamma") == "αLPHA βETA γAMMA"
doAssert swapCase("a✓B") == "A✓b"
doAssert swapCase("") == ""
doAssert isAlpha("r")
doAssert isAlpha("α")
doAssert(not isAlpha("$"))
doAssert(not isAlpha(""))
doAssert isAlpha("Βeta")
doAssert isAlpha("Args")
doAssert(not isAlpha("$Foo"))
doAssert isSpace("\t")
doAssert isSpace("\l")
doAssert(not isSpace("Β"))
doAssert(not isSpace("Βeta"))
doAssert isSpace("\t\l \v\r\f")
doAssert isSpace(" ")
doAssert(not isSpace(""))
doAssert(not isSpace("ΑΓc \td"))
doAssert isLower("a")
doAssert isLower("γ")
doAssert(not isLower("Γ"))
doAssert(not isLower("4"))
doAssert(not isLower(""))
doAssert isLower("abcdγ")
doAssert(not isLower("abCDΓ"))
doAssert(not isLower("33aaΓ"))
doAssert isUpper("Γ")
doAssert(not isUpper("b"))
doAssert(not isUpper("α"))
doAssert(not isUpper(""))
doAssert(not isUpper(""))
doAssert isUpper("ΑΒΓ")
doAssert(not isUpper("AAccβ"))
doAssert(not isUpper("A#"))
doAssert toUpper("Γ") == "Γ"
doAssert toUpper("b") == "B"
doAssert toUpper("α") == "Α"
doAssert toUpper("") == ""
doAssert toUpper("") == ""
doAssert toUpper("ΑΒΓ") == "ΑΒΓ"
doAssert toUpper("AAccβ") == "AACCΒ"
doAssert toUpper("A✓") == "A✓$Β"
doAssert toLower("a") == "a"
doAssert toLower("γ") == "γ"
doAssert toLower("Γ") == "γ"
doAssert toLower("4") == "4"
doAssert toLower("") == ""
doAssert toLower("abcdγ") == "abcdγ"
doAssert toLower("abCDΓ") == "abcdγ"
doAssert toLower("33aaΓ") == "33aaγ"
doAssert reversed("Reverse this!") == "!siht esreveR"
doAssert reversed("先秦兩漢") == "漢兩秦先"
doAssert reversed("as⃝df̅") == "f̅ds⃝a"
@@ -1360,3 +1781,44 @@ when isMainModule:
const test = "as⃝"
doAssert lastRune(test, test.len-1)[1] == 3
doAssert graphemeLen("è", 0) == 2
# test for rune positioning and runeSubStr()
let s = "Hänsel ««: 10,00€"
var t = ""
for c in s.utf8:
t.add c
doAssert(s == t)
doAssert(runeReverseOffset(s, 1) == (20, 18))
doAssert(runeReverseOffset(s, 19) == (-1, 18))
doAssert(runeStrAtPos(s, 0) == "H")
doAssert(runeSubStr(s, 0, 1) == "H")
doAssert(runeStrAtPos(s, 10) == ":")
doAssert(runeSubStr(s, 10, 1) == ":")
doAssert(runeStrAtPos(s, 9) == "«")
doAssert(runeSubStr(s, 9, 1) == "«")
doAssert(runeStrAtPos(s, 17) == "")
doAssert(runeSubStr(s, 17, 1) == "")
# echo runeStrAtPos(s, 18) # index error
doAssert(runeSubStr(s, 0) == "Hänsel ««: 10,00€")
doAssert(runeSubStr(s, -18) == "Hänsel ««: 10,00€")
doAssert(runeSubStr(s, 10) == ": 10,00€")
doAssert(runeSubStr(s, 18) == "")
doAssert(runeSubStr(s, 0, 10) == "Hänsel ««")
doAssert(runeSubStr(s, 12) == "10,00€")
doAssert(runeSubStr(s, -6) == "10,00€")
doAssert(runeSubStr(s, 12, 5) == "10,00")
doAssert(runeSubStr(s, 12, -1) == "10,00")
doAssert(runeSubStr(s, -6, 5) == "10,00")
doAssert(runeSubStr(s, -6, -1) == "10,00")
doAssert(runeSubStr(s, 0, 100) == "Hänsel ««: 10,00€")
doAssert(runeSubStr(s, -100, 100) == "Hänsel ««: 10,00€")
doAssert(runeSubStr(s, 0, -100) == "")
doAssert(runeSubStr(s, 100, -100) == "")

View File

@@ -41,7 +41,11 @@ when not defined(ECMAScript):
import terminal
type
TestStatus* = enum OK, FAILED ## The status of a test when it is done.
TestStatus* = enum ## The status of a test when it is done.
OK,
FAILED,
SKIPPED
OutputLevel* = enum ## The output verbosity of the tests.
PRINT_ALL, ## Print as much as possible.
PRINT_FAILURES, ## Print only the failed tests.
@@ -73,7 +77,7 @@ checkpoints = @[]
proc shouldRun(testName: string): bool =
result = true
template suite*(name: expr, body: stmt): stmt {.immediate, dirty.} =
template suite*(name, body) {.dirty.} =
## Declare a test suite identified by `name` with optional ``setup``
## and/or ``teardown`` section.
##
@@ -102,13 +106,13 @@ template suite*(name: expr, body: stmt): stmt {.immediate, dirty.} =
## [OK] 2 + 2 = 4
## [OK] (2 + -2) != 4
block:
template setup(setupBody: stmt): stmt {.immediate, dirty.} =
template setup(setupBody: untyped) {.dirty.} =
var testSetupIMPLFlag = true
template testSetupIMPL: stmt {.immediate, dirty.} = setupBody
template testSetupIMPL: untyped {.dirty.} = setupBody
template teardown(teardownBody: stmt): stmt {.immediate, dirty.} =
template teardown(teardownBody: untyped) {.dirty.} =
var testTeardownIMPLFlag = true
template testTeardownIMPL: stmt {.immediate, dirty.} = teardownBody
template testTeardownIMPL: untyped {.dirty.} = teardownBody
body
@@ -120,14 +124,18 @@ proc testDone(name: string, s: TestStatus) =
template rawPrint() = echo("[", $s, "] ", name)
when not defined(ECMAScript):
if colorOutput and not defined(ECMAScript):
var color = (if s == OK: fgGreen else: fgRed)
var color = case s
of OK: fgGreen
of FAILED: fgRed
of SKIPPED: fgYellow
else: fgWhite
styledEcho styleBright, color, "[", $s, "] ", fgWhite, name
else:
rawPrint()
else:
rawPrint()
template test*(name: expr, body: stmt): stmt {.immediate, dirty.} =
template test*(name, body) {.dirty.} =
## Define a single test case identified by `name`.
##
## .. code-block:: nim
@@ -203,7 +211,22 @@ template fail* =
checkpoints = @[]
macro check*(conditions: stmt): stmt {.immediate.} =
template skip* =
## Makes test to be skipped. Should be used directly
## in case when it is not possible to perform test
## for reasons depending on outer environment,
## or certain application logic conditions or configurations.
##
## .. code-block:: nim
##
## if not isGLConextCreated():
## skip()
bind checkpoints
testStatusIMPL = SKIPPED
checkpoints = @[]
macro check*(conditions: untyped): untyped =
## Verify if a statement or a list of statements is true.
## A helpful error message and set checkpoints are printed out on
## failure (if ``outputLevel`` is not ``PRINT_NONE``).
@@ -236,31 +259,34 @@ macro check*(conditions: stmt): stmt {.immediate.} =
proc inspectArgs(exp: NimNode): NimNode =
result = copyNimTree(exp)
for i in countup(1, exp.len - 1):
if exp[i].kind notin nnkLiterals:
inc counter
var arg = newIdentNode(":p" & $counter)
var argStr = exp[i].toStrLit
var paramAst = exp[i]
if exp[i].kind == nnkIdent:
argsPrintOuts.add getAst(print(argStr, paramAst))
if exp[i].kind in nnkCallKinds:
var callVar = newIdentNode(":c" & $counter)
argsAsgns.add getAst(asgn(callVar, paramAst))
result[i] = callVar
argsPrintOuts.add getAst(print(argStr, callVar))
if exp[i].kind == nnkExprEqExpr:
# ExprEqExpr
# Ident !"v"
# IntLit 2
result[i] = exp[i][1]
if exp[i].typekind notin {ntyTypeDesc}:
argsAsgns.add getAst(asgn(arg, paramAst))
argsPrintOuts.add getAst(print(argStr, arg))
if exp[i].kind != nnkExprEqExpr:
result[i] = arg
else:
result[i][1] = arg
if exp[0].kind == nnkIdent and
$exp[0] in ["and", "or", "not", "in", "notin", "==", "<=",
">=", "<", ">", "!=", "is", "isnot"]:
for i in countup(1, exp.len - 1):
if exp[i].kind notin nnkLiterals:
inc counter
var arg = newIdentNode(":p" & $counter)
var argStr = exp[i].toStrLit
var paramAst = exp[i]
if exp[i].kind == nnkIdent:
argsPrintOuts.add getAst(print(argStr, paramAst))
if exp[i].kind in nnkCallKinds:
var callVar = newIdentNode(":c" & $counter)
argsAsgns.add getAst(asgn(callVar, paramAst))
result[i] = callVar
argsPrintOuts.add getAst(print(argStr, callVar))
if exp[i].kind == nnkExprEqExpr:
# ExprEqExpr
# Ident !"v"
# IntLit 2
result[i] = exp[i][1]
if exp[i].typekind notin {ntyTypeDesc}:
argsAsgns.add getAst(asgn(arg, paramAst))
argsPrintOuts.add getAst(print(argStr, arg))
if exp[i].kind != nnkExprEqExpr:
result[i] = arg
else:
result[i][1] = arg
case checked.kind
of nnkCallKinds:
@@ -292,7 +318,7 @@ macro check*(conditions: stmt): stmt {.immediate.} =
result = getAst(rewrite(checked, checked.lineinfo, checked.toStrLit))
template require*(conditions: stmt): stmt {.immediate.} =
template require*(conditions: untyped) =
## Same as `check` except any failed test causes the program to quit
## immediately. Any teardown statements are not executed and the failed
## test output is not generated.
@@ -302,7 +328,7 @@ template require*(conditions: stmt): stmt {.immediate.} =
check conditions
abortOnError = savedAbortOnError
macro expect*(exceptions: varargs[expr], body: stmt): stmt {.immediate.} =
macro expect*(exceptions: varargs[typed], body: untyped): untyped =
## Test if `body` raises an exception found in the passed `exceptions`.
## The test passes if the raised exception is part of the acceptable
## exceptions. Otherwise, it fails.
@@ -310,7 +336,7 @@ macro expect*(exceptions: varargs[expr], body: stmt): stmt {.immediate.} =
##
## .. code-block:: nim
##
## import math
## import math, random
## proc defectiveRobot() =
## randomize()
## case random(1..4)

View File

@@ -51,6 +51,9 @@ const
# Illegal characters
illegalChars = {'>', '<', '&', '"'}
# standard xml: attribute names
# see https://www.w3.org/XML/1998/namespace
stdattrnames = ["lang", "space", "base", "id"]
type
Feature = tuple[name: string, version: string]
@@ -229,12 +232,15 @@ proc createAttributeNS*(doc: PDocument, namespaceURI: string, qualifiedName: str
raise newException(EInvalidCharacterErr, "Invalid character")
# Exceptions
if qualifiedName.contains(':'):
let qfnamespaces = qualifiedName.toLower().split(':')
if isNil(namespaceURI):
raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil")
elif qualifiedName.split(':')[0].toLower() == "xml" and namespaceURI != "http://www.w3.org/XML/1998/namespace":
elif qfnamespaces[0] == "xml" and
namespaceURI != "http://www.w3.org/XML/1998/namespace" and
qfnamespaces[1] notin stdattrnames:
raise newException(ENamespaceErr,
"When the namespace prefix is \"xml\" namespaceURI has to be \"http://www.w3.org/XML/1998/namespace\"")
elif qualifiedName.split(':')[1].toLower() == "xmlns" and namespaceURI != "http://www.w3.org/2000/xmlns/":
elif qfnamespaces[1] == "xmlns" and namespaceURI != "http://www.w3.org/2000/xmlns/":
raise newException(ENamespaceErr,
"When the namespace prefix is \"xmlns\" namespaceURI has to be \"http://www.w3.org/2000/xmlns/\"")
@@ -305,9 +311,12 @@ proc createElement*(doc: PDocument, tagName: string): PElement =
proc createElementNS*(doc: PDocument, namespaceURI: string, qualifiedName: string): PElement =
## Creates an element of the given qualified name and namespace URI.
if qualifiedName.contains(':'):
let qfnamespaces = qualifiedName.toLower().split(':')
if isNil(namespaceURI):
raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil")
elif qualifiedName.split(':')[0].toLower() == "xml" and namespaceURI != "http://www.w3.org/XML/1998/namespace":
elif qfnamespaces[0] == "xml" and
namespaceURI != "http://www.w3.org/XML/1998/namespace" and
qfnamespaces[1] notin stdattrnames:
raise newException(ENamespaceErr,
"When the namespace prefix is \"xml\" namespaceURI has to be \"http://www.w3.org/XML/1998/namespace\"")

View File

@@ -1,6 +1,6 @@
[Package]
name = "stdlib"
version = "0.13.0"
version = "0.14.3"
author = "Dominik Picheta"
description = "Nim's standard library."
license = "MIT"

View File

@@ -66,8 +66,10 @@ type
`ref`* {.magic: Pointer.}[T] ## built-in generic traced pointer type
`nil` {.magic: "Nil".}
expr* {.magic: Expr.} ## meta type to denote an expression (for templates)
stmt* {.magic: Stmt.} ## meta type to denote a statement (for templates)
expr* {.magic: Expr, deprecated.} ## meta type to denote an expression (for templates)
## **Deprecated** since version 0.15. Use ``untyped`` instead.
stmt* {.magic: Stmt, deprecated.} ## meta type to denote a statement (for templates)
## **Deprecated** since version 0.15. Use ``typed`` instead.
typedesc* {.magic: TypeDesc.} ## meta type to denote a type description
void* {.magic: "VoidType".} ## meta type to denote the absence of any type
auto* {.magic: Expr.} ## meta type for automatic type determination
@@ -302,8 +304,7 @@ proc `==` *(x, y: pointer): bool {.magic: "EqRef", noSideEffect.}
## echo (a == b) # true due to the special meaning of `nil`/0 as a pointer
proc `==` *(x, y: string): bool {.magic: "EqStr", noSideEffect.}
## Checks for equality between two `string` variables
proc `==` *(x, y: cstring): bool {.magic: "EqCString", noSideEffect.}
## Checks for equality between two `cstring` variables
proc `==` *(x, y: char): bool {.magic: "EqCh", noSideEffect.}
## Checks for equality between two `char` variables
proc `==` *(x, y: bool): bool {.magic: "EqB", noSideEffect.}
@@ -339,15 +340,15 @@ proc `<` *[T](x, y: ref T): bool {.magic: "LtPtr", noSideEffect.}
proc `<` *[T](x, y: ptr T): bool {.magic: "LtPtr", noSideEffect.}
proc `<` *(x, y: pointer): bool {.magic: "LtPtr", noSideEffect.}
template `!=` * (x, y: expr): expr {.immediate.} =
template `!=` * (x, y: untyped): untyped =
## unequals operator. This is a shorthand for ``not (x == y)``.
not (x == y)
template `>=` * (x, y: expr): expr {.immediate.} =
template `>=` * (x, y: untyped): untyped =
## "is greater or equals" operator. This is the same as ``y <= x``.
y <= x
template `>` * (x, y: expr): expr {.immediate.} =
template `>` * (x, y: untyped): untyped =
## "is greater" operator. This is the same as ``y < x``.
y < x
@@ -588,6 +589,9 @@ proc sizeof*[T](x: T): int {.magic: "SizeOf", noSideEffect.}
## that one never needs to know ``x``'s size. As a special semantic rule,
## ``x`` may also be a type identifier (``sizeof(int)`` is valid).
##
## Limitations: If used within nim VM context ``sizeof`` will only work
## for simple types.
##
## .. code-block:: nim
## sizeof('A') #=> 1
## sizeof(2) #=> 8
@@ -667,6 +671,12 @@ proc newSeq*[T](len = 0.Natural): seq[T] =
## #inputStrings[3] = "out of bounds"
newSeq(result, len)
proc newSeqOfCap*[T](cap: Natural): seq[T] {.
magic: "NewSeqOfCap", noSideEffect.} =
## creates a new sequence of type ``seq[T]`` with length 0 and capacity
## ``cap``.
discard
proc len*[TOpenArray: openArray|varargs](x: TOpenArray): int {.
magic: "LengthOpenArray", noSideEffect.}
proc len*(x: string): int {.magic: "LengthStr", noSideEffect.}
@@ -1090,13 +1100,13 @@ proc contains*[T](s: Slice[T], value: T): bool {.noSideEffect, inline.} =
## assert((1..3).contains(4) == false)
result = s.a <= value and value <= s.b
template `in` * (x, y: expr): expr {.immediate, dirty.} = contains(y, x)
template `in` * (x, y: untyped): untyped {.dirty.} = contains(y, x)
## Sugar for contains
##
## .. code-block:: Nim
## assert(1 in (1..3) == true)
## assert(5 in (1..3) == false)
template `notin` * (x, y: expr): expr {.immediate, dirty.} = not contains(y, x)
template `notin` * (x, y: untyped): untyped {.dirty.} = not contains(y, x)
## Sugar for not containing
##
## .. code-block:: Nim
@@ -1115,7 +1125,7 @@ proc `is` *[T, S](x: T, y: S): bool {.magic: "Is", noSideEffect.}
##
## assert(test[int](3) == 3)
## assert(test[string]("xyz") == 0)
template `isnot` *(x, y: expr): expr {.immediate.} = not (x is y)
template `isnot` *(x, y: untyped): untyped = not (x is y)
## Negated version of `is`. Equivalent to ``not(x is y)``.
proc `of` *[T, S](x: T, y: S): bool {.magic: "Of", noSideEffect.}
@@ -1291,7 +1301,7 @@ const
when hasThreadSupport and defined(tcc) and not compileOption("tlsEmulation"):
# tcc doesn't support TLS
{.error: "``--tlsEmulation:on`` must be used when using threads with tcc backend".}
when defined(boehmgc):
when defined(windows):
const boehmLib = "boehmgc.dll"
@@ -1521,7 +1531,7 @@ type # these work for most platforms:
## This is the same as the type ``unsigned long long`` in *C*.
cstringArray* {.importc: "char**", nodecl.} = ptr
array [0..ArrayDummySize, cstring]
array[0..ArrayDummySize, cstring]
## This is binary compatible to the type ``char**`` in *C*. The array's
## high value is large enough to disable bounds checking in practice.
## Use `cstringArrayToSeq` to convert it into a ``seq[string]``.
@@ -1591,34 +1601,32 @@ proc substr*(s: string, first, last: int): string {.
## is used instead: This means ``substr`` can also be used to `cut`:idx:
## or `limit`:idx: a string's length.
when not defined(nimscript):
proc zeroMem*(p: pointer, size: Natural) {.importc, noDecl, benign.}
when not defined(nimscript) and not defined(JS):
proc zeroMem*(p: pointer, size: Natural) {.inline, benign.}
## overwrites the contents of the memory at ``p`` with the value 0.
## Exactly ``size`` bytes will be overwritten. Like any procedure
## dealing with raw memory this is *unsafe*.
proc copyMem*(dest, source: pointer, size: Natural) {.
importc: "memcpy", header: "<string.h>", benign.}
proc copyMem*(dest, source: pointer, size: Natural) {.inline, benign.}
## copies the contents from the memory at ``source`` to the memory
## at ``dest``. Exactly ``size`` bytes will be copied. The memory
## regions may not overlap. Like any procedure dealing with raw
## memory this is *unsafe*.
proc moveMem*(dest, source: pointer, size: Natural) {.
importc: "memmove", header: "<string.h>", benign.}
proc moveMem*(dest, source: pointer, size: Natural) {.inline, benign.}
## copies the contents from the memory at ``source`` to the memory
## at ``dest``. Exactly ``size`` bytes will be copied. The memory
## regions may overlap, ``moveMem`` handles this case appropriately
## and is thus somewhat more safe than ``copyMem``. Like any procedure
## dealing with raw memory this is still *unsafe*, though.
proc equalMem*(a, b: pointer, size: Natural): bool {.
importc: "equalMem", noDecl, noSideEffect.}
proc equalMem*(a, b: pointer, size: Natural): bool {.inline, noSideEffect.}
## compares the memory blocks ``a`` and ``b``. ``size`` bytes will
## be compared. If the blocks are equal, true is returned, false
## otherwise. Like any procedure dealing with raw memory this is
## *unsafe*.
when not defined(nimscript):
when hasAlloc:
proc alloc*(size: Natural): pointer {.noconv, rtl, tags: [], benign.}
## allocates a new memory block with at least ``size`` bytes. The
@@ -1736,11 +1744,18 @@ proc swap*[T](a, b: var T) {.magic: "Swap", noSideEffect.}
## swaps the values `a` and `b`. This is often more efficient than
## ``tmp = a; a = b; b = tmp``. Particularly useful for sorting algorithms.
template `>=%` *(x, y: expr): expr {.immediate.} = y <=% x
when not defined(js) and not defined(booting) and defined(nimTrMacros):
template swapRefsInArray*{swap(arr[a], arr[b])}(arr: openarray[ref], a, b: int) =
# Optimize swapping of array elements if they are refs. Default swap
# implementation will cause unsureAsgnRef to be emitted which causes
# unnecessary slow down in this case.
swap(cast[ptr pointer](addr arr[a])[], cast[ptr pointer](addr arr[b])[])
template `>=%` *(x, y: untyped): untyped = y <=% x
## treats `x` and `y` as unsigned and compares them.
## Returns true iff ``unsigned(x) >= unsigned(y)``.
template `>%` *(x, y: expr): expr {.immediate.} = y <% x
template `>%` *(x, y: untyped): untyped = y <% x
## treats `x` and `y` as unsigned and compares them.
## Returns true iff ``unsigned(x) > unsigned(y)``.
@@ -1807,10 +1822,10 @@ const
NimMajor*: int = 0
## is the major number of Nim's version.
NimMinor*: int = 13
NimMinor*: int = 14
## is the minor number of Nim's version.
NimPatch*: int = 1
NimPatch*: int = 3
## is the patch number of Nim's version.
NimVersion*: string = $NimMajor & "." & $NimMinor & "." & $NimPatch
@@ -1868,7 +1883,7 @@ iterator countdown*[T](a, b: T, step = 1): T {.inline.} =
yield res
dec(res, step)
template countupImpl(incr: stmt) {.immediate, dirty.} =
template countupImpl(incr: untyped) {.oldimmediate, dirty.} =
when T is IntLikeForCount:
var res = int(a)
while res <= int(b):
@@ -2163,7 +2178,7 @@ proc `&` *[T](x: T, y: seq[T]): seq[T] {.noSideEffect.} =
result[i+1] = y[i]
when not defined(nimscript):
when not defined(JS):
when not defined(JS) or defined(nimphp):
proc seqToPtr[T](x: seq[T]): pointer {.inline, nosideeffect.} =
result = cast[pointer](x)
else:
@@ -2293,12 +2308,15 @@ proc `$`*[T: tuple|object](x: T): string =
if not firstElement: result.add(", ")
result.add(name)
result.add(": ")
when compiles(value.isNil):
if value.isNil: result.add "nil"
else: result.add($value)
when compiles($value):
when compiles(value.isNil):
if value.isNil: result.add "nil"
else: result.add($value)
else:
result.add($value)
firstElement = false
else:
result.add($value)
firstElement = false
result.add("...")
result.add(")")
proc collectionToString[T: set | seq](x: T, b, e: string): string =
@@ -2511,7 +2529,7 @@ template newException*(exceptn: typedesc, message: string): expr =
e
when hostOS == "standalone":
include panicoverride
include "$projectpath/panicoverride"
when not declared(sysFatal):
when hostOS == "standalone":
@@ -2564,15 +2582,11 @@ else:
when not defined(JS): #and not defined(nimscript):
{.push stack_trace: off, profiler:off.}
when not (
defined(nimscript) or
defined(boehmgc) or
defined(gogc) or
(defined(nogc) and defined(useMalloc))):
proc initAllocator() {.inline.}
when not defined(nimscript) and not defined(nogc):
proc initGC()
when not defined(nimscript) and (not defined(nogc) or not defined(useMalloc)):
when not defined(gcStack):
proc initGC()
when not defined(boehmgc) and not defined(gogc) and not defined(gcStack):
proc initAllocator() {.inline.}
proc initStackBottom() {.inline, compilerproc.} =
# WARNING: This is very fragile! An array size of 8 does not work on my
@@ -2617,11 +2631,21 @@ when not defined(JS): #and not defined(nimscript):
{.deprecated: [TFile: File, TFileHandle: FileHandle, TFileMode: FileMode].}
when not defined(nimscript):
include "system/ansi_c"
include "system/ansi_c"
when not defined(nimscript):
proc cmp(x, y: string): int =
result = int(c_strcmp(x, y))
proc zeroMem(p: pointer, size: Natural) =
c_memset(p, 0, size)
proc copyMem(dest, source: pointer, size: Natural) =
c_memcpy(dest, source, size)
proc moveMem(dest, source: pointer, size: Natural) =
c_memmove(dest, source, size)
proc equalMem(a, b: pointer, size: Natural): bool =
c_memcmp(a, b, size) == 0
else:
proc cmp(x, y: string): int =
if x < y: result = -1
@@ -2629,28 +2653,20 @@ when not defined(JS): #and not defined(nimscript):
else: result = 0
when defined(nimscript):
proc readFile*(filename: string): string {.tags: [ReadIOEffect], benign.}
## Opens a file named `filename` for reading.
##
## Then calls `readAll <#readAll>`_ and closes the file afterwards.
## Returns the string. Raises an IO exception in case of an error. If
## you need to call this inside a compile time macro you can use
## `staticRead <#staticRead>`_.
proc writeFile*(filename, content: string) {.tags: [WriteIOEffect], benign.}
## Opens a file named `filename` for writing. Then writes the
## `content` completely to the file and closes the file afterwards.
## Raises an IO exception in case of an error.
when not defined(nimscript) and hostOS != "standalone":
when defined(windows):
# work-around C's sucking abstraction:
# BUGFIX: stdin and stdout should be binary files!
proc setmode(handle, mode: int) {.importc: "setmode",
header: "<io.h>".}
proc fileno(f: C_TextFileStar): int {.importc: "fileno",
header: "<fcntl.h>".}
var
O_BINARY {.importc: "O_BINARY", nodecl.}: int
# we use binary mode on Windows:
setmode(fileno(c_stdin), O_BINARY)
setmode(fileno(c_stdout), O_BINARY)
when defined(endb):
proc endbStep()
# text file handling:
var
@@ -2661,6 +2677,21 @@ when not defined(JS): #and not defined(nimscript):
stderr* {.importc: "stderr", header: "<stdio.h>".}: File
## The standard error stream.
when defined(windows):
# work-around C's sucking abstraction:
# BUGFIX: stdin and stdout should be binary files!
proc c_setmode(handle, mode: cint) {.importc: "_setmode",
header: "<io.h>".}
var
O_BINARY {.importc: "O_BINARY", nodecl.}: cint
# we use binary mode on Windows:
c_setmode(c_fileno(stdin), O_BINARY)
c_setmode(c_fileno(stdout), O_BINARY)
when defined(endb):
proc endbStep()
when defined(useStdoutAsStdmsg):
template stdmsg*: File = stdout
else:
@@ -2699,17 +2730,19 @@ when not defined(JS): #and not defined(nimscript):
##
## Default mode is readonly. Returns true iff the file could be reopened.
proc close*(f: File) {.importc: "fclose", header: "<stdio.h>", tags: [].}
proc setStdIoUnbuffered*() {.tags: [], benign.}
## Configures `stdin`, `stdout` and `stderr` to be unbuffered.
proc close*(f: File) {.tags: [].}
## Closes the file.
proc endOfFile*(f: File): bool {.tags: [], benign.}
## Returns true iff `f` is at the end.
proc readChar*(f: File): char {.
importc: "fgetc", header: "<stdio.h>", tags: [ReadIOEffect].}
proc readChar*(f: File): char {.tags: [ReadIOEffect].}
## Reads a single character from the stream `f`.
proc flushFile*(f: File) {.
importc: "fflush", header: "<stdio.h>", tags: [WriteIOEffect].}
proc flushFile*(f: File) {.tags: [WriteIOEffect].}
## Flushes `f`'s buffer.
proc readAll*(file: File): TaintedString {.tags: [ReadIOEffect], benign.}
@@ -2779,6 +2812,9 @@ when not defined(JS): #and not defined(nimscript):
## reads `len` bytes into the buffer `a` starting at ``a[start]``. Returns
## the actual number of bytes that have been read which may be less than
## `len` (if not as many bytes are remaining), but not greater.
##
## **Warning:** The buffer `a` must be pre-allocated. This can be done
## using, for example, ``newString``.
proc readBuffer*(f: File, buffer: pointer, len: Natural): int {.
tags: [ReadIOEffect], benign.}
@@ -2812,8 +2848,7 @@ when not defined(JS): #and not defined(nimscript):
## retrieves the current position of the file pointer that is used to
## read from the file `f`. The file's first byte has the index zero.
proc getFileHandle*(f: File): FileHandle {.importc: "fileno",
header: "<stdio.h>"}
proc getFileHandle*(f: File): FileHandle
## returns the OS file handle of the file ``f``. This is only useful for
## platform specific programming.
@@ -2879,6 +2914,7 @@ when not defined(JS): #and not defined(nimscript):
when declared(initAllocator):
initAllocator()
when hasThreadSupport:
const insideRLocksModule = false
include "system/syslocks"
when hostOS != "standalone": include "system/threads"
elif not defined(nogc) and not defined(nimscript):
@@ -3031,34 +3067,6 @@ when not defined(JS): #and not defined(nimscript):
{.pop.} # stacktrace
when not defined(nimscript):
proc likely*(val: bool): bool {.importc: "likely", nodecl, nosideeffect.}
## Hints the optimizer that `val` is likely going to be true.
##
## You can use this proc to decorate a branch condition. On certain
## platforms this can help the processor predict better which branch is
## going to be run. Example:
##
## .. code-block:: nim
## for value in inputValues:
## if likely(value <= 100):
## process(value)
## else:
## echo "Value too big!"
proc unlikely*(val: bool): bool {.importc: "unlikely", nodecl, nosideeffect.}
## Hints the optimizer that `val` is likely going to be false.
##
## You can use this proc to decorate a branch condition. On certain
## platforms this can help the processor predict better which branch is
## going to be run. Example:
##
## .. code-block:: nim
## for value in inputValues:
## if unlikely(value > 100):
## echo "Value too big!"
## else:
## process(value)
proc rawProc*[T: proc](x: T): pointer {.noSideEffect, inline.} =
## retrieves the raw proc pointer of the closure `x`. This is
## useful for interfacing closures with C.
@@ -3126,11 +3134,63 @@ proc quit*(errormsg: string, errorcode = QuitFailure) {.noReturn.} =
{.pop.} # checks
{.pop.} # hints
when not defined(JS):
proc likely_proc(val: bool): bool {.importc: "likely", nodecl, nosideeffect.}
proc unlikely_proc(val: bool): bool {.importc: "unlikely", nodecl, nosideeffect.}
template likely*(val: bool): bool =
## Hints the optimizer that `val` is likely going to be true.
##
## You can use this template to decorate a branch condition. On certain
## platforms this can help the processor predict better which branch is
## going to be run. Example:
##
## .. code-block:: nim
## for value in inputValues:
## if likely(value <= 100):
## process(value)
## else:
## echo "Value too big!"
##
## On backends without branch prediction (JS and the nimscript VM), this
## template will not affect code execution.
when nimvm:
val
else:
when defined(JS):
val
else:
likely_proc(val)
template unlikely*(val: bool): bool =
## Hints the optimizer that `val` is likely going to be false.
##
## You can use this proc to decorate a branch condition. On certain
## platforms this can help the processor predict better which branch is
## going to be run. Example:
##
## .. code-block:: nim
## for value in inputValues:
## if unlikely(value > 100):
## echo "Value too big!"
## else:
## process(value)
##
## On backends without branch prediction (JS and the nimscript VM), this
## template will not affect code execution.
when nimvm:
val
else:
when defined(JS):
val
else:
unlikely_proc(val)
proc `/`*(x, y: int): float {.inline, noSideEffect.} =
## integer division that results in a float.
result = toFloat(x) / toFloat(y)
template spliceImpl(s, a, L, b: expr): stmt {.immediate.} =
template spliceImpl(s, a, L, b: untyped): untyped =
# make room for additional elements or cut:
var slen = s.len
var shift = b.len - L
@@ -3172,7 +3232,7 @@ proc `[]`*[Idx, T](a: array[Idx, T], x: Slice[int]): seq[T] =
when low(a) < 0:
{.error: "Slicing for arrays with negative indices is unsupported.".}
var L = x.b - x.a + 1
newSeq(result, L)
result = newSeq[T](L)
for i in 0.. <L: result[i] = a[i + x.a]
proc `[]=`*[Idx, T](a: var array[Idx, T], x: Slice[int], b: openArray[T]) =
@@ -3308,7 +3368,11 @@ proc astToStr*[T](x: T): string {.magic: "AstToStr", noSideEffect.}
proc instantiationInfo*(index = -1, fullPaths = false): tuple[
filename: string, line: int] {. magic: "InstantiationInfo", noSideEffect.}
## provides access to the compiler's instantiation stack line information.
## provides access to the compiler's instantiation stack line information
## of a template.
##
## While similar to the `caller info`:idx: of other languages, it is determined
## at compile time.
##
## This proc is mostly useful for meta programming (eg. ``assert`` template)
## to retrieve information about the current filename and line number.
@@ -3412,7 +3476,7 @@ iterator mitems*(a: var string): var char {.inline.} =
when not defined(nimhygiene):
{.pragma: inject.}
template onFailedAssert*(msg: expr, code: stmt): stmt {.dirty, immediate.} =
template onFailedAssert*(msg, code: untyped): untyped {.dirty.} =
## Sets an assertion failure handler that will intercept any assert
## statements following `onFailedAssert` in the current module scope.
##
@@ -3425,7 +3489,7 @@ template onFailedAssert*(msg: expr, code: stmt): stmt {.dirty, immediate.} =
## e.lineinfo = instantiationInfo(-2)
## raise e
##
template failedAssertImpl(msgIMPL: string): stmt {.dirty.} =
template failedAssertImpl(msgIMPL: string): untyped {.dirty.} =
let msg = msgIMPL
code
@@ -3578,7 +3642,43 @@ proc xlen*[T](x: seq[T]): int {.magic: "XLenSeq", noSideEffect.} =
## This is an optimization that rarely makes sense.
discard
proc `==` *(x, y: cstring): bool {.magic: "EqCString", noSideEffect,
inline.} =
## Checks for equality between two `cstring` variables.
proc strcmp(a, b: cstring): cint {.noSideEffect,
importc, header: "<string.h>".}
if pointer(x) == pointer(y): result = true
elif x.isNil or y.isNil: result = false
else: result = strcmp(x, y) == 0
template closureScope*(body: untyped): untyped =
## Useful when creating a closure in a loop to capture local loop variables by
## their current iteration values. Example:
##
## .. code-block:: nim
## var myClosure : proc()
## # without closureScope:
## for i in 0 .. 5:
## let j = i
## if j == 3:
## myClosure = proc() = echo j
## myClosure() # outputs 5. `j` is changed after closure creation
## # with closureScope:
## for i in 0 .. 5:
## closureScope: # Everything in this scope is locked after closure creation
## let j = i
## if j == 3:
## myClosure = proc() = echo j
## myClosure() # outputs 3
(proc() = body)()
{.pop.} #{.push warning[GcMem]: off, warning[Uninit]: off.}
when defined(nimconfig):
include "system/nimscript"
when defined(windows) and appType == "console" and not defined(nimconfig):
proc setConsoleOutputCP(codepage: cint): cint {.stdcall, dynlib: "kernel32",
importc: "SetConsoleOutputCP".}
discard setConsoleOutputCP(65001) # 65001 - utf-8 codepage

View File

@@ -27,15 +27,14 @@ const
type
PTrunk = ptr Trunk
Trunk {.final.} = object
Trunk = object
next: PTrunk # all nodes are connected with this pointer
key: int # start address at bit 0
bits: array[0..IntsPerTrunk-1, int] # a bit vector
TrunkBuckets = array[0..255, PTrunk]
IntSet {.final.} = object
IntSet = object
data: TrunkBuckets
{.deprecated: [TIntSet: IntSet, TTrunk: Trunk, TTrunkBuckets: TrunkBuckets].}
type
AlignType = BiggestFloat
@@ -64,8 +63,6 @@ type
next, prev: PBigChunk # chunks of the same (or bigger) size
align: int
data: AlignType # start of usable memory
{.deprecated: [TAlignType: AlignType, TFreeCell: FreeCell, TBaseChunk: BaseChunk,
TBigChunk: BigChunk, TSmallChunk: SmallChunk].}
template smallChunkOverhead(): expr = sizeof(SmallChunk)-sizeof(AlignType)
template bigChunkOverhead(): expr = sizeof(BigChunk)-sizeof(AlignType)
@@ -79,18 +76,18 @@ template bigChunkOverhead(): expr = sizeof(BigChunk)-sizeof(AlignType)
type
PLLChunk = ptr LLChunk
LLChunk {.pure.} = object ## *low-level* chunk
LLChunk = object ## *low-level* chunk
size: int # remaining size
acc: int # accumulator
next: PLLChunk # next low-level chunk; only needed for dealloc
PAvlNode = ptr AvlNode
AvlNode {.pure, final.} = object
AvlNode = object
link: array[0..1, PAvlNode] # Left (0) and right (1) links
key, upperBound: int
level: int
MemRegion {.final, pure.} = object
MemRegion = object
minLargeObj, maxLargeObj: int
freeSmallChunks: array[0..SmallChunkSize div MemAlign-1, PSmallChunk]
llmem: PLLChunk
@@ -99,6 +96,7 @@ type
freeChunksList: PBigChunk # XXX make this a datastructure with O(1) access
chunkStarts: IntSet
root, deleted, last, freeAvlNodes: PAvlNode
locked: bool # if locked, we cannot free pages.
{.deprecated: [TLLChunk: LLChunk, TAvlNode: AvlNode, TMemRegion: MemRegion].}
# shared:
@@ -234,7 +232,8 @@ proc isSmallChunk(c: PChunk): bool {.inline.} =
proc chunkUnused(c: PChunk): bool {.inline.} =
result = not c.used
iterator allObjects(m: MemRegion): pointer {.inline.} =
iterator allObjects(m: var MemRegion): pointer {.inline.} =
m.locked = true
for s in elements(m.chunkStarts):
# we need to check here again as it could have been modified:
if s in m.chunkStarts:
@@ -252,6 +251,7 @@ iterator allObjects(m: MemRegion): pointer {.inline.} =
else:
let c = cast[PBigChunk](c)
yield addr(c.data)
m.locked = false
proc iterToProc*(iter: typed, envType: typedesc; procName: untyped) {.
magic: "Plugin", compileTime.}
@@ -311,7 +311,7 @@ proc freeOsChunks(a: var MemRegion, p: pointer, size: int) =
osDeallocPages(p, size)
decCurrMem(a, size)
dec(a.freeMem, size)
#c_fprintf(c_stdout, "[Alloc] back to OS: %ld\n", size)
#c_fprintf(stdout, "[Alloc] back to OS: %ld\n", size)
proc isAccessible(a: MemRegion, p: pointer): bool {.inline.} =
result = contains(a.chunkStarts, pageIndex(p))
@@ -324,9 +324,9 @@ proc contains[T](list, x: T): bool =
proc writeFreeList(a: MemRegion) =
var it = a.freeChunksList
c_fprintf(c_stdout, "freeChunksList: %p\n", it)
c_fprintf(stdout, "freeChunksList: %p\n", it)
while it != nil:
c_fprintf(c_stdout, "it: %p, next: %p, prev: %p\n",
c_fprintf(stdout, "it: %p, next: %p, prev: %p\n",
it, it.next, it.prev)
it = it.next
@@ -385,7 +385,7 @@ proc freeBigChunk(a: var MemRegion, c: PBigChunk) =
excl(a.chunkStarts, pageIndex(c))
c = cast[PBigChunk](le)
if c.size < ChunkOsReturn or doNotUnmap:
if c.size < ChunkOsReturn or doNotUnmap or a.locked:
incl(a, a.chunkStarts, pageIndex(c))
updatePrevSize(a, c, c.size)
listAdd(a.freeChunksList, c)
@@ -442,26 +442,29 @@ proc getSmallChunk(a: var MemRegion): PSmallChunk =
# -----------------------------------------------------------------------------
proc isAllocatedPtr(a: MemRegion, p: pointer): bool {.benign.}
proc allocInv(a: MemRegion): bool =
## checks some (not all yet) invariants of the allocator's data structures.
for s in low(a.freeSmallChunks)..high(a.freeSmallChunks):
var c = a.freeSmallChunks[s]
while not (c == nil):
if c.next == c:
echo "[SYSASSERT] c.next == c"
return false
if not (c.size == s * MemAlign):
echo "[SYSASSERT] c.size != s * MemAlign"
return false
var it = c.freeList
while not (it == nil):
if not (it.zeroField == 0):
echo "[SYSASSERT] it.zeroField != 0"
c_printf("%ld %p\n", it.zeroField, it)
when true:
template allocInv(a: MemRegion): bool = true
else:
proc allocInv(a: MemRegion): bool =
## checks some (not all yet) invariants of the allocator's data structures.
for s in low(a.freeSmallChunks)..high(a.freeSmallChunks):
var c = a.freeSmallChunks[s]
while not (c == nil):
if c.next == c:
echo "[SYSASSERT] c.next == c"
return false
it = it.next
c = c.next
result = true
if not (c.size == s * MemAlign):
echo "[SYSASSERT] c.size != s * MemAlign"
return false
var it = c.freeList
while not (it == nil):
if not (it.zeroField == 0):
echo "[SYSASSERT] it.zeroField != 0"
c_printf("%ld %p\n", it.zeroField, it)
return false
it = it.next
c = c.next
result = true
proc rawAlloc(a: var MemRegion, requestedSize: int): pointer =
sysAssert(allocInv(a), "rawAlloc: begin")
@@ -469,7 +472,7 @@ proc rawAlloc(a: var MemRegion, requestedSize: int): pointer =
sysAssert(requestedSize >= sizeof(FreeCell), "rawAlloc: requested size too small")
var size = roundup(requestedSize, MemAlign)
sysAssert(size >= requestedSize, "insufficient allocated size!")
#c_fprintf(c_stdout, "alloc; size: %ld; %ld\n", requestedSize, size)
#c_fprintf(stdout, "alloc; size: %ld; %ld\n", requestedSize, size)
if size <= SmallChunkSize-smallChunkOverhead():
# allocate a small block: for small chunks, we use only its next pointer
var s = size div MemAlign
@@ -490,7 +493,7 @@ proc rawAlloc(a: var MemRegion, requestedSize: int): pointer =
sysAssert(allocInv(a), "rawAlloc: begin c != nil")
sysAssert c.next != c, "rawAlloc 5"
#if c.size != size:
# c_fprintf(c_stdout, "csize: %lld; size %lld\n", c.size, size)
# c_fprintf(stdout, "csize: %lld; size %lld\n", c.size, size)
sysAssert c.size == size, "rawAlloc 6"
if c.freeList == nil:
sysAssert(c.acc + smallChunkOverhead() + size <= SmallChunkSize,

View File

@@ -13,60 +13,43 @@
{.push hints:off}
proc c_strcmp(a, b: cstring): cint {.header: "<string.h>",
noSideEffect, importc: "strcmp".}
proc c_memcmp(a, b: cstring, size: int): cint {.header: "<string.h>",
noSideEffect, importc: "memcmp".}
proc c_memcpy(a, b: cstring, size: int) {.header: "<string.h>", importc: "memcpy".}
proc c_strlen(a: cstring): int {.header: "<string.h>",
noSideEffect, importc: "strlen".}
proc c_memset(p: pointer, value: cint, size: int) {.
header: "<string.h>", importc: "memset".}
when not declared(File):
type
C_TextFile {.importc: "FILE", header: "<stdio.h>",
final, incompleteStruct.} = object
C_BinaryFile {.importc: "FILE", header: "<stdio.h>",
final, incompleteStruct.} = object
C_TextFileStar = ptr C_TextFile
C_BinaryFileStar = ptr C_BinaryFile
else:
type
C_TextFileStar = File
C_BinaryFileStar = File
proc c_memchr(s: pointer, c: cint, n: csize): pointer {.
importc: "memchr", header: "<string.h>".}
proc c_memcmp(a, b: pointer, size: csize): cint {.
importc: "memcmp", header: "<string.h>", noSideEffect.}
proc c_memcpy(a, b: pointer, size: csize): pointer {.
importc: "memcpy", header: "<string.h>", discardable.}
proc c_memmove(a, b: pointer, size: csize): pointer {.
importc: "memmove", header: "<string.h>",discardable.}
proc c_memset(p: pointer, value: cint, size: csize): pointer {.
importc: "memset", header: "<string.h>", discardable.}
proc c_strcmp(a, b: cstring): cint {.
importc: "strcmp", header: "<string.h>", noSideEffect.}
type
C_JmpBuf {.importc: "jmp_buf", header: "<setjmp.h>".} = object
when not defined(vm):
var
c_stdin {.importc: "stdin", nodecl.}: C_TextFileStar
c_stdout {.importc: "stdout", nodecl.}: C_TextFileStar
c_stderr {.importc: "stderr", nodecl.}: C_TextFileStar
# constants faked as variables:
when not declared(SIGINT):
when defined(windows):
const
SIGABRT = cint(22)
SIGFPE = cint(8)
SIGILL = cint(4)
SIGINT = cint(2)
SIGSEGV = cint(11)
SIGTERM = cint(15)
elif defined(macosx) or defined(linux) or defined(freebsd) or
defined(openbsd) or defined(netbsd) or defined(solaris):
const
SIGABRT = cint(6)
SIGFPE = cint(8)
SIGILL = cint(4)
SIGINT = cint(2)
SIGSEGV = cint(11)
SIGTERM = cint(15)
SIGPIPE = cint(13)
else:
when NoFakeVars:
when defined(windows):
const
SIGABRT = cint(22)
SIGFPE = cint(8)
SIGILL = cint(4)
SIGINT = cint(2)
SIGSEGV = cint(11)
SIGTERM = cint(15)
elif defined(macosx) or defined(linux):
const
SIGABRT = cint(6)
SIGFPE = cint(8)
SIGILL = cint(4)
SIGINT = cint(2)
SIGSEGV = cint(11)
SIGTERM = cint(15)
SIGPIPE = cint(13)
else:
{.error: "SIGABRT not ported to your platform".}
{.error: "SIGABRT not ported to your platform".}
else:
var
SIGINT {.importc: "SIGINT", nodecl.}: cint
@@ -78,10 +61,7 @@ when not declared(SIGINT):
var SIGPIPE {.importc: "SIGPIPE", nodecl.}: cint
when defined(macosx):
when NoFakeVars:
const SIGBUS = cint(10)
else:
var SIGBUS {.importc: "SIGBUS", nodecl.}: cint
const SIGBUS = cint(10)
else:
template SIGBUS: expr = SIGSEGV
@@ -103,72 +83,27 @@ else:
proc c_setjmp(jmpb: C_JmpBuf): cint {.
header: "<setjmp.h>", importc: "setjmp".}
proc c_signal(sign: cint, handler: proc (a: cint) {.noconv.}) {.
importc: "signal", header: "<signal.h>".}
proc c_raise(sign: cint) {.importc: "raise", header: "<signal.h>".}
type c_sighandler_t = proc (a: cint) {.noconv.}
proc c_signal(sign: cint, handler: proc (a: cint) {.noconv.}): c_sighandler_t {.
importc: "signal", header: "<signal.h>", discardable.}
proc c_fputs(c: cstring, f: C_TextFileStar) {.importc: "fputs",
header: "<stdio.h>".}
proc c_fgets(c: cstring, n: int, f: C_TextFileStar): cstring {.
importc: "fgets", header: "<stdio.h>".}
proc c_fgetc(stream: C_TextFileStar): int {.importc: "fgetc",
header: "<stdio.h>".}
proc c_ungetc(c: int, f: C_TextFileStar) {.importc: "ungetc",
header: "<stdio.h>".}
proc c_putc(c: char, stream: C_TextFileStar) {.importc: "putc",
header: "<stdio.h>".}
proc c_fprintf(f: C_TextFileStar, frmt: cstring) {.
importc: "fprintf", header: "<stdio.h>", varargs.}
proc c_printf(frmt: cstring) {.
importc: "printf", header: "<stdio.h>", varargs.}
proc c_fprintf(f: File, frmt: cstring): cint {.
importc: "fprintf", header: "<stdio.h>", varargs, discardable.}
proc c_printf(frmt: cstring): cint {.
importc: "printf", header: "<stdio.h>", varargs, discardable.}
proc c_fopen(filename, mode: cstring): C_TextFileStar {.
importc: "fopen", header: "<stdio.h>".}
proc c_fclose(f: C_TextFileStar) {.importc: "fclose", header: "<stdio.h>".}
proc c_sprintf(buf, frmt: cstring): cint {.header: "<stdio.h>",
importc: "sprintf", varargs, noSideEffect.}
proc c_sprintf(buf, frmt: cstring): cint {.
importc: "sprintf", header: "<stdio.h>", varargs, noSideEffect.}
# we use it only in a way that cannot lead to security issues
proc c_fread(buf: pointer, size, n: int, f: C_BinaryFileStar): int {.
importc: "fread", header: "<stdio.h>".}
proc c_fseek(f: C_BinaryFileStar, offset: clong, whence: int): int {.
importc: "fseek", header: "<stdio.h>".}
proc c_fileno(f: File): cint {.
importc: "fileno", header: "<fcntl.h>".}
proc c_fwrite(buf: pointer, size, n: int, f: C_BinaryFileStar): int {.
importc: "fwrite", header: "<stdio.h>".}
proc c_exit(errorcode: cint) {.importc: "exit", header: "<stdlib.h>".}
proc c_ferror(stream: C_TextFileStar): bool {.
importc: "ferror", header: "<stdio.h>".}
proc c_fflush(stream: C_TextFileStar) {.importc: "fflush", header: "<stdio.h>".}
proc c_abort() {.importc: "abort", header: "<stdlib.h>".}
proc c_feof(stream: C_TextFileStar): bool {.
importc: "feof", header: "<stdio.h>".}
proc c_malloc(size: int): pointer {.importc: "malloc", header: "<stdlib.h>".}
proc c_free(p: pointer) {.importc: "free", header: "<stdlib.h>".}
proc c_realloc(p: pointer, newsize: int): pointer {.
proc c_malloc(size: csize): pointer {.
importc: "malloc", header: "<stdlib.h>".}
proc c_free(p: pointer) {.
importc: "free", header: "<stdlib.h>".}
proc c_realloc(p: pointer, newsize: csize): pointer {.
importc: "realloc", header: "<stdlib.h>".}
when hostOS != "standalone":
when not declared(errno):
when defined(NimrodVM):
var vmErrnoWrapper {.importc.}: ptr cint
template errno: expr =
bind vmErrnoWrapper
vmErrnoWrapper[]
else:
var errno {.importc, header: "<errno.h>".}: cint ## error variable
proc strerror(errnum: cint): cstring {.importc, header: "<string.h>".}
proc c_remove(filename: cstring): cint {.
importc: "remove", header: "<stdio.h>".}
proc c_rename(oldname, newname: cstring): cint {.
importc: "rename", header: "<stdio.h>".}
proc c_system(cmd: cstring): cint {.importc: "system", header: "<stdlib.h>".}
proc c_getenv(env: cstring): cstring {.importc: "getenv", header: "<stdlib.h>".}
proc c_putenv(env: cstring): cint {.importc: "putenv", header: "<stdlib.h>".}
{.pop}

View File

@@ -211,14 +211,14 @@ proc genericReset(dest: pointer, mt: PNimType) =
zeroMem(dest, mt.size) # set raw bits to zero
proc selectBranch(discVal, L: int,
a: ptr array [0..0x7fff, ptr TNimNode]): ptr TNimNode =
a: ptr array[0..0x7fff, ptr TNimNode]): ptr TNimNode =
result = a[L] # a[L] contains the ``else`` part (but may be nil)
if discVal <% L:
var x = a[discVal]
if x != nil: result = x
proc FieldDiscriminantCheck(oldDiscVal, newDiscVal: int,
a: ptr array [0..0x7fff, ptr TNimNode],
a: ptr array[0..0x7fff, ptr TNimNode],
L: int) {.compilerProc.} =
var oldBranch = selectBranch(oldDiscVal, L, a)
var newBranch = selectBranch(newDiscVal, L, a)

View File

@@ -51,7 +51,6 @@ proc chckRangeF(x, a, b: float): float =
proc chckNil(p: pointer) =
if p == nil:
sysFatal(ValueError, "attempt to write to a nil address")
#c_raise(SIGSEGV)
proc chckObj(obj, subclass: PNimType) {.compilerproc.} =
# checks if obj is of type subclass:

View File

@@ -108,8 +108,8 @@ proc fileMatches(c, bp: cstring): bool =
# and the character for the suffix does not exist or
# is one of: \ / :
# depending on the OS case does not matter!
var blen: int = c_strlen(bp)
var clen: int = c_strlen(c)
var blen: int = bp.len
var clen: int = c.len
if blen > clen: return false
# check for \ / :
if clen-blen-1 >= 0 and c[clen-blen-1] notin {'\\', '/', ':'}:
@@ -159,7 +159,7 @@ type
{.deprecated: [THash: Hash, TWatchpoint: Watchpoint].}
var
watchpoints: array [0..99, Watchpoint]
watchpoints: array[0..99, Watchpoint]
watchpointsLen: int
proc `!&`(h: Hash, val: int): Hash {.inline.} =

View File

@@ -36,7 +36,7 @@ proc copyDeepString(src: NimString): NimString {.inline.} =
if src != nil:
result = rawNewStringNoInit(src.len)
result.len = src.len
c_memcpy(result.data, src.data, src.len + 1)
copyMem(addr(result.data), addr(src.data), src.len + 1)
proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) =
var
@@ -124,7 +124,9 @@ proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) =
copyMem(dest, src, mt.size)
proc genericDeepCopy(dest, src: pointer, mt: PNimType) {.compilerProc.} =
GC_disable()
genericDeepCopyAux(dest, src, mt)
GC_enable()
proc genericSeqDeepCopy(dest, src: pointer, mt: PNimType) {.compilerProc.} =
# also invoked for 'string'

View File

@@ -52,11 +52,14 @@ when defined(posix):
#
# c stuff:
var
RTLD_NOW {.importc: "RTLD_NOW", header: "<dlfcn.h>".}: int
when defined(linux) or defined(macosx):
const RTLD_NOW = cint(2)
else:
var
RTLD_NOW {.importc: "RTLD_NOW", header: "<dlfcn.h>".}: cint
proc dlclose(lib: LibHandle) {.importc, header: "<dlfcn.h>".}
proc dlopen(path: cstring, mode: int): LibHandle {.
proc dlopen(path: cstring, mode: cint): LibHandle {.
importc, header: "<dlfcn.h>".}
proc dlsym(lib: LibHandle, name: cstring): ProcAddr {.
importc, header: "<dlfcn.h>".}
@@ -109,9 +112,30 @@ elif defined(windows) or defined(dos):
proc nimGetProcAddr(lib: LibHandle, name: cstring): ProcAddr =
result = getProcAddress(cast[THINSTANCE](lib), name)
if result != nil: return
var decorated: array[250, char]
const decorated_length = 250
var decorated: array[decorated_length, char]
decorated[0] = '_'
var m = 1
while m < (decorated_length - 5):
if name[m - 1] == '\x00': break
decorated[m] = name[m - 1]
inc(m)
decorated[m] = '@'
for i in countup(0, 50):
discard csprintf(decorated, "_%s@%ld", name, i*4)
var k = i * 4
if k div 100 == 0:
if k div 10 == 0:
m = m + 1
else:
m = m + 2
else:
m = m + 3
decorated[m + 1] = '\x00'
while true:
decorated[m] = chr(ord('0') + (k %% 10))
dec(m)
k = k div 10
if k == 0: break
result = getProcAddress(cast[THINSTANCE](lib), decorated)
if result != nil: return
procAddrError(name)

View File

@@ -329,14 +329,14 @@ proc dbgStackFrame(s: cstring, start: int, currFrame: PFrame) =
proc readLine(f: File, line: var StaticStr): bool =
while true:
var c = fgetc(f)
var c = c_fgetc(f)
if c < 0'i32:
if line.len > 0: break
else: return false
if c == 10'i32: break # LF
if c == 13'i32: # CR
c = fgetc(f) # is the next char LF?
if c != 10'i32: ungetc(c, f) # no, put the character back
c = c_fgetc(f) # is the next char LF?
if c != 10'i32: discard c_ungetc(c, f) # no, put the character back
break
add line, chr(int(c))
result = true
@@ -475,7 +475,7 @@ proc dbgWriteStackTrace(f: PFrame) =
it = f
i = 0
total = 0
tempFrames: array [0..127, PFrame]
tempFrames: array[0..127, PFrame]
# setup long head:
while it != nil and i <= high(tempFrames)-firstCalls:
tempFrames[i] = it

View File

@@ -90,13 +90,13 @@ when defined(nativeStacktrace) and nativeStackTraceSupported:
when not hasThreadSupport:
var
tempAddresses: array [0..127, pointer] # should not be alloc'd on stack
tempAddresses: array[0..127, pointer] # should not be alloc'd on stack
tempDlInfo: TDl_info
proc auxWriteStackTraceWithBacktrace(s: var string) =
when hasThreadSupport:
var
tempAddresses: array [0..127, pointer] # but better than a threadvar
tempAddresses: array[0..127, pointer] # but better than a threadvar
tempDlInfo: TDl_info
# This is allowed to be expensive since it only happens during crashes
# (but this way you don't need manual stack tracing)
@@ -124,12 +124,12 @@ when defined(nativeStacktrace) and nativeStackTraceSupported:
when not hasThreadSupport:
var
tempFrames: array [0..127, PFrame] # should not be alloc'd on stack
tempFrames: array[0..127, PFrame] # should not be alloc'd on stack
proc auxWriteStackTrace(f: PFrame, s: var string) =
when hasThreadSupport:
var
tempFrames: array [0..127, PFrame] # but better than a threadvar
tempFrames: array[0..127, PFrame] # but better than a threadvar
const
firstCalls = 32
var
@@ -250,12 +250,12 @@ proc raiseExceptionAux(e: ref Exception) =
inc L, slen
template add(buf, s: expr) =
xadd(buf, s, s.len)
var buf: array [0..2000, char]
var buf: array[0..2000, char]
var L = 0
add(buf, "Error: unhandled exception: ")
if not isNil(e.msg): add(buf, e.msg)
add(buf, " [")
xadd(buf, e.name, c_strlen(e.name))
xadd(buf, e.name, e.name.len)
add(buf, "]\n")
showErrorMessage(buf)
quitOrDebug()

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2015 Andreas Rumpf
# (c) Copyright 2016 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -9,13 +9,8 @@
# Garbage Collector
#
# The basic algorithm is *Deferred Reference Counting* with cycle detection.
# This is achieved by combining a Deutsch-Bobrow garbage collector
# together with Christoper's partial mark-sweep garbage collector.
#
# Special care has been taken to avoid recursion as far as possible to avoid
# stack overflows when traversing deep datastructures. It is well-suited
# for soft real time applications (like games).
# Refcounting + Mark&Sweep. Complex algorithms avoided.
# Been there, done that, didn't work.
when defined(nimCoroutines):
import arch
@@ -30,7 +25,7 @@ const
# this seems to be a good value
withRealTime = defined(useRealtimeGC)
useMarkForDebug = defined(gcGenerational)
useBackupGc = false # use a simple M&S GC to collect
useBackupGc = true # use a simple M&S GC to collect
# cycles instead of the complex
# algorithm
@@ -55,8 +50,8 @@ type
WalkOp = enum
waMarkGlobal, # part of the backup/debug mark&sweep
waMarkPrecise, # part of the backup/debug mark&sweep
waZctDecRef, waPush, waCycleDecRef, waMarkGray, waScan, waScanBlack,
waCollectWhite #, waDebug
waZctDecRef, waPush
#, waDebug
Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, benign.}
# A ref type can have a finalizer that is called before the object's
@@ -87,7 +82,6 @@ type
idGenerator: int
zct: CellSeq # the zero count table
decStack: CellSeq # cells in the stack that are to decref again
cycleRoots: CellSet
tempStack: CellSeq # temporary stack for recursion elimination
recGcLock: int # prevent recursion via finalizers; no thread lock
when withRealTime:
@@ -96,6 +90,7 @@ type
stat: GcStat
when useMarkForDebug or useBackupGc:
marked: CellSet
additionalRoots: CellSeq # dummy roots for GC_ref/unref
when hasThreadSupport:
toDispose: SharedList[pointer]
@@ -136,9 +131,6 @@ proc usrToCell(usr: pointer): PCell {.inline.} =
# convert pointer to userdata to object (=pointer to refcount)
result = cast[PCell](cast[ByteAddress](usr)-%ByteAddress(sizeof(Cell)))
proc canBeCycleRoot(c: PCell): bool {.inline.} =
result = ntfAcyclic notin c.typ.flags
proc extGetCellType(c: pointer): PNimType {.compilerproc.} =
# used for code generation concerning debugging
result = usrToCell(c).typ
@@ -161,10 +153,10 @@ proc writeCell(msg: cstring, c: PCell) =
var kind = -1
if c.typ != nil: kind = ord(c.typ.kind)
when leakDetector:
c_fprintf(c_stdout, "[GC] %s: %p %d rc=%ld from %s(%ld)\n",
c_fprintf(stdout, "[GC] %s: %p %d rc=%ld from %s(%ld)\n",
msg, c, kind, c.refcount shr rcShift, c.filename, c.line)
else:
c_fprintf(c_stdout, "[GC] %s: %p %d rc=%ld; color=%ld\n",
c_fprintf(stdout, "[GC] %s: %p %d rc=%ld; color=%ld\n",
msg, c, kind, c.refcount shr rcShift, c.color)
template gcTrace(cell, state: expr): stmt {.immediate.} =
@@ -200,14 +192,16 @@ proc prepareDealloc(cell: PCell) =
(cast[Finalizer](cell.typ.finalizer))(cellToUsr(cell))
dec(gch.recGcLock)
template beforeDealloc(gch: var GcHeap; c: PCell; msg: typed) =
when false:
for i in 0..gch.decStack.len-1:
if gch.decStack.d[i] == c:
sysAssert(false, msg)
proc rtlAddCycleRoot(c: PCell) {.rtl, inl.} =
# we MUST access gch as a global here, because this crosses DLL boundaries!
when hasThreadSupport and hasSharedHeap:
acquireSys(HeapLock)
when cycleGC:
if c.color != rcPurple:
c.setColor(rcPurple)
incl(gch.cycleRoots, c)
when hasThreadSupport and hasSharedHeap:
releaseSys(HeapLock)
@@ -224,22 +218,30 @@ proc decRef(c: PCell) {.inline.} =
gcAssert(c.refcount >=% rcIncrement, "decRef")
if --c.refcount:
rtlAddZCT(c)
elif canbeCycleRoot(c):
# unfortunately this is necessary here too, because a cycle might just
# have been broken up and we could recycle it.
rtlAddCycleRoot(c)
#writeCell("decRef", c)
proc incRef(c: PCell) {.inline.} =
gcAssert(isAllocatedPtr(gch.region, c), "incRef: interiorPtr")
c.refcount = c.refcount +% rcIncrement
# and not colorMask
#writeCell("incRef", c)
if canbeCycleRoot(c):
rtlAddCycleRoot(c)
proc nimGCref(p: pointer) {.compilerProc, inline.} = incRef(usrToCell(p))
proc nimGCunref(p: pointer) {.compilerProc, inline.} = decRef(usrToCell(p))
proc nimGCref(p: pointer) {.compilerProc.} =
# we keep it from being collected by pretending it's not even allocated:
add(gch.additionalRoots, usrToCell(p))
incRef(usrToCell(p))
proc nimGCunref(p: pointer) {.compilerProc.} =
let cell = usrToCell(p)
var L = gch.additionalRoots.len-1
var i = L
let d = gch.additionalRoots.d
while i >= 0:
if d[i] == cell:
d[i] = d[L]
dec gch.additionalRoots.len
break
dec(i)
decRef(usrToCell(p))
proc GC_addCycleRoot*[T](p: ref T) {.inline.} =
## adds 'p' to the cycle candidate set for the cycle collector. It is
@@ -306,10 +308,10 @@ proc initGC() =
# init the rt
init(gch.zct)
init(gch.tempStack)
init(gch.cycleRoots)
init(gch.decStack)
when useMarkForDebug or useBackupGc:
init(gch.marked)
init(gch.additionalRoots)
when hasThreadSupport:
gch.toDispose = initSharedList[pointer]()
@@ -451,10 +453,13 @@ proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap): pointer =
gcAssert((cast[ByteAddress](res) and (MemAlign-1)) == 0, "newObj: 2")
# now it is buffered in the ZCT
res.typ = typ
when leakDetector and not hasThreadSupport:
if framePtr != nil and framePtr.prev != nil:
res.filename = framePtr.prev.filename
res.line = framePtr.prev.line
when leakDetector:
res.filename = nil
res.line = 0
when not hasThreadSupport:
if framePtr != nil and framePtr.prev != nil:
res.filename = framePtr.prev.filename
res.line = framePtr.prev.line
# refcount is zero, color is black, but mark it to be in the ZCT
res.refcount = ZctFlag
sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3")
@@ -502,10 +507,13 @@ proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} =
sysAssert((cast[ByteAddress](res) and (MemAlign-1)) == 0, "newObj: 2")
# now it is buffered in the ZCT
res.typ = typ
when leakDetector and not hasThreadSupport:
if framePtr != nil and framePtr.prev != nil:
res.filename = framePtr.prev.filename
res.line = framePtr.prev.line
when leakDetector:
res.filename = nil
res.line = 0
when not hasThreadSupport:
if framePtr != nil and framePtr.prev != nil:
res.filename = framePtr.prev.filename
res.line = framePtr.prev.line
res.refcount = rcIncrement # refcount is 1
sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3")
when logGC: writeCell("new cell", res)
@@ -563,7 +571,7 @@ proc growObj(old: pointer, newsize: int, gch: var GcHeap): pointer =
d[j] = res
break
dec(j)
if canbeCycleRoot(ol): excl(gch.cycleRoots, ol)
beforeDealloc(gch, ol, "growObj stack trash")
rawDealloc(gch.region, ol)
else:
# we split the old refcount in 2 parts. XXX This is still not entirely
@@ -597,54 +605,12 @@ proc freeCyclicCell(gch: var GcHeap, c: PCell) =
when logGC: writeCell("cycle collector dealloc cell", c)
when reallyDealloc:
sysAssert(allocInv(gch.region), "free cyclic cell")
beforeDealloc(gch, c, "freeCyclicCell: stack trash")
rawDealloc(gch.region, c)
else:
gcAssert(c.typ != nil, "freeCyclicCell")
zeroMem(c, sizeof(Cell))
proc markGray(s: PCell) =
if s.color != rcGray:
setColor(s, rcGray)
forAllChildren(s, waMarkGray)
proc scanBlack(s: PCell) =
s.setColor(rcBlack)
forAllChildren(s, waScanBlack)
proc scan(s: PCell) =
if s.color == rcGray:
if s.refcount >=% rcIncrement:
scanBlack(s)
else:
s.setColor(rcWhite)
forAllChildren(s, waScan)
proc collectWhite(s: PCell) =
# This is a hacky way to deal with the following problem (bug #1796)
# Consider this content in cycleRoots:
# x -> a; y -> a where 'a' is an acyclic object so not included in
# cycleRoots itself. Then 'collectWhite' used to free 'a' twice. The
# 'isAllocatedPtr' check prevents this. This also means we do not need
# to query 's notin gch.cycleRoots' at all.
if isAllocatedPtr(gch.region, s) and s.color == rcWhite:
s.setColor(rcBlack)
forAllChildren(s, waCollectWhite)
freeCyclicCell(gch, s)
proc markRoots(gch: var GcHeap) =
var tabSize = 0
for s in elements(gch.cycleRoots):
#writeCell("markRoot", s)
inc tabSize
if s.color == rcPurple and s.refcount >=% rcIncrement:
markGray(s)
else:
excl(gch.cycleRoots, s)
# (s.color == rcBlack and rc == 0) as 1 condition:
if s.refcount == 0:
freeCyclicCell(gch, s)
gch.stat.cycleTableSize = max(gch.stat.cycleTableSize, tabSize)
when useBackupGc:
proc sweep(gch: var GcHeap) =
for x in allObjects(gch.region):
@@ -666,16 +632,8 @@ when useMarkForDebug or useBackupGc:
proc markGlobals(gch: var GcHeap) =
for i in 0 .. < globalMarkersLen: globalMarkers[i]()
proc stackMarkS(gch: var GcHeap, p: pointer) {.inline.} =
# the addresses are not as cells on the stack, so turn them to cells:
var cell = usrToCell(p)
var c = cast[ByteAddress](cell)
if c >% PageSize:
# fast check: does it look like a cell?
var objStart = cast[PCell](interiorAllocatedPtr(gch.region, cell))
if objStart != nil:
markS(gch, objStart)
let d = gch.additionalRoots.d
for i in 0 .. < gch.additionalRoots.len: markS(gch, d[i])
when logGC:
var
@@ -697,7 +655,7 @@ when logGC:
else:
writeCell("cell {", s)
forAllChildren(s, waDebug)
c_fprintf(c_stdout, "}\n")
c_fprintf(stdout, "}\n")
proc doOperation(p: pointer, op: WalkOp) =
if p == nil: return
@@ -708,7 +666,7 @@ proc doOperation(p: pointer, op: WalkOp) =
case op
of waZctDecRef:
#if not isAllocatedPtr(gch.region, c):
# c_fprintf(c_stdout, "[GC] decref bug: %p", c)
# c_fprintf(stdout, "[GC] decref bug: %p", c)
gcAssert(isAllocatedPtr(gch.region, c), "decRef: waZctDecRef")
gcAssert(c.refcount >=% rcIncrement, "doOperation 2")
#c.refcount = c.refcount -% rcIncrement
@@ -717,19 +675,6 @@ proc doOperation(p: pointer, op: WalkOp) =
#if c.refcount <% rcIncrement: addZCT(gch.zct, c)
of waPush:
add(gch.tempStack, c)
of waCycleDecRef:
gcAssert(c.refcount >=% rcIncrement, "doOperation 3")
c.refcount = c.refcount -% rcIncrement
of waMarkGray:
gcAssert(c.refcount >=% rcIncrement, "waMarkGray")
c.refcount = c.refcount -% rcIncrement
markGray(c)
of waScan: scan(c)
of waScanBlack:
c.refcount = c.refcount +% rcIncrement
if c.color != rcBlack:
scanBlack(c)
of waCollectWhite: collectWhite(c)
of waMarkGlobal:
when useMarkForDebug or useBackupGc:
when hasThreadSupport:
@@ -748,14 +693,6 @@ proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} =
proc collectZCT(gch: var GcHeap): bool {.benign.}
when useMarkForDebug or useBackupGc:
proc markStackAndRegistersForSweep(gch: var GcHeap) {.noinline, cdecl,
benign.}
proc collectRoots(gch: var GcHeap) =
for s in elements(gch.cycleRoots):
collectWhite(s)
proc collectCycles(gch: var GcHeap) =
when hasThreadSupport:
for c in gch.toDispose:
@@ -764,33 +701,12 @@ proc collectCycles(gch: var GcHeap) =
while gch.zct.len > 0: discard collectZCT(gch)
when useBackupGc:
cellsetReset(gch.marked)
markStackAndRegistersForSweep(gch)
var d = gch.decStack.d
for i in 0..gch.decStack.len-1:
sysAssert isAllocatedPtr(gch.region, d[i]), "collectCycles"
markS(gch, d[i])
markGlobals(gch)
sweep(gch)
else:
markRoots(gch)
# scanRoots:
for s in elements(gch.cycleRoots): scan(s)
collectRoots(gch)
cellsetReset(gch.cycleRoots)
# alive cycles need to be kept in 'cycleRoots' if they are referenced
# from the stack; otherwise the write barrier will add the cycle root again
# anyway:
when false:
var d = gch.decStack.d
var cycleRootsLen = 0
for i in 0..gch.decStack.len-1:
var c = d[i]
gcAssert isAllocatedPtr(gch.region, c), "addBackStackRoots"
gcAssert c.refcount >=% rcIncrement, "addBackStackRoots: dead cell"
if canBeCycleRoot(c):
#if c notin gch.cycleRoots:
inc cycleRootsLen
incl(gch.cycleRoots, c)
gcAssert c.typ != nil, "addBackStackRoots 2"
if cycleRootsLen != 0:
cfprintf(cstdout, "cycle roots: %ld\n", cycleRootsLen)
proc gcMark(gch: var GcHeap, p: pointer) {.inline.} =
# the addresses are not as cells on the stack, so turn them to cells:
@@ -812,31 +728,11 @@ proc gcMark(gch: var GcHeap, p: pointer) {.inline.} =
add(gch.decStack, cell)
sysAssert(allocInv(gch.region), "gcMark end")
proc markThreadStacks(gch: var GcHeap) =
when hasThreadSupport and hasSharedHeap:
{.error: "not fully implemented".}
var it = threadList
while it != nil:
# mark registers:
for i in 0 .. high(it.registers): gcMark(gch, it.registers[i])
var sp = cast[ByteAddress](it.stackBottom)
var max = cast[ByteAddress](it.stackTop)
# XXX stack direction?
# XXX unroll this loop:
while sp <=% max:
gcMark(gch, cast[ppointer](sp)[])
sp = sp +% sizeof(pointer)
it = it.next
include gc_common
proc markStackAndRegisters(gch: var GcHeap) {.noinline, cdecl.} =
forEachStackSlot(gch, gcMark)
when useMarkForDebug or useBackupGc:
proc markStackAndRegistersForSweep(gch: var GcHeap) =
forEachStackSlot(gch, stackMarkS)
proc collectZCT(gch: var GcHeap): bool =
# Note: Freeing may add child objects to the ZCT! So essentially we do
# deep freeing, which is bad for incremental operation. In order to
@@ -866,8 +762,6 @@ proc collectZCT(gch: var GcHeap): bool =
# as this might be too slow.
# In any case, it should be removed from the ZCT. But not
# freed. **KEEP THIS IN MIND WHEN MAKING THIS INCREMENTAL!**
when cycleGC:
if canbeCycleRoot(c): excl(gch.cycleRoots, c)
when logGC: writeCell("zct dealloc cell", c)
gcTrace(c, csZctFreed)
# We are about to free the object, call the finalizer BEFORE its
@@ -877,6 +771,7 @@ proc collectZCT(gch: var GcHeap): bool =
forAllChildren(c, waZctDecRef)
when reallyDealloc:
sysAssert(allocInv(gch.region), "collectZCT: rawDealloc")
beforeDealloc(gch, c, "collectZCT: stack trash")
rawDealloc(gch.region, c)
else:
sysAssert(c.typ != nil, "collectZCT 2")
@@ -915,7 +810,6 @@ proc collectCTBody(gch: var GcHeap) =
sysAssert(gch.decStack.len == 0, "collectCT")
prepareForInteriorPointerChecking(gch.region)
markStackAndRegisters(gch)
markThreadStacks(gch)
gch.stat.maxStackCells = max(gch.stat.maxStackCells, gch.decStack.len)
inc(gch.stat.stackScans)
if collectZCT(gch):
@@ -935,12 +829,7 @@ proc collectCTBody(gch: var GcHeap) =
gch.stat.maxPause = max(gch.stat.maxPause, duration)
when defined(reportMissedDeadlines):
if gch.maxPause > 0 and duration > gch.maxPause:
c_fprintf(c_stdout, "[GC] missed deadline: %ld\n", duration)
when useMarkForDebug or useBackupGc:
proc markForDebug(gch: var GcHeap) =
markStackAndRegistersForSweep(gch)
markGlobals(gch)
c_fprintf(stdout, "[GC] missed deadline: %ld\n", duration)
when defined(nimCoroutines):
proc currentStackSizes(): int =
@@ -980,7 +869,19 @@ when withRealTime:
collectCTBody(gch)
release(gch)
proc GC_step*(us: int, strongAdvice = false) = GC_step(gch, us, strongAdvice)
proc GC_step*(us: int, strongAdvice = false, stackSize = -1) {.noinline.} =
var stackTop {.volatile.}: pointer
let prevStackBottom = gch.stackBottom
if stackSize >= 0:
stackTop = addr(stackTop)
when stackIncreases:
gch.stackBottom = cast[pointer](
cast[ByteAddress](stackTop) - sizeof(pointer) * 6 - stackSize)
else:
gch.stackBottom = cast[pointer](
cast[ByteAddress](stackTop) + sizeof(pointer) * 6 + stackSize)
GC_step(gch, us, strongAdvice)
gch.stackBottom = prevStackBottom
when not defined(useNimRtl):
proc GC_disable() =
@@ -1023,7 +924,7 @@ when not defined(useNimRtl):
"[GC] max threshold: " & $gch.stat.maxThreshold & "\n" &
"[GC] zct capacity: " & $gch.zct.cap & "\n" &
"[GC] max cycle table size: " & $gch.stat.cycleTableSize & "\n" &
"[GC] max pause time [ms]: " & $(gch.stat.maxPause div 1000_000)
"[GC] max pause time [ms]: " & $(gch.stat.maxPause div 1000_000) & "\n"
when defined(nimCoroutines):
result = result & "[GC] number of stacks: " & $gch.stack.len & "\n"
for stack in items(gch.stack):

View File

@@ -97,6 +97,8 @@ type
additionalRoots: CellSeq # dummy roots for GC_ref/unref
spaceIter: ObjectSpaceIter
dumpHeapFile: File # File that is used for GC_dumpHeap
when hasThreadSupport:
toDispose: SharedList[pointer]
var
gch {.rtlThreadVar.}: GcHeap
@@ -119,6 +121,8 @@ proc initGC() =
init(gch.decStack)
init(gch.additionalRoots)
init(gch.greyStack)
when hasThreadSupport:
gch.toDispose = initSharedList[pointer]()
# Which color to use for new objects is tricky: When we're marking,
# they have to be *white* so that everything is marked that is only
@@ -185,7 +189,7 @@ proc writeCell(file: File; msg: cstring, c: PCell) =
msg, id, kind, c.refcount shr rcShift, col)
proc writeCell(msg: cstring, c: PCell) =
c_stdout.writeCell(msg, c)
stdout.writeCell(msg, c)
proc myastToStr[T](x: T): string {.magic: "AstToStr", noSideEffect.}
@@ -259,7 +263,7 @@ proc nimGCunref(p: pointer) {.compilerProc.} =
template markGrey(x: PCell) =
if x.color != 1-gch.black and gch.phase == Phase.Marking:
if not isAllocatedPtr(gch.region, x):
c_fprintf(c_stdout, "[GC] markGrey proc: %p\n", x)
c_fprintf(stdout, "[GC] markGrey proc: %p\n", x)
#GC_dumpHeap()
sysAssert(false, "wtf")
x.setColor(rcGrey)
@@ -675,7 +679,7 @@ proc sweep(gch: var GcHeap): bool =
takeStartTime(100)
#echo "loop start"
let white = 1-gch.black
#cfprintf(cstdout, "black is %d\n", black)
#c_fprintf(stdout, "black is %d\n", black)
while true:
let x = allObjectsAsProc(gch.region, addr gch.spaceIter)
if gch.spaceIter.state < 0: break
@@ -715,7 +719,7 @@ proc markIncremental(gch: var GcHeap): bool =
while L[] > 0:
var c = gch.greyStack.d[0]
if not isAllocatedPtr(gch.region, c):
c_fprintf(c_stdout, "[GC] not allocated anymore: %p\n", c)
c_fprintf(stdout, "[GC] not allocated anymore: %p\n", c)
#GC_dumpHeap()
sysAssert(false, "wtf")
@@ -760,7 +764,7 @@ proc doOperation(p: pointer, op: WalkOp) =
case op
of waZctDecRef:
#if not isAllocatedPtr(gch.region, c):
# c_fprintf(c_stdout, "[GC] decref bug: %p", c)
# c_fprintf(stdout, "[GC] decref bug: %p", c)
gcAssert(isAllocatedPtr(gch.region, c), "decRef: waZctDecRef")
gcAssert(c.refcount >=% rcIncrement, "doOperation 2")
#c.refcount = c.refcount -% rcIncrement
@@ -779,14 +783,14 @@ proc doOperation(p: pointer, op: WalkOp) =
else:
#gcAssert(isAllocatedPtr(gch.region, c), "doOperation: waMarkGlobal")
if not isAllocatedPtr(gch.region, c):
c_fprintf(c_stdout, "[GC] not allocated anymore: MarkGlobal %p\n", c)
c_fprintf(stdout, "[GC] not allocated anymore: MarkGlobal %p\n", c)
#GC_dumpHeap()
sysAssert(false, "wtf")
handleRoot()
discard allocInv(gch.region)
of waMarkGrey:
if not isAllocatedPtr(gch.region, c):
c_fprintf(c_stdout, "[GC] not allocated anymore: MarkGrey %p\n", c)
c_fprintf(stdout, "[GC] not allocated anymore: MarkGrey %p\n", c)
#GC_dumpHeap()
sysAssert(false, "wtf")
if c.color == 1-gch.black:
@@ -800,6 +804,10 @@ proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} =
proc collectZCT(gch: var GcHeap): bool {.benign.}
proc collectCycles(gch: var GcHeap): bool =
when hasThreadSupport:
for c in gch.toDispose:
nimGCunref(c)
# ensure the ZCT 'color' is not used:
while gch.zct.len > 0: discard collectZCT(gch)
@@ -808,7 +816,7 @@ proc collectCycles(gch: var GcHeap): bool =
gch.phase = Phase.Marking
markGlobals(gch)
cfprintf(stdout, "collectCycles: introduced bug E %ld\n", gch.phase)
c_fprintf(stdout, "collectCycles: introduced bug E %ld\n", gch.phase)
discard allocInv(gch.region)
of Phase.Marking:
# since locals do not have a write barrier, we need
@@ -922,7 +930,7 @@ proc collectCTBody(gch: var GcHeap) =
gch.stat.maxPause = max(gch.stat.maxPause, duration)
when defined(reportMissedDeadlines):
if gch.maxPause > 0 and duration > gch.maxPause:
c_fprintf(c_stdout, "[GC] missed deadline: %ld\n", duration)
c_fprintf(stdout, "[GC] missed deadline: %ld\n", duration)
when defined(nimCoroutines):
proc currentStackSizes(): int =
@@ -956,7 +964,19 @@ when withRealTime:
strongAdvice:
collectCTBody(gch)
proc GC_step*(us: int, strongAdvice = false) = GC_step(gch, us, strongAdvice)
proc GC_step*(us: int, strongAdvice = false, stackSize = -1) {.noinline.} =
var stackTop {.volatile.}: pointer
let prevStackBottom = gch.stackBottom
if stackSize >= 0:
stackTop = addr(stackTop)
when stackIncreases:
gch.stackBottom = cast[pointer](
cast[ByteAddress](stackTop) - sizeof(pointer) * 6 - stackSize)
else:
gch.stackBottom = cast[pointer](
cast[ByteAddress](stackTop) + sizeof(pointer) * 6 + stackSize)
GC_step(gch, us, strongAdvice)
gch.stackBottom = prevStackBottom
when not defined(useNimRtl):
proc GC_disable() =

View File

@@ -63,7 +63,7 @@ when defined(nimCoroutines):
stack.next = gch.stack
gch.stack.prev = stack
gch.stack = stack
# c_fprintf(c_stdout, "[GC] added stack 0x%016X\n", starts)
# c_fprintf(stdout, "[GC] added stack 0x%016X\n", starts)
proc GC_removeStack*(starts: pointer) {.cdecl, exportc.} =
var stack = gch.stack
@@ -143,7 +143,7 @@ else:
when not defined(useNimRtl):
{.push stack_trace: off.}
proc setStackBottom(theStackBottom: pointer) =
#c_fprintf(c_stdout, "stack bottom: %p;\n", theStackBottom)
#c_fprintf(stdout, "stack bottom: %p;\n", theStackBottom)
# the first init must be the one that defines the stack bottom:
when defined(nimCoroutines):
GC_addStack(theStackBottom)
@@ -152,7 +152,7 @@ when not defined(useNimRtl):
else:
var a = cast[ByteAddress](theStackBottom) # and not PageMask - PageSize*2
var b = cast[ByteAddress](gch.stackBottom)
#c_fprintf(c_stdout, "old: %p new: %p;\n",gch.stackBottom,theStackBottom)
#c_fprintf(stdout, "old: %p new: %p;\n",gch.stackBottom,theStackBottom)
when stackIncreases:
gch.stackBottom = cast[pointer](min(a, b))
else:
@@ -239,7 +239,7 @@ else:
# We use a jmp_buf buffer that is in the C stack.
# Used to traverse the stack and registers assuming
# that 'setjmp' will save registers in the C stack.
type PStackSlice = ptr array [0..7, pointer]
type PStackSlice = ptr array[0..7, pointer]
var registers {.noinit.}: Registers
getRegisters(registers)
for i in registers.low .. registers.high:
@@ -277,7 +277,7 @@ else:
# We use a jmp_buf buffer that is in the C stack.
# Used to traverse the stack and registers assuming
# that 'setjmp' will save registers in the C stack.
type PStackSlice = ptr array [0..7, pointer]
type PStackSlice = ptr array[0..7, pointer]
var registers {.noinit.}: C_JmpBuf
if c_setjmp(registers) == 0'i32: # To fill the C stack with registers.
var max = cast[ByteAddress](gch.stackBottom)

View File

@@ -28,6 +28,9 @@ template mulThreshold(x): expr {.immediate.} = x * 2
when defined(memProfiler):
proc nimProfile(requestedSize: int)
when hasThreadSupport:
import sharedlist
type
WalkOp = enum

View File

@@ -8,7 +8,23 @@
# "Stack GC" for embedded devices or ultra performance requirements.
include osalloc
when defined(nimphpext):
proc roundup(x, v: int): int {.inline.} =
result = (x + (v-1)) and not (v-1)
proc emalloc(size: int): pointer {.importc: "_emalloc".}
proc efree(mem: pointer) {.importc: "_efree".}
proc osAllocPages(size: int): pointer {.inline.} =
emalloc(size)
proc osTryAllocPages(size: int): pointer {.inline.} =
emalloc(size)
proc osDeallocPages(p: pointer, size: int) {.inline.} =
efree(p)
else:
include osalloc
# We manage memory as a thread local stack. Since the allocation pointer
# is detached from the control flow pointer, this model is vastly more
@@ -36,33 +52,34 @@ type
BaseChunk = object
next: Chunk
size: int
head, last: ptr ObjHeader # first and last object in chunk that
head, tail: ptr ObjHeader # first and last object in chunk that
# has a finalizer attached to it
type
StackPtr = object
chunk: pointer
bump: pointer
remaining: int
current: Chunk
MemRegion* = object
remaining: int
chunk: pointer
head, last: Chunk
bump: pointer
head, tail: Chunk
nextChunkSize, totalSize: int
hole: ptr Hole # we support individual freeing
lock: SysLock
when hasThreadSupport:
lock: SysLock
var
region {.threadVar.}: MemRegion
tlRegion {.threadVar.}: MemRegion
template withRegion*(r: MemRegion; body: untyped) =
let oldRegion = region
region = r
let oldRegion = tlRegion
tlRegion = r
try:
body
finally:
region = oldRegion
tlRegion = oldRegion
template inc(p: pointer, s: int) =
p = cast[pointer](cast[int](p) +% s)
@@ -71,7 +88,7 @@ template `+!`(p: pointer, s: int): pointer =
cast[pointer](cast[int](p) +% s)
template `-!`(p: pointer, s: int): pointer =
cast[pointer](cast[int](p) +% s)
cast[pointer](cast[int](p) -% s)
proc allocSlowPath(r: var MemRegion; size: int) =
# we need to ensure that the underlying linked list
@@ -84,7 +101,7 @@ proc allocSlowPath(r: var MemRegion; size: int) =
r.nextChunkSize =
if r.totalSize < 64 * 1024: PageSize*4
else: r.nextChunkSize*2
var s = align(size+sizeof(BaseChunk), PageSize)
var s = roundup(size+sizeof(BaseChunk), PageSize)
var fresh: Chunk
if s > r.nextChunkSize:
fresh = cast[Chunk](osAllocPages(s))
@@ -97,22 +114,26 @@ proc allocSlowPath(r: var MemRegion; size: int) =
else:
s = r.nextChunkSize
fresh.size = s
fresh.final = nil
r.totalSize += s
let old = r.last
fresh.head = nil
fresh.tail = nil
fresh.next = nil
inc r.totalSize, s
let old = r.tail
if old == nil:
r.head = fresh
else:
r.last.next = fresh
r.chunk = fresh +! sizeof(BaseChunk)
r.last = fresh
r.tail.next = fresh
r.bump = fresh +! sizeof(BaseChunk)
r.tail = fresh
r.remaining = s - sizeof(BaseChunk)
proc alloc(r: var MemRegion; size: int): pointer {.inline.} =
if unlikely(r.remaining < size): allocSlowPath(r, size)
if size > r.remaining:
allocSlowPath(r, size)
sysAssert(size <= r.remaining, "size <= r.remaining")
dec(r.remaining, size)
result = r.chunk
inc r.chunk, size
result = r.bump
inc r.bump, size
proc runFinalizers(c: Chunk) =
var it = c.head
@@ -120,228 +141,245 @@ proc runFinalizers(c: Chunk) =
# indivually freed objects with finalizer stay in the list, but
# their typ is nil then:
if it.typ != nil and it.typ.finalizer != nil:
(cast[Finalizer](cell.typ.finalizer))(cell+!sizeof(ObjHeader))
it = it.next
(cast[Finalizer](it.typ.finalizer))(it+!sizeof(ObjHeader))
it = it.nextFinal
proc dealloc(r: var MemRegion; p: pointer) =
let it = p-!sizeof(ObjHeader)
if it.typ != nil and it.typ.finalizer != nil:
(cast[Finalizer](cell.typ.finalizer))(p)
it.typ = nil
when false:
proc dealloc(r: var MemRegion; p: pointer) =
let it = cast[ptr ObjHeader](p-!sizeof(ObjHeader))
if it.typ != nil and it.typ.finalizer != nil:
(cast[Finalizer](it.typ.finalizer))(p)
it.typ = nil
proc deallocAll(head: Chunk) =
proc deallocAll(r: var MemRegion; head: Chunk) =
var it = head
while it != nil:
let nxt = it.next
runFinalizers(it)
dec r.totalSize, it.size
osDeallocPages(it, it.size)
it = it.next
it = nxt
proc deallocAll*(r: var MemRegion) =
deallocAll(r.head)
deallocAll(r, r.head)
zeroMem(addr r, sizeof r)
proc obstackPtr*(r: MemRegion): StackPtr =
result.chunk = r.chunk
result.bump = r.bump
result.remaining = r.remaining
result.current = r.last
result.current = r.tail
proc setObstackPtr*(r: MemRegion; sp: StackPtr) =
template computeRemaining(r): untyped =
r.tail.size -% (cast[int](r.bump) -% cast[int](r.tail))
proc setObstackPtr*(r: var MemRegion; sp: StackPtr) =
# free everything after 'sp':
if sp.current != nil:
deallocAll(sp.current.next)
r.chunk = sp.chunk
deallocAll(r, sp.current.next)
sp.current.next = nil
else:
deallocAll(r, r.head)
r.head = nil
r.bump = sp.bump
r.tail = sp.current
r.remaining = sp.remaining
r.last = sp.current
proc obstackPtr*(): StackPtr = tlRegion.obstackPtr()
proc setObstackPtr*(sp: StackPtr) = tlRegion.setObstackPtr(sp)
proc deallocAll*() = tlRegion.deallocAll()
proc deallocOsPages(r: var MemRegion) = r.deallocAll()
proc joinRegion*(dest: var MemRegion; src: MemRegion) =
# merging is not hard.
if dest.head.isNil:
dest.head = src.head
else:
dest.last.next = src.head
dest.last = src.last
dest.chunk = src.chunk
dest.tail.next = src.head
dest.tail = src.tail
dest.bump = src.bump
dest.remaining = src.remaining
dest.nextChunkSize = max(dest.nextChunkSize, src.nextChunkSize)
dest.totalSize += src.totalSize
if dest.hole.size < src.hole.size:
dest.hole = src.hole
inc dest.totalSize, src.totalSize
proc isOnHeap*(r: MemRegion; p: pointer): bool =
# the last chunk is the largest, so check it first. It's also special
# the tail chunk is the largest, so check it first. It's also special
# in that contains the current bump pointer:
if r.last >= p and p < r.chunk:
if r.tail >= p and p < r.bump:
return true
var it = r.head
while it != r.last:
while it != r.tail:
if it >= p and p <= it+!it.size: return true
it = it.next
proc isInteriorPointer(r: MemRegion; p: pointer): pointer =
discard " we cannot patch stack pointers anyway!"
when false:
# essential feature for later: copy data over from one region to another
type
PointerStackChunk = object
next, prev: ptr PointerStackChunk
len: int
data: array[128, pointer]
proc isInteriorPointer(r: MemRegion; p: pointer): pointer =
discard " we cannot patch stack pointers anyway!"
template head(s: PointerStackChunk): untyped = s.prev
template tail(s: PointerStackChunk): untyped = s.next
type
PointerStackChunk = object
next, prev: ptr PointerStackChunk
len: int
data: array[128, pointer]
include chains
template head(s: PointerStackChunk): untyped = s.prev
template tail(s: PointerStackChunk): untyped = s.next
proc push(r: var MemRegion; s: var PointerStackChunk; x: pointer) =
if s.len < high(s.data):
s.data[s.len] = x
inc s.len
else:
let fresh = cast[ptr PointerStackChunk](alloc(r, sizeof(PointerStackChunk)))
fresh.len = 1
fresh.data[0] = x
fresh.next = nil
fresh.prev = nil
append(s, fresh)
include chains
proc genericDeepCopyAux(dr: var MemRegion; stack: var PointerStackChunk;
dest, src: pointer, mt: PNimType) {.benign.}
proc genericDeepCopyAux(dr: var MemRegion; stack: var PointerStackChunk;
dest, src: pointer, n: ptr TNimNode) {.benign.} =
var
d = cast[ByteAddress](dest)
s = cast[ByteAddress](src)
case n.kind
of nkSlot:
genericDeepCopyAux(cast[pointer](d +% n.offset),
cast[pointer](s +% n.offset), n.typ)
of nkList:
for i in 0..n.len-1:
genericDeepCopyAux(dest, src, n.sons[i])
of nkCase:
var dd = selectBranch(dest, n)
var m = selectBranch(src, n)
# reset if different branches are in use; note different branches also
# imply that's not self-assignment (``x = x``)!
if m != dd and dd != nil:
genericResetAux(dest, dd)
copyMem(cast[pointer](d +% n.offset), cast[pointer](s +% n.offset),
n.typ.size)
if m != nil:
genericDeepCopyAux(dest, src, m)
of nkNone: sysAssert(false, "genericDeepCopyAux")
proc copyDeepString(dr: var MemRegion; stack: var PointerStackChunk; src: NimString): NimString {.inline.} =
result = rawNewStringNoInit(dr, src.len)
result.len = src.len
c_memcpy(result.data, src.data, src.len + 1)
proc genericDeepCopyAux(dr: var MemRegion; stack: var PointerStackChunk;
dest, src: pointer, mt: PNimType) =
var
d = cast[ByteAddress](dest)
s = cast[ByteAddress](src)
sysAssert(mt != nil, "genericDeepCopyAux 2")
case mt.kind
of tyString:
var x = cast[PPointer](dest)
var s2 = cast[PPointer](s)[]
if s2 == nil:
x[] = nil
proc push(r: var MemRegion; s: var PointerStackChunk; x: pointer) =
if s.len < high(s.data):
s.data[s.len] = x
inc s.len
else:
x[] = copyDeepString(cast[NimString](s2))
of tySequence:
var s2 = cast[PPointer](src)[]
var seq = cast[PGenericSeq](s2)
var x = cast[PPointer](dest)
if s2 == nil:
x[] = nil
return
sysAssert(dest != nil, "genericDeepCopyAux 3")
x[] = newSeq(mt, seq.len)
var dst = cast[ByteAddress](cast[PPointer](dest)[])
for i in 0..seq.len-1:
genericDeepCopyAux(dr, stack,
cast[pointer](dst +% i*% mt.base.size +% GenericSeqSize),
cast[pointer](cast[ByteAddress](s2) +% i *% mt.base.size +%
GenericSeqSize),
mt.base)
of tyObject:
# we need to copy m_type field for tyObject, as it could be empty for
# sequence reallocations:
var pint = cast[ptr PNimType](dest)
pint[] = cast[ptr PNimType](src)[]
if mt.base != nil:
genericDeepCopyAux(dr, stack, dest, src, mt.base)
genericDeepCopyAux(dr, stack, dest, src, mt.node)
of tyTuple:
genericDeepCopyAux(dr, stack, dest, src, mt.node)
of tyArray, tyArrayConstr:
for i in 0..(mt.size div mt.base.size)-1:
genericDeepCopyAux(dr, stack,
cast[pointer](d +% i*% mt.base.size),
cast[pointer](s +% i*% mt.base.size), mt.base)
of tyRef:
let s2 = cast[PPointer](src)[]
if s2 == nil:
cast[PPointer](dest)[] = nil
else:
# we modify the header of the cell temporarily; instead of the type
# field we store a forwarding pointer. XXX This is bad when the cloning
# fails due to OOM etc.
let x = usrToCell(s2)
let forw = cast[int](x.typ)
if (forw and 1) == 1:
# we stored a forwarding pointer, so let's use that:
let z = cast[pointer](forw and not 1)
unsureAsgnRef(cast[PPointer](dest), z)
let fresh = cast[ptr PointerStackChunk](alloc(r, sizeof(PointerStackChunk)))
fresh.len = 1
fresh.data[0] = x
fresh.next = nil
fresh.prev = nil
append(s, fresh)
proc genericDeepCopyAux(dr: var MemRegion; stack: var PointerStackChunk;
dest, src: pointer, mt: PNimType) {.benign.}
proc genericDeepCopyAux(dr: var MemRegion; stack: var PointerStackChunk;
dest, src: pointer, n: ptr TNimNode) {.benign.} =
var
d = cast[ByteAddress](dest)
s = cast[ByteAddress](src)
case n.kind
of nkSlot:
genericDeepCopyAux(cast[pointer](d +% n.offset),
cast[pointer](s +% n.offset), n.typ)
of nkList:
for i in 0..n.len-1:
genericDeepCopyAux(dest, src, n.sons[i])
of nkCase:
var dd = selectBranch(dest, n)
var m = selectBranch(src, n)
# reset if different branches are in use; note different branches also
# imply that's not self-assignment (``x = x``)!
if m != dd and dd != nil:
genericResetAux(dest, dd)
copyMem(cast[pointer](d +% n.offset), cast[pointer](s +% n.offset),
n.typ.size)
if m != nil:
genericDeepCopyAux(dest, src, m)
of nkNone: sysAssert(false, "genericDeepCopyAux")
proc copyDeepString(dr: var MemRegion; stack: var PointerStackChunk; src: NimString): NimString {.inline.} =
result = rawNewStringNoInit(dr, src.len)
result.len = src.len
copyMem(result.data, src.data, src.len + 1)
proc genericDeepCopyAux(dr: var MemRegion; stack: var PointerStackChunk;
dest, src: pointer, mt: PNimType) =
var
d = cast[ByteAddress](dest)
s = cast[ByteAddress](src)
sysAssert(mt != nil, "genericDeepCopyAux 2")
case mt.kind
of tyString:
var x = cast[PPointer](dest)
var s2 = cast[PPointer](s)[]
if s2 == nil:
x[] = nil
else:
let realType = x.typ
let z = newObj(realType, realType.base.size)
x[] = copyDeepString(cast[NimString](s2))
of tySequence:
var s2 = cast[PPointer](src)[]
var seq = cast[PGenericSeq](s2)
var x = cast[PPointer](dest)
if s2 == nil:
x[] = nil
return
sysAssert(dest != nil, "genericDeepCopyAux 3")
x[] = newSeq(mt, seq.len)
var dst = cast[ByteAddress](cast[PPointer](dest)[])
for i in 0..seq.len-1:
genericDeepCopyAux(dr, stack,
cast[pointer](dst +% i*% mt.base.size +% GenericSeqSize),
cast[pointer](cast[ByteAddress](s2) +% i *% mt.base.size +%
GenericSeqSize),
mt.base)
of tyObject:
# we need to copy m_type field for tyObject, as it could be empty for
# sequence reallocations:
var pint = cast[ptr PNimType](dest)
pint[] = cast[ptr PNimType](src)[]
if mt.base != nil:
genericDeepCopyAux(dr, stack, dest, src, mt.base)
genericDeepCopyAux(dr, stack, dest, src, mt.node)
of tyTuple:
genericDeepCopyAux(dr, stack, dest, src, mt.node)
of tyArray, tyArrayConstr:
for i in 0..(mt.size div mt.base.size)-1:
genericDeepCopyAux(dr, stack,
cast[pointer](d +% i*% mt.base.size),
cast[pointer](s +% i*% mt.base.size), mt.base)
of tyRef:
let s2 = cast[PPointer](src)[]
if s2 == nil:
cast[PPointer](dest)[] = nil
else:
# we modify the header of the cell temporarily; instead of the type
# field we store a forwarding pointer. XXX This is bad when the cloning
# fails due to OOM etc.
let x = usrToCell(s2)
let forw = cast[int](x.typ)
if (forw and 1) == 1:
# we stored a forwarding pointer, so let's use that:
let z = cast[pointer](forw and not 1)
unsureAsgnRef(cast[PPointer](dest), z)
else:
let realType = x.typ
let z = newObj(realType, realType.base.size)
unsureAsgnRef(cast[PPointer](dest), z)
x.typ = cast[PNimType](cast[int](z) or 1)
genericDeepCopyAux(dr, stack, z, s2, realType.base)
x.typ = realType
else:
copyMem(dest, src, mt.size)
proc joinAliveDataFromRegion*(dest: var MemRegion; src: var MemRegion;
root: pointer): pointer =
# we mark the alive data and copy only alive data over to 'dest'.
# This is O(liveset) but it nicely compacts memory, so it's fine.
# We use the 'typ' field as a forwarding pointer. The forwarding
# pointers have bit 0 set, so we can disambiguate them.
# We allocate a temporary stack in 'src' that we later free:
var s: PointerStackChunk
s.len = 1
s.data[0] = root
while s.len > 0:
var p: pointer
if s.tail == nil:
p = s.data[s.len-1]
dec s.len
unsureAsgnRef(cast[PPointer](dest), z)
x.typ = cast[PNimType](cast[int](z) or 1)
genericDeepCopyAux(dr, stack, z, s2, realType.base)
x.typ = realType
else:
p = s.tail.data[s.tail.len-1]
dec s.tail.len
if s.tail.len == 0:
unlink(s, s.tail)
copyMem(dest, src, mt.size)
proc joinAliveDataFromRegion*(dest: var MemRegion; src: var MemRegion;
root: pointer): pointer =
# we mark the alive data and copy only alive data over to 'dest'.
# This is O(liveset) but it nicely compacts memory, so it's fine.
# We use the 'typ' field as a forwarding pointer. The forwarding
# pointers have bit 0 set, so we can disambiguate them.
# We allocate a temporary stack in 'src' that we later free:
var s: PointerStackChunk
s.len = 1
s.data[0] = root
while s.len > 0:
var p: pointer
if s.tail == nil:
p = s.data[s.len-1]
dec s.len
else:
p = s.tail.data[s.tail.len-1]
dec s.tail.len
if s.tail.len == 0:
unlink(s, s.tail)
proc rawNewObj(r: var MemRegion, typ: PNimType, size: int): pointer =
var res = cast[ptr ObjHeader](alloc(r, size + sizeof(ObjHeader)))
res.typ = typ
if typ.finalizer != nil:
res.nextFinal = r.chunk.head
r.chunk.head = res
res.nextFinal = r.head.head
r.head.head = res
result = res +! sizeof(ObjHeader)
proc newObj(typ: PNimType, size: int): pointer {.compilerRtl.} =
result = rawNewObj(typ, size, region)
result = rawNewObj(tlRegion, typ, size)
zeroMem(result, size)
when defined(memProfiler): nimProfile(size)
proc newObjNoInit(typ: PNimType, size: int): pointer {.compilerRtl.} =
result = rawNewObj(typ, size, region)
result = rawNewObj(tlRegion, typ, size)
when defined(memProfiler): nimProfile(size)
proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} =
@@ -351,7 +389,7 @@ proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} =
cast[PGenericSeq](result).reserved = len
proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} =
result = rawNewObj(typ, size, gch)
result = rawNewObj(tlRegion, typ, size)
zeroMem(result, size)
proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl.} =
@@ -360,23 +398,70 @@ proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl.} =
cast[PGenericSeq](result).len = len
cast[PGenericSeq](result).reserved = len
proc growObj(old: pointer, newsize: int, gch: var GcHeap): pointer =
collectCT(gch)
var ol = usrToCell(old)
sysAssert(ol.typ != nil, "growObj: 1")
gcAssert(ol.typ.kind in {tyString, tySequence}, "growObj: 2")
var res = cast[PCell](rawAlloc(gch.region, newsize + sizeof(Cell)))
var elemSize = 1
if ol.typ.kind != tyString: elemSize = ol.typ.base.size
var oldsize = cast[PGenericSeq](old).len*elemSize + GenericSeqSize
copyMem(res, ol, oldsize + sizeof(Cell))
zeroMem(cast[pointer](cast[ByteAddress](res)+% oldsize +% sizeof(Cell)),
newsize-oldsize)
sysAssert((cast[ByteAddress](res) and (MemAlign-1)) == 0, "growObj: 3")
result = cellToUsr(res)
proc growObj(region: var MemRegion; old: pointer, newsize: int): pointer =
let typ = cast[ptr ObjHeader](old -! sizeof(ObjHeader)).typ
result = rawNewObj(region, typ, newsize)
let elemSize = if typ.kind == tyString: 1 else: typ.base.size
let oldsize = cast[PGenericSeq](old).len*elemSize + GenericSeqSize
copyMem(result, old, oldsize)
zeroMem(result +! oldsize, newsize-oldsize)
proc growObj(old: pointer, newsize: int): pointer {.rtl.} =
result = growObj(old, newsize, region)
result = growObj(tlRegion, old, newsize)
proc unsureAsgnRef(dest: PPointer, src: pointer) {.compilerproc, inline.} =
dest[] = src
proc asgnRef(dest: PPointer, src: pointer) {.compilerproc, inline.} =
dest[] = src
proc asgnRefNoCycle(dest: PPointer, src: pointer) {.compilerproc, inline.} =
dest[] = src
proc alloc(size: Natural): pointer =
result = c_malloc(size)
if result == nil: raiseOutOfMem()
proc alloc0(size: Natural): pointer =
result = alloc(size)
zeroMem(result, size)
proc realloc(p: pointer, newsize: Natural): pointer =
result = c_realloc(p, newsize)
if result == nil: raiseOutOfMem()
proc dealloc(p: pointer) = c_free(p)
proc alloc0(r: var MemRegion; size: Natural): pointer =
# ignore the region. That is correct for the channels module
# but incorrect in general. XXX
result = alloc0(size)
proc dealloc(r: var MemRegion; p: pointer) = dealloc(p)
proc allocShared(size: Natural): pointer =
result = c_malloc(size)
if result == nil: raiseOutOfMem()
proc allocShared0(size: Natural): pointer =
result = alloc(size)
zeroMem(result, size)
proc reallocShared(p: pointer, newsize: Natural): pointer =
result = c_realloc(p, newsize)
if result == nil: raiseOutOfMem()
proc deallocShared(p: pointer) = c_free(p)
when hasThreadSupport:
proc getFreeSharedMem(): int = 0
proc getTotalSharedMem(): int = 0
proc getOccupiedSharedMem(): int = 0
proc GC_disable() = discard
proc GC_enable() = discard
proc GC_fullCollect() = discard
proc GC_setStrategy(strategy: GC_Strategy) = discard
proc GC_enableMarkAndSweep() = discard
proc GC_disableMarkAndSweep() = discard
proc GC_getStatistics(): string = return ""
proc getOccupiedMem(): int =
result = tlRegion.totalSize - tlRegion.remaining
proc getFreeMem(): int = tlRegion.remaining
proc getTotalMem(): int =
result = tlRegion.totalSize
proc setStackBottom(theStackBottom: pointer) = discard

View File

@@ -71,7 +71,7 @@ type
typ: ptr TNimType
name: cstring
len: int
sons: ptr array [0..0x7fff, ptr TNimNode]
sons: ptr array[0..0x7fff, ptr TNimNode]
TNimTypeFlag = enum
ntfNoRefs = 0, # type contains no tyRef, tySequence, tyString

View File

@@ -19,6 +19,11 @@
when not defined(nimNewShared):
{.pragma: gcsafe.}
when not defined(nimImmediateDeprecated):
{.pragma: oldimmediate, immediate.}
else:
{.pragma: oldimmediate.}
when defined(createNimRtl):
when defined(useNimRtl):
{.error: "Cannot create and use nimrtl at the same time!".}

View File

@@ -65,7 +65,7 @@ proc auxWriteStackTrace(f: PCallFrame): string =
it = f
i = 0
total = 0
tempFrames: array [0..63, TempFrame]
tempFrames: array[0..63, TempFrame]
while it != nil and i <= high(tempFrames):
tempFrames[i].procname = it.procname
tempFrames[i].line = it.line
@@ -97,6 +97,8 @@ proc rawWriteStackTrace(): string =
else:
result = "No stack traceback available\n"
proc getStackTrace*(): string = rawWriteStackTrace()
proc unhandledException(e: ref Exception) {.
compilerproc, asmNoStackFrame.} =
when NimStackTrace:
@@ -119,7 +121,10 @@ proc raiseException(e: ref Exception, ename: cstring) {.
when not defined(noUnhandledHandler):
if excHandler == 0:
unhandledException(e)
asm "throw `e`;"
when defined(nimphp):
asm """throw new Exception($`e`["message"]);"""
else:
asm "throw `e`;"
proc reraiseException() {.compilerproc, asmNoStackFrame.} =
if lastJSError == nil:
@@ -243,8 +248,12 @@ proc toJSStr(s: string): cstring {.asmNoStackFrame, compilerproc.} =
for (var i = 0; i < len; ++i) {
if (nonAsciiPart !== null) {
var offset = (i - nonAsciiOffset) * 2;
var code = `s`[i].toString(16);
if (code.length == 1) {
code = "0"+code;
}
nonAsciiPart[offset] = "%";
nonAsciiPart[offset + 1] = `s`[i].toString(16);
nonAsciiPart[offset + 1] = code;
}
else if (`s`[i] < 128)
asciiPart[i] = fcc(`s`[i]);
@@ -729,16 +738,19 @@ proc genericReset(x: JSRef, ti: PNimType): JSRef {.compilerproc.} =
else:
discard
proc arrayConstr(len: int, value: JSRef, typ: PNimType): JSRef {.
asmNoStackFrame, compilerproc.} =
# types are fake
when defined(nimphp):
when defined(nimphp):
proc arrayConstr(len: int, value: string, typ: string): JSRef {.
asmNoStackFrame, compilerproc.} =
# types are fake
asm """
$result = array();
for ($i = 0; $i < `len`; $i++) $result[] = `value`;
return $result;
"""
else:
else:
proc arrayConstr(len: int, value: JSRef, typ: PNimType): JSRef {.
asmNoStackFrame, compilerproc.} =
# types are fake
asm """
var result = new Array(`len`);
for (var i = 0; i < `len`; ++i) result[i] = nimCopy(null, `value`, `typ`);

View File

@@ -300,7 +300,7 @@ elif defined(gogc):
proc setStackBottom(theStackBottom: pointer) = discard
proc alloc(size: Natural): pointer =
result = cmalloc(size)
result = c_malloc(size)
if result == nil: raiseOutOfMem()
proc alloc0(size: Natural): pointer =
@@ -308,13 +308,13 @@ elif defined(gogc):
zeroMem(result, size)
proc realloc(p: pointer, newsize: Natural): pointer =
result = crealloc(p, newsize)
result = c_realloc(p, newsize)
if result == nil: raiseOutOfMem()
proc dealloc(p: pointer) = cfree(p)
proc dealloc(p: pointer) = c_free(p)
proc allocShared(size: Natural): pointer =
result = cmalloc(size)
result = c_malloc(size)
if result == nil: raiseOutOfMem()
proc allocShared0(size: Natural): pointer =
@@ -322,10 +322,10 @@ elif defined(gogc):
zeroMem(result, size)
proc reallocShared(p: pointer, newsize: Natural): pointer =
result = crealloc(p, newsize)
result = c_realloc(p, newsize)
if result == nil: raiseOutOfMem()
proc deallocShared(p: pointer) = cfree(p)
proc deallocShared(p: pointer) = c_free(p)
when hasThreadSupport:
proc getFreeSharedMem(): int = discard
@@ -354,6 +354,12 @@ elif defined(gogc):
cast[PGenericSeq](result).reserved = len
cast[PGenericSeq](result).elemSize = typ.base.size
proc nimNewSeqOfCap(typ: PNimType, cap: int): pointer {.compilerproc.} =
result = newObj(typ, cap * typ.base.size + GenericSeqSize)
cast[PGenericSeq](result).len = 0
cast[PGenericSeq](result).reserved = cap
cast[PGenericSeq](result).elemSize = typ.base.size
proc growObj(old: pointer, newsize: int): pointer =
# the Go GC doesn't have a realloc
var
@@ -389,26 +395,41 @@ elif defined(nogc) and defined(useMalloc):
when not defined(useNimRtl):
proc alloc(size: Natural): pointer =
result = cmalloc(size)
if result == nil: raiseOutOfMem()
var x = c_malloc(size + sizeof(size))
if x == nil: raiseOutOfMem()
cast[ptr int](x)[] = size
result = cast[pointer](cast[int](x) + sizeof(size))
proc alloc0(size: Natural): pointer =
result = alloc(size)
zeroMem(result, size)
proc realloc(p: pointer, newsize: Natural): pointer =
result = crealloc(p, newsize)
if result == nil: raiseOutOfMem()
proc dealloc(p: pointer) = cfree(p)
var x = cast[pointer](cast[int](p) - sizeof(newsize))
let oldsize = cast[ptr int](x)[]
x = c_realloc(x, newsize + sizeof(newsize))
if x == nil: raiseOutOfMem()
cast[ptr int](x)[] = newsize
result = cast[pointer](cast[int](x) + sizeof(newsize))
if newsize > oldsize:
zeroMem(cast[pointer](cast[int](result) + oldsize), newsize - oldsize)
proc dealloc(p: pointer) = c_free(cast[pointer](cast[int](p) - sizeof(int)))
proc allocShared(size: Natural): pointer =
result = cmalloc(size)
result = c_malloc(size)
if result == nil: raiseOutOfMem()
proc allocShared0(size: Natural): pointer =
result = alloc(size)
zeroMem(result, size)
proc reallocShared(p: pointer, newsize: Natural): pointer =
result = crealloc(p, newsize)
result = c_realloc(p, newsize)
if result == nil: raiseOutOfMem()
proc deallocShared(p: pointer) = cfree(p)
proc deallocShared(p: pointer) = c_free(p)
proc GC_disable() = discard
proc GC_enable() = discard
@@ -432,6 +453,7 @@ elif defined(nogc) and defined(useMalloc):
result = newObj(typ, addInt(mulInt(len, typ.base.size), GenericSeqSize))
cast[PGenericSeq](result).len = len
cast[PGenericSeq](result).reserved = len
proc newObjNoInit(typ: PNimType, size: int): pointer =
result = alloc(size)
@@ -491,6 +513,7 @@ elif defined(nogc):
result = newObj(typ, addInt(mulInt(len, typ.base.size), GenericSeqSize))
cast[PGenericSeq](result).len = len
cast[PGenericSeq](result).reserved = len
proc growObj(old: pointer, newsize: int): pointer =
result = realloc(old, newsize)
@@ -511,11 +534,12 @@ elif defined(nogc):
include "system/cellsets"
else:
include "system/alloc"
when not defined(gcStack):
include "system/alloc"
include "system/cellsets"
when not leakDetector and not useCellIds:
sysAssert(sizeof(Cell) == sizeof(FreeCell), "sizeof FreeCell")
include "system/cellsets"
when not leakDetector and not useCellIds:
sysAssert(sizeof(Cell) == sizeof(FreeCell), "sizeof FreeCell")
when compileOption("gc", "v2"):
include "system/gc2"
elif defined(gcStack):
@@ -529,4 +553,10 @@ else:
else:
include "system/gc"
when not declared(nimNewSeqOfCap):
proc nimNewSeqOfCap(typ: PNimType, cap: int): pointer {.compilerproc.} =
result = newObj(typ, addInt(mulInt(cap, typ.base.size), GenericSeqSize))
cast[PGenericSeq](result).len = 0
cast[PGenericSeq](result).reserved = cap
{.pop.}

View File

@@ -39,6 +39,9 @@ proc getCurrentDir(): string = builtin
proc rawExec(cmd: string): int {.tags: [ExecIOEffect], raises: [OSError].} =
builtin
proc warningImpl(arg, orig: string) = discard
proc hintImpl(arg, orig: string) = discard
proc paramStr*(i: int): string =
## Retrieves the ``i``'th command line parameter.
builtin
@@ -52,6 +55,31 @@ proc switch*(key: string, val="") =
## example ``switch("checks", "on")``.
builtin
proc warning*(name: string; val: bool) =
## Disables or enables a specific warning.
let v = if val: "on" else: "off"
warningImpl(name & "]:" & v, "warning[" & name & "]:" & v)
proc hint*(name: string; val: bool) =
## Disables or enables a specific hint.
let v = if val: "on" else: "off"
hintImpl(name & "]:" & v, "hint[" & name & "]:" & v)
proc patchFile*(package, filename, replacement: string) =
## Overrides the location of a given file belonging to the
## passed package.
## If the ``replacement`` is not an absolute path, the path
## is interpreted to be local to the Nimscript file that contains
## the call to ``patchFile``, Nim's ``--path`` is not used at all
## to resolve the filename!
##
## Example:
##
## .. code-block:: nim
##
## patchFile("stdlib", "asyncdispatch", "patches/replacement")
discard
proc getCommand*(): string =
## Gets the Nim command that the compiler has been invoked with, for example
## "c", "js", "build", "help".
@@ -94,6 +122,10 @@ proc existsDir*(dir: string): bool =
## An alias for ``dirExists``.
dirExists(dir)
proc selfExe*(): string =
## Returns the currently running nim or nimble executable.
builtin
proc toExe*(filename: string): string =
## On Windows adds ".exe" to `filename`, else returns `filename` unmodified.
(when defined(windows): filename & ".exe" else: filename)
@@ -180,6 +212,15 @@ proc exec*(command: string, input: string, cache = "") {.
log "exec: " & command:
echo staticExec(command, input, cache)
proc selfExec*(command: string) =
## Executes an external command with the current nim/nimble executable.
## ``Command`` must not contain the "nim " part.
let c = selfExe() & " " & command
log "exec: " & c:
if rawExec(c) != 0:
raise newException(OSError, "FAILED: " & c)
checkOsError()
proc put*(key, value: string) =
## Sets a configuration 'key' like 'gcc.options.always' to its value.
builtin
@@ -206,7 +247,7 @@ proc cd*(dir: string) {.raises: [OSError].} =
##
## The change is permanent for the rest of the execution, since this is just
## a shortcut for `os.setCurrentDir()
## <http://nim-lang.org/os.html#setCurrentDir,string>`_ . Use the `withDir()
## <http://nim-lang.org/docs/os.html#setCurrentDir,string>`_ . Use the `withDir()
## <#withDir>`_ template if you want to perform a temporary change only.
setCurrentDir(dir)
checkOsError()

View File

@@ -68,11 +68,11 @@ when defined(emscripten):
mmapDescr.realSize = realSize
mmapDescr.realPointer = realPointer
c_fprintf(c_stdout, "[Alloc] size %d %d realSize:%d realPos:%d\n", block_size, cast[int](result), realSize, cast[int](realPointer))
#c_fprintf(stdout, "[Alloc] size %d %d realSize:%d realPos:%d\n", block_size, cast[int](result), realSize, cast[int](realPointer))
proc osTryAllocPages(size: int): pointer = osAllocPages(size)
proc osDeallocPages(p: pointer, size: int) {.inline} =
proc osDeallocPages(p: pointer, size: int) {.inline.} =
var mmapDescrPos = cast[ByteAddress](p) -% sizeof(EmscriptenMMapBlock)
var mmapDescr = cast[EmscriptenMMapBlock](mmapDescrPos)
munmap(mmapDescr.realPointer, mmapDescr.realSize)
@@ -87,6 +87,8 @@ elif defined(posix):
const MAP_ANONYMOUS = 0x1000
elif defined(solaris):
const MAP_ANONYMOUS = 0x100
elif defined(linux):
const MAP_ANONYMOUS = 0x20
else:
var
MAP_ANONYMOUS {.importc: "MAP_ANONYMOUS", header: "<sys/mman.h>".}: cint
@@ -107,7 +109,7 @@ elif defined(posix):
MAP_PRIVATE or MAP_ANONYMOUS, -1, 0)
if result == cast[pointer](-1): result = nil
proc osDeallocPages(p: pointer, size: int) {.inline} =
proc osDeallocPages(p: pointer, size: int) {.inline.} =
when reallyOsDealloc: discard munmap(p, size)
elif defined(windows):
@@ -148,8 +150,9 @@ elif defined(windows):
#VirtualFree(p, size, MEM_DECOMMIT)
elif hostOS == "standalone":
const StandaloneHeapSize {.intdefine.}: int = 1024 * PageSize
var
theHeap: array[1024*PageSize, float64] # 'float64' for alignment
theHeap: array[StandaloneHeapSize, float64] # 'float64' for alignment
bumpPointer = cast[int](addr theHeap)
proc osAllocPages(size: int): pointer {.inline.} =

View File

@@ -19,10 +19,14 @@ const
MaxTraceLen = 20 # tracking the last 20 calls is enough
type
StackTrace* = array [0..MaxTraceLen-1, cstring]
StackTrace* = object
lines*: array[0..MaxTraceLen-1, cstring]
files*: array[0..MaxTraceLen-1, cstring]
ProfilerHook* = proc (st: StackTrace) {.nimcall.}
{.deprecated: [TStackTrace: StackTrace, TProfilerHook: ProfilerHook].}
proc `[]`*(st: StackTrace, i: int): cstring = st.lines[i]
proc captureStackTrace(f: PFrame, st: var StackTrace) =
const
firstCalls = 5
@@ -30,9 +34,10 @@ proc captureStackTrace(f: PFrame, st: var StackTrace) =
it = f
i = 0
total = 0
while it != nil and i <= high(st)-(firstCalls-1):
while it != nil and i <= high(st.lines)-(firstCalls-1):
# the (-1) is for the "..." entry
st[i] = it.procname
st.lines[i] = it.procname
st.files[i] = it.filename
inc(i)
inc(total)
it = it.prev
@@ -43,10 +48,12 @@ proc captureStackTrace(f: PFrame, st: var StackTrace) =
for j in 1..total-i-(firstCalls-1):
if b != nil: b = b.prev
if total != i:
st[i] = "..."
st.lines[i] = "..."
st.files[i] = "..."
inc(i)
while b != nil and i <= high(st):
st[i] = b.procname
while b != nil and i <= high(st.lines):
st.lines[i] = b.procname
st.files[i] = b.filename
inc(i)
b = b.prev

View File

@@ -16,7 +16,7 @@ proc reprInt(x: int64): string {.compilerproc.} = return $x
proc reprFloat(x: float): string {.compilerproc.} = return $x
proc reprPointer(x: pointer): string {.compilerproc.} =
var buf: array [0..59, char]
var buf: array[0..59, char]
discard c_sprintf(buf, "%p", x)
return $buf
@@ -24,7 +24,7 @@ proc `$`(x: uint64): string =
if x == 0:
result = "0"
else:
var buf: array [60, char]
var buf: array[60, char]
var i = 0
var n = x
while n != 0:
@@ -73,23 +73,20 @@ proc reprChar(x: char): string {.compilerRtl.} =
add result, "\'"
proc reprEnum(e: int, typ: PNimType): string {.compilerRtl.} =
# we read an 'int' but this may have been too large, so mask the other bits:
let b = (sizeof(int)-typ.size)*8 # bits
let m = 1 shl (b-1) # mask
var o = e and ((1 shl b)-1) # clear upper bits
o = (o xor m) - m # sign extend
# XXX we need a proper narrowing based on signedness here
#e and ((1 shl (typ.size*8)) - 1)
## Return string representation for enumeration values
var n = typ.node
if ntfEnumHole notin typ.flags:
if o <% typ.node.len:
return $typ.node.sons[o].name
let o = e - n.sons[0].offset
if o >= 0 and o <% typ.node.len:
return $n.sons[o].name
else:
# ugh we need a slow linear search:
var n = typ.node
var s = n.sons
for i in 0 .. n.len-1:
if s[i].offset == o: return $s[i].name
result = $o & " (invalid data!)"
if s[i].offset == e:
return $s[i].name
result = $e & " (invalid data!)"
type
PByteArray = ptr array[0.. 0xffff, int8]

View File

@@ -10,7 +10,7 @@
# set handling
type
NimSet = array [0..4*2048-1, uint8]
NimSet = array[0..4*2048-1, uint8]
{.deprecated: [TNimSet: NimSet].}
proc countBits32(n: int32): int {.compilerproc.} =

Some files were not shown because too many files have changed in this diff Show More