Merge branch 'devel' of https://github.com/Araq/Nimrod into devel

Conflicts:
	compiler/ccgexprs.nim
This commit is contained in:
Araq 2014-08-05 21:53:26 +02:00
commit cf61072cc9
28 changed files with 1125 additions and 389 deletions

View file

@ -1604,15 +1604,15 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
of mGetTypeInfo: genGetTypeInfo(p, e, d) of mGetTypeInfo: genGetTypeInfo(p, e, d)
of mSwap: genSwap(p, e, d) of mSwap: genSwap(p, e, d)
of mUnaryLt: of mUnaryLt:
if optOverflowCheck notin p.options: unaryExpr(p, e, d, "$1 - 1") if optOverflowCheck notin p.options: unaryExpr(p, e, d, "($1 - 1)")
else: unaryExpr(p, e, d, "#subInt($1, 1)") else: unaryExpr(p, e, d, "#subInt($1, 1)")
of mPred: of mPred:
# XXX: range checking? # XXX: range checking?
if optOverflowCheck notin p.options: binaryExpr(p, e, d, "$1 - $2") if optOverflowCheck notin p.options: binaryExpr(p, e, d, "($1 - $2)")
else: binaryExpr(p, e, d, "#subInt($1, $2)") else: binaryExpr(p, e, d, "#subInt($1, $2)")
of mSucc: of mSucc:
# XXX: range checking? # XXX: range checking?
if optOverflowCheck notin p.options: binaryExpr(p, e, d, "$1 + $2") if optOverflowCheck notin p.options: binaryExpr(p, e, d, "($1 + $2)")
else: binaryExpr(p, e, d, "#addInt($1, $2)") else: binaryExpr(p, e, d, "#addInt($1, $2)")
of mInc: of mInc:
if optOverflowCheck notin p.options: if optOverflowCheck notin p.options:

View file

@ -291,8 +291,12 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo) =
of "excludepath": of "excludepath":
expectArg(switch, arg, pass, info) expectArg(switch, arg, pass, info)
let path = processPath(arg) let path = processPath(arg)
lists.excludeStr(options.searchPaths, path) lists.excludePath(options.searchPaths, path)
lists.excludeStr(options.lazyPaths, path) lists.excludePath(options.lazyPaths, path)
if (len(path) > 0) and (path[len(path) - 1] == DirSep):
let strippedPath = path[0 .. (len(path) - 2)]
lists.excludePath(options.searchPaths, strippedPath)
lists.excludePath(options.lazyPaths, strippedPath)
of "nimcache": of "nimcache":
expectArg(switch, arg, pass, info) expectArg(switch, arg, pass, info)
options.nimcacheDir = processPath(arg) options.nimcacheDir = processPath(arg)

View file

@ -1321,7 +1321,7 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
of mLengthSeq, mLengthOpenArray, mLengthArray: of mLengthSeq, mLengthOpenArray, mLengthArray:
unaryExpr(p, n, r, "", "$1.length") unaryExpr(p, n, r, "", "$1.length")
of mHigh: of mHigh:
if skipTypes(n.sons[0].typ, abstractVar).kind == tyString: if skipTypes(n.sons[1].typ, abstractVar).kind == tyString:
unaryExpr(p, n, r, "", "($1.length-2)") unaryExpr(p, n, r, "", "($1.length-2)")
else: else:
unaryExpr(p, n, r, "", "($1.length-1)") unaryExpr(p, n, r, "", "($1.length-1)")
@ -1532,6 +1532,7 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) =
genSym(p, n, r) genSym(p, n, r)
of nkCharLit..nkInt64Lit: of nkCharLit..nkInt64Lit:
r.res = toRope(n.intVal) r.res = toRope(n.intVal)
r.kind = resExpr
of nkNilLit: of nkNilLit:
if isEmptyType(n.typ): if isEmptyType(n.typ):
discard discard
@ -1539,8 +1540,10 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) =
r.typ = etyBaseIndex r.typ = etyBaseIndex
r.address = toRope"null" | toRope"nil" r.address = toRope"null" | toRope"nil"
r.res = toRope"0" r.res = toRope"0"
r.kind = resExpr
else: else:
r.res = toRope"null" | toRope"nil" r.res = toRope"null" | toRope"nil"
r.kind = resExpr
of nkStrLit..nkTripleStrLit: of nkStrLit..nkTripleStrLit:
if skipTypes(n.typ, abstractVarRange).kind == tyString: if skipTypes(n.typ, abstractVarRange).kind == tyString:
useMagic(p, "cstrToNimstr") useMagic(p, "cstrToNimstr")
@ -1556,6 +1559,7 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) =
if f > 0.0: r.res = toRope"Infinity" if f > 0.0: r.res = toRope"Infinity"
else: r.res = toRope"-Infinity" else: r.res = toRope"-Infinity"
else: r.res = toRope(f.toStrMaxPrecision) else: r.res = toRope(f.toStrMaxPrecision)
r.kind = resExpr
of nkCallKinds: of nkCallKinds:
if (n.sons[0].kind == nkSym) and (n.sons[0].sym.magic != mNone): if (n.sons[0].kind == nkSym) and (n.sons[0].sym.magic != mNone):
genMagic(p, n, r) genMagic(p, n, r)

View file

@ -8,7 +8,8 @@
# #
# This module implements a generic doubled linked list. # This module implements a generic doubled linked list.
# TODO Remove this and replace it with something sensible
import os
type type
PListEntry* = ref TListEntry PListEntry* = ref TListEntry
TListEntry* = object of TObject TListEntry* = object of TObject
@ -103,11 +104,12 @@ proc bringToFront*(list: var TLinkedList, entry: PListEntry) =
entry.next = list.head entry.next = list.head
list.head = entry list.head = entry
proc excludeStr*(list: var TLinkedList, data: string) = proc excludePath*(list: var TLinkedList, data: string) =
var it = list.head var it = list.head
while it != nil: while it != nil:
let nxt = it.next let nxt = it.next
if PStrEntry(it).data == data: remove(list, it) if cmpPaths(PStrEntry(it).data, data) == 0:
remove(list, it)
it = nxt it = nxt
proc find*(list: TLinkedList, fn: TCompareProc, closure: pointer): PListEntry = proc find*(list: TLinkedList, fn: TCompareProc, closure: pointer): PListEntry =

View file

@ -58,7 +58,7 @@ proc handleCmdLine() =
if msgs.gErrorCounter == 0: if msgs.gErrorCounter == 0:
when hasTinyCBackend: when hasTinyCBackend:
if gCmd == cmdRun: if gCmd == cmdRun:
tccgen.run() tccgen.run(service.arguments)
if optRun in gGlobalOptions: if optRun in gGlobalOptions:
if gCmd == cmdCompileToJS: if gCmd == cmdCompileToJS:
var ex: string var ex: string

View file

@ -59,7 +59,7 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string) =
inc argsCount inc argsCount
if pass == passCmd2: if pass == passCmd2:
if optRun notin gGlobalOptions and arguments != "": if optRun notin gGlobalOptions and arguments != "" and options.command.normalize != "run":
rawMessage(errArgsNeedRunOption, []) rawMessage(errArgsNeedRunOption, [])
proc serve*(action: proc (){.nimcall.}) = proc serve*(action: proc (){.nimcall.}) =

View file

@ -68,11 +68,9 @@ proc compileCCode*(ccode: string) =
setupEnvironment() setupEnvironment()
discard compileString(gTinyC, ccode) discard compileString(gTinyC, ccode)
proc run*() = proc run*(args: string) =
var a: array[0..1, cstring] var s = @[cstring(gProjectName)] & map(split(args), proc(x: string): cstring = cstring(x))
a[0] = "" var err = tinyc.run(gTinyC, cint(len(s)), cast[cstringArray](addr(s[0]))) != 0'i32
a[1] = ""
var err = tinyc.run(gTinyC, 0'i32, cast[cstringArray](addr(a))) != 0'i32
closeCCState(gTinyC) closeCCState(gTinyC)
if err: rawMessage(errExecutionOfProgramFailed, "") if err: rawMessage(errExecutionOfProgramFailed, "")

View file

@ -12,6 +12,7 @@ Advanced commands:
module dependency graph module dependency graph
//dump dump all defined conditionals and search paths //dump dump all defined conditionals and search paths
//check checks the project for syntax and semantic //check checks the project for syntax and semantic
//pretty homogenizes source code style
//idetools compiler support for IDEs: possible options: //idetools compiler support for IDEs: possible options:
--track:FILE,LINE,COL track a file/cursor position --track:FILE,LINE,COL track a file/cursor position
--trackDirty:DIRTY_FILE,ORIG_FILE,LINE,COL --trackDirty:DIRTY_FILE,ORIG_FILE,LINE,COL

View file

@ -594,7 +594,8 @@ compiler.
.. raw:: html .. raw:: html
<div id="officialPkgList"></div> <div id="officialPkgList"><b>If you are reading this you are missing
babelpkglist.js or have javascript disabled in your browser.</b></div>
Unofficial packages Unofficial packages
------------------- -------------------
@ -605,7 +606,8 @@ Nimrod programming language.
.. raw:: html .. raw:: html
<div id="unofficialPkgList"></div> <div id="unofficialPkgList"><b>If you are reading this you are missing
babelpkglist.js or have javascript disabled in your browser.</b></div>
<script type="text/javascript" src="babelpkglist.js"></script> <script type="text/javascript" src="babelpkglist.js"></script>
<script type="text/javascript" src="http://build.nimrod-lang.org/packages?callback=gotPackageList"></script> <script type="text/javascript" src="http://build.nimrod-lang.org/packages?callback=gotPackageList"></script>

View file

@ -1221,38 +1221,8 @@ branch switch ``system.reset`` has to be used.
Set type Set type
-------- --------
The set type models the mathematical notion of a set. The set's
basetype can only be an ordinal type. The reason is that sets are implemented
as high performance bit vectors.
Sets can be constructed via the set constructor: ``{}`` is the empty set. The
empty set is type compatible with any special set type. The constructor
can also be used to include elements (and ranges of elements) in the set:
.. code-block:: nimrod
{'a'..'z', '0'..'9'} # This constructs a set that contains the
# letters from 'a' to 'z' and the digits
# from '0' to '9'
These operations are supported by sets:
================== ========================================================
operation meaning
================== ========================================================
``A + B`` union of two sets
``A * B`` intersection of two sets
``A - B`` difference of two sets (A without B's elements)
``A == B`` set equality
``A <= B`` subset relation (A is subset of B or equal to B)
``A < B`` strong subset relation (A is a real subset of B)
``e in A`` set membership (A contains element e)
``A -+- B`` symmetric set difference (= (A - B) + (B - A))
``card(A)`` the cardinality of A (number of elements in A)
``incl(A, elem)`` same as A = A + {elem}
``excl(A, elem)`` same as A = A - {elem}
================== ========================================================
.. include:: sets_fragment.txt
Reference and pointer types Reference and pointer types
--------------------------- ---------------------------

View file

@ -540,6 +540,58 @@ in C/C++).
**Note**: This pragma will not exist for the LLVM backend. **Note**: This pragma will not exist for the LLVM backend.
Source code style
=================
Nimrod allows you to `mix freely case and underscores as identifier separators
<manual.html#identifiers-keywords>`_, so variables named ``MyPrecioussInt`` and
``my_preciouss_int`` are equivalent:
.. code-block:: Nimrod
var MyPrecioussInt = 3
# Following line compiles fine!
echo my_preciouss_int
Since this can lead to many variants of the same source code (you can use
`nimgrep <nimgrep.html>`_ instead of your typical ``grep`` to ignore style
problems) the compiler provides the command ``pretty`` to help unifying the
style of source code. Running ``nimrod pretty ugly_test.nim`` with this
example will generate a secondary file named ``ugly_test.pretty.nim`` with the
following content:
.. code-block:: Nimrod
var MyPrecioussInt = 3
# Following line compiles fine!
echo MyPrecioussInt
During execution the ``pretty`` command will also run on Nimrod's standard
library, since it doesn't differentiate the standard library as something
special, and hence will warn of many *errors* which are out of your hand to
fix, creating respective ``.pretty.nim`` files all the way. You can ignore
these errors if they don't belong to your source and simply compare your
original version to the new pretty one. In fact, running ``pretty`` on our test
file will show the following::
Hint: ugly_test [Processing]
ugly_test.nim(1, 4) Error: name should be: myPrecioussInt
ugly_test.nim(1, 4) Error: name should be: myPrecioussInt
At the moment ``pretty`` will homogenize the style of symbols but will leave
important changes for you to review. In this case the command is warning that a
variable name should not start with a capital letter, which is usually reserved
to `object types <tut2.html#objects>`_. To learn about the accepted `camel case
style <https://en.wikipedia.org/wiki/Camelcase>`_ read `Coding Guidelines in
the Internals of Nimrod Compiler <intern.html#coding-guidelines>`_ or `Coding
Guidelines <https://github.com/Araq/Nimrod/wiki/Coding-Guidelines>`_ and `NEP 1
: Style Guide for Nimrod Code
<https://github.com/Araq/Nimrod/wiki/NEP-1-:-Style-Guide-for-Nimrod-Code>`_
from the Nimrod `GitHub wiki<https://github.com/Araq/Nimrod/wiki>`_.
This command is safe to run because it will never attempt to overwrite your
existing sources, but the respective ``.pretty.nim`` files **will** be
overwritten without notice.
DynlibOverride DynlibOverride
============== ==============

40
doc/sets_fragment.txt Normal file
View file

@ -0,0 +1,40 @@
The set type models the mathematical notion of a set. The set's
basetype can only be an ordinal type. The reason is that sets are implemented
as high performance bit vectors.
Sets can be constructed via the set constructor: ``{}`` is the empty set. The
empty set is type compatible with any concrete set type. The constructor
can also be used to include elements (and ranges of elements):
.. code-block:: nimrod
type
TCharSet = set[char]
var
x: TCharSet
x = {'a'..'z', '0'..'9'} # This constructs a set that contains the
# letters from 'a' to 'z' and the digits
# from '0' to '9'
These operations are supported by sets:
================== ========================================================
operation meaning
================== ========================================================
``A + B`` union of two sets
``A * B`` intersection of two sets
``A - B`` difference of two sets (A without B's elements)
``A == B`` set equality
``A <= B`` subset relation (A is subset of B or equal to B)
``A < B`` strong subset relation (A is a real subset of B)
``e in A`` set membership (A contains element e)
``e notin A`` A does not contain element e
``contains(A, e)`` A contains element e
``A -+- B`` symmetric set difference (= (A - B) + (B - A))
``card(A)`` the cardinality of A (number of elements in A)
``incl(A, elem)`` same as ``A = A + {elem}``
``excl(A, elem)`` same as ``A = A - {elem}``
================== ========================================================
Sets are often used to define a type for the *flags* of a procedure. This is
a much cleaner (and type safe) solution than just defining integer
constants that should be ``or``'ed together.

View file

@ -1117,47 +1117,8 @@ avoid this common programming error.
Sets Sets
---- ----
The set type models the mathematical notion of a set. The set's
basetype can only be an ordinal type. The reason is that sets are implemented
as high performance bit vectors.
Sets can be constructed via the set constructor: ``{}`` is the empty set. The
empty set is type compatible with any concrete set type. The constructor
can also be used to include elements (and ranges of elements):
.. code-block:: nimrod
type
TCharSet = set[char]
var
x: TCharSet
x = {'a'..'z', '0'..'9'} # This constructs a set that contains the
# letters from 'a' to 'z' and the digits
# from '0' to '9'
These operations are supported by sets:
================== ========================================================
operation meaning
================== ========================================================
``A + B`` union of two sets
``A * B`` intersection of two sets
``A - B`` difference of two sets (A without B's elements)
``A == B`` set equality
``A <= B`` subset relation (A is subset of B or equal to B)
``A < B`` strong subset relation (A is a real subset of B)
``e in A`` set membership (A contains element e)
``e notin A`` A does not contain element e
``contains(A, e)`` A contains element e
``A -+- B`` symmetric set difference (= (A - B) + (B - A))
``card(A)`` the cardinality of A (number of elements in A)
``incl(A, elem)`` same as ``A = A + {elem}``
``excl(A, elem)`` same as ``A = A - {elem}``
================== ========================================================
Sets are often used to define a type for the *flags* of a procedure. This is
a much cleaner (and type safe) solution than just defining integer
constants that should be ``or``'ed together.
.. include:: sets_fragment.txt
Arrays Arrays
------ ------

View file

@ -420,6 +420,59 @@ proc setBiggestInt*(x: TAny, y: biggestInt) =
of tyUInt32: cast[ptr uint32](x.value)[] = uint32(y) of tyUInt32: cast[ptr uint32](x.value)[] = uint32(y)
else: assert false else: assert false
proc getUInt*(x: TAny): uint =
## retrieve the uint value out of `x`, `x` needs to represent an uint.
assert skipRange(x.rawtype).kind == tyUInt
result = cast[ptr uint](x.value)[]
proc getUInt8*(x: TAny): uint8 =
## retrieve the uint8 value out of `x`, `x` needs to represent an
## uint8.
assert skipRange(x.rawtype).kind == tyUInt8
result = cast[ptr uint8](x.value)[]
proc getUInt16*(x: TAny): uint16 =
## retrieve the uint16 value out of `x`, `x` needs to represent an
## uint16.
assert skipRange(x.rawtype).kind == tyUInt16
result = cast[ptr uint16](x.value)[]
proc getUInt32*(x: TAny): uint32 =
## retrieve the uint32 value out of `x`, `x` needs to represent an
## uint32.
assert skipRange(x.rawtype).kind == tyUInt32
result = cast[ptr uint32](x.value)[]
proc getUInt64*(x: TAny): uint64 =
## retrieve the uint64 value out of `x`, `x` needs to represent an
## uint64.
assert skipRange(x.rawtype).kind == tyUInt64
result = cast[ptr uint64](x.value)[]
proc getBiggestUint*(x: TAny): uint64 =
## retrieve the unsigned integer value out of `x`. `x` needs to
## represent an unsigned integer.
var t = skipRange(x.rawtype)
case t.kind
of tyUInt: result = uint64(cast[ptr uint](x.value)[])
of tyUInt8: result = uint64(cast[ptr uint8](x.value)[])
of tyUInt16: result = uint64(cast[ptr uint16](x.value)[])
of tyUInt32: result = uint64(cast[ptr uint32](x.value)[])
of tyUInt64: result = uint64(cast[ptr uint64](x.value)[])
else: assert false
proc setBiggestUint*(x: TAny; y: uint64) =
## sets the unsigned integer value of `c`. `c` needs to represent an
## unsigned integer.
var t = skipRange(x.rawtype)
case t.kind:
of tyUInt: cast[ptr uint](x.value)[] = uint(y)
of tyUInt8: cast[ptr uint8](x.value)[] = uint8(y)
of tyUInt16: cast[ptr uint16](x.value)[] = uint16(y)
of tyUInt32: cast[ptr uint32](x.value)[] = uint32(y)
of tyUInt64: cast[ptr uint64](x.value)[] = uint64(y)
else: assert false
proc getChar*(x: TAny): char = proc getChar*(x: TAny): char =
## retrieve the char value out of `x`. `x` needs to represent a char. ## retrieve the char value out of `x`. `x` needs to represent a char.
var t = skipRange(x.rawtype) var t = skipRange(x.rawtype)

View file

@ -775,7 +775,7 @@ proc renderCodeBlock(d: PDoc, n: PRstNode, result: var string) =
dispA(d.target, result, "<pre>", "\\begin{rstpre}\n", []) dispA(d.target, result, "<pre>", "\\begin{rstpre}\n", [])
if lang == langNone: if lang == langNone:
d.msgHandler(d.filename, 1, 0, mwUnsupportedLanguage, langstr) d.msgHandler(d.filename, 1, 0, mwUnsupportedLanguage, langstr)
result.add(m.text) for letter in m.text: escChar(d.target, result, letter)
else: else:
var g: TGeneralTokenizer var g: TGeneralTokenizer
initGeneralTokenizer(g, m.text) initGeneralTokenizer(g, m.text)

View file

@ -90,7 +90,7 @@ type
d_ino*: Tino ## File serial number. d_ino*: Tino ## File serial number.
d_name*: array [0..255, char] ## Name of entry. d_name*: array [0..255, char] ## Name of entry.
Tflock* {.importc: "flock", final, pure, Tflock* {.importc: "struct flock", final, pure,
header: "<fcntl.h>".} = object ## flock type header: "<fcntl.h>".} = object ## flock type
l_type*: cshort ## Type of lock; F_RDLCK, F_WRLCK, F_UNLCK. l_type*: cshort ## Type of lock; F_RDLCK, F_WRLCK, F_UNLCK.
l_whence*: cshort ## Flag for starting offset. l_whence*: cshort ## Flag for starting offset.

View file

@ -58,7 +58,7 @@ template encodeInternal(s: expr, lineLen: int, newLine: string): stmt {.immediat
if r+4 != result.len: if r+4 != result.len:
setLen(result, r+4) setLen(result, r+4)
else: else:
assert(r == result.len) #assert(r == result.len)
proc encode*[T:TInteger|char](s: openarray[T], lineLen = 75, newLine="\13\10"): string = proc encode*[T:TInteger|char](s: openarray[T], lineLen = 75, newLine="\13\10"): string =
## encodes `s` into base64 representation. After `lineLen` characters, a ## encodes `s` into base64 representation. After `lineLen` characters, a

View file

@ -9,6 +9,10 @@
## The ``sets`` module implements an efficient hash set and ordered hash set. ## The ``sets`` module implements an efficient hash set and ordered hash set.
## ##
## Hash sets are different from the `built in set type
## <manual.html#set-type>`_. Sets allow you to store any value that can be
## `hashed <hashes.html>`_ and they don't contain duplicate entries.
##
## **Note**: The data types declared here have *value semantics*: This means ## **Note**: The data types declared here have *value semantics*: This means
## that ``=`` performs a copy of the set. ## that ``=`` performs a copy of the set.
@ -23,20 +27,69 @@ type
TSlotEnum = enum seEmpty, seFilled, seDeleted TSlotEnum = enum seEmpty, seFilled, seDeleted
TKeyValuePair[A] = tuple[slot: TSlotEnum, key: A] TKeyValuePair[A] = tuple[slot: TSlotEnum, key: A]
TKeyValuePairSeq[A] = seq[TKeyValuePair[A]] TKeyValuePairSeq[A] = seq[TKeyValuePair[A]]
TSet* {.final, myShallow.}[A] = object ## a generic hash set TSet* {.final, myShallow.}[A] = object ## \
## A generic hash set.
##
## Use `init() <#init,TSet[A],int>`_ or `initSet[type]() <#initSet>`_
## before calling other procs on it.
data: TKeyValuePairSeq[A] data: TKeyValuePairSeq[A]
counter: int counter: int
proc isValid*[A](s: TSet[A]): bool =
## Returns `true` if the set has been initialized with `initSet <#initSet>`_.
##
## Most operations over an uninitialized set will crash at runtime and
## `assert <system.html#assert>`_ in debug builds. You can use this proc in
## your own procs to verify that sets passed to your procs are correctly
## initialized. Example:
##
## .. code-block :: nimrod
## proc savePreferences(options: TSet[string]) =
## assert options.isValid, "Pass an initialized set!"
## # Do stuff here, may crash in release builds!
result = not s.data.isNil
proc len*[A](s: TSet[A]): int = proc len*[A](s: TSet[A]): int =
## returns the number of keys in `s`. ## Returns the number of keys in `s`.
##
## Due to an implementation detail you can call this proc on variables which
## have not been initialized yet. The proc will return zero as the length
## then. Example:
##
## .. code-block::
##
## var values: TSet[int]
## assert(not values.isValid)
## assert values.len == 0
result = s.counter result = s.counter
proc card*[A](s: TSet[A]): int = proc card*[A](s: TSet[A]): int =
## alias for `len`. ## Alias for `len() <#len,TSet[A]>`_.
##
## Card stands for the `cardinality
## <http://en.wikipedia.org/wiki/Cardinality>`_ of a set.
result = s.counter result = s.counter
iterator items*[A](s: TSet[A]): A = iterator items*[A](s: TSet[A]): A =
## iterates over any key in the table `t`. ## Iterates over keys in the set `s`.
##
## If you need a sequence with the keys you can use `sequtils.toSeq()
## <sequtils.html#toSeq>`_ on the iterator. Usage example:
##
## .. code-block::
## type
## pair = tuple[a, b: int]
## var
## a, b = initSet[pair]()
## a.incl((2, 3))
## a.incl((3, 2))
## a.incl((2, 3))
## for x, y in a.items:
## b.incl((x - 2, y + 1))
## assert a.len == 2
## echo b
## # --> {(a: 1, b: 3), (a: 0, b: 4)}
assert s.isValid, "The set needs to be initialized."
for h in 0..high(s.data): for h in 0..high(s.data):
if s.data[h].slot == seFilled: yield s.data[h].key if s.data[h].slot == seFilled: yield s.data[h].key
@ -73,12 +126,24 @@ proc mget*[A](s: var TSet[A], key: A): var A =
## value as 'key' or raises the ``EInvalidKey`` exception. This is useful ## value as 'key' or raises the ``EInvalidKey`` exception. This is useful
## when one overloaded 'hash' and '==' but still needs reference semantics ## when one overloaded 'hash' and '==' but still needs reference semantics
## for sharing. ## for sharing.
assert s.isValid, "The set needs to be initialized."
var index = rawGet(s, key) var index = rawGet(s, key)
if index >= 0: result = t.data[index].key if index >= 0: result = t.data[index].key
else: raise newException(EInvalidKey, "key not found: " & $key) else: raise newException(EInvalidKey, "key not found: " & $key)
proc contains*[A](s: TSet[A], key: A): bool = proc contains*[A](s: TSet[A], key: A): bool =
## returns true iff `key` is in `s`. ## Returns true iff `key` is in `s`.
##
## Example:
##
## .. code-block::
## var values = initSet[int]()
## assert(not values.contains(2))
## values.incl(2)
## assert values.contains(2)
## values.excl(2)
## assert(not values.contains(2))
assert s.isValid, "The set needs to be initialized."
var index = rawGet(s, key) var index = rawGet(s, key)
result = index >= 0 result = index >= 0
@ -109,38 +174,124 @@ template containsOrInclImpl() {.dirty.} =
inc(s.counter) inc(s.counter)
proc incl*[A](s: var TSet[A], key: A) = proc incl*[A](s: var TSet[A], key: A) =
## includes an element `key` in `s`. ## Includes an element `key` in `s`.
##
## This doesn't do anything if `key` is already in `s`. Example:
##
## .. code-block::
## var values = initSet[int]()
## values.incl(2)
## values.incl(2)
## assert values.len == 1
assert s.isValid, "The set needs to be initialized."
inclImpl() inclImpl()
proc incl*[A](s: var TSet[A], other: TSet[A]) = proc incl*[A](s: var TSet[A], other: TSet[A]) =
## includes everything in `other` in `s` ## Includes all elements from `other` into `s`.
##
## Example:
##
## .. code-block::
## var values = initSet[int]()
## values.incl(2)
## var others = toSet([6, 7])
## values.incl(others)
## assert values.len == 3
assert s.isValid, "The set `s` needs to be initialized."
assert other.isValid, "The set `other` needs to be initialized."
for item in other: incl(s, item) for item in other: incl(s, item)
proc excl*[A](s: var TSet[A], key: A) = proc excl*[A](s: var TSet[A], key: A) =
## excludes `key` from the set `s`. ## Excludes `key` from the set `s`.
##
## This doesn't do anything if `key` is not found in `s`. Example:
##
## .. code-block::
## var s = toSet([2, 3, 6, 7])
## s.excl(2)
## s.excl(2)
## assert s.len == 3
assert s.isValid, "The set needs to be initialized."
var index = rawGet(s, key) var index = rawGet(s, key)
if index >= 0: if index >= 0:
s.data[index].slot = seDeleted s.data[index].slot = seDeleted
dec(s.counter) dec(s.counter)
proc excl*[A](s: var TSet[A], other: TSet[A]) = proc excl*[A](s: var TSet[A], other: TSet[A]) =
## excludes everything in `other` from `s`. ## Excludes everything in `other` from `s`.
##
## Example:
##
## .. code-block::
## var
## numbers = toSet([1, 2, 3, 4, 5])
## even = toSet([2, 4, 6, 8])
## numbers.excl(even)
## echo numbers
## # --> {1, 3, 5}
assert s.isValid, "The set `s` needs to be initialized."
assert other.isValid, "The set `other` needs to be initialized."
for item in other: excl(s, item) for item in other: excl(s, item)
proc containsOrIncl*[A](s: var TSet[A], key: A): bool = proc containsOrIncl*[A](s: var TSet[A], key: A): bool =
## returns true if `s` contains `key`, otherwise `key` is included in `s` ## Includes `key` in the set `s` and tells if `key` was added to `s`.
## and false is returned. ##
## The difference with regards to the `incl() <#incl,TSet[A],A>`_ proc is
## that this proc returns `true` if `key` was already present in `s`. The
## proc will return false if `key` was added as a new value to `s` during
## this call. Example:
##
## .. code-block::
## var values = initSet[int]()
## assert values.containsOrIncl(2) == false
## assert values.containsOrIncl(2) == true
assert s.isValid, "The set needs to be initialized."
containsOrInclImpl() containsOrInclImpl()
proc initSet*[A](initialSize=64): TSet[A] = proc init*[A](s: var TSet[A], initialSize=64) =
## creates a new hash set that is empty. `initialSize` needs to be ## Initializes a hash set.
## a power of two. ##
## The `initialSize` parameter needs to be a power of too. You can use
## `math.nextPowerOfTwo() <math.html#nextPowerOfTwo>`_ to guarantee that at
## runtime. All set variables have to be initialized before you can use them
## with other procs from this module with the exception of `isValid()
## <#isValid,TSet[A]>`_ and `len() <#len,TSet[A]>`_.
##
## You can call this proc on a previously initialized hash set, which will
## discard all its values. This might be more convenient than iterating over
## existing values and calling `excl() <#excl,TSet[A],A>`_ on them. Example:
##
## .. code-block ::
## var a: TSet[int]
## a.init(4)
## a.incl(2)
## a.init
## assert a.len == 0 and a.isValid
assert isPowerOfTwo(initialSize) assert isPowerOfTwo(initialSize)
result.counter = 0 s.counter = 0
newSeq(result.data, initialSize) newSeq(s.data, initialSize)
proc initSet*[A](initialSize=64): TSet[A] =
## Wrapper around `init() <#init,TSet[A],int>`_ for initialization of hash
## sets.
##
## Returns an empty hash set you can assign directly in ``var`` blocks in a
## single line. Example:
##
## .. code-block ::
## var a = initSet[int](4)
## a.incl(2)
result.init(initialSize)
proc toSet*[A](keys: openArray[A]): TSet[A] = proc toSet*[A](keys: openArray[A]): TSet[A] =
## creates a new hash set that contains the given `keys`. ## Creates a new hash set that contains the given `keys`.
##
## Example:
##
## .. code-block::
## var numbers = toSet([1, 2, 3, 4, 5])
## assert numbers.contains(2)
## assert numbers.contains(4)
result = initSet[A](nextPowerOfTwo(keys.len+10)) result = initSet[A](nextPowerOfTwo(keys.len+10))
for key in items(keys): result.incl(key) for key in items(keys): result.incl(key)
@ -152,57 +303,190 @@ template dollarImpl(): stmt {.dirty.} =
result.add("}") result.add("}")
proc `$`*[A](s: TSet[A]): string = proc `$`*[A](s: TSet[A]): string =
## The `$` operator for hash sets. ## Converts the set `s` to a string, mostly for logging purposes.
##
## Don't use this proc for serialization, the representation may change at
## any moment and values are not escaped. Example:
##
## Example:
##
## .. code-block::
## echo toSet([2, 4, 5])
## # --> {2, 4, 5}
## echo toSet(["no", "esc'aping", "is \" provided"])
## # --> {no, esc'aping, is " provided}
assert s.isValid, "The set needs to be initialized."
dollarImpl() dollarImpl()
proc union*[A](s1, s2: TSet[A]): TSet[A] = proc union*[A](s1, s2: TSet[A]): TSet[A] =
## returns a new set of all items that are contained in at ## Returns the union of the sets `s1` and `s2`.
## least one of `s1` and `s2` ##
## The union of two sets is represented mathematically as *A ∪ B* and is the
## set of all objects that are members of `s1`, `s2` or both. Example:
##
## .. code-block::
## var
## a = toSet(["a", "b"])
## b = toSet(["b", "c"])
## c = union(a, b)
## assert c == toSet(["a", "b", "c"])
assert s1.isValid, "The set `s1` needs to be initialized."
assert s2.isValid, "The set `s2` needs to be initialized."
result = s1 result = s1
incl(result, s2) incl(result, s2)
proc intersection*[A](s1, s2: TSet[A]): TSet[A] = proc intersection*[A](s1, s2: TSet[A]): TSet[A] =
## returns a new set of all items that are contained in both `s1` and `s2` ## Returns the intersection of the sets `s1` and `s2`.
##
## The intersection of two sets is represented mathematically as *A ∩ B* and
## is the set of all objects that are members of `s1` and `s2` at the same
## time. Example:
##
## .. code-block::
## var
## a = toSet(["a", "b"])
## b = toSet(["b", "c"])
## c = intersection(a, b)
## assert c == toSet(["b"])
assert s1.isValid, "The set `s1` needs to be initialized."
assert s2.isValid, "The set `s2` needs to be initialized."
result = initSet[A](min(s1.data.len, s2.data.len)) result = initSet[A](min(s1.data.len, s2.data.len))
for item in s1: for item in s1:
if item in s2: incl(result, item) if item in s2: incl(result, item)
proc difference*[A](s1, s2: TSet[A]): TSet[A] = proc difference*[A](s1, s2: TSet[A]): TSet[A] =
## returns a new set of all items that are contained in `s1`, but not in `s2` ## Returns the difference of the sets `s1` and `s2`.
##
## The difference of two sets is represented mathematically as *A \ B* and is
## the set of all objects that are members of `s1` and not members of `s2`.
## Example:
##
## .. code-block::
## var
## a = toSet(["a", "b"])
## b = toSet(["b", "c"])
## c = difference(a, b)
## assert c == toSet(["a"])
assert s1.isValid, "The set `s1` needs to be initialized."
assert s2.isValid, "The set `s2` needs to be initialized."
result = initSet[A]() result = initSet[A]()
for item in s1: for item in s1:
if not contains(s2, item): if not contains(s2, item):
incl(result, item) incl(result, item)
proc symmetricDifference*[A](s1, s2: TSet[A]): TSet[A] = proc symmetricDifference*[A](s1, s2: TSet[A]): TSet[A] =
## returns a new set of all items that are contained in either ## Returns the symmetric difference of the sets `s1` and `s2`.
## `s1` or `s2`, but not both ##
## The symmetric difference of two sets is represented mathematically as *A △
## B* or *A ⊖ B* and is the set of all objects that are members of `s1` or
## `s2` but not both at the same time. Example:
##
## .. code-block::
## var
## a = toSet(["a", "b"])
## b = toSet(["b", "c"])
## c = symmetricDifference(a, b)
## assert c == toSet(["a", "c"])
assert s1.isValid, "The set `s1` needs to be initialized."
assert s2.isValid, "The set `s2` needs to be initialized."
result = s1 result = s1
for item in s2: for item in s2:
if containsOrIncl(result, item): excl(result, item) if containsOrIncl(result, item): excl(result, item)
proc `+`*[A](s1, s2: TSet[A]): TSet[A] {.inline.} = proc `+`*[A](s1, s2: TSet[A]): TSet[A] {.inline.} =
## alias for `union` ## Alias for `union(s1, s2) <#union>`_.
result = union(s1, s2) result = union(s1, s2)
proc `*`*[A](s1, s2: TSet[A]): TSet[A] {.inline.} = proc `*`*[A](s1, s2: TSet[A]): TSet[A] {.inline.} =
## alias for `intersection` ## Alias for `intersection(s1, s2) <#intersection>`_.
result = intersection(s1, s2) result = intersection(s1, s2)
proc `-`*[A](s1, s2: TSet[A]): TSet[A] {.inline.} = proc `-`*[A](s1, s2: TSet[A]): TSet[A] {.inline.} =
## alias for `difference` ## Alias for `difference(s1, s2) <#difference>`_.
result = difference(s1, s2) result = difference(s1, s2)
proc `-+-`*[A](s1, s2: TSet[A]): TSet[A] {.inline.} = proc `-+-`*[A](s1, s2: TSet[A]): TSet[A] {.inline.} =
## alias for `symmetricDifference` ## Alias for `symmetricDifference(s1, s2) <#symmetricDifference>`_.
result = symmetricDifference(s1, s2) result = symmetricDifference(s1, s2)
proc disjoint*[A](s1, s2: TSet[A]): bool = proc disjoint*[A](s1, s2: TSet[A]): bool =
## returns true iff `s1` and `s2` have no items in common ## Returns true iff the sets `s1` and `s2` have no items in common.
##
## Example:
##
## .. code-block::
## var
## a = toSet(["a", "b"])
## b = toSet(["b", "c"])
## assert disjoint(a, b) == false
## assert disjoint(a, b - a) == true
assert s1.isValid, "The set `s1` needs to be initialized."
assert s2.isValid, "The set `s2` needs to be initialized."
for item in s1: for item in s1:
if item in s2: return false if item in s2: return false
return true return true
proc `<`*[A](s, t: TSet[A]): bool =
## Returns true if `s` is a strict or proper subset of `t`.
##
## A strict or proper subset `s` has all of its members in `t` but `t` has
## more elements than `s`. Example:
##
## .. code-block::
## var
## a = toSet(["a", "b"])
## b = toSet(["b", "c"])
## c = intersection(a, b)
## assert c < a and c < b
## assert((a < a) == false)
s.counter != t.counter and s <= t
proc `<=`*[A](s, t: TSet[A]): bool =
## Returns true if `s` is subset of `t`.
##
## A subset `s` has all of its members in `t` and `t` doesn't necessarily
## have more members than `s`. That is, `s` can be equal to `t`. Example:
##
## .. code-block::
## var
## a = toSet(["a", "b"])
## b = toSet(["b", "c"])
## c = intersection(a, b)
## assert c <= a and c <= b
## assert((a <= a))
result = false
if s.counter > t.counter: return
result = true
for item in s:
if not(t.contains(item)):
result = false
return
proc `==`*[A](s, t: TSet[A]): bool =
## Returns true if both `s` and `t` have the same members and set size.
##
## Example:
##
## .. code-block::
## var
## a = toSet([1, 2])
## b = toSet([1])
## b.incl(2)
## assert a == b
s.counter == t.counter and s <= t
proc map*[A, B](data: TSet[A], op: proc (x: A): B {.closure.}): TSet[B] =
## Returns a new set after applying `op` on each of the elements of `data`.
##
## You can use this proc to transform the elements from a set. Example:
##
## .. code-block::
## var a = toSet([1, 2, 3])
## var b = a.map(proc (x: int): string = $x)
## assert b == toSet(["1", "2", "3"])
result = initSet[B]()
for item in data: result.incl(op(item))
# ------------------------------ ordered set ------------------------------ # ------------------------------ ordered set ------------------------------
type type
@ -210,16 +494,48 @@ type
slot: TSlotEnum, next: int, key: A] slot: TSlotEnum, next: int, key: A]
TOrderedKeyValuePairSeq[A] = seq[TOrderedKeyValuePair[A]] TOrderedKeyValuePairSeq[A] = seq[TOrderedKeyValuePair[A]]
TOrderedSet* {. TOrderedSet* {.
final, myShallow.}[A] = object ## set that remembers insertion order final, myShallow.}[A] = object ## \
## A generic hash set that remembers insertion order.
##
## Use `init() <#init,TOrderedSet[A],int>`_ or `initOrderedSet[type]()
## <#initOrderedSet>`_ before calling other procs on it.
data: TOrderedKeyValuePairSeq[A] data: TOrderedKeyValuePairSeq[A]
counter, first, last: int counter, first, last: int
proc isValid*[A](s: TOrderedSet[A]): bool =
## Returns `true` if the ordered set has been initialized with `initSet
## <#initOrderedSet>`_.
##
## Most operations over an uninitialized ordered set will crash at runtime
## and `assert <system.html#assert>`_ in debug builds. You can use this proc
## in your own procs to verify that ordered sets passed to your procs are
## correctly initialized. Example:
##
## .. code-block :: nimrod
## proc saveTarotCards(cards: TOrderedSet[int]) =
## assert cards.isValid, "Pass an initialized set!"
## # Do stuff here, may crash in release builds!
result = not s.data.isNil
proc len*[A](s: TOrderedSet[A]): int {.inline.} = proc len*[A](s: TOrderedSet[A]): int {.inline.} =
## returns the number of keys in `s`. ## Returns the number of keys in `s`.
##
## Due to an implementation detail you can call this proc on variables which
## have not been initialized yet. The proc will return zero as the length
## then. Example:
##
## .. code-block::
##
## var values: TOrderedSet[int]
## assert(not values.isValid)
## assert values.len == 0
result = s.counter result = s.counter
proc card*[A](s: TOrderedSet[A]): int {.inline.} = proc card*[A](s: TOrderedSet[A]): int {.inline.} =
## alias for `len`. ## Alias for `len() <#len,TOrderedSet[A]>`_.
##
## Card stands for the `cardinality
## <http://en.wikipedia.org/wiki/Cardinality>`_ of a set.
result = s.counter result = s.counter
template forAllOrderedPairs(yieldStmt: stmt) {.dirty, immediate.} = template forAllOrderedPairs(yieldStmt: stmt) {.dirty, immediate.} =
@ -230,7 +546,24 @@ template forAllOrderedPairs(yieldStmt: stmt) {.dirty, immediate.} =
h = nxt h = nxt
iterator items*[A](s: TOrderedSet[A]): A = iterator items*[A](s: TOrderedSet[A]): A =
## iterates over any key in the set `s` in insertion order. ## Iterates over keys in the ordered set `s` in insertion order.
##
## If you need a sequence with the keys you can use `sequtils.toSeq()
## <sequtils.html#toSeq>`_ on the iterator. Usage example:
##
## .. code-block::
## var a = initOrderedSet[int]()
## for value in [9, 2, 1, 5, 1, 8, 4, 2]:
## a.incl(value)
## for value in a.items:
## echo "Got ", value
## # --> Got 9
## # --> Got 2
## # --> Got 1
## # --> Got 5
## # --> Got 8
## # --> Got 4
assert s.isValid, "The set needs to be initialized."
forAllOrderedPairs: forAllOrderedPairs:
yield s.data[h].key yield s.data[h].key
@ -238,7 +571,16 @@ proc rawGet[A](s: TOrderedSet[A], key: A): int =
rawGetImpl() rawGetImpl()
proc contains*[A](s: TOrderedSet[A], key: A): bool = proc contains*[A](s: TOrderedSet[A], key: A): bool =
## returns true iff `key` is in `s`. ## Returns true iff `key` is in `s`.
##
## Example:
##
## .. code-block::
## var values = initOrderedSet[int]()
## assert(not values.contains(2))
## values.incl(2)
## assert values.contains(2)
assert s.isValid, "The set needs to be initialized."
var index = rawGet(s, key) var index = rawGet(s, key)
result = index >= 0 result = index >= 0
@ -264,53 +606,279 @@ proc enlarge[A](s: var TOrderedSet[A]) =
swap(s.data, n) swap(s.data, n)
proc incl*[A](s: var TOrderedSet[A], key: A) = proc incl*[A](s: var TOrderedSet[A], key: A) =
## includes an element `key` in `s`. ## Includes an element `key` in `s`.
##
## This doesn't do anything if `key` is already in `s`. Example:
##
## .. code-block::
## var values = initOrderedSet[int]()
## values.incl(2)
## values.incl(2)
## assert values.len == 1
assert s.isValid, "The set needs to be initialized."
inclImpl() inclImpl()
proc incl*[A](s: var TSet[A], other: TOrderedSet[A]) = proc incl*[A](s: var TSet[A], other: TOrderedSet[A]) =
## includes everything in `other` in `s` ## Includes all elements from `other` into `s`.
##
## Example:
##
## .. code-block::
## var values = initOrderedSet[int]()
## values.incl(2)
## var others = toOrderedSet([6, 7])
## values.incl(others)
## assert values.len == 3
assert s.isValid, "The set `s` needs to be initialized."
assert other.isValid, "The set `other` needs to be initialized."
for item in other: incl(s, item) for item in other: incl(s, item)
proc containsOrIncl*[A](s: var TOrderedSet[A], key: A): bool = proc containsOrIncl*[A](s: var TOrderedSet[A], key: A): bool =
## returns true if `s` contains `key`, otherwise `key` is included in `s` ## Includes `key` in the set `s` and tells if `key` was added to `s`.
## and false is returned. ##
## The difference with regards to the `incl() <#incl,TOrderedSet[A],A>`_ proc
## is that this proc returns `true` if `key` was already present in `s`. The
## proc will return false if `key` was added as a new value to `s` during
## this call. Example:
##
## .. code-block::
## var values = initOrderedSet[int]()
## assert values.containsOrIncl(2) == false
## assert values.containsOrIncl(2) == true
assert s.isValid, "The set needs to be initialized."
containsOrInclImpl() containsOrInclImpl()
proc initOrderedSet*[A](initialSize=64): TOrderedSet[A] = proc init*[A](s: var TOrderedSet[A], initialSize=64) =
## creates a new ordered hash set that is empty. `initialSize` needs to be ## Initializes an ordered hash set.
## a power of two. ##
## The `initialSize` parameter needs to be a power of too. You can use
## `math.nextPowerOfTwo() <math.html#nextPowerOfTwo>`_ to guarantee that at
## runtime. All set variables have to be initialized before you can use them
## with other procs from this module with the exception of `isValid()
## <#isValid,TOrderedSet[A]>`_ and `len() <#len,TOrderedSet[A]>`_.
##
## You can call this proc on a previously initialized ordered hash set to
## discard its values. At the moment this is the only proc to remove elements
## from an ordered hash set. Example:
##
## .. code-block ::
## var a: TOrderedSet[int]
## a.init(4)
## a.incl(2)
## a.init
## assert a.len == 0 and a.isValid
assert isPowerOfTwo(initialSize) assert isPowerOfTwo(initialSize)
result.counter = 0 s.counter = 0
result.first = -1 s.first = -1
result.last = -1 s.last = -1
newSeq(result.data, initialSize) newSeq(s.data, initialSize)
proc initOrderedSet*[A](initialSize=64): TOrderedSet[A] =
## Wrapper around `init() <#init,TOrderedSet[A],int>`_ for initialization of
## ordered hash sets.
##
## Returns an empty ordered hash set you can assign directly in ``var``
## blocks in a single line. Example:
##
## .. code-block ::
## var a = initOrderedSet[int](4)
## a.incl(2)
result.init(initialSize)
proc toOrderedSet*[A](keys: openArray[A]): TOrderedSet[A] = proc toOrderedSet*[A](keys: openArray[A]): TOrderedSet[A] =
## creates a new ordered hash set that contains the given `keys`. ## Creates a new ordered hash set that contains the given `keys`.
##
## Example:
##
## .. code-block::
## var numbers = toOrderedSet([1, 2, 3, 4, 5])
## assert numbers.contains(2)
## assert numbers.contains(4)
result = initOrderedSet[A](nextPowerOfTwo(keys.len+10)) result = initOrderedSet[A](nextPowerOfTwo(keys.len+10))
for key in items(keys): result.incl(key) for key in items(keys): result.incl(key)
proc `$`*[A](s: TOrderedSet[A]): string = proc `$`*[A](s: TOrderedSet[A]): string =
## The `$` operator for ordered hash sets. ## Converts the ordered hash set `s` to a string, mostly for logging purposes.
##
## Don't use this proc for serialization, the representation may change at
## any moment and values are not escaped. Example:
##
## Example:
##
## .. code-block::
## echo toOrderedSet([2, 4, 5])
## # --> {2, 4, 5}
## echo toOrderedSet(["no", "esc'aping", "is \" provided"])
## # --> {no, esc'aping, is " provided}
assert s.isValid, "The set needs to be initialized."
dollarImpl() dollarImpl()
proc `<`*[A](s, t: TSet[A]): bool = proc testModule() =
## Is s a strict subset of t? ## Internal micro test to validate docstrings and such.
s.counter != t.counter and s <= t block isValidTest:
var options: TSet[string]
proc savePreferences(options: TSet[string]) =
assert options.isValid, "Pass an initialized set!"
options = initSet[string]()
options.savePreferences
proc `<=`*[A](s, t: TSet[A]): bool = block lenTest:
## Is s a subset of t? var values: TSet[int]
result = false assert(not values.isValid)
if s.counter > t.counter: return assert values.len == 0
result = true assert values.card == 0
for item in s:
if not(t.contains(item)):
result = false
return
proc `==`*[A](s, t: TSet[A]): bool = block setIterator:
s.counter == t.counter and s <= t type pair = tuple[a, b: int]
var a, b = initSet[pair]()
a.incl((2, 3))
a.incl((3, 2))
a.incl((2, 3))
for x, y in a.items:
b.incl((x - 2, y + 1))
assert a.len == b.card
assert a.len == 2
#echo b
proc map*[A, B](data: TSet[A], op: proc (x: A): B {.closure.}): TSet[B] = block setContains:
result = initSet[B]() var values = initSet[int]()
for item in data: result.incl(op(item)) assert(not values.contains(2))
values.incl(2)
assert values.contains(2)
values.excl(2)
assert(not values.contains(2))
values.incl(4)
var others = toSet([6, 7])
values.incl(others)
assert values.len == 3
values.init
assert values.containsOrIncl(2) == false
assert values.containsOrIncl(2) == true
var
a = toSet([1, 2])
b = toSet([1])
b.incl(2)
assert a == b
block exclusions:
var s = toSet([2, 3, 6, 7])
s.excl(2)
s.excl(2)
assert s.len == 3
var
numbers = toSet([1, 2, 3, 4, 5])
even = toSet([2, 4, 6, 8])
numbers.excl(even)
#echo numbers
# --> {1, 3, 5}
block toSeqAndString:
var a = toSet([2, 4, 5])
var b = initSet[int]()
for x in [2, 4, 5]: b.incl(x)
assert($a == $b)
#echo a
#echo toSet(["no", "esc'aping", "is \" provided"])
#block orderedToSeqAndString:
# echo toOrderedSet([2, 4, 5])
# echo toOrderedSet(["no", "esc'aping", "is \" provided"])
block setOperations:
var
a = toSet(["a", "b"])
b = toSet(["b", "c"])
c = union(a, b)
assert c == toSet(["a", "b", "c"])
var d = intersection(a, b)
assert d == toSet(["b"])
var e = difference(a, b)
assert e == toSet(["a"])
var f = symmetricDifference(a, b)
assert f == toSet(["a", "c"])
assert d < a and d < b
assert((a < a) == false)
assert d <= a and d <= b
assert((a <= a))
# Alias test.
assert a + b == toSet(["a", "b", "c"])
assert a * b == toSet(["b"])
assert a - b == toSet(["a"])
assert a -+- b == toSet(["a", "c"])
assert disjoint(a, b) == false
assert disjoint(a, b - a) == true
block mapSet:
var a = toSet([1, 2, 3])
var b = a.map(proc (x: int): string = $x)
assert b == toSet(["1", "2", "3"])
block isValidTest:
var cards: TOrderedSet[string]
proc saveTarotCards(cards: TOrderedSet[string]) =
assert cards.isValid, "Pass an initialized set!"
cards = initOrderedSet[string]()
cards.saveTarotCards
block lenTest:
var values: TOrderedSet[int]
assert(not values.isValid)
assert values.len == 0
assert values.card == 0
block setIterator:
type pair = tuple[a, b: int]
var a, b = initOrderedSet[pair]()
a.incl((2, 3))
a.incl((3, 2))
a.incl((2, 3))
for x, y in a.items:
b.incl((x - 2, y + 1))
assert a.len == b.card
assert a.len == 2
#block orderedSetIterator:
# var a = initOrderedSet[int]()
# for value in [9, 2, 1, 5, 1, 8, 4, 2]:
# a.incl(value)
# for value in a.items:
# echo "Got ", value
block setContains:
var values = initOrderedSet[int]()
assert(not values.contains(2))
values.incl(2)
assert values.contains(2)
block toSeqAndString:
var a = toOrderedSet([2, 4, 5])
var b = initOrderedSet[int]()
for x in [2, 4, 5]: b.incl(x)
assert($a == $b)
# assert(a == b) # https://github.com/Araq/Nimrod/issues/1413
block initBlocks:
var a: TOrderedSet[int]
a.init(4)
a.incl(2)
a.init
assert a.len == 0 and a.isValid
a = initOrderedSet[int](4)
a.incl(2)
assert a.len == 1
var b: TSet[int]
b.init(4)
b.incl(2)
b.init
assert b.len == 0 and b.isValid
b = initSet[int](4)
b.incl(2)
assert b.len == 1
echo "Micro tests run successfully."
when isMainModule and not defined(release): testModule()

View file

@ -246,7 +246,8 @@ template equalsImpl() =
# different insertion orders mean different 'data' seqs, so we have # different insertion orders mean different 'data' seqs, so we have
# to use the slow route here: # to use the slow route here:
for key, val in s: for key, val in s:
if not hasKey(t, key): return false # 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[key] != val: return false
return true return true
@ -332,7 +333,9 @@ proc `$`*[A, B](t: PTable[A, B]): string =
dollarImpl() dollarImpl()
proc `==`*[A, B](s, t: PTable[A, B]): bool = proc `==`*[A, B](s, t: PTable[A, B]): bool =
equalsImpl() if isNil(s): result = isNil(t)
elif isNil(t): result = false
else: result = equalsImpl()
proc newTableFrom*[A, B, C](collection: A, index: proc(x: B): C): PTable[C, B] = proc newTableFrom*[A, B, C](collection: A, index: proc(x: B): C): PTable[C, B] =
## Index the collection with the proc provided. ## Index the collection with the proc provided.

View file

@ -621,9 +621,13 @@ proc `%`*(elements: openArray[PJsonNode]): PJsonNode =
proc `==`* (a,b: PJsonNode): bool = proc `==`* (a,b: PJsonNode): bool =
## Check two nodes for equality ## Check two nodes for equality
if a.kind != b.kind: false if a.isNil:
if b.isNil: return true
return false
elif b.isNil or a.kind != b.kind:
return false
else: else:
case a.kind return case a.kind
of JString: of JString:
a.str == b.str a.str == b.str
of JInt: of JInt:

View file

@ -955,11 +955,12 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1",
## ##
## If this fails, `EOS` is raised. On the Windows platform this proc will ## If this fails, `EOS` is raised. On the Windows platform this proc will
## copy the source file's attributes into dest. On other platforms you need ## copy the source file's attributes into dest. On other platforms you need
## to use getFilePermissions and setFilePermissions to copy them by hand (or ## to use `getFilePermissions() <#getFilePermissions>`_ and
## use the convenience copyFileWithPermissions() proc), otherwise `dest` will ## `setFilePermissions() <#setFilePermissions>`_ to copy them by hand (or use
## inherit the default permissions of a newly created file for the user. If ## the convenience `copyFileWithPermissions() <#copyFileWithPermissions>`_
## `dest` already exists, the file attributes will be preserved and the ## proc), otherwise `dest` will inherit the default permissions of a newly
## content overwritten. ## created file for the user. If `dest` already exists, the file attributes
## will be preserved and the content overwritten.
when defined(Windows): when defined(Windows):
when useWinUnicode: when useWinUnicode:
let s = newWideCString(source) let s = newWideCString(source)
@ -1363,7 +1364,13 @@ proc createDir*(dir: string) {.rtl, extern: "nos$1", tags: [FWriteDir].} =
proc copyDir*(source, dest: string) {.rtl, extern: "nos$1", proc copyDir*(source, dest: string) {.rtl, extern: "nos$1",
tags: [FWriteIO, FReadIO].} = tags: [FWriteIO, FReadIO].} =
## Copies a directory from `source` to `dest`. If this fails, `EOS` is raised. ## Copies a directory from `source` to `dest`.
##
## If this fails, `EOS` is raised. On the Windows platform this proc will
## copy the attributes from `source` into `dest`. On other platforms created
## files and directories will inherit the default permissions of a newly
## created file/directory for the user. To preserve attributes recursively on
## these platforms use `copyDirWithPermissions() <#copyDirWithPermissions>`_.
createDir(dest) createDir(dest)
for kind, path in walkDir(source): for kind, path in walkDir(source):
var noSource = path.substr(source.len()+1) var noSource = path.substr(source.len()+1)
@ -1507,14 +1514,17 @@ proc copyFileWithPermissions*(source, dest: string,
ignorePermissionErrors = true) = ignorePermissionErrors = true) =
## Copies a file from `source` to `dest` preserving file permissions. ## Copies a file from `source` to `dest` preserving file permissions.
## ##
## This is a wrapper proc around copyFile, getFilePermissions and ## This is a wrapper proc around `copyFile() <#copyFile>`_,
## setFilePermissions on non Windows platform. On windows this proc is just a ## `getFilePermissions() <#getFilePermissions>`_ and `setFilePermissions()
## wrapper for copyFile since that proc already copies attributes. ## <#setFilePermissions>`_ on non Windows platform. On Windows this proc is
## just a wrapper for `copyFile() <#copyFile>`_ since that proc already
## copies attributes.
## ##
## On non windows systems permissions are copied after the file itself has ## On non Windows systems permissions are copied after the file itself has
## been copied, which won't happen atomically and could lead to a race ## been copied, which won't happen atomically and could lead to a race
## condition. If ignorePermissionErrors is true, errors while reading/setting ## condition. If `ignorePermissionErrors` is true, errors while
## file attributes will be ignored, otherwise will raise `OSError`. ## reading/setting file attributes will be ignored, otherwise will raise
## `OSError`.
copyFile(source, dest) copyFile(source, dest)
when not defined(Windows): when not defined(Windows):
try: try:
@ -1523,6 +1533,37 @@ proc copyFileWithPermissions*(source, dest: string,
if not ignorePermissionErrors: if not ignorePermissionErrors:
raise raise
proc copyDirWithPermissions*(source, dest: string,
ignorePermissionErrors = true) {.rtl, extern: "nos$1",
tags: [FWriteIO, FReadIO].} =
## Copies a directory from `source` to `dest` preserving file permissions.
##
## If this fails, `EOS` is raised. This is a wrapper proc around `copyDir()
## <#copyDir>`_ and `copyFileWithPermissions() <#copyFileWithPermissions>`_
## on non Windows platforms. On Windows this proc is just a wrapper for
## `copyDir() <#copyDir>`_ since that proc already copies attributes.
##
## On non Windows systems permissions are copied after the file or directory
## itself has been copied, which won't happen atomically and could lead to a
## race condition. If `ignorePermissionErrors` is true, errors while
## reading/setting file attributes will be ignored, otherwise will raise
## `OSError`.
createDir(dest)
when not defined(Windows):
try:
setFilePermissions(dest, getFilePermissions(source))
except:
if not ignorePermissionErrors:
raise
for kind, path in walkDir(source):
var noSource = path.substr(source.len()+1)
case kind
of pcFile:
copyFileWithPermissions(path, dest / noSource, ignorePermissionErrors)
of pcDir:
copyDirWithPermissions(path, dest / noSource, ignorePermissionErrors)
else: discard
proc inclFilePermissions*(filename: string, proc inclFilePermissions*(filename: string,
permissions: set[TFilePermission]) {. permissions: set[TFilePermission]) {.
rtl, extern: "nos$1", tags: [FReadDir, FWriteDir].} = rtl, extern: "nos$1", tags: [FReadDir, FWriteDir].} =

View file

@ -763,7 +763,7 @@ elif not defined(useNimRtl):
discard write(data.pErrorPipe[writeIdx], addr error, sizeof(error)) discard write(data.pErrorPipe[writeIdx], addr error, sizeof(error))
exitnow(1) exitnow(1)
when defined(macosx): when defined(macosx) or defined(freebsd):
var environ {.importc.}: cstringArray var environ {.importc.}: cstringArray
proc startProcessAfterFork(data: ptr TStartProcessData) = proc startProcessAfterFork(data: ptr TStartProcessData) =
@ -793,7 +793,7 @@ elif not defined(useNimRtl):
discard fcntl(data.pErrorPipe[writeIdx], F_SETFD, FD_CLOEXEC) discard fcntl(data.pErrorPipe[writeIdx], F_SETFD, FD_CLOEXEC)
if data.optionPoUsePath: if data.optionPoUsePath:
when defined(macosx): when defined(macosx) or defined(freebsd):
# MacOSX doesn't have execvpe, so we need workaround. # MacOSX doesn't have execvpe, so we need workaround.
# On MacOSX we can arrive here only from fork, so this is safe: # On MacOSX we can arrive here only from fork, so this is safe:
environ = data.sysEnv environ = data.sysEnv

View file

@ -228,7 +228,7 @@ proc createAttributeNS*(doc: PDocument, namespaceURI: string, qualifiedName: str
raise newException(EInvalidCharacterErr, "Invalid character") raise newException(EInvalidCharacterErr, "Invalid character")
# Exceptions # Exceptions
if qualifiedName.contains(':'): if qualifiedName.contains(':'):
if namespaceURI == nil: if isNil(namespaceURI):
raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil") 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 qualifiedName.split(':')[0].toLower() == "xml" and namespaceURI != "http://www.w3.org/XML/1998/namespace":
raise newException(ENamespaceErr, raise newException(ENamespaceErr,
@ -304,7 +304,7 @@ proc createElement*(doc: PDocument, tagName: string): PElement =
proc createElementNS*(doc: PDocument, namespaceURI: string, qualifiedName: string): PElement = proc createElementNS*(doc: PDocument, namespaceURI: string, qualifiedName: string): PElement =
## Creates an element of the given qualified name and namespace URI. ## Creates an element of the given qualified name and namespace URI.
if qualifiedName.contains(':'): if qualifiedName.contains(':'):
if namespaceURI == nil: if isNIl(namespaceURI):
raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil") 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 qualifiedName.split(':')[0].toLower() == "xml" and namespaceURI != "http://www.w3.org/XML/1998/namespace":
raise newException(ENamespaceErr, raise newException(ENamespaceErr,
@ -445,7 +445,7 @@ proc importNode*(doc: PDocument, importedNode: PNode, deep: bool): PNode =
proc firstChild*(n: PNode): PNode = proc firstChild*(n: PNode): PNode =
## Returns this node's first child ## Returns this node's first child
if n.childNodes.len() > 0: if not isNil(n.childNodes) and n.childNodes.len() > 0:
return n.childNodes[0] return n.childNodes[0]
else: else:
return nil return nil
@ -453,7 +453,7 @@ proc firstChild*(n: PNode): PNode =
proc lastChild*(n: PNode): PNode = proc lastChild*(n: PNode): PNode =
## Returns this node's last child ## Returns this node's last child
if n.childNodes.len() > 0: if not isNil(n.childNodes) and n.childNodes.len() > 0:
return n.childNodes[n.childNodes.len() - 1] return n.childNodes[n.childNodes.len() - 1]
else: else:
return nil return nil
@ -474,6 +474,8 @@ proc `namespaceURI=`*(n: PNode, value: string) =
proc nextSibling*(n: PNode): PNode = proc nextSibling*(n: PNode): PNode =
## Returns the next sibling of this node ## Returns the next sibling of this node
if isNil(n.FParentNode) or isNil(n.FParentNode.childNodes):
return nil
var nLow: int = low(n.FParentNode.childNodes) var nLow: int = low(n.FParentNode.childNodes)
var nHigh: int = high(n.FParentNode.childNodes) var nHigh: int = high(n.FParentNode.childNodes)
for i in nLow..nHigh: for i in nLow..nHigh:
@ -504,6 +506,8 @@ proc parentNode*(n: PNode): PNode =
proc previousSibling*(n: PNode): PNode = proc previousSibling*(n: PNode): PNode =
## Returns the previous sibling of this node ## Returns the previous sibling of this node
if isNil(n.FParentNode) or isNil(n.FParentNode.childNodes):
return nil
var nLow: int = low(n.FParentNode.childNodes) var nLow: int = low(n.FParentNode.childNodes)
var nHigh: int = high(n.FParentNode.childNodes) var nHigh: int = high(n.FParentNode.childNodes)
for i in nLow..nHigh: for i in nLow..nHigh:
@ -519,7 +523,7 @@ proc `prefix=`*(n: PNode, value: string) =
if illegalChars in value: if illegalChars in value:
raise newException(EInvalidCharacterErr, "Invalid character") raise newException(EInvalidCharacterErr, "Invalid character")
if n.FNamespaceURI == nil: if isNil(n.FNamespaceURI):
raise newException(ENamespaceErr, "namespaceURI cannot be nil") raise newException(ENamespaceErr, "namespaceURI cannot be nil")
elif value.toLower() == "xml" and n.FNamespaceURI != "http://www.w3.org/XML/1998/namespace": elif value.toLower() == "xml" and n.FNamespaceURI != "http://www.w3.org/XML/1998/namespace":
raise newException(ENamespaceErr, raise newException(ENamespaceErr,
@ -545,7 +549,7 @@ proc appendChild*(n: PNode, newChild: PNode) =
## If the newChild is already in the tree, it is first removed. ## If the newChild is already in the tree, it is first removed.
# Check if n contains newChild # Check if n contains newChild
if not IsNil(n.childNodes): if not isNil(n.childNodes):
for i in low(n.childNodes)..high(n.childNodes): for i in low(n.childNodes)..high(n.childNodes):
if n.childNodes[i] == newChild: if n.childNodes[i] == newChild:
raise newException(EHierarchyRequestErr, "The node to append is already in this nodes children.") raise newException(EHierarchyRequestErr, "The node to append is already in this nodes children.")
@ -586,7 +590,7 @@ proc cloneNode*(n: PNode, deep: bool): PNode =
# Import the childNodes # Import the childNodes
var tmp: seq[PNode] = n.childNodes var tmp: seq[PNode] = n.childNodes
n.childNodes = @[] n.childNodes = @[]
if deep: if deep and not isNil(tmp):
for i in low(tmp.len())..high(tmp.len()): for i in low(tmp.len())..high(tmp.len()):
n.childNodes.add(cloneNode(tmp[i], deep)) n.childNodes.add(cloneNode(tmp[i], deep))
return newNode return newNode
@ -598,11 +602,11 @@ proc cloneNode*(n: PNode, deep: bool): PNode =
proc hasAttributes*(n: PNode): bool = proc hasAttributes*(n: PNode): bool =
## Returns whether this node (if it is an element) has any attributes. ## Returns whether this node (if it is an element) has any attributes.
return n.attributes.len() > 0 return not isNil(n.attributes) and n.attributes.len() > 0
proc hasChildNodes*(n: PNode): bool = proc hasChildNodes*(n: PNode): bool =
## Returns whether this node has any children. ## Returns whether this node has any children.
return n.childNodes.len() > 0 return not isNil(n.childNodes) and n.childNodes.len() > 0
proc insertBefore*(n: PNode, newChild: PNode, refChild: PNode): PNode = proc insertBefore*(n: PNode, newChild: PNode, refChild: PNode): PNode =
## Inserts the node ``newChild`` before the existing child node ``refChild``. ## Inserts the node ``newChild`` before the existing child node ``refChild``.
@ -612,10 +616,15 @@ proc insertBefore*(n: PNode, newChild: PNode, refChild: PNode): PNode =
if n.FOwnerDocument != newChild.FOwnerDocument: if n.FOwnerDocument != newChild.FOwnerDocument:
raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.")
if isNil(n.childNodes):
n.ChildNodes = @[]
for i in low(n.childNodes)..high(n.childNodes): for i in low(n.childNodes)..high(n.childNodes):
if n.childNodes[i] == refChild: if n.childNodes[i] == refChild:
n.childNodes.insert(newChild, i - 1) n.childNodes.insert(newChild, i - 1)
return return
n.ChildNodes.add(newChild)
proc isSupported*(n: PNode, feature: string, version: string): bool = proc isSupported*(n: PNode, feature: string, version: string): bool =
## Tests whether the DOM implementation implements a specific ## Tests whether the DOM implementation implements a specific
@ -624,7 +633,7 @@ proc isSupported*(n: PNode, feature: string, version: string): bool =
proc isEmpty(s: string): bool = proc isEmpty(s: string): bool =
if s == "" or s == nil: if isNil(s) or s == "":
return True return True
for i in items(s): for i in items(s):
if i != ' ': if i != ' ':
@ -638,7 +647,7 @@ proc normalize*(n: PNode) =
var newChildNodes: seq[PNode] = @[] var newChildNodes: seq[PNode] = @[]
while True: while True:
if i >= n.childNodes.len: if isNil(n.childNodes) or i >= n.childNodes.len:
break break
if n.childNodes[i].nodeType == TextNode: if n.childNodes[i].nodeType == TextNode:
@ -646,7 +655,7 @@ proc normalize*(n: PNode) =
if PText(n.childNodes[i]).data.isEmpty(): if PText(n.childNodes[i]).data.isEmpty():
inc(i) inc(i)
if curTextNode == nil: if isNil(curTextNode):
curTextNode = n.childNodes[i] curTextNode = n.childNodes[i]
else: else:
PText(curTextNode).data.add(PText(n.childNodes[i]).data) PText(curTextNode).data.add(PText(n.childNodes[i]).data)
@ -662,11 +671,12 @@ proc normalize*(n: PNode) =
proc removeChild*(n: PNode, oldChild: PNode): PNode = proc removeChild*(n: PNode, oldChild: PNode): PNode =
## Removes the child node indicated by ``oldChild`` from the list of children, and returns it. ## Removes the child node indicated by ``oldChild`` from the list of children, and returns it.
for i in low(n.childNodes)..high(n.childNodes): if not isNil(n.childNodes):
if n.childNodes[i] == oldChild: for i in low(n.childNodes)..high(n.childNodes):
result = n.childNodes[i] if n.childNodes[i] == oldChild:
n.childNodes.delete(i) result = n.childNodes[i]
return result n.childNodes.delete(i)
return result
raise newException(ENotFoundErr, "Node not found") raise newException(ENotFoundErr, "Node not found")
@ -677,11 +687,12 @@ proc replaceChild*(n: PNode, newChild: PNode, oldChild: PNode): PNode =
if n.FOwnerDocument != newChild.FOwnerDocument: if n.FOwnerDocument != newChild.FOwnerDocument:
raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.")
for i in low(n.childNodes)..high(n.childNodes): if not isNil(n.childNodes):
if n.childNodes[i] == oldChild: for i in low(n.childNodes)..high(n.childNodes):
result = n.childNodes[i] if n.childNodes[i] == oldChild:
n.childNodes[i] = newChild result = n.childNodes[i]
return result n.childNodes[i] = newChild
return result
raise newException(ENotFoundErr, "Node not found") raise newException(ENotFoundErr, "Node not found")
@ -753,7 +764,7 @@ proc setNamedItem*(NList: var seq[PNode], arg: PNode): PNode =
#Exceptions End #Exceptions End
var item: PNode = NList.getNamedItem(arg.NodeName()) var item: PNode = NList.getNamedItem(arg.NodeName())
if item == nil: if isNil(item):
NList.add(arg) NList.add(arg)
return nil return nil
else: else:
@ -769,18 +780,18 @@ proc setNamedItem*(NList: var seq[PNode], arg: PNode): PNode =
proc setNamedItem*(NList: var seq[PAttr], arg: PAttr): PAttr = proc setNamedItem*(NList: var seq[PAttr], arg: PAttr): PAttr =
## Adds ``arg`` as a ``Node`` to the ``NList`` ## Adds ``arg`` as a ``Node`` to the ``NList``
## If a node with the same name is already present in this map, it is replaced by the new one. ## If a node with the same name is already present in this map, it is replaced by the new one.
if not IsNil(NList): if not isNil(NList):
if NList.len() > 0: if NList.len() > 0:
# Check if newChild is from this nodes document # Check if newChild is from this nodes document
if NList[0].FOwnerDocument != arg.FOwnerDocument: if NList[0].FOwnerDocument != arg.FOwnerDocument:
raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.")
if arg.FOwnerElement != nil: if not isNil(arg.FOwnerElement):
raise newException(EInuseAttributeErr, "This attribute is in use by another element, use cloneNode") raise newException(EInuseAttributeErr, "This attribute is in use by another element, use cloneNode")
# Exceptions end # Exceptions end
var item: PAttr = NList.getNamedItem(arg.nodeName()) var item: PAttr = NList.getNamedItem(arg.nodeName())
if item == nil: if isNil(item):
NList.add(arg) NList.add(arg)
return nil return nil
else: else:
@ -795,7 +806,7 @@ proc setNamedItem*(NList: var seq[PAttr], arg: PAttr): PAttr =
proc setNamedItemNS*(NList: var seq[PNode], arg: PNode): PNode = proc setNamedItemNS*(NList: var seq[PNode], arg: PNode): PNode =
## Adds a node using its ``namespaceURI`` and ``localName`` ## Adds a node using its ``namespaceURI`` and ``localName``
if not IsNil(NList): if not isNil(NList):
if NList.len() > 0: if NList.len() > 0:
# Check if newChild is from this nodes document # Check if newChild is from this nodes document
if NList[0].FOwnerDocument != arg.FOwnerDocument: if NList[0].FOwnerDocument != arg.FOwnerDocument:
@ -803,7 +814,7 @@ proc setNamedItemNS*(NList: var seq[PNode], arg: PNode): PNode =
#Exceptions end #Exceptions end
var item: PNode = NList.getNamedItemNS(arg.namespaceURI(), arg.localName()) var item: PNode = NList.getNamedItemNS(arg.namespaceURI(), arg.localName())
if item == nil: if isNil(item):
NList.add(arg) NList.add(arg)
return nil return nil
else: else:
@ -824,12 +835,12 @@ proc setNamedItemNS*(NList: var seq[PAttr], arg: PAttr): PAttr =
if NList[0].FOwnerDocument != arg.FOwnerDocument: if NList[0].FOwnerDocument != arg.FOwnerDocument:
raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.")
if arg.FOwnerElement != nil: if not isNil(arg.FOwnerElement):
raise newException(EInuseAttributeErr, "This attribute is in use by another element, use cloneNode") raise newException(EInuseAttributeErr, "This attribute is in use by another element, use cloneNode")
# Exceptions end # Exceptions end
var item: PAttr = NList.getNamedItemNS(arg.namespaceURI(), arg.localName()) var item: PAttr = NList.getNamedItemNS(arg.namespaceURI(), arg.localName())
if item == nil: if isNil(item):
NList.add(arg) NList.add(arg)
return nil return nil
else: else:
@ -873,16 +884,20 @@ proc tagName*(el: PElement): string =
# Procedures # Procedures
proc getAttribute*(el: PElement, name: string): string = proc getAttribute*(el: PElement, name: string): string =
## Retrieves an attribute value by ``name`` ## Retrieves an attribute value by ``name``
if isNil(el.attributes):
return nil
var attribute = el.attributes.getNamedItem(name) var attribute = el.attributes.getNamedItem(name)
if attribute != nil: if not isNil(attribute):
return attribute.value return attribute.value
else: else:
return nil return nil
proc getAttributeNS*(el: PElement, namespaceURI: string, localName: string): string = proc getAttributeNS*(el: PElement, namespaceURI: string, localName: string): string =
## Retrieves an attribute value by ``localName`` and ``namespaceURI`` ## Retrieves an attribute value by ``localName`` and ``namespaceURI``
if isNil(el.attributes):
return nil
var attribute = el.attributes.getNamedItemNS(namespaceURI, localName) var attribute = el.attributes.getNamedItemNS(namespaceURI, localName)
if attribute != nil: if not isNil(attribute):
return attribute.value return attribute.value
else: else:
return nil return nil
@ -890,10 +905,14 @@ proc getAttributeNS*(el: PElement, namespaceURI: string, localName: string): str
proc getAttributeNode*(el: PElement, name: string): PAttr = proc getAttributeNode*(el: PElement, name: string): PAttr =
## Retrieves an attribute node by ``name`` ## Retrieves an attribute node by ``name``
## To retrieve an attribute node by qualified name and namespace URI, use the `getAttributeNodeNS` method ## To retrieve an attribute node by qualified name and namespace URI, use the `getAttributeNodeNS` method
if isNil(el.attributes):
return nil
return el.attributes.getNamedItem(name) return el.attributes.getNamedItem(name)
proc getAttributeNodeNS*(el: PElement, namespaceURI: string, localName: string): PAttr = proc getAttributeNodeNS*(el: PElement, namespaceURI: string, localName: string): PAttr =
## Retrieves an `Attr` node by ``localName`` and ``namespaceURI`` ## Retrieves an `Attr` node by ``localName`` and ``namespaceURI``
if isNil(el.attributes):
return nil
return el.attributes.getNamedItemNS(namespaceURI, localName) return el.attributes.getNamedItemNS(namespaceURI, localName)
proc getElementsByTagName*(el: PElement, name: string): seq[PNode] = proc getElementsByTagName*(el: PElement, name: string): seq[PNode] =
@ -911,34 +930,41 @@ proc getElementsByTagNameNS*(el: PElement, namespaceURI: string, localName: stri
proc hasAttribute*(el: PElement, name: string): bool = proc hasAttribute*(el: PElement, name: string): bool =
## Returns ``true`` when an attribute with a given ``name`` is specified ## Returns ``true`` when an attribute with a given ``name`` is specified
## on this element , ``false`` otherwise. ## on this element , ``false`` otherwise.
return el.attributes.getNamedItem(name) != nil if isNil(el.attributes):
return false
return not isNil(el.attributes.getNamedItem(name))
proc hasAttributeNS*(el: PElement, namespaceURI: string, localName: string): bool = proc hasAttributeNS*(el: PElement, namespaceURI: string, localName: string): bool =
## Returns ``true`` when an attribute with a given ``localName`` and ## Returns ``true`` when an attribute with a given ``localName`` and
## ``namespaceURI`` is specified on this element , ``false`` otherwise ## ``namespaceURI`` is specified on this element , ``false`` otherwise
return el.attributes.getNamedItemNS(namespaceURI, localName) != nil if isNil(el.attributes):
return false
return not isNil(el.attributes.getNamedItemNS(namespaceURI, localName))
proc removeAttribute*(el: PElement, name: string) = proc removeAttribute*(el: PElement, name: string) =
## Removes an attribute by ``name`` ## Removes an attribute by ``name``
for i in low(el.attributes)..high(el.attributes): if not isNil(el.attributes):
if el.attributes[i].FName == name: for i in low(el.attributes)..high(el.attributes):
el.attributes.delete(i) if el.attributes[i].FName == name:
el.attributes.delete(i)
proc removeAttributeNS*(el: PElement, namespaceURI: string, localName: string) = proc removeAttributeNS*(el: PElement, namespaceURI: string, localName: string) =
## Removes an attribute by ``localName`` and ``namespaceURI`` ## Removes an attribute by ``localName`` and ``namespaceURI``
for i in low(el.attributes)..high(el.attributes): if not isNil(el.attributes):
if el.attributes[i].FNamespaceURI == namespaceURI and for i in low(el.attributes)..high(el.attributes):
el.attributes[i].FLocalName == localName: if el.attributes[i].FNamespaceURI == namespaceURI and
el.attributes.delete(i) el.attributes[i].FLocalName == localName:
el.attributes.delete(i)
proc removeAttributeNode*(el: PElement, oldAttr: PAttr): PAttr = proc removeAttributeNode*(el: PElement, oldAttr: PAttr): PAttr =
## Removes the specified attribute node ## Removes the specified attribute node
## If the attribute node cannot be found raises ``ENotFoundErr`` ## If the attribute node cannot be found raises ``ENotFoundErr``
for i in low(el.attributes)..high(el.attributes): if not isNil(el.attributes):
if el.attributes[i] == oldAttr: for i in low(el.attributes)..high(el.attributes):
result = el.attributes[i] if el.attributes[i] == oldAttr:
el.attributes.delete(i) result = el.attributes[i]
return result el.attributes.delete(i)
return result
raise newException(ENotFoundErr, "oldAttr is not a member of el's Attributes") raise newException(ENotFoundErr, "oldAttr is not a member of el's Attributes")
@ -952,7 +978,7 @@ proc setAttributeNode*(el: PElement, newAttr: PAttr): PAttr =
raise newException(EWrongDocumentErr, raise newException(EWrongDocumentErr,
"This node belongs to a different document, use importNode.") "This node belongs to a different document, use importNode.")
if newAttr.FOwnerElement != nil: if not isNil(newAttr.FOwnerElement):
raise newException(EInuseAttributeErr, raise newException(EInuseAttributeErr,
"This attribute is in use by another element, use cloneNode") "This attribute is in use by another element, use cloneNode")
# Exceptions end # Exceptions end
@ -970,7 +996,7 @@ proc setAttributeNodeNS*(el: PElement, newAttr: PAttr): PAttr =
raise newException(EWrongDocumentErr, raise newException(EWrongDocumentErr,
"This node belongs to a different document, use importNode.") "This node belongs to a different document, use importNode.")
if newAttr.FOwnerElement != nil: if not isNil(newAttr.FOwnerElement):
raise newException(EInuseAttributeErr, raise newException(EInuseAttributeErr,
"This attribute is in use by another element, use cloneNode") "This attribute is in use by another element, use cloneNode")
# Exceptions end # Exceptions end
@ -1025,7 +1051,7 @@ proc splitData*(TextNode: PText, offset: int): PText =
TextNode.data = left TextNode.data = left
var right: string = TextNode.data.substr(offset, TextNode.data.len()) var right: string = TextNode.data.substr(offset, TextNode.data.len())
if TextNode.FParentNode != nil: if not isNil(TextNode.FParentNode) and not isNil(TextNode.FParentNode.childNodes):
for i in low(TextNode.FParentNode.childNodes)..high(TextNode.FParentNode.childNodes): for i in low(TextNode.FParentNode.childNodes)..high(TextNode.FParentNode.childNodes):
if TextNode.FParentNode.childNodes[i] == TextNode: if TextNode.FParentNode.childNodes[i] == TextNode:
var newNode: PText = TextNode.FOwnerDocument.createTextNode(right) var newNode: PText = TextNode.FOwnerDocument.createTextNode(right)
@ -1057,10 +1083,11 @@ proc addEscaped(s: string): string =
proc nodeToXml(n: PNode, indent: int = 0): string = proc nodeToXml(n: PNode, indent: int = 0): string =
result = repeatChar(indent, ' ') & "<" & n.nodeName result = repeatChar(indent, ' ') & "<" & n.nodeName
for i in items(n.Attributes): if not isNil(n.attributes):
result.add(" " & i.name & "=\"" & addEscaped(i.value) & "\"") for i in items(n.attributes):
result.add(" " & i.name & "=\"" & addEscaped(i.value) & "\"")
if n.childNodes.len() == 0: if isNil(n.childNodes) or n.childNodes.len() == 0:
result.add("/>") # No idea why this doesn't need a \n :O result.add("/>") # No idea why this doesn't need a \n :O
else: else:
# End the beginning of this tag # End the beginning of this tag

View file

@ -2031,7 +2031,7 @@ proc echo*[T](x: varargs[T, `$`]) {.magic: "Echo", tags: [FWriteIO], gcsafe.}
## Special built-in that takes a variable number of arguments. Each argument ## Special built-in that takes a variable number of arguments. Each argument
## is converted to a string via ``$``, so it works for user-defined ## is converted to a string via ``$``, so it works for user-defined
## types that have an overloaded ``$`` operator. ## types that have an overloaded ``$`` operator.
## It is roughly equivalent to ``writeln(stdout, x); flush(stdout)``, but ## It is roughly equivalent to ``writeln(stdout, x); flushFile(stdout)``, but
## available for the JavaScript target too. ## available for the JavaScript target too.
## ##
## Unlike other IO operations this is guaranteed to be thread-safe as ## Unlike other IO operations this is guaranteed to be thread-safe as

View file

@ -104,6 +104,15 @@ block countTableTest1:
block SyntaxTest: block SyntaxTest:
var x = newTable[int, string]({:}) var x = newTable[int, string]({:})
block nilTest:
var i, j: PTable[int, int] = nil
assert i == j
j = newTable[int, int]()
assert i != j
assert j != i
i = newTable[int, int]()
assert i == j
proc orderedTableSortTest() = proc orderedTableSortTest() =
var t = newOrderedTable[string, int](2) var t = newOrderedTable[string, int](2)
for key, val in items(data): t[key] = val for key, val in items(data): t[key] = val

View file

@ -24,7 +24,7 @@ proc execCode(code: string): string =
f.close() f.close()
result = osproc.execProcess( result = osproc.execProcess(
"$# $# --verbosity:0 --hint[Conf]:off temp.nim" % [nimExe, runCmd], "$# $# --verbosity:0 --hint[Conf]:off temp.nim" % [nimExe, runCmd],
{poStdErrToStdOut}) options = {poStdErrToStdOut})
else: else:
result = "cannot open file 'temp.nim'" result = "cannot open file 'temp.nim'"

View file

@ -18,7 +18,7 @@ proc walker(dir: string) =
else: else:
echo "Required: ", path echo "Required: ", path
# copy back: # copy back:
moveFile(dest=path, sourc=newName(path)) moveFile(dest=path, source=newName(path))
of pcDir: of pcDir:
walker(path) walker(path)
else: discard else: discard

View file

@ -21,7 +21,7 @@ proc processContent(content: string) =
var jsonArr = jsonDoc.elems var jsonArr = jsonDoc.elems
jsonArr.sort do (x, y: PJsonNode) -> int: jsonArr.sort do (x, y: PJsonNode) -> int:
system.cmp(x["name"].str, y["name"].str) strutils.cmpIgnoreCase(x["name"].str, y["name"].str)
var var
officialList = "" officialList = ""
@ -38,8 +38,7 @@ proc processContent(content: string) =
else: pkg["url"].str else: pkg["url"].str
let let
desc = pkg["description"].str desc = pkg["description"].str
# Review array index access when #1291 is solved. dot = if desc.high > 0 and desc[desc.high] in endings: "" else: "."
dot = if desc.high > 0 and desc[<desc.high] in endings: "" else: "."
listItem = li(a(href=pkgWeb, pkg["name"].str), " ", desc & dot) listItem = li(a(href=pkgWeb, pkg["name"].str), " ", desc & dot)
if pkg["url"].str.startsWith("git://github.com/nimrod-code") or if pkg["url"].str.startsWith("git://github.com/nimrod-code") or
"official" in pkg["tags"].elems: "official" in pkg["tags"].elems:
@ -51,19 +50,17 @@ proc processContent(content: string) =
var officialPkgListDiv = document.getElementById("officialPkgList") var officialPkgListDiv = document.getElementById("officialPkgList")
officialPkgListDiv.innerHTML.add( officialPkgListDiv.innerHTML =
p("There are currently " & $officialCount & p("There are currently " & $officialCount &
" official packages in the Babel package repository.") & " official packages in the Babel package repository.") &
ul(officialList) ul(officialList)
)
var unofficialPkgListDiv = document.getElementById("unofficialPkgList") var unofficialPkgListDiv = document.getElementById("unofficialPkgList")
unofficialPkgListDiv.innerHTML.add( unofficialPkgListDiv.innerHTML =
p("There are currently " & $unofficialCount & p("There are currently " & $unofficialCount &
" unofficial packages in the Babel package repository.") & " unofficial packages in the Babel package repository.") &
ul(unofficialList) ul(unofficialList)
)
proc gotPackageList(apiReply: TData) {.exportc.} = proc gotPackageList(apiReply: TData) {.exportc.} =
let decoded = decodeContent($apiReply.content) let decoded = decodeContent($apiReply.content)