From be1e3c4e09e4c5592428e71d875ef6a623c82804 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Thu, 3 Nov 2016 21:11:39 +0800 Subject: [PATCH 01/58] add a simple sizeof checker to compare nim & c types --- compiler/ccgtypes.nim | 5 ++++- lib/nimbase.h | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index bd2e2cdda..0327d9b82 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -701,7 +701,10 @@ proc getTypeDescAux(m: BModule, typ: PType, check: var IntSet): Rope = idTablePut(m.typeCache, t, result) # always call for sideeffects: let recdesc = if t.kind != tyTuple: getRecordDesc(m, t, result, check) else: getTupleDesc(m, t, result, check) - if not isImportedType(t): add(m.s[cfsTypes], recdesc) + if not isImportedType(t): + add(m.s[cfsTypes], recdesc) + elif tfIncompleteStruct notin t.flags: + addf(m.s[cfsTypeInfo], "NIM_CHECK_SIZE($1, $2);$n", [result, rope(getSize(t))]) of tySet: result = getTypeName(t.lastSon) & "Set" idTablePut(m.typeCache, t, result) diff --git a/lib/nimbase.h b/lib/nimbase.h index 52de60969..a75016ed7 100644 --- a/lib/nimbase.h +++ b/lib/nimbase.h @@ -459,3 +459,11 @@ typedef int Nim_and_C_compiler_disagree_on_target_architecture[sizeof(NI) == siz #elif defined(__FreeBSD__) # include #endif + +/* Compile with -t:-DNIM_CHECK_ABI to enable */ +#ifdef NIM_CHECK_ABI +# define NIM_CHECK_SIZE(typ, sz) \ + _Static_assert(sizeof(typ) == sz, "Nim & C disagree on type size") +#else +# define NIM_CHECK_SIZE(typ, sz) +#endif From fa86571448ea89143ca78576c8248bc02ad056bd Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Thu, 3 Nov 2016 22:30:00 +0800 Subject: [PATCH 02/58] abi check: prefer nim constant to enable, document --- compiler/ccgtypes.nim | 2 +- doc/nimc.rst | 4 ++++ lib/nimbase.h | 8 ++------ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 0327d9b82..a9c27217a 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -703,7 +703,7 @@ proc getTypeDescAux(m: BModule, typ: PType, check: var IntSet): Rope = else: getTupleDesc(m, t, result, check) if not isImportedType(t): add(m.s[cfsTypes], recdesc) - elif tfIncompleteStruct notin t.flags: + elif tfIncompleteStruct notin t.flags and isDefined("checkabi"): addf(m.s[cfsTypeInfo], "NIM_CHECK_SIZE($1, $2);$n", [result, rope(getSize(t))]) of tySet: result = getTypeName(t.lastSon) & "Set" diff --git a/doc/nimc.rst b/doc/nimc.rst index eb1beb549..5d9ed03ab 100644 --- a/doc/nimc.rst +++ b/doc/nimc.rst @@ -258,6 +258,10 @@ Define Effect ``ssl`` Enables OpenSSL support for the sockets module. ``memProfiler`` Enables memory profiling for the native GC. ``uClibc`` Use uClibc instead of libc. (Relevant for Unix-like OSes) +``checkAbi`` When using types from C headers, add checks that compare + what's in the Nim file with what's in the C header + (requires a C compiler with _Static_assert support, like + any C11 compiler) ================== ========================================================= diff --git a/lib/nimbase.h b/lib/nimbase.h index a75016ed7..818bff462 100644 --- a/lib/nimbase.h +++ b/lib/nimbase.h @@ -460,10 +460,6 @@ typedef int Nim_and_C_compiler_disagree_on_target_architecture[sizeof(NI) == siz # include #endif -/* Compile with -t:-DNIM_CHECK_ABI to enable */ -#ifdef NIM_CHECK_ABI -# define NIM_CHECK_SIZE(typ, sz) \ +/* Compile with -d:checkAbi and a sufficiently C11:ish compiler to enable */ +#define NIM_CHECK_SIZE(typ, sz) \ _Static_assert(sizeof(typ) == sz, "Nim & C disagree on type size") -#else -# define NIM_CHECK_SIZE(typ, sz) -#endif From fb7850a10a8dbbbbc11568fc58584af37a507386 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Thu, 3 Nov 2016 23:04:33 +0800 Subject: [PATCH 03/58] add primitive type abi check --- compiler/ccgtypes.nim | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index a9c27217a..60ee0eaee 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -206,6 +206,10 @@ proc cacheGetType(tab: TIdTable, key: PType): Rope = # linear search is not necessary anymore: result = Rope(idTableGet(tab, key)) +proc addAbiCheck(m: BModule, t: PType, name: Rope) = + if isDefined("checkabi"): + addf(m.s[cfsTypeInfo], "NIM_CHECK_SIZE($1, $2);$n", [name, rope(getSize(t))]) + proc getTempName(m: BModule): Rope = result = m.tmpBase & rope(m.labels) inc m.labels @@ -267,6 +271,11 @@ proc getSimpleTypeDesc(m: BModule, typ: PType): Rope = result = getSimpleTypeDesc(m, lastSon typ) else: result = nil + if result != nil and typ.isImportedType(): + if cacheGetType(m.typeCache, typ) == nil: + idTablePut(m.typeCache, typ, result) + addAbiCheck(m, typ, result) + proc pushType(m: BModule, typ: PType) = add(m.typeStack, typ) @@ -656,6 +665,7 @@ proc getTypeDescAux(m: BModule, typ: PType, check: var IntSet): Rope = let foo = getTypeDescAux(m, t.sons[1], check) addf(m.s[cfsTypes], "typedef $1 $2[$3];$n", [foo, result, rope(n)]) + else: addAbiCheck(m, t, result) of tyObject, tyTuple: if isImportedCppType(t) and typ.kind == tyGenericInst: # for instantiated templates we do not go through the type cache as the @@ -703,8 +713,7 @@ proc getTypeDescAux(m: BModule, typ: PType, check: var IntSet): Rope = else: getTupleDesc(m, t, result, check) if not isImportedType(t): add(m.s[cfsTypes], recdesc) - elif tfIncompleteStruct notin t.flags and isDefined("checkabi"): - addf(m.s[cfsTypeInfo], "NIM_CHECK_SIZE($1, $2);$n", [result, rope(getSize(t))]) + elif tfIncompleteStruct notin t.flags: addAbiCheck(m, t, result) of tySet: result = getTypeName(t.lastSon) & "Set" idTablePut(m.typeCache, t, result) From d857b4bc0e1d07ab8c47a264ed6e3220b5a16d54 Mon Sep 17 00:00:00 2001 From: Yuriy Glukhov Date: Tue, 11 Oct 2016 12:28:41 +0300 Subject: [PATCH 04/58] DRY frame info setting out to a template. --- lib/system/gc.nim | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/lib/system/gc.nim b/lib/system/gc.nim index 11897ce80..c8623f2f1 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -445,6 +445,15 @@ proc gcInvariant*() = markForDebug(gch) {.pop.} +template setFrameInfo(c: PCell) = + when leakDetector: + if framePtr != nil and framePtr.prev != nil: + c.filename = framePtr.prev.filename + c.line = framePtr.prev.line + else: + c.filename = nil + c.line = 0 + proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap): pointer = # generates a new object and sets its reference counter to 0 sysAssert(allocInv(gch.region), "rawNewObj begin") @@ -455,13 +464,7 @@ 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: - 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 + setFrameInfo(res) # refcount is zero, color is black, but mark it to be in the ZCT res.refcount = ZctFlag sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3") @@ -509,13 +512,7 @@ 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: - 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 + setFrameInfo(res) res.refcount = rcIncrement # refcount is 1 sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3") when logGC: writeCell("new cell", res) From 2f48dbd166d38a06a99c6f850abe9214969ac36a Mon Sep 17 00:00:00 2001 From: Axel Pahl Date: Tue, 8 Nov 2016 09:08:42 +0100 Subject: [PATCH 05/58] Update .gitignore Update .gitignore to reflect the new target dir of the generated documentation. `./koch doc` now writes the documentation to `doc/html/...` --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 57b8a68d4..f5b8e4826 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ tags install.sh deinstall.sh +doc/html/ doc/*.html doc/*.pdf doc/*.idx From 9a68e1ad0547c420a81db4bfccd0ca2563af0450 Mon Sep 17 00:00:00 2001 From: Axel Pahl Date: Tue, 8 Nov 2016 15:09:37 +0100 Subject: [PATCH 06/58] added dist/ to .gitignore When using `./koch tools`, the directory `dist/` is generated (it contains the `nimble` sources). The `dist` directory was added to .gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f5b8e4826..50fa9a431 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,7 @@ xcuserdata/ /testresults.json testament.db /csources +dist/ # Private directories and files (IDEs) .*/ From 9b2aaf0df62a7159861602ab4e0073d87d4be81d Mon Sep 17 00:00:00 2001 From: Felix Krause Date: Tue, 8 Nov 2016 20:57:53 +0100 Subject: [PATCH 07/58] Fixed timezone sign error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * This was introduced in recent "cosmetic" fix. Not so cosmetic after all… --- lib/pure/times.nim | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 1e869d301..4260179ed 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -824,21 +824,21 @@ proc formatToken(info: TimeInfo, token: string, buf: var string) = buf.add(fyear) of "z": let hours = abs(info.timezone) div secondsInHour - if info.timezone < 0: buf.add('-') - else: buf.add('+') + if info.timezone < 0: buf.add('+') + else: buf.add('-') buf.add($hours) of "zz": let hours = abs(info.timezone) div secondsInHour - if info.timezone < 0: buf.add('-') - else: buf.add('+') + if info.timezone < 0: buf.add('+') + else: buf.add('-') if hours < 10: buf.add('0') buf.add($hours) of "zzz": let hours = abs(info.timezone) div secondsInHour minutes = abs(info.timezone) mod 60 - if info.timezone < 0: buf.add('-') - else: buf.add('+') + if info.timezone < 0: buf.add('+') + else: buf.add('-') if hours < 10: buf.add('0') buf.add($hours) buf.add(':') From 2f3c8c2f3441783df5f2516eaab414a9b43f362c Mon Sep 17 00:00:00 2001 From: Yuriy Glukhov Date: Wed, 9 Nov 2016 15:09:08 +0200 Subject: [PATCH 08/58] Fixed openssl for android --- lib/wrappers/openssl.nim | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/wrappers/openssl.nim b/lib/wrappers/openssl.nim index 204e5bb40..241ad17ae 100644 --- a/lib/wrappers/openssl.nim +++ b/lib/wrappers/openssl.nim @@ -261,11 +261,14 @@ proc ERR_error_string*(e: cInt, buf: cstring): cstring{.cdecl, proc ERR_get_error*(): cInt{.cdecl, dynlib: DLLUtilName, importc.} proc ERR_peek_last_error*(): cInt{.cdecl, dynlib: DLLUtilName, importc.} -proc OpenSSL_add_all_algorithms*(){.cdecl, dynlib: DLLUtilName, importc: "OPENSSL_add_all_algorithms_conf".} +when defined(android): + template OpenSSL_add_all_algorithms*() = discard +else: + proc OpenSSL_add_all_algorithms*(){.cdecl, dynlib: DLLUtilName, importc: "OPENSSL_add_all_algorithms_conf".} proc OPENSSL_config*(configName: cstring){.cdecl, dynlib: DLLSSLName, importc.} -when not useWinVersion and not defined(macosx): +when not useWinVersion and not defined(macosx) and not defined(android): proc CRYPTO_set_mem_functions(a,b,c: pointer){.cdecl, dynlib: DLLUtilName, importc.} @@ -279,7 +282,7 @@ when not useWinVersion and not defined(macosx): if p != nil: dealloc(p) proc CRYPTO_malloc_init*() = - when not useWinVersion and not defined(macosx): + when not useWinVersion and not defined(macosx) and not defined(android): CRYPTO_set_mem_functions(allocWrapper, reallocWrapper, deallocWrapper) proc SSL_CTX_ctrl*(ctx: SslCtx, cmd: cInt, larg: int, parg: pointer): int{. From 91a067496119ce1b3b6b5401e029e7d6c6ed4c9f Mon Sep 17 00:00:00 2001 From: Felix Krause Date: Thu, 10 Nov 2016 19:03:46 +0100 Subject: [PATCH 09/58] Fixed timezone rendering, added test --- lib/pure/times.nim | 9 +++++---- tests/stdlib/ttime.nim | 12 ++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 4260179ed..f74003820 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -732,6 +732,7 @@ const secondsInMin = 60 secondsInHour = 60*60 secondsInDay = 60*60*24 + minutesInHour = 60 epochStartYear = 1970 proc formatToken(info: TimeInfo, token: string, buf: var string) = @@ -824,20 +825,20 @@ proc formatToken(info: TimeInfo, token: string, buf: var string) = buf.add(fyear) of "z": let hours = abs(info.timezone) div secondsInHour - if info.timezone < 0: buf.add('+') + if info.timezone <= 0: buf.add('+') else: buf.add('-') buf.add($hours) of "zz": let hours = abs(info.timezone) div secondsInHour - if info.timezone < 0: buf.add('+') + if info.timezone <= 0: buf.add('+') else: buf.add('-') if hours < 10: buf.add('0') buf.add($hours) of "zzz": let hours = abs(info.timezone) div secondsInHour - minutes = abs(info.timezone) mod 60 - if info.timezone < 0: buf.add('+') + minutes = (abs(info.timezone) div secondsInMin) mod minutesInHour + if info.timezone <= 0: buf.add('+') else: buf.add('-') if hours < 10: buf.add('0') buf.add($hours) diff --git a/tests/stdlib/ttime.nim b/tests/stdlib/ttime.nim index 5d3c8325e..c1559ec7a 100644 --- a/tests/stdlib/ttime.nim +++ b/tests/stdlib/ttime.nim @@ -190,3 +190,15 @@ doAssert cmpTimeNoSideEffect(0.fromSeconds, 0.fromSeconds) let seqA: seq[Time] = @[] let seqB: seq[Time] = @[] doAssert seqA == seqB + +for tz in [ + (0, "+0", "+00", "+00:00"), # UTC + (-3600, "+1", "+01", "+01:00"), # CET + (-39600, "+11", "+11", "+11:00"), # two digits + (-1800, "+0", "+00", "+00:30"), # half an hour + (7200, "-2", "-02", "-02:00"), # positive + (38700, "-10", "-10", "-10:45")]: # positive with three quaters hour + let ti = TimeInfo(monthday: 1, timezone: tz[0]) + doAssert ti.format("z") == tz[1] + doAssert ti.format("zz") == tz[2] + doAssert ti.format("zzz") == tz[3] \ No newline at end of file From bd818d4fe14c27ebf19fbc90a2bb5a785896dd80 Mon Sep 17 00:00:00 2001 From: Parashurama Date: Thu, 10 Nov 2016 23:19:23 +0100 Subject: [PATCH 10/58] make 'excessivestacktrace' option available for testing. --- compiler/commands.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/commands.nim b/compiler/commands.nim index 85951a28f..e545e7ab5 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -264,6 +264,7 @@ proc testCompileOption*(switch: string, info: TLineInfo): bool = of "implicitstatic": result = contains(gOptions, optImplicitStatic) of "patterns": result = contains(gOptions, optPatterns) of "experimental": result = gExperimentalMode + of "excessivestacktrace": result = contains(gGlobalOptions, optExcessiveStackTrace) else: invalidCmdLineOption(passCmd1, switch, info) proc processPath(path: string, info: TLineInfo, From e695d3bfbaa9f0d8b811259535be296a6441c468 Mon Sep 17 00:00:00 2001 From: Dmitry Polienko Date: Mon, 14 Nov 2016 02:55:57 -0800 Subject: [PATCH 11/58] Fix asyncfile in Windows Server 2003 Fixes #5022 --- lib/pure/asyncfile.nim | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/pure/asyncfile.nim b/lib/pure/asyncfile.nim index ffe6a391e..0241e4796 100644 --- a/lib/pure/asyncfile.nim +++ b/lib/pure/asyncfile.nim @@ -118,8 +118,8 @@ proc readBuffer*(f: AsyncFile, buf: pointer, size: int): Future[int] = ## Read ``size`` bytes from the specified file asynchronously starting at ## the current position of the file pointer. ## - ## If the file pointer is past the end of the file then an empty string is - ## returned. + ## If the file pointer is past the end of the file then zero is returned + ## and no bytes are read into ``buf`` var retFuture = newFuture[int]("asyncfile.readBuffer") when defined(windows) or defined(nimdoc): @@ -149,7 +149,11 @@ proc readBuffer*(f: AsyncFile, buf: pointer, size: int): Future[int] = let err = osLastError() if err.int32 != ERROR_IO_PENDING: GC_unref(ol) - retFuture.fail(newException(OSError, osErrorMsg(err))) + if err.int32 == ERROR_HANDLE_EOF: + # This happens in Windows Server 2003 + retFuture.complete(0) + else: + retFuture.fail(newException(OSError, osErrorMsg(err))) else: # Request completed immediately. var bytesRead: DWord @@ -233,7 +237,12 @@ proc read*(f: AsyncFile, size: int): Future[string] = dealloc buffer buffer = nil GC_unref(ol) - retFuture.fail(newException(OSError, osErrorMsg(err))) + + if err.int32 == ERROR_HANDLE_EOF: + # This happens in Windows Server 2003 + retFuture.complete("") + else: + retFuture.fail(newException(OSError, osErrorMsg(err))) else: # Request completed immediately. var bytesRead: DWord From 544a2cfe1a9c4805568f6dd781cd85fa4537cb73 Mon Sep 17 00:00:00 2001 From: Felix Krause Date: Thu, 10 Nov 2016 22:22:48 +0100 Subject: [PATCH 12/58] Fixed daylight saving time * When formatting timezone, substract 1 hour from timezone when isDST * Do not depend DST in current timezone when parsing arbitrary date because formatted timestamps are never in DST. * On the way, removed an unnecessary line in parsing code which could cause bugs. * Added DST tests --- lib/pure/times.nim | 35 ++++++++++++++++------------------- tests/stdlib/ttime.nim | 23 ++++++++++++++++++++++- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index f74003820..cfc39bc55 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -144,8 +144,9 @@ type yearday*: range[0..365] ## The number of days since January 1, ## in the range 0 to 365. ## Always 0 if the target is JS. - isDST*: bool ## Determines whether DST is in effect. Always - ## ``False`` if time is UTC. + isDST*: bool ## Determines whether DST is in effect. + ## Semantically, this adds another negative hour + ## offset to the time in addition to the timezone. timezone*: int ## The offset of the (non-DST) timezone in seconds ## west of UTC. Note that the sign of this number ## is the opposite of the one in a formatted @@ -824,28 +825,32 @@ proc formatToken(info: TimeInfo, token: string, buf: var string) = if fyear.len != 5: fyear = repeat('0', 5-fyear.len()) & fyear buf.add(fyear) of "z": - let hours = abs(info.timezone) div secondsInHour - if info.timezone <= 0: buf.add('+') + let + nonDstTz = info.timezone - int(info.isDst) * secondsInHour + hours = abs(nonDstTz) div secondsInHour + if nonDstTz <= 0: buf.add('+') else: buf.add('-') buf.add($hours) of "zz": - let hours = abs(info.timezone) div secondsInHour - if info.timezone <= 0: buf.add('+') + let + nonDstTz = info.timezone - int(info.isDst) * secondsInHour + hours = abs(nonDstTz) div secondsInHour + if nonDstTz <= 0: buf.add('+') else: buf.add('-') if hours < 10: buf.add('0') buf.add($hours) of "zzz": let - hours = abs(info.timezone) div secondsInHour - minutes = (abs(info.timezone) div secondsInMin) mod minutesInHour - if info.timezone <= 0: buf.add('+') + nonDstTz = info.timezone - int(info.isDst) * secondsInHour + hours = abs(nonDstTz) div secondsInHour + minutes = (abs(nonDstTz) div secondsInMin) mod minutesInHour + if nonDstTz <= 0: buf.add('+') else: buf.add('-') if hours < 10: buf.add('0') buf.add($hours) buf.add(':') if minutes < 10: buf.add('0') buf.add($minutes) - of "": discard else: @@ -1000,7 +1005,6 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) = of "M": var pd = parseInt(value[j..j+1], sv) info.month = Month(sv-1) - info.monthday = sv j += pd of "MM": var month = value[j..j+1].parseInt() @@ -1166,6 +1170,7 @@ proc parse*(value, layout: string): TimeInfo = info.hour = 0 info.minute = 0 info.second = 0 + info.isDST = false # DST is never encoded in timestamps. while true: case layout[i] of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',': @@ -1195,14 +1200,6 @@ proc parse*(value, layout: string): TimeInfo = parseToken(info, token, value, j) token = "" - # We are going to process the date to find out if we are in DST, because the - # default based on the current time may be wrong. Calling getLocalTime will - # set this correctly, but the actual time may be offset from when we called - # toTime with a possibly incorrect DST setting, so we are only going to take - # the isDST from this result. - let correctDST = getLocalTime(toTime(info)) - info.isDST = correctDST.isDST - # Now we process it again with the correct isDST to correct things like # weekday and yearday. return getLocalTime(toTime(info)) diff --git a/tests/stdlib/ttime.nim b/tests/stdlib/ttime.nim index c1559ec7a..6d3d7c93a 100644 --- a/tests/stdlib/ttime.nim +++ b/tests/stdlib/ttime.nim @@ -201,4 +201,25 @@ for tz in [ let ti = TimeInfo(monthday: 1, timezone: tz[0]) doAssert ti.format("z") == tz[1] doAssert ti.format("zz") == tz[2] - doAssert ti.format("zzz") == tz[3] \ No newline at end of file + doAssert ti.format("zzz") == tz[3] + +block dstTest: + let nonDst = TimeInfo(year: 2015, month: mJan, monthday: 01, yearday: 0, + weekday: dThu, hour: 00, minute: 00, second: 00, isDST: false, timezone: 0) + var dst = nonDst + dst.isDst = true + # note that both isDST == true and isDST == false are valid here because + # DST is in effect on January 1st in some southern parts of Australia. + + doAssert nonDst.toTime() - dst.toTime() == 3600 + doAssert nonDst.format("z") == "+0" + doAssert dst.format("z") == "+1" + + # parsing will set isDST in relation to the local time. We take a date in + # January and one in July to maximize the probability to hit one date with DST + # and one without on the local machine. However, this is not guaranteed. + let + parsedJul = parse("2016-07-01 04:00:00+01:00", "yyyy-MM-dd HH:mm:sszzz") + parsedJan = parse("2016-01-05 04:00:00+01:00", "yyyy-MM-ss HH:mm:sszzz") + doAssert toTime(parsedJan) == fromSeconds(1452394800) + doAssert toTime(parsedJul) == fromSeconds(1467342000) From aa08c32c2b6a32da1aa1f98234512f37330a6691 Mon Sep 17 00:00:00 2001 From: Felix Krause Date: Fri, 11 Nov 2016 17:52:53 +0100 Subject: [PATCH 13/58] Improved `-`; fixed tests * added prefix `-` operator for TimeInterval * improved `-` for both TimeInterval and TimeInfo * Fixed a DST test --- lib/pure/times.nim | 33 +++++++++++++++------------------ tests/stdlib/ttime.nim | 4 ++-- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index cfc39bc55..b6b9fad5b 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -279,16 +279,20 @@ proc `+`*(ti1, ti2: TimeInterval): TimeInterval = carryO = `div`(ti1.months + ti2.months, 12) result.years = carryO + ti1.years + ti2.years +proc `-`*(ti: TimeInterval): TimeInterval = + result = TimeInterval( + milliseconds: -ti.milliseconds, + seconds: -ti.seconds, + minutes: -ti.minutes, + hours: -ti.hours, + days: -ti.days, + months: -ti.months, + years: -ti.years + ) + proc `-`*(ti1, ti2: TimeInterval): TimeInterval = ## Subtracts TimeInterval ``ti1`` from ``ti2``. - result = ti1 - result.milliseconds -= ti2.milliseconds - result.seconds -= ti2.seconds - result.minutes -= ti2.minutes - result.hours -= ti2.hours - result.days -= ti2.days - result.months -= ti2.months - result.years -= ti2.years + result = ti1 + (-ti2) proc isLeapYear*(year: int): bool = ## returns true if ``year`` is a leap year @@ -364,16 +368,9 @@ proc `-`*(a: TimeInfo, interval: TimeInterval): TimeInfo = ## ## **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(toTime(a)) - var intval: TimeInterval - intval.milliseconds = - interval.milliseconds - intval.seconds = - interval.seconds - intval.minutes = - interval.minutes - intval.hours = - interval.hours - intval.days = - interval.days - intval.months = - interval.months - intval.years = - interval.years - let secs = toSeconds(a, intval) + let + t = toSeconds(toTime(a)) + secs = toSeconds(a, -interval) if a.timezone == 0: result = getGMTime(fromSeconds(t + secs)) else: diff --git a/tests/stdlib/ttime.nim b/tests/stdlib/ttime.nim index 6d3d7c93a..39106f1e0 100644 --- a/tests/stdlib/ttime.nim +++ b/tests/stdlib/ttime.nim @@ -219,7 +219,7 @@ block dstTest: # January and one in July to maximize the probability to hit one date with DST # and one without on the local machine. However, this is not guaranteed. let + parsedJan = parse("2016-01-05 04:00:00+01:00", "yyyy-MM-dd HH:mm:sszzz") parsedJul = parse("2016-07-01 04:00:00+01:00", "yyyy-MM-dd HH:mm:sszzz") - parsedJan = parse("2016-01-05 04:00:00+01:00", "yyyy-MM-ss HH:mm:sszzz") - doAssert toTime(parsedJan) == fromSeconds(1452394800) + doAssert toTime(parsedJan) == fromSeconds(1451962800) doAssert toTime(parsedJul) == fromSeconds(1467342000) From 0587a578075498dffaadbe7cf0ba1885eb597536 Mon Sep 17 00:00:00 2001 From: Felix Krause Date: Mon, 14 Nov 2016 18:36:03 +0100 Subject: [PATCH 14/58] Assume local DST iff no timezone is given --- lib/pure/times.nim | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index b6b9fad5b..ef23c4cf9 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -1097,6 +1097,7 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) = else: raise newException(ValueError, "Couldn't parse timezone offset (z), got: " & value[j]) + info.isDST = false j += 2 of "zz": if value[j] == '+': @@ -1106,6 +1107,7 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) = else: raise newException(ValueError, "Couldn't parse timezone offset (zz), got: " & value[j]) + info.isDST = false j += 3 of "zzz": var factor = 0 @@ -1118,6 +1120,7 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) = j += 4 info.timezone += factor * value[j..j+1].parseInt() * 60 j += 2 + info.isDST = false else: # Ignore the token and move forward in the value string by the same length j += token.len @@ -1167,7 +1170,8 @@ proc parse*(value, layout: string): TimeInfo = info.hour = 0 info.minute = 0 info.second = 0 - info.isDST = false # DST is never encoded in timestamps. + info.isDST = true # using this is flag for checking whether a timezone has \ + # been read (because DST is always false when a tz is parsed) while true: case layout[i] of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',': @@ -1197,8 +1201,17 @@ proc parse*(value, layout: string): TimeInfo = parseToken(info, token, value, j) token = "" - # Now we process it again with the correct isDST to correct things like - # weekday and yearday. + if info.isDST: + # means that no timezone has been parsed. In this case, we need to check + # whether the date is within DST of the local time. + let tmp = getLocalTime(toTime(info)) + # correctly set isDST so that the following step works on the correct time + info.isDST = tmp.isDST + + # Correct weekday and yearday; transform timestamp to local time. + # There currently is no way of returning this with the original (parsed) + # timezone while also setting weekday and yearday (we are depending on stdlib + # to provide this calculation). return getLocalTime(toTime(info)) # Leap year calculations are adapted from: From 434c27343e72c4e90530d9b90c0851bc20c8ea32 Mon Sep 17 00:00:00 2001 From: Felix Krause Date: Mon, 14 Nov 2016 18:46:35 +0100 Subject: [PATCH 15/58] Parse 'Z' as valid timezone if offset is expected --- lib/pure/times.nim | 18 +++++++++++++++--- tests/stdlib/ttime.nim | 4 ++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index ef23c4cf9..0ab14e183 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -1090,29 +1090,42 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) = info.year = value[j..j+3].parseInt() j += 4 of "z": + info.isDST = false if value[j] == '+': info.timezone = 0 - parseInt($value[j+1]) * secondsInHour elif value[j] == '-': info.timezone = parseInt($value[j+1]) * secondsInHour + elif value[j] == 'Z': + info.timezone = 0 + j += 1 + return else: raise newException(ValueError, "Couldn't parse timezone offset (z), got: " & value[j]) - info.isDST = false j += 2 of "zz": + info.isDST = false if value[j] == '+': info.timezone = 0 - value[j+1..j+2].parseInt() * secondsInHour elif value[j] == '-': info.timezone = value[j+1..j+2].parseInt() * secondsInHour + elif value[j] == 'Z': + info.timezone = 0 + j += 1 + return else: raise newException(ValueError, "Couldn't parse timezone offset (zz), got: " & value[j]) - info.isDST = false j += 3 of "zzz": + info.isDST = false var factor = 0 if value[j] == '+': factor = -1 elif value[j] == '-': factor = 1 + elif value[j] == 'Z': + info.timezone = 0 + j += 1 + return else: raise newException(ValueError, "Couldn't parse timezone offset (zzz), got: " & value[j]) @@ -1120,7 +1133,6 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) = j += 4 info.timezone += factor * value[j..j+1].parseInt() * 60 j += 2 - info.isDST = false else: # Ignore the token and move forward in the value string by the same length j += token.len diff --git a/tests/stdlib/ttime.nim b/tests/stdlib/ttime.nim index 39106f1e0..b28d8aecd 100644 --- a/tests/stdlib/ttime.nim +++ b/tests/stdlib/ttime.nim @@ -96,6 +96,10 @@ parseTest("2006-01-12T15:04:05Z-07:00", "yyyy-MM-dd'T'HH:mm:ss'Z'zzz", # RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" parseTest("2006-01-12T15:04:05.999999999Z-07:00", "yyyy-MM-ddTHH:mm:ss.999999999Zzzz", "2006-01-12T22:04:05+00:00", 11) +for tzFormat in ["z", "zz", "zzz"]: + # formatting timezone as 'Z' for UTC + parseTest("2001-01-12T22:04:05Z", "yyyy-MM-dd'T'HH:mm:ss" & tzFormat, + "2001-01-12T22:04:05+00:00", 11) # Kitchen = "3:04PM" parseTestTimeOnly("3:04PM", "h:mmtt", "15:04:00") #when not defined(testing): From 0ffd14e169cdaa94aa3f8a9bb29fbf02596042e8 Mon Sep 17 00:00:00 2001 From: Felix Krause Date: Mon, 14 Nov 2016 19:18:23 +0100 Subject: [PATCH 16/58] Updated times.parse() documentation --- lib/pure/times.nim | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 0ab14e183..1767a37be 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -1138,8 +1138,11 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) = j += token.len proc parse*(value, layout: string): TimeInfo = - ## This function parses a date/time string using the standard format identifiers (below) - ## The function defaults information not provided in the format string from the running program (timezone, month, year, etc) + ## This function parses a date/time string using the standard format + ## identifiers as listed below. The function defaults information not provided + ## in the format string from the running program (timezone, month, year, etc). + ## Daylight saving time is only set if no timezone is given and the given date + ## lies within the DST period of the current locale. ## ## ========== ================================================================================= ================================================ ## Specifier Description Example @@ -1164,7 +1167,7 @@ proc parse*(value, layout: string): TimeInfo = ## tt Same as above, but ``AM`` and ``PM`` instead of ``A`` and ``P`` respectively. ## yy Displays the year to two digits. ``2012 -> 12`` ## yyyy Displays the year to four digits. ``2012 -> 2012`` - ## z Displays the timezone offset from UTC. ``GMT+7 -> +7``, ``GMT-5 -> -5`` + ## z Displays the timezone offset from UTC. ``Z`` is parsed as ``+0`` ``GMT+7 -> +7``, ``GMT-5 -> -5`` ## zz Same as above but with leading 0. ``GMT+7 -> +07``, ``GMT-5 -> -05`` ## zzz Same as above but with ``:mm`` where *mm* represents minutes. ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00`` ## ========== ================================================================================= ================================================ From 2c46fdd0abc60879a2e66288a13a9e6eb08f0ad6 Mon Sep 17 00:00:00 2001 From: Dmitry Polienko Date: Tue, 15 Nov 2016 12:13:05 +0700 Subject: [PATCH 17/58] Add newFileLogger overload accepting file descriptor --- lib/pure/logging.nim | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim index b23b1e5bb..5544a4b3f 100644 --- a/lib/pure/logging.nim +++ b/lib/pure/logging.nim @@ -172,18 +172,26 @@ when not defined(js): var (path, name, _) = splitFile(getAppFilename()) result = changeFileExt(path / name, "log") + proc newFileLogger*(file: File, + levelThreshold = lvlAll, + fmtStr = defaultFmtStr): FileLogger = + ## Creates a new file logger. This logger logs to ``file``. + new(result) + result.file = file + result.levelThreshold = levelThreshold + result.fmtStr = fmtStr + proc newFileLogger*(filename = defaultFilename(), mode: FileMode = fmAppend, levelThreshold = lvlAll, fmtStr = defaultFmtStr, bufSize: int = -1): FileLogger = - ## Creates a new file logger. This logger logs to a file. + ## Creates a new file logger. This logger logs to a file, specified + ## by ``fileName``. ## Use ``bufSize`` as size of the output buffer when writing the file ## (-1: use system defaults, 0: unbuffered, >0: fixed buffer size). - new(result) - result.levelThreshold = levelThreshold - result.file = open(filename, mode, bufSize = bufSize) - result.fmtStr = fmtStr + let file = open(filename, mode, bufSize = bufSize) + newFileLogger(file, levelThreshold, fmtStr) # ------ From c62c38ca462e5abedbfea4440d80eb2aa9994bec Mon Sep 17 00:00:00 2001 From: Dmitry Polienko Date: Tue, 15 Nov 2016 13:43:39 +0700 Subject: [PATCH 18/58] Use default colors for test and suite names --- lib/pure/unittest.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index 12553e3da..cdca02ed7 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -98,7 +98,7 @@ proc startSuite(name: string) = template rawPrint() = echo("\n[Suite] ", name) when not defined(ECMAScript): if colorOutput: - styledEcho styleBright, fgBlue, "\n[Suite] ", fgWhite, name + styledEcho styleBright, fgBlue, "\n[Suite] ", resetStyle, name else: rawPrint() else: rawPrint() @@ -159,7 +159,7 @@ proc testDone(name: string, s: TestStatus, indent: bool) = of FAILED: fgRed of SKIPPED: fgYellow else: fgWhite - styledEcho styleBright, color, prefix, "[", $s, "] ", fgWhite, name + styledEcho styleBright, color, prefix, "[", $s, "] ", resetStyle, name else: rawPrint() else: From 5058ae1ba55b4a883044f2280160af818ba58b32 Mon Sep 17 00:00:00 2001 From: David Krause Date: Thu, 17 Nov 2016 16:47:36 +0100 Subject: [PATCH 19/58] fixed typo --- lib/pure/net.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/net.nim b/lib/pure/net.nim index 58f5e5777..863a8a6f4 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -1250,7 +1250,7 @@ proc IPv6_loopback*(): IpAddress = address_v6: [0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]) proc `==`*(lhs, rhs: IpAddress): bool = - ## Compares two IpAddresses for Equality. Returns two if the addresses are equal + ## Compares two IpAddresses for Equality. Returns true if the addresses are equal if lhs.family != rhs.family: return false if lhs.family == IpAddressFamily.IPv4: for i in low(lhs.address_v4) .. high(lhs.address_v4): From 8875ca750f839de0c3eefdc73e665228f45b52e0 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 17 Nov 2016 19:57:10 +0100 Subject: [PATCH 20/58] deepCopy: proper sharing of refs --- lib/system/deepcopy.nim | 104 ++++++++++++++++++++++++++++------------ 1 file changed, 73 insertions(+), 31 deletions(-) diff --git a/lib/system/deepcopy.nim b/lib/system/deepcopy.nim index 38cc8cbf3..0a661d0cd 100644 --- a/lib/system/deepcopy.nim +++ b/lib/system/deepcopy.nim @@ -7,18 +7,64 @@ # distribution, for details about the copyright. # -proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) {.benign.} -proc genericDeepCopyAux(dest, src: pointer, n: ptr TNimNode) {.benign.} = +type + PtrTable = ptr object + counter, max: int + data: array[0..0xff_ffff, (pointer, pointer)] + +template hashPtr(key: pointer): int = cast[int](key) shr 8 + +proc rehash(t: PtrTable): PtrTable = + let cap = (t.max+1) * 2 + result = cast[PtrTable](alloc0(sizeof(int)*2 + sizeof(pointer)*cap)) + result.counter = t.counter + result.max = cap-1 + for i in 0..t.max: + let k = t.data[i][0] + if k != nil: + var h = hashPtr(k) + while result.data[h and result.max][0] != nil: inc h + result.data[h and result.max] = t.data[i] + dealloc t + +proc initPtrTable(): PtrTable = + const cap = 32 + result = cast[PtrTable](alloc0(sizeof(int)*2 + sizeof(pointer)*cap)) + result.counter = 0 + result.max = cap-1 + +template deinit(t: PtrTable) = dealloc(t) + +proc get(t: PtrTable; key: pointer): pointer = + var h = hashPtr(key) + while true: + let k = t.data[h and t.max][0] + if k == nil: break + if k == key: + return t.data[h and t.max][1] + inc h + +proc put(t: var PtrTable; key, val: pointer) = + if (t.max+1) * 2 < t.counter * 3: t = rehash(t) + var h = hashPtr(key) + while t.data[h and t.max][0] != nil: inc h + t.data[h and t.max] = (key, val) + inc t.counter + +proc genericDeepCopyAux(dest, src: pointer, mt: PNimType; + tab: var PtrTable) {.benign.} +proc genericDeepCopyAux(dest, src: pointer, n: ptr TNimNode; + tab: var PtrTable) {.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) + cast[pointer](s +% n.offset), n.typ, tab) of nkList: for i in 0..n.len-1: - genericDeepCopyAux(dest, src, n.sons[i]) + genericDeepCopyAux(dest, src, n.sons[i], tab) of nkCase: var dd = selectBranch(dest, n) var m = selectBranch(src, n) @@ -29,10 +75,10 @@ proc genericDeepCopyAux(dest, src: pointer, n: ptr TNimNode) {.benign.} = copyMem(cast[pointer](d +% n.offset), cast[pointer](s +% n.offset), n.typ.size) if m != nil: - genericDeepCopyAux(dest, src, m) + genericDeepCopyAux(dest, src, m, tab) of nkNone: sysAssert(false, "genericDeepCopyAux") -proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) = +proc genericDeepCopyAux(dest, src: pointer, mt: PNimType; tab: var PtrTable) = var d = cast[ByteAddress](dest) s = cast[ByteAddress](src) @@ -60,22 +106,22 @@ proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) = cast[pointer](dst +% i*% mt.base.size +% GenericSeqSize), cast[pointer](cast[ByteAddress](s2) +% i *% mt.base.size +% GenericSeqSize), - mt.base) + mt.base, tab) of tyObject: # we need to copy m_type field for tyObject, as it could be empty for # sequence reallocations: if mt.base != nil: - genericDeepCopyAux(dest, src, mt.base) + genericDeepCopyAux(dest, src, mt.base, tab) else: var pint = cast[ptr PNimType](dest) pint[] = cast[ptr PNimType](src)[] - genericDeepCopyAux(dest, src, mt.node) + genericDeepCopyAux(dest, src, mt.node, tab) of tyTuple: - genericDeepCopyAux(dest, src, mt.node) + genericDeepCopyAux(dest, src, mt.node, tab) of tyArray, tyArrayConstr: for i in 0..(mt.size div mt.base.size)-1: genericDeepCopyAux(cast[pointer](d +% i*% mt.base.size), - cast[pointer](s +% i*% mt.base.size), mt.base) + cast[pointer](s +% i*% mt.base.size), mt.base, tab) of tyRef: let s2 = cast[PPointer](src)[] if s2 == nil: @@ -84,30 +130,24 @@ proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) = let z = mt.base.deepcopy(s2) unsureAsgnRef(cast[PPointer](dest), z) 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. - when declared(usrToCell): - # unfortunately we only have cycle detection for our native GCs. - 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 z = tab.get(s2) + if z == nil: + when declared(usrToCell) and false: + let x = usrToCell(s2) 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(z, s2, realType.base) - x.typ = realType + tab.put(s2, z) + genericDeepCopyAux(z, s2, realType.base, tab) + else: + # this version should work for any possible GC: + let size = if mt.base.kind == tyObject: cast[ptr PNimType](s2)[].size else: mt.base.size + let z = newObj(mt, size) + unsureAsgnRef(cast[PPointer](dest), z) + tab.put(s2, z) + genericDeepCopyAux(z, s2, mt.base, tab) else: - let size = if mt.base.kind == tyObject: cast[ptr PNimType](s2)[].size - else: mt.base.size - let z = newObj(mt, size) unsureAsgnRef(cast[PPointer](dest), z) - genericDeepCopyAux(z, s2, mt.base) of tyPtr: # no cycle check here, but also not really required let s2 = cast[PPointer](src)[] @@ -120,7 +160,9 @@ proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) = proc genericDeepCopy(dest, src: pointer, mt: PNimType) {.compilerProc.} = GC_disable() - genericDeepCopyAux(dest, src, mt) + var tab = initPtrTable() + genericDeepCopyAux(dest, src, mt, tab) + deinit tab GC_enable() proc genericSeqDeepCopy(dest, src: pointer, mt: PNimType) {.compilerProc.} = From 7c1b5b3c2ba0e76c55cd3ea34dd1ac5beea1af42 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Fri, 18 Nov 2016 09:54:12 +0100 Subject: [PATCH 21/58] fixes deepcopy regression --- lib/system/deepcopy.nim | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/system/deepcopy.nim b/lib/system/deepcopy.nim index 0a661d0cd..b1609252c 100644 --- a/lib/system/deepcopy.nim +++ b/lib/system/deepcopy.nim @@ -13,10 +13,12 @@ type data: array[0..0xff_ffff, (pointer, pointer)] template hashPtr(key: pointer): int = cast[int](key) shr 8 +template allocPtrTable: untyped = + cast[PtrTable](alloc0(sizeof(int)*2 + sizeof(pointer)*2*cap)) proc rehash(t: PtrTable): PtrTable = let cap = (t.max+1) * 2 - result = cast[PtrTable](alloc0(sizeof(int)*2 + sizeof(pointer)*cap)) + result = allocPtrTable() result.counter = t.counter result.max = cap-1 for i in 0..t.max: @@ -29,7 +31,7 @@ proc rehash(t: PtrTable): PtrTable = proc initPtrTable(): PtrTable = const cap = 32 - result = cast[PtrTable](alloc0(sizeof(int)*2 + sizeof(pointer)*cap)) + result = allocPtrTable() result.counter = 0 result.max = cap-1 From 93a998204c62ce473e125f059250f4a64a10ce0a Mon Sep 17 00:00:00 2001 From: Felix Krause Date: Fri, 18 Nov 2016 23:42:15 +0100 Subject: [PATCH 22/58] Fixes #5035 --- lib/pure/collections/tableimpl.nim | 14 ++++++++++---- lib/pure/collections/tables.nim | 13 +++---------- tests/collections/ttables.nim | 25 ++++++++++++++++++++++++- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/lib/pure/collections/tableimpl.nim b/lib/pure/collections/tableimpl.nim index a3dfd43a1..674fdddd2 100644 --- a/lib/pure/collections/tableimpl.nim +++ b/lib/pure/collections/tableimpl.nim @@ -39,16 +39,22 @@ template rawGetKnownHCImpl() {.dirty.} = h = nextTry(h, maxHash(t)) result = -1 - h # < 0 => MISSING; insert idx = -1 - result -template rawGetImpl() {.dirty.} = +template genHashImpl(key, hc: typed) = hc = hash(key) if hc == 0: # This almost never taken branch should be very predictable. hc = 314159265 # Value doesn't matter; Any non-zero favorite is fine. + +template genHash(key: typed): Hash = + var res: Hash + genHashImpl(key, res) + res + +template rawGetImpl() {.dirty.} = + genHashImpl(key, hc) rawGetKnownHCImpl() template rawGetDeepImpl() {.dirty.} = # Search algo for unconditional add - hc = hash(key) - if hc == 0: - hc = 314159265 + genHashImpl(key, hc) var h: Hash = hc and maxHash(t) while isFilled(t.data[h].hcode): h = nextTry(h, maxHash(t)) diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index bee0a41b2..dfd822852 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -224,7 +224,7 @@ template withValue*[A, B](t: var Table[A, B], key: A, 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) + var h: Hash = genHash(key) and high(t.data) while isFilled(t.data[h].hcode): if t.data[h].key == key: yield t.data[h].val @@ -479,7 +479,7 @@ proc clear*[A, B](t: var OrderedTableRef[A, B]) = ## Resets the table so that is is empty. clear(t[]) -template forAllOrderedPairs(yieldStmt: untyped) {.oldimmediate, dirty.} = +template forAllOrderedPairs(yieldStmt: untyped): typed {.dirty.} = var h = t.first while h >= 0: var nxt = t.data[h].next @@ -674,13 +674,6 @@ proc len*[A, B](t: OrderedTableRef[A, B]): int {.inline.} = ## returns the number of keys in `t`. result = t.counter -template forAllOrderedPairs(yieldStmt: untyped) {.oldimmediate, dirty.} = - var h = t.first - while h >= 0: - var nxt = t.data[h].next - if isFilled(t.data[h].hcode): yieldStmt - h = nxt - iterator pairs*[A, B](t: OrderedTableRef[A, B]): (A, B) = ## iterates over any (key, value) pair in the table `t` in insertion ## order. @@ -786,7 +779,7 @@ proc sort*[A, B](t: OrderedTableRef[A, B], 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) + let hc = genHash(key) forAllOrderedPairs: if t.data[h].hcode == hc: if t.first == h: diff --git a/tests/collections/ttables.nim b/tests/collections/ttables.nim index 4f286d0ed..ef5ed92f5 100644 --- a/tests/collections/ttables.nim +++ b/tests/collections/ttables.nim @@ -112,7 +112,7 @@ block orderedTableTest2: block countTableTest1: var s = data.toTable var t = initCountTable[string]() - + for k in s.keys: t.inc(k) for k in t.keys: assert t[k] == 1 t.inc("90", 3) @@ -167,6 +167,29 @@ block mpairsTableTest1: block SyntaxTest: var x = toTable[int, string]({:}) +block zeroHashKeysTest: + proc doZeroHashValueTest[T, K, V](t: T, nullHashKey: K, value: V) = + let initialLen = t.len + var testTable = t + testTable[nullHashKey] = value + assert testTable[nullHashKey] == value + assert testTable.len == initialLen + 1 + testTable.del(nullHashKey) + assert testTable.len == initialLen + + # with empty table + doZeroHashValueTest(toTable[int,int]({:}), 0, 42) + doZeroHashValueTest(toTable[string,int]({:}), "", 23) + doZeroHashValueTest(toOrderedTable[int,int]({:}), 0, 42) + doZeroHashValueTest(toOrderedTable[string,int]({:}), "", 23) + + # with non-empty table + doZeroHashValueTest(toTable[int,int]({1:2}), 0, 42) + doZeroHashValueTest(toTable[string,string]({"foo": "bar"}), "", "zero") + doZeroHashValueTest(toOrderedTable[int,int]({3:4}), 0, 42) + doZeroHashValueTest(toOrderedTable[string,string]({"egg": "sausage"}), + "", "spam") + # Until #4448 is fixed, these tests will fail when false: block clearTableTest: From 3eba4b510f853c314bbd596873f77ccfcc4f55da Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Sat, 19 Nov 2016 09:22:29 +0100 Subject: [PATCH 23/58] added test case for deepcopy --- tests/system/tdeepcopy.nim | 94 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100755 tests/system/tdeepcopy.nim diff --git a/tests/system/tdeepcopy.nim b/tests/system/tdeepcopy.nim new file mode 100755 index 000000000..f7a6e87fa --- /dev/null +++ b/tests/system/tdeepcopy.nim @@ -0,0 +1,94 @@ +discard """ + output: "ok" +""" + +import tables, lists + +type + ListTable[K, V] = object + valList: DoublyLinkedList[V] + table: Table[K, DoublyLinkedNode[V]] + + ListTableRef*[K, V] = ref ListTable[K, V] + +proc initListTable*[K, V](initialSize = 64): ListTable[K, V] = + result.valList = initDoublyLinkedList[V]() + result.table = initTable[K, DoublyLinkedNode[V]]() + +proc newListTable*[K, V](initialSize = 64): ListTableRef[K, V] = + new(result) + result[] = initListTable[K, V](initialSize) + +proc `[]=`*[K, V](t: var ListTable[K, V], key: K, val: V) = + if key in t.table: + t.table[key].value = val + else: + let node = newDoublyLinkedNode(val) + t.valList.append(node) + t.table[key] = node + +proc `[]`*[K, V](t: ListTable[K, V], key: K): var V {.inline.} = + result = t.table[key].value + +proc len*[K, V](t: ListTable[K, V]): Natural {.inline.} = + result = t.table.len + +iterator values*[K, V](t: ListTable[K, V]): V = + for val in t.valList.items(): + yield val + +proc `[]=`*[K, V](t: ListTableRef[K, V], key: K, val: V) = + t[][key] = val + +proc `[]`*[K, V](t: ListTableRef[K, V], key: K): var V {.inline.} = + t[][key] + +proc len*[K, V](t: ListTableRef[K, V]): Natural {.inline.} = + t[].len + +iterator values*[K, V](t: ListTableRef[K, V]): V = + for val in t[].values: + yield val + +proc main() = + type SomeObj = ref object + + for outer in 0..10_000: + let myObj = new(SomeObj) + let table = newListTable[int, SomeObj]() + + table[0] = myObj + for i in 1..100: + table[i] = new(SomeObj) + + var myObj2: SomeObj + for val in table.values(): + if myObj2.isNil: + myObj2 = val + assert(myObj == myObj2) # passes + + var tableCopy: ListTableRef[int, SomeObj] + deepCopy(tableCopy, table) + + let myObjCopy = tableCopy[0] + var myObjCopy2: SomeObj = nil + for val in tableCopy.values(): + if myObjCopy2.isNil: + myObjCopy2 = val + + #echo cast[int](myObj) + #echo cast[int](myObjCopy) + #echo cast[int](myObjCopy2) + + assert(myObjCopy == myObjCopy2) # fails + + +type + PtrTable = object + counter, max: int + data: array[0..99, (pointer, pointer)] + +assert(sizeof(PtrTable) == 2*sizeof(int)+sizeof(pointer)*2*100) + +main() +echo "ok" From 80744fe4f7c2194b8fdc39c5390108ef5b20de6e Mon Sep 17 00:00:00 2001 From: Federico Ceratto Date: Sat, 19 Nov 2016 16:55:47 +0000 Subject: [PATCH 24/58] Add [un]marshalling examples --- lib/pure/marshal.nim | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/pure/marshal.nim b/lib/pure/marshal.nim index 36e6cf52f..5eb8f7c1c 100644 --- a/lib/pure/marshal.nim +++ b/lib/pure/marshal.nim @@ -29,6 +29,12 @@ ## a = b ## echo($$a[]) # produces "{}", not "{f: 0}" ## +## # unmarshal +## let c = to[B]("""{"f": 2}""") +## +## # marshal +## let s = $$c + ## **Note**: The ``to`` and ``$$`` operations are available at compile-time! import streams, typeinfo, json, intsets, tables, unicode From 0ce459ac5344da7cda28600349a1f369e2df76c4 Mon Sep 17 00:00:00 2001 From: Federico Ceratto Date: Sat, 19 Nov 2016 18:52:28 +0000 Subject: [PATCH 25/58] Add marshal format warning --- lib/pure/marshal.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pure/marshal.nim b/lib/pure/marshal.nim index 5eb8f7c1c..c4c731acf 100644 --- a/lib/pure/marshal.nim +++ b/lib/pure/marshal.nim @@ -9,6 +9,7 @@ ## This module contains procs for `serialization`:idx: and `deseralization`:idx: ## of arbitrary Nim data structures. The serialization format uses `JSON`:idx:. +## Warning: The serialization format could change in future! ## ## **Restriction**: For objects their type is **not** serialized. This means ## essentially that it does not work if the object has some other runtime From d847d350097a36a6008fbb7ce056f470725a29a7 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sat, 19 Nov 2016 20:06:23 +0100 Subject: [PATCH 26/58] Async: Further callbacks will no longer be called after an EAGAIN. For context, see discussion here https://gitter.im/nim-lang/Nim?at=583090a2df9f0f6e7f576e43 or here http://irclogs.nim-lang.org/19-11-2016.html#17:30:59. --- lib/pure/asyncdispatch.nim | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index 8c4a0e41d..58028847e 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -1062,17 +1062,27 @@ else: let currentCBs = data.readCBs data.readCBs = @[] for cb in currentCBs: - if not cb(data.fd): - # Callback wants to be called again. + if data.readCBs.len > 0: + # A callback has already returned with EAGAIN, don't call any + # others until next `poll`. data.readCBs.add(cb) + else: + if not cb(data.fd): + # Callback wants to be called again. + data.readCBs.add(cb) if EvWrite in info.events or info.events == {EvError}: let currentCBs = data.writeCBs data.writeCBs = @[] for cb in currentCBs: - if not cb(data.fd): - # Callback wants to be called again. + if data.writeCBs.len > 0: + # A callback has already returned with EAGAIN, don't call any + # others until next `poll`. data.writeCBs.add(cb) + else: + if not cb(data.fd): + # Callback wants to be called again. + data.writeCBs.add(cb) if info.key in p.selector: var newEvents: set[Event] From ffbe7382f813e979f5b6efae2766fdd9f469145e Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sat, 19 Nov 2016 20:16:15 +0100 Subject: [PATCH 27/58] Async: Fixes problem when callbacks add other callbacks. For context, see http://irclogs.nim-lang.org/19-11-2016.html#19:08:51 --- lib/pure/asyncdispatch.nim | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index 58028847e..b93390221 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -1061,28 +1061,34 @@ else: # make a copy to iterate over. let currentCBs = data.readCBs data.readCBs = @[] + # Using another sequence because callbacks themselves can add + # other callbacks. + var newCBs: seq[Callback] = @[] for cb in currentCBs: - if data.readCBs.len > 0: + if newCBs.len > 0: # A callback has already returned with EAGAIN, don't call any # others until next `poll`. - data.readCBs.add(cb) + newCBs.add(cb) else: if not cb(data.fd): # Callback wants to be called again. - data.readCBs.add(cb) + newCBs.add(cb) + data.readCBs = newCBs & data.readCBs if EvWrite in info.events or info.events == {EvError}: let currentCBs = data.writeCBs data.writeCBs = @[] + var newCBs: seq[Callback] = @[] for cb in currentCBs: - if data.writeCBs.len > 0: + if newCBs.len > 0: # A callback has already returned with EAGAIN, don't call any # others until next `poll`. - data.writeCBs.add(cb) + newCBs.add(cb) else: if not cb(data.fd): # Callback wants to be called again. - data.writeCBs.add(cb) + newCBs.add(cb) + data.writeCBs = newCBs & data.writeCBs if info.key in p.selector: var newEvents: set[Event] From beb44ef13dc60df45921b4f3bd9b557164c3d810 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sat, 19 Nov 2016 20:21:52 +0100 Subject: [PATCH 28/58] Async: Refactors asyncdispatch.poll. --- lib/pure/asyncdispatch.nim | 53 ++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index b93390221..06301b08d 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -1043,6 +1043,26 @@ else: p.selector[fd.SocketHandle].data.PData.writeCBs.add(cb) update(fd, p.selector[fd.SocketHandle].events + {EvWrite}) + template processCallbacks(callbacks: expr) = + # Callback may add items to ``callbacks`` which causes issues if + # we are iterating over it at the same time. We therefore + # make a copy to iterate over. + let currentCBs = callbacks + callbacks = @[] + # Using another sequence because callbacks themselves can add + # other callbacks. + var newCBs: seq[Callback] = @[] + for cb in currentCBs: + if newCBs.len > 0: + # A callback has already returned with EAGAIN, don't call any + # others until next `poll`. + newCBs.add(cb) + else: + if not cb(data.fd): + # Callback wants to be called again. + newCBs.add(cb) + callbacks = newCBs & callbacks + proc poll*(timeout = 500) = let p = getGlobalDispatcher() @@ -1056,39 +1076,10 @@ else: # `recv(...)` routines. if EvRead in info.events or info.events == {EvError}: - # Callback may add items to ``data.readCBs`` which causes issues if - # we are iterating over ``data.readCBs`` at the same time. We therefore - # make a copy to iterate over. - let currentCBs = data.readCBs - data.readCBs = @[] - # Using another sequence because callbacks themselves can add - # other callbacks. - var newCBs: seq[Callback] = @[] - for cb in currentCBs: - if newCBs.len > 0: - # A callback has already returned with EAGAIN, don't call any - # others until next `poll`. - newCBs.add(cb) - else: - if not cb(data.fd): - # Callback wants to be called again. - newCBs.add(cb) - data.readCBs = newCBs & data.readCBs + processCallbacks(data.readCBs) if EvWrite in info.events or info.events == {EvError}: - let currentCBs = data.writeCBs - data.writeCBs = @[] - var newCBs: seq[Callback] = @[] - for cb in currentCBs: - if newCBs.len > 0: - # A callback has already returned with EAGAIN, don't call any - # others until next `poll`. - newCBs.add(cb) - else: - if not cb(data.fd): - # Callback wants to be called again. - newCBs.add(cb) - data.writeCBs = newCBs & data.writeCBs + processCallbacks(data.writeCBs) if info.key in p.selector: var newEvents: set[Event] From b835df0a2b718ec49f26486a5ee0d88bbc39779f Mon Sep 17 00:00:00 2001 From: cheatfate Date: Sun, 20 Nov 2016 01:20:59 +0200 Subject: [PATCH 29/58] make semantic equal to current version --- lib/upcoming/asyncdispatch.nim | 96 +++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 36 deletions(-) diff --git a/lib/upcoming/asyncdispatch.nim b/lib/upcoming/asyncdispatch.nim index 731ef52dc..e7dc4abcc 100644 --- a/lib/upcoming/asyncdispatch.nim +++ b/lib/upcoming/asyncdispatch.nim @@ -9,7 +9,7 @@ include "system/inclrtl" -import os, oids, tables, strutils, times, heapqueue +import os, oids, tables, strutils, times, heapqueue, lists import nativesockets, net, queues @@ -729,7 +729,7 @@ when defined(windows) or defined(nimdoc): var lpOutputBuf = newString(lpOutputLen) var dwBytesReceived: Dword let dwReceiveDataLength = 0.Dword # We don't want any data to be read. - let dwLocalAddressLength = Dword(sizeof (Sockaddr_in) + 16) + let dwLocalAddressLength = Dword(sizeof(Sockaddr_in) + 16) let dwRemoteAddressLength = Dword(sizeof(Sockaddr_in) + 16) template completeAccept() {.dirty.} = @@ -1095,9 +1095,11 @@ else: AsyncFD* = distinct cint Callback = proc (fd: AsyncFD): bool {.closure,gcsafe.} + DoublyLinkedListRef = ref DoublyLinkedList[Callback] + AsyncData = object - readCB: Callback - writeCB: Callback + readCBs: DoublyLinkedListRef + writeCBs: DoublyLinkedListRef AsyncEvent* = distinct SelectEvent @@ -1121,7 +1123,10 @@ else: proc register*(fd: AsyncFD) = let p = getGlobalDispatcher() - var data = AsyncData() + var data = AsyncData( + readCBs: DoublyLinkedListRef(), + writeCBs: DoublyLinkedListRef() + ) p.selector.registerHandle(fd.SocketHandle, {}, data) proc newAsyncNativeSocket*(domain: cint, sockType: cint, @@ -1156,8 +1161,9 @@ else: let p = getGlobalDispatcher() var newEvents = {Event.Read} withData(p.selector, fd.SocketHandle, adata) do: - adata.readCB = cb - if adata.writeCB != nil: + adata.readCBs[].append(cb) + newEvents.incl(Event.Read) + if not isNil(adata.writeCBs.head): newEvents.incl(Event.Write) do: raise newException(ValueError, "File descriptor not registered.") @@ -1167,8 +1173,9 @@ else: let p = getGlobalDispatcher() var newEvents = {Event.Write} withData(p.selector, fd.SocketHandle, adata) do: - adata.writeCB = cb - if adata.readCB != nil: + adata.writeCBs[].append(cb) + newEvents.incl(Event.Write) + if not isNil(adata.readCBs.head): newEvents.incl(Event.Read) do: raise newException(ValueError, "File descriptor not registered.") @@ -1195,31 +1202,32 @@ else: let events = keys[i].events if Event.Read in events or events == {Event.Error}: - let cb = keys[i].data.readCB - if cb != nil: - if cb(fd.AsyncFD): - p.selector.withData(fd, adata) do: - if adata.readCB == cb: - adata.readCB = nil + for node in keys[i].data.readCBs[].nodes(): + let cb = node.value + if cb != nil: + if cb(fd.AsyncFD): + keys[i].data.readCBs[].remove(node) + else: + break if Event.Write in events or events == {Event.Error}: - let cb = keys[i].data.writeCB - if cb != nil: - if cb(fd.AsyncFD): - p.selector.withData(fd, adata) do: - if adata.writeCB == cb: - adata.writeCB = nil + for node in keys[i].data.writeCBs[].nodes(): + let cb = node.value + if cb != nil: + if cb(fd.AsyncFD): + keys[i].data.writeCBs[].remove(node) + else: + break when supportedPlatform: if (customSet * events) != {}: - let cb = keys[i].data.readCB - doAssert(cb != nil) - custom = true - if cb(fd.AsyncFD): - p.selector.withData(fd, adata) do: - if adata.readCB == cb: - adata.readCB = nil - p.selector.unregister(fd) + for node in keys[i].data.readCBs[].nodes(): + let cb = node.value + doAssert(cb != nil) + custom = true + if cb(fd.AsyncFD): + keys[i].data.readCBs[].remove(node) + p.selector.unregister(fd) # because state `data` can be modified in callback we need to update # descriptor events with currently registered callbacks. @@ -1227,8 +1235,8 @@ else: var update = false var newEvents: set[Event] = {} p.selector.withData(fd, adata) do: - if adata.readCB != nil: incl(newEvents, Event.Read) - if adata.writeCB != nil: incl(newEvents, Event.Write) + if not isNil(adata.readCBs.head): incl(newEvents, Event.Read) + if not isNil(adata.writeCBs.head): incl(newEvents, Event.Write) update = true if update: p.selector.updateHandle(fd, newEvents) @@ -1491,21 +1499,33 @@ else: ## ``oneshot`` - if ``true`` only one event will be dispatched, ## if ``false`` continuous events every ``timeout`` milliseconds. let p = getGlobalDispatcher() - var data = AsyncData(readCB: cb) + var data = AsyncData( + readCBs: DoublyLinkedListRef(), + writeCBs: DoublyLinkedListRef() + ) + data.readCBs[].append(cb) p.selector.registerTimer(timeout, oneshot, data) proc addSignal*(signal: int, cb: Callback) = ## Start watching signal ``signal``, and when signal appears, call the ## callback ``cb``. let p = getGlobalDispatcher() - var data = AsyncData(readCB: cb) + var data = AsyncData( + readCBs: DoublyLinkedListRef(), + writeCBs: DoublyLinkedListRef() + ) + data.readCBs[].append(cb) p.selector.registerSignal(signal, data) proc addProcess*(pid: int, cb: Callback) = ## Start watching for process exit with pid ``pid``, and then call ## the callback ``cb``. let p = getGlobalDispatcher() - var data = AsyncData(readCB: cb) + var data = AsyncData( + readCBs: DoublyLinkedListRef(), + writeCBs: DoublyLinkedListRef() + ) + data.readCBs[].append(cb) p.selector.registerProcess(pid, data) proc newAsyncEvent*(): AsyncEvent = @@ -1524,7 +1544,11 @@ else: ## Start watching for event ``ev``, and call callback ``cb``, when ## ev will be set to signaled state. let p = getGlobalDispatcher() - var data = AsyncData(readCB: cb) + var data = AsyncData( + readCBs: DoublyLinkedListRef(), + writeCBs: DoublyLinkedListRef() + ) + data.readCBs[].append(cb) p.selector.registerEvent(SelectEvent(ev), data) proc sleepAsync*(ms: int): Future[void] = @@ -1591,7 +1615,7 @@ proc recvLine*(socket: AsyncFD): Future[string] {.async.} = ## **Note**: This procedure is mostly used for testing. You likely want to ## use ``asyncnet.recvLine`` instead. - template addNLIfEmpty(): stmt = + template addNLIfEmpty(): typed = if result.len == 0: result.add("\c\L") From 0c527c6d8020589aa185cfb5e769b33b438783f7 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 20 Nov 2016 20:57:46 +0100 Subject: [PATCH 30/58] Add new article about Nim in Action. --- web/assets/niminaction/banner2.png | Bin 0 -> 71587 bytes web/news.rst | 3 + web/news/e030_nim_in_action_in_production.rst | 53 ++++++++++++++++++ web/ticker.html | 20 +++---- 4 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 web/assets/niminaction/banner2.png create mode 100644 web/news/e030_nim_in_action_in_production.rst diff --git a/web/assets/niminaction/banner2.png b/web/assets/niminaction/banner2.png new file mode 100644 index 0000000000000000000000000000000000000000..3cabd195d428aed79b47e6071ba87484661be0a1 GIT binary patch literal 71587 zcmYg%WmFtZ6D|aIcL`2#3APX*Ebi{^?yiBw-Q9xQ;=y%+;1b+n2^w62Bm}toe)pVv z?ys3M(_LNlblFtb#Hy*tVxg0x!@~Dj)->|Y=C;-v*5+3J?i1FZ;NTG8)ReTO-2{Z{vt^g6{8FT)=5pMu z0AizU(p}ZEd_bXZEe$?4dQ&n$du!c=sZQ@GM;!q{836$)8>x)?2qz^WYkjR4LE%^* zYXLi8PK;b*b+(R%e7ht-OI}u5SNga+O*2ZNTSg)WAhNmq9RUTEUmK7j37C)q*yu`s z1OPX{k!NEyF*&{&83lH>R_7;6TGkpu+LAIrL7NP%=^C-4?dfVMiS4#{Z5csue(Y9f z&Uhc#&&$ro!73-xt{xyVsVVhiz&OTRKQq8a%U9mV$2T{|OI}00!CkSd+9S$AS3h3W zPvWz$t#pj9bZn%bt>otwSxJDWWNvocL{-Gfc$8I~W^_Uv5iNt4HK04kzdF*!BTr9S zK_k``P!;dNA|+&REz_+bJ)A2U-{9C7FJbAaU+-_pz`~GutlG!E&-brNL43dwXt%h1N6x zxDL+D%k=*>S`Zg$GZ^oF)K}~ltLIx`;8$U}p|6^41>|GloiI?AVc`W@0(KL8wG}iJ z9hG$rEawJ3hvr&Emz#9!s_3xr?kOsi>8mODYB+kj;*!#426_$+HCSdSBxJBa6!~y_n)G8>3Makb@L38Y_S%AQ91qB-gMSzAjAW)G90001iEIa@f z9v&bNsGy+0!vla_gaN`D8X7zR9v0YpPoP4e0#HE$_JW6p$G|`xHq<~v0~iP!{@K95 zKnVy0D2TykvjAW-cr-K&coal}BEr=T3;+Ngpr-=tv4SUTj6z{WnS!T+x`7%n5J=0z zqXb(i5NMF0!2?vF1@IVZ7|;R$v^)S`1;vDnjKM)|K!n1mf&!3*1!}K*cs$-V&Fz%xk8~FhP)kvW04g_x4&L=#m?W#SOlkdkBXb zKEXv4lKD$ZO9xxO1P2Ev{j#q*8|xOkk+_u=H(<$em2sQ6MI$yXAVE2Q%kwoqN7{29 zAD_zi34J;O+{K||*sj#)vlOg9F6x>FU(8hfo&5Xvuj$!adztx9r!!}!r@ATTr-?t0 zy>@TMy=Qc8?eNnGy@z36LX&$hA{)=wZYHPeM0~)T9amr8u7YtJFE?)%gGFp?{!)53 z5OichcU|=bAic`h+s@ctYFd#yl~q>W8>9!SvPx4b_}OJirvR5T$;ys`utqp8-`!ViHBTHS5LX|k{IcZF?71x*c@e2olV{j;)_s{Z9eB_`bDx_t3KAV z75w62XAe3|&Fzlel)LtI#%iYz#<@usZW>gvMK0@XunQC)=kkjVIo@(6bKAtc8)*Ej z(s7xY+uu)i#P9$}>qv#}4%j+2QQ0eUmo5FOp33nNx}^+m3>}*7<-N@HI*2;gt1XC^gEitx0FnJ~$!%dN72`?qCSfVlw|poKx+#42k1IEL z2<`5&fo>j6XSnku1aMnRY|S=DhN575wxX-FYdXF z^nQ_^CP#p1avm4p3WgSj=JOUTl1WQjTl;%sA$+i!i0;O+yqYBF)#Ln+?|6J8DwiCO zZl?0sf|{_7op-HjLYf(=se5_*kIW4<8Hhb%FVj|lWlp>$%*KS?78Dw$Fe2us|AEY9 zkT_d2ODV6tjAmb3(r~`hKh#24%Yx=3v}U_XX!hyQ!R=6~%SaQ@fLxW1D55?d?ngEe zMO0#|n+KKN2z0_~w^Rp>)kCXm6|S>VJSuljeTCv0s$Godr@LSKKiQ_$@Qf@a!|$O5 z(?#Q2+x!#KfSTjruF2uXNKAo7ReS?K&zdBa`u|t{r(ZRflvN zO&;DalQbx)hEr|{efRvT*#96^*R;_8emS^tHfVoYmHc;Xta_-fHSI6W%4pxaJ|-X& zexDYd+>yGRfjY=_tJeHKOoQu)J}86V0AmDo#8<^!*)obQP{d zp_#X&qtYajz9LLzmjy7;<8?|?a@E8fV=hk34=Ugr+5e|gQoZ@_GUIqDH8aqRHJG(@ zjSI`F-llw}JSD?a|B(jp?VT!{VD!tQB8)`gcgQ@x6ac4SqinY4n5q>}=r z@BkQwUkUxeEdP18yztMMbjh1EJTavS+W@R|z_)amd4E6miC7FeTT{&Ev05!8W{aU?mA5{g7xh1X%g?BHjHublegO9S`R;VF@4eaW{;14 z5B8z%6g5Kn>A0;vX}&hTAE_0IBAB=Ymqw2&<7cgjU}0_5Aw&5t5JZCVT(?jj6O|T| z4d4v;95cxnGce-$xy5SIWnDuULioOR?~qSdaDnFOZ#}sny^z=!(#b#p_P2qAwWonX z>dV^%Dq*Qz>c-;fxec9ytHRzOUh#qE&5wXs@%ZQzw|C*E3QBI?qzH<=|DG-ZqjzGr=2AH?R$c$O}KX{TS{}- zPUYvU4AP*%L^Sz@bmC5A_YX#lQe6oAViL$^+mvHrq@FrShg912B_%tO1*G+86JbPj zPu=z80+26IJW?Em1-sRQhM&qh4<+ev*JJskwRei*uTSF>{~X?Id4|%;!w_59U2q4# zV{YhS>ghy(S}_M~mNEx! zwB-HBJZdguTF`(aL69w(IaalByzSfo~MDji8+_wLCEiWb>(l^ zSiZLLXpg0g)sR%@_kjH#1S>pCeO?L#$4fj#b$wnf~vnEjhp z0y=P9Z9gqPUD=JyLoUToHws!N0P)R8L`4p-QdQj7_tGt6p2~mY(c5cwY8w9YM^=^k zmOap(K4o$rb-Z!%R!Ht-7Gw%lUd$;YgA-7mr(s${!tR1MV{ijK42SA(rHU zkm6i-%}UJvo_l_@qGiP;GS?I(|^zY>QA~E|hj25lzON04DN*njV~Z z3BeHSFy+r-xhfmE>NvXEI%H4;4kogyJeRCa6qLjnbXEQ10VOg8Io!#g^Jh)g zS$qf4mmuDOEqac}zN^urZdC<^Y#B1|tr0+W@msnilrkR#d_NtsoOU$KJ7AN_zO z>H1%CgUflQB(wflz~g*H*EdS2XI0by7Fz%cFSp2aBfi(O2O-o{97_`y?LVabW{nsSsb<7q z__YXG9EL2Wz8JsCeW()>>DfsqTFAIs6I3cFW$xsrK zkx%H2@7cHhZnL!=F&|F^o~3im)&bv>Hp}-o(-~3ChY);93=b)_A0d528SAr*K5cFN z;#NJc#{~J>+g%PqZjH3o5&l??ri5vh$}t=k7K*dY&eO#$9;$c4j>$xlAe}~PF;Q}K zG)YcNZf$&NL>ghZED;CM?o{THzNVei(>+7S{p4CVI)iG`!g$hM**55V{Py%~|EIR# zzn$xP0jCaT(Dre7-a!>Zdj~(K_p>TW&nSJ#zn`~%M@tUtCkxfIKouFWYTvYU5tHgh zEZWSTia;319dL05_)!VUl%(WljOOaAv386zMYl(JM+#xbO1UWMVVKetL7j1ul@wiA zASD39)kdT$7!HzIoD7Xj9IoH(yZnx@DjAY#ZW!&V37~7KQ;ffQAxzYhQbZi%aP@cB z3GTA>{~n{GeMbN%RAdljiQ&N$sb+NooHGzPpze1Fl9=b6E-mf+o+DDFUD7hW%q?e?QxWzAK^)o7|H%JsU+t*5?ci4%{oHMv z2lC*S5t`60c_gii8^3Qv z?s%*14nZMUCR$F}y~owcn+~Lw2*nS9*oW^YTyIXM@I8s1`6KV&?bAS{EPX(JDJ7K~ z>uAZLKc2SG|Tzn7^8KFFoWr zD0$h}Fq1}oJ)N|_<}I5%awGyl7xRc5K6%{6=o)5aS;{Rj5Zwxa&jLV1j9vscTIPXS zXpmYjVrFGdZByJ|xSePjN^!l>Cw(P(lQzY=xIj1841({98HhEr!;j-w$;Z=|COejT zd3-%|h^B`(4|dlxLk~lBd#vA*L8E$q+RxMPy#RCZmS9VkWi1x-SbvnQiST)C3;Z?n zcfw#z0-m!$EFOXcIJ1#Rslv&A1yfJ$50hah=x!pB;Hys(Mx6?9Kl=DvMACkfR?>dg zje(Y#W+1=4R)k`$U8r8Tai280d`f$9x3nUA`w||TqKDChZX+>&^FfDb$g@{&5UG_H zvE03z7Q2Zb126Y0*qle7Zaihu__lJP^TU{@st0H~R*SyUd@yk)dJSkC6Gwcjr$eI8 z0%~;o_Lto0Ic6DkqOmY}DchAz>_A@zt5rLVTSvnXsu#~_ID(rx=KKpxszm#7G1flo z+6*%#-&^K8u_zn#>|!WQ!1PQyJg?N7&b4 zf%v6cdq4gMG}~#=<;#aEf>%x))3;*q{O{uQQw$vqQM(##t3KZt{4_Wu6^_XoJ*gPy ztAi944_CTU4>Bd#yhKtd4%U=~(#RYDJe)x>n}o zuXP)Bhlm!SB(m)Zy`3NGRb6OR`>nF+b;Pq5RUG*=1`6su>hsX?!A+BmlSi-Sg;IL* z2~9}~${JDY4qG2a=(m^e^&ExMX0zg=lLHSY$=8_AZ|=`G zTeT`5VtLQh4O&}Lt#@oZR&j`Yf4~`hifdVzN%eNu>NGjo*69i(6!&OGwVP=KP>LjS zCUvFuAxxQ^_qe7A;zz2x9I@G8|6=Rvw3@eDo1y$yBjJD z?Jrw`N!YV|7~c^v@;{POa#tc(2vZo#f0A_KNvfadlFl0L>SAqRU|;n>9p}Rm{T18d zER($kr7l*jzNH~7?Vk9<8aRj+xLqopKuzZDLlIP+Ui8$9I5#_0TVzCwb=0`yZdQ|x z!QT5KpWmoGe)wPH^|I9 za8AMQx-C#9YfIzrHe*i5{=@g{rzgJ=IbW|0G%5lCZmiZWrW{hb5@;kp^^&&uRKg9C z8{RnOFQi7pp;jHLj(P7eL~pUlvL*7nFuQta9pM%#78WfnInbygncjezNV335k9tZ~ z4thsGY5F7^!pQfuQ92i>*sh_ob803cq9bOpU1Z$sMUsgS;ecL5(S;6|pYh}!i74SK zqDjw2#69Z5BR`LurvFF=49Lb7Sf7bZmG9`Zb{p2#1}09!?;sSY9Uj6=PwhvO%z@gt zWX=18`}(}hMOwdpa-D_*WUfCM%-NtFCO>ab!BtX4V2e#bf<@(^5d1F+Yy3~>zw+}4 zQ>lyDu+x|7T?CmC%HFZ5!JT$bOGis7ZOP$cFX^oC-8sbt{tmseQ1*l}5VKmds;ghg^zN;q@*uED zUOO%SGrEkQH_L}E#}Wutv;3O*WM6^kIM{@&k`pIWW!EG+g^#Bg#gv0s*PX;7A9s|p zc@X%WfrsN$Ekyw?aoxQN0a_bSQMpwWH99BJt_BSFJxvQ=IG@1ME}1LS#94VWMk5M3 zt}--q!s;)Rga?f+wnA7e+?CS2#CdReFa`S4aFa-A6D_TOF4+X)DZz8mYyQ^zTaE1U z4r@GvM$G7^qkZjfG^d3n++>G{GG5)`3E_&%lpxex;}J_FojB;9t}S&~CE?_WU%(*T zzQQ9;0z|H&+-c)EPh)9m7~g&k`b^uisGZf;45^bjPGRp+W>7%vmE-`vE4Gr1h^#%D zRDgd+%W1Huq*xNb5P*qMao0hk3_3pa;94gSl9fGk{S>&!hRP;$dLGtQ z8~&a~<~0Whw##&~h&%zSf|^(SqLGU-v=)+qlKZGKQ=<_=gZZf$ z;6m)R|M!H>Bw;OftG6W zIU1M}65l4VK^_!c=@CV!Sk=e-d3_0*leUo$Z=!ega3b7>dn*%|Qqf8^ZN?zeyn{Aw zz)L~ME(PEsDwB6a4MBZ>{ihT6r&JS>ESn)K4i;V_a_osbc^`5FbK1&JpKe4~SdFNm z29<#0NOfPStB-the?ct3a_c79=*Hb)D~c6Z>#Xx$jVLH{x8#@(Q$v;UNjxa1mf=^4Q7; z3l~jy!i;GLFJ&akxJ!20$E%HZ=2`JT&AdZ;mNvt8UrH z)+C=!BLBK1m2rzQ6k{2fphv|aClm0!d`naR*WI5rhL|R?2q}TU+7(^U(5Fp-<2o=n zRo}MxsEYVXn(1mxq7+4^2wL%f3zMLiq9+bjI@A=Jq-Oe0MEuLSHhIMS(f@2R6A&B6 zv+}(ayk?y3C1)}-MP-Bg$=xs{qflo_o``BS7TNs;I*#pd$Z650PjT;Q32z3k!gF5R z$E|UvH_3y1BRu*8uQ=HB;S=Q)60n?XzoS02s-BQZmSxd2>G&ux5K;1q5Jb7naL76a zIwbu~LeDH%XK9(&)OM=tV_B7{3*tft#w_633rVOc$FB;hGE8$>B0ut?Cc%hjmwT)| zB^$aVqBiNTo^C;Qovt&a2Kht%0suIjsHsFBxJ9DluV?r=zUK0qA?xnXD?+rW+_?^MMb$R*_SmaLPw(64$M&K9vn=5>C_l)eSCGzIROyV zmd@&%OVjB+lA_j=PVRxO1^ z!qVMC4}{Ay{@D7zK^E7tl19}n`nW!<({18|k}r6ts#ICMt~1>xgsx zc8=HEsgwS6wk}1JNSFhro-J;Gs8Ar36vSMy3H3yLq_&LbYO;mvrmkuKmnnL^Z!nJK4m0AZPX_=HJo+3r z8}3l!&>OG9#asl3g0j}`oLK?r*(joj4%zZ8y&J`oRa`HnoSSugvgLk&OqxYCdLp@$ z_-e(ehhl5C7Q(OB8-k8>=z2CR(o)dL^gaFL2x@YF_2Ok5nnm`Tn(|y)UIrKggzIX< zaG&XO3e^?;+C`J7dX$uMXa3aPqoL3w?kfX3HxzQ!f`3~`2|h{QSE?(^{b=1v|CTL( z4G+`asF?jz!x=UVJxIi*#osdrHx>Q^z+~LyT+gT~>lBiwumf0y5Nh?W4=mqdZ zUZolzD!RP+5jc|agFenH?lvONLose^nmXBLfVf{%z)WOtFvUfrn`c|vkg^H$sN2N_!U8AhnNM_n7&Zg2e-T$1W87s3sao3+E&4Tj?!t>vD+(LQj`*|MxLbvF)D@4S^54+DB zhB$nkTT$;@UnV~{qLWe_;9n6qd=3Ys=6ouRn$YFH%NY`F5n*`G@knaGy(VqzDhMfs zc}pY+#HGN0F^z{ba+aaO>uW>;Q&q@$-rv}%TwR@qamVE8>GRDSgQ7+0kUQ6^bP}c| zQY&w?LS;paKMs!msWFstP4Aa=&@&R4a%)(HW?lk!(LQY#_<8?u5eZj?D)5r935f&_L)^Kz-Li*e;R7kv1 z2Rp?E66@nq4xuE|>NA3Vl0bkwx8099x7$Mxv?6if%nDRQ-~l`EL|LN?PDD`B{8^+T z;^?96y-RJZ$E6i^Zcfg)`~A#$&U!~fQ{yEwrvk$$U5Or zirKjq{--2{wI3N3XxJ%;@{tX>2n;bn7Z-+X0$Gm-p^hZl1N|O9-;E|- z6g)4Qm}_t6-{PN9=lU-~R({b~wnlByk4CDHHFG@mQW6OzV0C>zuCR&0r^IAJsFS+B z|DQrze6tWY3FtUBS+IYYWI}u3o18dFP4bB7j7uasY?)ozBV;6wuUUwgnjdi4c5DO` zI30_gc&BMl!Hph(AsB5twCu4)e{if8320-@oxaUB5JRqDnJo#aIc2%xoLS}wiFT?jd@s_h)FPO60x z%B3ddLebwIWNAiJWZyI+U2Qty4bdD)grnEESwTXa5rcnOZTP;#cc?>;-w5Vq}9jhy88~eY!yaap8 z^MX%>grrzjw&h}OV^|S2HM6`qs`tztE{&Bz2(b9}ho_q2X8fm&o^eLjT1+gn@ z!?9y&b!8AUu93PrwG~BDC^Ux;4}rb}L{`*Ave3n_;+=p-Yn!5r+j}G(*As&hDaFA9 z{vZ)P-M|FFkgfjjdtuZB_-JCZggU0Ybi3fU7|Vi zHMgIcnpr!F7qIws)bV4Z^087iE3$}EkZ6G%OUV*VP)9T}L~x%58_Ws$?*r;ivrB?J znSG@a_v1)b(gPEucSQ1aWecCkAz;V|$1i4BB;OwtO{AkbeCp-^%RkkbDL+CP3IG}j zh2F`meZ6v+Er)5FUrmOu6f zzt%eOz>Ejd;#7>Opx?|8Pdvd~#1-yqKbF`eOe z27wCPAez9OtcK;AFogJI#{+%}@#d2PdDITe9fO=aNNzZmCfC>-wEy3Z$&dzy1S1&S zxHG(OSVMIcjlsc)-rU5w`V`dJS6Wl!ENQ6OYKDu(Jh*1T5f;s_qeb!0*@>=l;PA=k zORALXJ#~4Eh4p1-zgJX#nD14i;MuA$QFYt;JRg@hElg=6OB?Mw zBxvev`nC|I>A3Puw5PR8CSwgSmBzrxfW;ieXstFBE*|&dGqgAU{9wq*ALY#iNIYxSuRw2&=mN_+o53(uk$1Dmx}~6tXHcOev^lqTbwGW)VEL!tbb3 z|8g~!baC)|Y2FCYvcOCRMAbx#H9CmZQ<&7>#8#!?C?LgFAXH$n=LkhNy-;XKW%l?G=+44E8dFV!cAoLS^D zV-5>4=nG$J`d+o?X`GbZI2%o^1TOuP{sU1xCZrb@;sX8|JnCU+bgjfkx5GCj>^%rz zOZy65*gG`Z?QZm&i+0AeRS6Vq{IYCqy!nuB#?jV2js0TPwwEKkmp5xCclwz-O*D!p zF7~Z=_r$sI1@ynH`gnB_+>AuPju2AzdOe!>okEa0qyYs>s@Pd%rQLiuO7nWY-RFWF z_vbccvM>*A8eamz0(Tt-!jztdWq4fPon_U>T**CR3sMLow$D8|ANR8~9_*9^7I;(` z_0UvGks|O0mMLnxM%tVl(R)&_)*ymA{>Mu-ScZa5#u%n5ZTx8Stnj%ZyqMHN{K@=8 zXww@UjNWu(haSgJb0t13_NbLj=Wnwi?uhX)E&El%K<>*yBQ&Vu$n&poqNs3hHtnPp zWmoTcl)`Zr(6(2|U$wFFjmq%>O(a>%4>K)iUsv5y?_D0svewx3$VkRiW;ohriUf4V z03PX;648{Z#0I7Y>SqvBa-)2jlUaqNzON6u}b|DnzB|bN4N+sa+es zyTX3w&@aIVxUUE4*;S_4(fMq`?QP?f@`XASirP`pnzk#f&3*}ZqI(|Q0dxBh#FbAv zK`XE(x=o`5?w0OGl1aZ(+m7t0V#&Tex2JT`-KPTzlw#4&C<^9?&h@77@S822N7&JFCDf&G50uB1`omeQdQ z{JUkwb%i`nu|H^0iW%*AJvG%-VfWb*p|+cHj`}#3!|5XmKj{1v(kXiXs#u>|GnW!uJ((BO!;bw(EMGvx{%dN5qulCq(O`fxI6_EW=qh z+aW4{q$IN>ZXyTIof-53xQUi^zvMiMmj+z+OPieh+Y*ic=YaI@c`wQM8KGFrUm{@` zf!gRmI{X~+9KLF*YmjX*hAv&VlponRFmtZd|IE3pcmV%6$d@AWvSP?3GL)1p4)ANL z-$#Wrnm2iMOQF(gY|-(x$AydH%CYw5iTp{x7L*!~o4{4|6H+IxW!iHE>3SyiFFEZB zeApel(p%XQ&AzEpHNic<$#(Udhh`p6zln2SC^lPGBybBb{qi0u{1L99lJRj%KBIs< zr>Mv$A`IGL67sl4fs)+Taj2_xzU9X5(2a%A`))IUtL8@H>#7+#7&C3~oaD2cj__Jg zfa?7{SC%)?%7_J`FPDtleJ@IZshcsZf3Qy8ZCczO+2tv{R#nOPlwrb7KB?6+K=|B@ z6Sm(dyMFt6fq!P>;;>+<6O5|P2B^id#8M;~U&0lXLC(cKYpwh2W+4Wtc@MHTOXq*n zU+w+=iFk1ZIu4RWg!TOWVDcl<>=Mca_lNB1X8(@J_<J|aHp<#@aeM{m7cJdLC1zm%6|-)R%;n;~Gsx2h4v=E?Q+6@JkXI178hwdBu+$9=zDE9I z@-(N4Y-5@3xr=O*?akr?s|*TviDhDVVgHW85$mxXR93%e4ln*p{>bSE^@W4BS8h9B zP^@(DKj#(itMLt#T3SlWj-?JC-p72X;Rj5hgOMTec5W*i&Ff#nog>AC^mWaRkRg_y z;>}oV8;5^>R0hHpF_N=tc^0Mz7t3;7T%0}Pwp4UD&k61XV0;=8L0R9p6;XDkSV4fy z>F^7BR@)jnh-mzw)i2MvM`3FwHKBs-1GH|kn?b?0J>X)4OHKSUvUr!<}Q zP!9`GYaeUVt(@mXT!Z!#+Xa&fh3{21FWVefoa{D@DH3H;?eCLv=$1uZoUWH>4Aobz z{b$}H?XNQkDAYo?AIAPEX@Pk4HDaCl)%j;cUbivzD&@~ZP zH=#h;Dk`>-Ejwt6-fyq(F3~Ly=D8F`O?Z!TRkzWcmM9z6el))picXkoGhc?eEpA{@ z2!QQ(@0=)5uUmV{-i^`U;R8k3ghSR$RMtQ2NywK)F%QYkPB*|b$>F-{%S!f;&CbT= ziT5a76FaCPGJf&yt&flEdlC`ZJBsSX$)x*e5rG0>BF64O#ID1(&u*K|$3B8o zjv<_<`v=?>JX)>wWo4zM1ud@%M9=jBC1UtEVD$fySK^4Ad8MEr`-wsL(HL|ybng1r zwqVc=6Jb(^e)h_s314Y?@&WekfOT=UdWmLASBTJ7rBb-@X1sb|KNoIHRPG(mE3Et?rViV%nH~c zuN@?mGGAl(qeWI+;Xu3gBJKH-Cx=WaW!l9{aLSQlK->ZjAVCdTDpCE1oN`&e~XGcDpQXt)|yMhCf2h4h<9`c zUz`bo;muv|+d$qC%Vpsj&9JpQ;t9lE#* z1@IW7=L7Ue)}k)YdgvGFo)*8*G+%evzwvUyu*Xtv6bS39yN~(Y(p6D&m~)liHcoJ9 zztL4#G-SkQWMM)su{9g_zLJ47La3azwp>3;H`kaSl>Gy$^{x$3z_Tb@ zX?EZaLh;P7z^rHRw7FFH3S-?v--sq;MO+5_*Ik-6MEr&TNrR^lBb&www1)lF^{3shP`C7heO|EcA?vlA0i*A_}#Zut?k3re>&OLiN4b8lJ=P?x#bZ|%5!j28Ya-XoJ12n%?mu=#eW1SrvD5$LgPe0fY}{iE+k* zgfh17ANf4dxqEF&`RMqYj$0|ygkYR8M=7^@gwoCQ=a7xi-x=}EuGV1Dr>LZixB4fo zSe)p43HC68h9txXo6`uem$J}ye~c=LZ}#93q>CB)rw)%z#Vz1J8-@d$D@A3RPwetj zkEFVn)lFSH8i3#?vdl@BxRs5Wl?f5Z5Q{3iO$%LP*t$Vb~+PZ+&cXo zNSS5$XAsQw_!i!_nJP4&@WyRM0cEz{; zZ4wc(pS8;!V~!Urji;g+{#cd8KAJsr6c?DYo9WrZ_e2a6=QtZmlV5-?4F2$XW0rlC zjQFxo+IubQbWkqyVT-OTj6sB|L4dp=*ykeeq5dovuJLNE+uKnZ$q)LT7qpj^7(I1} z8fl8m!LN~X60z`=uC1XP`3h$?Gk|0a_AeAoXS1~JA^+3Se=8*t)Dv6ZF=d)R9Xu}H zMSU6gvV~I#l75%$>m69Ft!c@W4m+Bc9{6+HI@e&|xYpfo^lZP@bCQZ zrORib9MsW_wG*LCOiA^n71){AC7=w~lrkTh*}Wcq7l+rO815Rv;uEB^=bT6ouEAQm zi&v269;MGJ`yz9#BxpY7i_;(7_7Mp!D3x47^@Hgp;r0j0BM0C2hJlzF*|{b%H?vqY zL)VKqx$z&kSh#j<$f&UrB1TM>xxJk1q!tnC>LH%qQJ3Fgf!vzNVzoD9ahO@{KO6gk60Lo$dwhFjGB8u&m#X2l~M0~q-my}SsTBQSO-Fg zy3=UGme4?f&okeX4Gh*6<8Ywp-nCa@E9B6MhX^sma+13wEf2Lw@I)HaHba7OxjPLf zR)xu$C3{x1wPHsKCZ`N}>?(BJ*l70wyPD3$*ud?P=&mM8Ns3u6fWh~8$;Bzdn{=#s_vZ@b2kPOoD7 zb6rf#95CczZY7Iopz`SV@86$|!&_;~W`9kGHmdj_s$)7X@s_)97{C1ZvD)9^fA^A) z(T_wJjyT2}o;Oezf`86ILy7&xcJ7j&U0`r*CC*0;+ICgo5LolmO-tZTQSr+Sbd~OV zzJT>{GTHfHAW=Hu(89Xek%QxP+f2c#B|&Fx??NIKcG(pD(@HSje=3l5_(lHsv!oCT*4KC((ceE7^WO)&Jh`jwd4Bz#@*^ziZ`%3C8&$?to|K;o^;YOs?r(?k6;SJ!`BY zadnS90-46HUH|Uqu7Jg#7+>;h|I3oH@B-1u;hr(jF>g23vQtylQFet0z;>aG4nLHB zDM1!-M7=(Be5J|8Gg52HrE2xwd0)PLnCME8rN;RcPSpFkI8-EV8NMEZX+4 z-ZrD|`nqd8+h4$MLzbA`+7dr!85^1$I!BJ?0M+9A*uOyzW{;ftLmErIPAu`S)3|jeb_nmPbb$wn>YTG5Z}a&xpvvI0CQCvx1IQMPZpnYs`|b~??tj{ zv8H7BAG-+&pHj@3DH;*LVH&LA?2Roam`9K&p98ag$skbb zK3{q|t~TiRv|p@S*6e5Q*06MC>x0U7PL+gWO zz%8Xp0+BY-R9-Fsj2V7=rZ%J^M24zo;q>Py>#FUD}`D;O! z!_R_Mlv0#Z*-NvhC*;1Xh`-DZ0dGlsVR?W5C$VH<;U`#9CW605 z)-kxJIJn*Zu^G!zwe8}Pe;e4^)6@Bl=d66S1>1Q;YwKi=64ChmEJIFH9ywAKKK=N> zDS1#Eb`R;z9lhAxFsG->#sQt@qEE$tit~#$=eO{g#^vh#>Z8q@qpB$EXC)n-&b}rv zX9+7NcP0_$x0z#;-QGV%pg{DZ*NwZe0ptlIrUOfvmx-T2h{-dL)C>Rc*8T@m04fUo8sPZL)K1V{o1iT^8RRjmPfeiE!A19H&At`S z_|;3-`=R6G;*i&0TcC_YH8~w0`;`d`wLOP;5Nfnz+1!Qwh9~By>&rBjR6P%7R?gW; z)*?#a^7o4MbP|+(OsE97@J91`aEYrh@sNRH%f)!RrP~(-P*xc7peXoby1t2 z^wSYyaq*QX-QD9(l{*YDX2_!dU}L;?=-p}DUlRln#xq1BlN$St?}`St)Kejq!18{oW)`^ z8sF2v{>U^u4opTW(_8E$6j?yeoBXLqMatI+eruC0It@In5qzGa^p}3I_6CIGQ}h(E z%36VN`o#r1Xapah+{X`+hC6bba~NFMIA*EYcC-w(E}Hd(R{@~`K36*&O7!CL0s+dd z?)D)%_U&WWSy?W1uPHYeh+nFW*?&~OpGHH!>nl0^C#4sn`sGcy2NZj>uApGtSl4iQyVrd);TuQEs5hEmi$u&ZrnIk+6lvkgs&rd~ zx(Wz}dm#=K#25oc;`R5}bs|G79*BCWT23&X`>%jfzE8Wgh($*B+0B8d3q0^vQ}ws_ z9et3izPWfW5g>C3u*0*mGaP5%*jj`NebiMI5BaA%#zkD9>AkIKYG5#f%Gr>lho>$D zWx$1VQd5!XMO`s(5$o~GM#9+?I7((qfc_r2ix*rrc_xbX4YHH7VsV_Ax%tEU8N;s^eCwXg z;3VFE3_tWw7q{e0K*q^~U@$ z_RF9Q#$vdDYSaK1F{nr}Rn?PDwv5fzRO$w%8$P3xSgr=o+0^+D-@oty3zsqGpYfeY zli+LXZ3|!d)|X<(Egb~uWy=^Wi31~%k&)Mb@46S(_4SpN^*>cqxbA@m%C%>bEnt+sb7``&ju#v0X%m7b=~Ar?6?KL6Ko-lwXw581X$s`vckeXu)bwwWsbb< zGyRUfK1ZE1IRQ2z9v@Vm`0zv474drR@#C-m^FROco?mX>{LcjskL2jdAM3=*FKy&6 z@Aqu0kJOYm?|H5#?AE=d#fK8f{Nk)gD66=%7|cXgH&!Gt5y~LQ@CAbrtSL&W^I{^$ z=|}}gFl2Mi={o0+0;{0GDMXhnrd|1Q9(P&G*5LWlz5j;my2+*3aZ`R{$p$bGsU2}_ zQzsBy)W5x`tgy`CC@XT*m96W4pf3;Dc1@^YB0w|$^@%4yfeXHs`*d#Zum0!zzdZio z`_F&xzy0yUKO(LiU8~P3o%Hhg#oeCr{GDybd^KMG$>QQ}g)hz;@?>qh3=Ch+-$f-* zV63pN2!%Najm-5Q#}vusbQH0Fn0012rr83a^T&bPufZw0ZY-uH7E1Di^Ixy}!PMU3 zc=`m-#;chQKUn?i8?Wmomtx0F_-#!(8yXr??K~+B1|`lYd?C-Vt}G)jF9Qq(Xz;h4 zE?16AX9sA-|3euBfdBD{4>#vNfBex$@A=jH&krB}`t#5H&EJ0cgNdA+QR(8_`6oU3 z#h1?<%HC9ZIO;n*9svm^w7B|wBqY?=XBA)GrD@8rl$i-Gm<>!ZE|LpGQ&dTj&MK1b zazFyv4+{z!m_*^ zAXrxw^#brVJov=F{^N&F+<*W5ujl6G-g8gx6UQI@l~VQhfBR*~84-gdoVuDW>>KlKKQ_t;JMS~2-^)Uxg4+Uo9XW_ zDgq+`S!LY-Yd3OzU=wV|jcXWsGPMOR6_7q|^<5!WAd5^4Hw{E7ezpT&U z$g7*{Hmb|`u}!cY*A>uoH`|24W$p%mwVtS^B?fy_=F*Od}SQ;L7=gk1Zk1&G!ivZzN zVFIq@|FsFWW4-~_lkNewpOcwi05#9iUk0S)IWmgsT>brQN(5wf0|NuSSfd!n2#FUCE1)Fa|oIKv#O4R09pLP!_SmF zGqL3#jy^Q_;XfASzJK3i@BgeI_p!&%xZsz-?M$+KPro3I5~DhQmt|FND=yuXy^D68 z7b9|hR%5q?H7RzM?X}B2H$2FEgcMhZ-%!|EHN;s$h(3xk`dbB zXMPf}3AQ5@C*m~8_Hi-^ZpdKUF+h9E3h#x_kUV8LA9%SAJOMQfk7X(BPrQTy;MiVu zOUal2>d7rn{_(#&llwpa;{C_(yYKmLJ^uJbYi?gNAfF75cNvD9l|N}2)YDi$wrf98 zg6AXE)nnBc3C0*m&CD#w%{`Zim2<<)P3RY?0`ePI22&m)(OFltZ^z6!cwe_}$GVw& z`|m9)OKsJIOUtndwj&k#{(%AKfHS4Pjy%@>>!>S&KiRARJe>zOPB-M}05Ih(OHI`Y zVyO>s5`ga?f9IKh`160b@3B95{I4I+J^uJ{+6e$pgNFtM#)jo7Kbc=|jVVf(#x@V7 zoB|%!+0aY&?YXBP{a?8SPnDX+|p=} zQh)D|F}FK8mijv%iw(xlj{MF4_|n7A{I`Gjm%o1O@$cvUU(cVhz(gb)>OOJlzrkiV zYAnv)CDxB+D&s*<#0sXyq-ZfEfrD%-+{J(Tr~4kgfAfduhS{$DK}FIc6n-^gk_!Kf zL=(x3G5~nT4zLk3J9aQ71~*&=W;n0NQP-fKunD$fcCS0-e%V%x>AZ}-?S(HCg8wa> zdCK9;`?lj!(Fa7D4O13=n{eO6R^Y`z2 zg18%84bF_dx+ql@O|H+*j?n7*5J~tgk2gPrPlMIPN-UEMJ#^;jfBNgk|N6e49pC)X z6H4Z0HtS@V_;oc7dE$S>m`WQBd4)UH?c28l$bI3FSK!OKR|;P!D`Fe|!J@n3Ho>+B zKKn3R!bmN`f&T^bPbEzfLu`1==g{`_Ik^1QOTmnStzGURPp5wS6=aZJblE|g#TJIv0X``)?l z72NaueUJaipZxWoJpS0@&li03;W>P6_-st^V?zQBX2mo%{Ld)s2lxBPOkq(`(Y^59 zS6=zbx*e|+Zf7mwy9B56#Tv8k3Rpa`|i8%XMpgZy#Mw4esw?adnOYP#udWOHaNSN zd2CF=QO69+;O^ZH#%6mV5WMaM(BW4KnFtp#6>d0d6MUBk9+;jRY61$}*SEc_{{`@w zKyV+J2zRGAZh%`QBoi_#$ih^TDZ!t5m2IR@l}zo7cJb+p1?Mucel`{#`TIXFdH4hM z%m4N7|L=^9{ul1eaBIK8Ls{E?z}AjSf2Sriz`1l1L|iF&J@@^ufBo^t{vQDNv48*f z_y76a@Hu!DU^kA3CDt#bsJfmUxOd&YLI8Ms5fGe`;2k?s6$!Wne#eR&MULstfKBjS zt-yGIjrC&^>~Iv_+YcV%-ojTNDXJ^#f6YOLi78S=(hURt^s~7pY?+s7FspXO@R@>w zf~OzNy_lQ(JuUM+Jb3nl3(tJ{&;RO~EnkM0dSQE>)1S~w^79+Fm2N66emhgQJa3nl zPBIBE*u42Keg^T_V_$#lXFvP;`?()x4&zLm$%?{QBeA%op3TW|mAwMEGH7Sky#h9v z{je_ewxh794+v%=?4SXg;JY^hdc$;Gs*l~7!K!HYf`ix&IaARC4}80JN|F3@$WSze z`f)5KDLB>csInm>vB9&r;G=UN9)I-sqhDiVSHCx^E599%sX5>In?L=VKd#NlV6_5s znD8gR`lbK!m)R$`9a4$KH>2~bZcvvs_qyO?h{^(!+s-&bu{6LdY)~4+L#Max6A1`?S zzW0B|2!8DSe}Dg;`|&U+Fjl}u;7k%ctEvES#sf32ypmE}aK0(`z$y~2%ux(ZI8{>1 zgI~aaP4HbBt#37C%voTzkWo}tRtGM4286Gy?*C)&eSq4??|ebIbjtJ`w!3?5PBY*x z375cK@emr>Dz-8?xL2ChzLjQ1mg^M!4$WyGb`9Jsa5ByemM0GD<3TNVm#o*7dX8T* z){Xpj#}#`o*$Pz~QX8CfOD%QFEeYJl&<6K_fIwx=@A95+m)*R)x$pNUz<^<9?Bs6C zd$jl~x?A1o)1Ur)|L@)V>$G%NlEHdQp~FnV5CgVsmkALkM@6L|0ETOa48B{b@P%)nVex11g;BMH0*Xb=2)SpJFANs@fA$}> zn__)^J=4)K^Tw+c6aT;ffXh9Njg26Gfp%+_S-7kuB+j3Q8$}i2V5>Fo>vK+gW( zc;l@dKdryhk-9mx@9P1JrOE>aq4WHCg5R3+L{pRjBbR|V#<*M+0dU}f`nw?bx4-;U z74qW75+7-0*xOoUkNo!G{-pKb-bV2M8*bnmmXCmfajFa8x5_$Hx#S62tzRD>mm|Jx zdOEFQ*cj=T^j9UlA$SSU8^x+l_-Ys&$9lWMe#iZmu%pY=xQ0?;oy7_on5OC3ls5Xt z8y(Mgh#!6DmRP6L-r9{rvMgzS-9yXEYv% z7)dhFuoYPtJ-{TcT!ZX;gb3lX^G;&OSqX+elM>z(!7pNY=F5+b;1Pn|O*pvwda3wx zWIecFTvJ}Rz5IRvtp7Mrio=mKw4th@#@3}Fo9%XX=Z_U#meW3*l93S=0$Wj%MNyYV zBvC^FU>atWGED(}X?+g=S-;!iPiS$DAqcjVqe8U?8I`A`(Mj zLd|)5M6>9Pc7?nAU;%PHckXmF9D9Dpj&J_-?YBD$lBk2-sLK1Yayq>)Xe+N8s&v+r zy+h*4=#T&yqg@C3xDgQ?AfdUy)Dt845MLCBcn;I6P7ZgYD871uSg?`Pt#5KFkPmGMMWkh zv?uz!e!m}-a6WpXtBxTll_*PSTh-7<;!FSQYZY=wNBxO!|K{b)!Ci|TuXd>H++x9q zEzZ33r7247=s0%#nYJA}p8x40H8~^dkr_$KWWZ>Y$9L|mkVSQ30(f}H>1--1bH0P{ z#ef{B;-#`)knN>`9>~IU^AjT&E0DW?ZyYR<)>M(t#KCvB>yMw9fTisF*wz|B?C}Hw zL&HQ5-~PEI?XB&ul>xicARDmZ2ztkACEsD@~)R=8fG#ep>L|pfmh!G z+CBcvj^|%JaOciVUer`x#g|LogqQr;f4WIYnGvucXH}5ZEb?X5(6A*i zJQSpDH-20zkBC{|U zy1pLf6^6#*d`7)<{B0`D*M9ql`@h-oOvmxh?f*^f%xf>arZ%Xlnfm&Mj$>~hJ8-9? zeny>9QyEg5A)+Tuq6!6WQ4*7yeCO8q_}(-QgOT^ibilb(*5oYtAaIwNG~)B6v)vqJ8Wi%cMu`+<2vI!>ixE$Bg&p~8i3Qf^YM*)c znFbXv3}$NL*c-EY>Ps(td;bq_y#WT?f4}RN^u=8-%+ymUwH_B$y*c{kXlgXMTwpI5 zu-5|1v32d3Vo4QhYb(ZgW-%dNlJ8E?7HiE?Yh~F|69O0%M6ai+5eqd?2_p>$)i<9Q z!S`0jW1a3Mxth}T({cp;x{>S4-rdvJPp3XkT|a$~KRw;Nyvn^54!(ka+{nSD70_jQ z@AUO%T=Q=Cs;gKgFO)*XOq_1X?(3%`+sOQ^09NkZSShEsfvLX~2nH-S-dE-9xPr;& za%~#xgi!-W8ifA?R~QlB5S~U&w^%tmM^XH;xY%P)^%QA;o#FNQ|g~3qibJW-X&S@ z!_o^q97B8hv-$M47XskB@9!f;Y$ceK%JIu zQT3o*Ddb;qG6jE%av*_46PTD|6FGmEBj+{h`r1<+ue{sA&=1aFMV&~^ro`^AJ-chy zQI*=;@%A^lH~#MJj_-9avj9hce+p%^VHkDoY&(1;*hGSzV-wy5*2Q55VJR!WG$G4r z3jGo~FUm4w^Hh2oYfuIE_BMisD6b-kJ%ofUwvW`*lOlMTIzL>UO?3T{hLU8B6?gBm z&197qmpS;uRVh*0QOS}kt+3p2SX%KslQow+1g1t-TT7kZVkGGHvI&5hd-N&;g9^4< zgA;e<@o`1sm3wl82l|!xOlh!yB>TpFK7%umfWd~FL=bDDfeQtgHVqf+?Q=LBxrEu} zfWN4sHWREKc3Q0g8ulcmD|X(x^|jZg)SW-=Xt-1H#xuto>JRKqi>aIPs390!JYHhm zg`77Jk6dCdpJQ3BfY*;tie$%S>|UvYkWf{f=V_~_%Htfa>|N@0Ryi93fZ%e%!4?Y` zqRgj~@F#OHw&;y~ncxTk)s2%`o{fB1BG?rU{_Tem;MD209NheHdPZqS-Nc)=%)!Xn z5s9dX)t#a5177ImW?WOc`zye?bhrC6`1H0HSf=QYY_^Xu6Ac?mS>XnJJFD`MaU4D9 zlYQcH@moe86~(OVyOXZt1Y_Qe>YAoaA`4T9T?4au4a)3cz5cx8tk)F6i5XQ@`Po~4 zyHvBZ)Y|$zCOInp;R`REP+vY=do%Md-uT8Jf8*c$^w@z{U;S^gd`jfR9(!DfvBg9# z$L8~Sx7&Z#3l?F1Apx3#!qN64qLjt{t9%A?-g!8_8QNBU#^ZE0I-R|sf~yDtV>TCxJdwoL3DTZLG^a|eyvo5Tq}&hJaPaBt53M<}qvmJVtbk2Z#pPF8 z*;xb!fsWvx*IIJTL~Lv?pSFCT4$|mCBL+N9+4}X~A9u^VGA(6zN%8>)^E$~cm(#pF z&MgXLx&Sb)s6{7}QNXWe0Hjd?n5H50#J%}^xX-Iy>r0dFCe|M~oi(k_UZ<7O)SG|s z?H|h7A0FQS-`b`!Gcz}5rj9-Hci-Osaym)XQFhk8kasxpj?Pg2th=Sfe zID_(u8AVcg>7Gwkc`cIxlM=PL#54r02;oMLrx8=!K}A@IKLSZum70GS4lel>$d3T# z|C`LNUwPso2d}X2DhFSG_91tY9er5Bd})ObR~&Wg_x>TS`r;aITJwkN+PZEpTWW|K z6Sf-|N4Ahkb^6!8{&BX}r(`4r<-=V`0hE9Rm*l~$ljI&8lFSu2fGV18ni$n7$%G%x zC=hWJSQvyWpw8PK4YxDc0D~H+{{$ezi{{{^`-q^IQ%Ep8PLD8x8DBd z=YO+n*NJq}=n)rk3l2x81CuM=?(ep^-44GWOu{a2oD(SQNFiYzvn;D3{E*^tz^4de z;_HDkjY|Z;9uHB%0VH7yUXApG{8zsN2R}=?vHucHtTiI{<5l;<$TDS?{$<&c=#Jlx>@Iv~|3x^Nye=oq$%$?W1@r@t;=I|GOSepeNHx_dFPQU-GAAsw2 zxPJt@xsMQ@1B*@&=7IAiDVdbI$J6R4+#OOP>7*v|%mk3|P@|`jqz9uACL|2sKnZ_+ z=6B%W5(g5O37B8RIN%Bgqwsifn}a{YlDbU?$zZLa zDyz-*lM%)D{&=^nOQd{AQY0Ot$RH*ZZjtL3Oaw4Tfr=;~m`zhODUC9uC;%9c?dpL~ zFD6AKgueDtj=Z(eQ|ZK*?+}x$f1CO1TNBuv^3Tg`G(9U{_}X`N9sbSXXRk;}Bd)PU zBwhE9T3`t~I{hsUL@=;$j!nP|D=5cP$yE0G6@)OK86^PbMb(BuW)Ch_K(LjVasXoV z;?Xz0|KuE8;y?ng$Kl}Lt_evK=fblk99~~R@W@6EMlg31kH!iIqxbIPZ4Ukn8e;E9 zXv6_idMv?TXCu;xlFeR`;TzTsN!Y)aSMG5>&JYrSU^1+p5w)51(a?uD<#Skk^)SfYlkmSDCYVRBU*A=2pcmpy0!QQbp4;Gyc+Rf4U!j zd!ZnuqJoAZ_$(m!M=fr@KbIdvJOh9S9eLI+01b;j6DKt+1*y%#QDJ%~Hl%{@YH(=S z<7}(~6@d$;_F!DY0yh_ielYa}2<|38dO{9<7SXqcgID0Z%E8hm9K1Y0W`%=^fZ68Y zPX};%xhe3F)pDb@JDpZ|MOSdWHr|y;^sqpl;eyw3f+6j)tF7e^zD`@f)}p6I-{_F!yZvVsh2tUS1VW0 zHV1#Q2>vKIfyukpa$E5I@!LO6%RJcmBCjjheJ>-*wOLRQg+)UU`UQZn3x05cR6rff zE%p!}Fqm*67;`QYd$s50SQad?g~AChwXcB=+JJ&BQ=<(5%aDi3)IWb`Rvv!=5d3e? z2NA(9UAS;`*WoW5{%x9nGtNfYeBPh;|H$p{1R3FQxbNS0`0qOge^Cr2x)Lla=q4_j zrlmmH@X{!{)3C-AfGpXp9}I&IuBt{N#_m&?Az{M-2{zgiJomT=CZ4XfmfYiT@aZ*~ zZPFSZU0tDjBL}|>_`Py&W&P5(Uw*vJ!Jo>&^vZl>+m(pq1O2Orq(Qo*C|F=AjgNN& zmO(B6f{}g^!Dz`*0@`tcAyI~cb1n>u5?na$C7;iE9bIRIb}yIx^$pl}wWs`Ys^O2U zbh%|zYj|Cr6tBFnYyaW@eFy-|@Gl+x*Dw6$3x~h(Y$20$#WWV{?EU`m{T4SY_koLn zi5>pVv3%aKkcb%?223bZ#Qs1cW(=bxhpCq$FUu2F=kQQ9V7So(FfR9Co41m0I1nfg zGLMJg=92NX#(DNQ9DMrahce<(4*qb>-0zj0m3(y{F9+;Oo4vTr!JiIbASe*Q<$>Av zZ}YMO8Uj$8(GkHBF!q(mVqIOq1d9#$&xH@ubQ$PJ1184I1;GRgMiqg1JuWVm%RBP< zYjgQI(Ho{tOjm#mu5PTpJj$F3Ff^UKdFpLu?}3T~FYG`3pRo}(Bfb2Ge|Qwx~CN?4wl zmdp?nuFovP=AT1?XzLLJ1^-XhipmoVSPE-#9Fra`W zY=Vs7xPm|_1?-I~M&Vme%oVri92k%Aa$J6~e&5stvE$0=Ols;f!!V;WuXfb`;LQK= z(%$`t{}#v6(bAWWeh1CE{Vzz#B+HuYg5Q7E?RL2RKRW9VkL8h)-7UnU=E&y~aP7EE zbVHkBGL=fI@_0d&KffqoY}G^mL+$^zi!Kjh^Y{Py4_-sFZvQXUR1#D| zm%r1|au#TqlzVnM?)&e9QRns#`r*3Bv2YE=;qsy2s!1iaWJ+Efmr?Ryj6=pRJZ%LA z_F$V$%vCEpU)I}L>K}o1f7akg7G_pEaGaqp zU$*_3ul9@9KoBj+uM-6CKm06}OtmM>cE=!kfE{5#FnT2XooCOs`2Bz131T=LiU~O1 z78n?mH$WJyIeA|id$CGVdK`1sKo!dmu**F5WbIw=Ucq~@ zXXh)gKYw7h+Jg-PE6*%dTAA6@TW?J<;wW?Z)c5}7gX(hR;NS}{{SYT8|58nk&dnJK z5Whci17?2&JdEPuQrPSE-v#Rz~@N`^RN?4x>EUPAaL$8Ao$+t>!%|RS6t0-KYjfk`J=R)+rFIP4qsRP z_~C>rgtZokA$mz)5VBkjN z9&^PvE&0F}l13z1k<`1>>1j##Vbwg2L3`0#E})9v0K;NbjyM<)OlaO($it|jbtoOQUz^7&#uB$#Y5#-b+=gs_MtoKn+i zbe(`A&Q{#IeLF2@k}0NqxD4x-kJOZrhA53rPbI(@JraQ%&7Y3o?d7RhwCQ=J_Q&tv z8{unfuPD-8kq1L=L;)?F{ZNr4T>|yX)v+uWPq3~SkT3!mMFcLKQBcI2h#?*`g2gCS z$rW?C1$J)E(KTmyTR3`RieWAX8i9g)!KiW$(W3VH@dm5MMvvaKR(dQ|R4rMn{S9os?UoBcEp{LLq7yCF=%2CCa5OhTZn~Yvb(6L3(G-t5p206Sa6u-1EySh zy!OZGtWOp*Nw{vxnwm&kHEc`etZZy_4i9^fhJzE$+X(*WA%c}iBvM-&8PSw4jVmPM z3%*KMB8s9+8-}EtC>;c_H z;|yb7$Kaa)Dj1Bo7{M=m!KM&k!5IY#wI`1wV=*eHiDp4?vBkN37Z7Nl>Am;{JrT57 zf8cReV_3{nR_UY}hS_nfytN7Qc)_UhRC>w-Hj7O?dh}0UsD14vRh9d^#)+bRET1dp zbD>=4*qGl<^u7;GFS ztpJZ4!OnnARO6l@95(mh&20q#b6Aw=dsnj8eNsjS^GE0Tk$D9Xj9y;BhaorsvI_+; z4)qq|G0Z}9aY7GgQe45nrc)7|d7KeNoIML7qR?XxKc${D&=4_&BLmL~M%*nDmv?^w{|-WzlSNZfK~tL3t_?a(2{60k9& zK*1J|bE&3jsjTKmle4#xWRA(N?ce{yFIK2v;mvuCu3v-z!#TS>mn(+yo&K|(#ayR< zu(PuhV;VVO0VHzH?gAOCnK-SMc!iUsk+G`OcFQuhbW5s$XGPv;doa{=1lyrta&psB zqZPYeVQ_96!B77p{(NM5d_1Bgbx;sGFOFcfgUMkYxnRtrt4t!kvlz>|SPlh*fJR)w zkSWo?R;zH+2Ez}N(hL*(^%y-YSaP{UKFSPmF|=bf^prl#R0!oe8V2&mf#e%hD6 z0z2+XM7oQE1~HLcQ+$$+f`Jp}fo^pPJE($cFyjKt#jz$%00GPuD4jIYQAVT@l)(~~ zD1#jr6#?^!gguu49bvS4?_g<$CEz(TR6e72bky5|ml-Cx=lRRktxaWcYsYl=vNA%# z^z6=EyLSEc&f5$#G1tX~#z2RR+3f@U1E3^wXK@^Xe+)DP@G;OY>&V;fu@Zu@xD?Az zNsdnnYf0{AyZ5Hk686&sDIv@0eGNg2X9-6yztgldi~|!9zvb2kv)c%M+C_vf5{dXC zI`;m}NPI@}EgIZHLP!Yc?KO3@%+QjHu|g~cqJmH`P#%x9YPzI|x|qafk|JeNq87y) zx{=7m>})8P6TOLg%y$b~tDGJS7;viXD#J{v%v;B%W-nWYJWilQbk^25tAK+u*~7a& ztoZW@nx62U<%;&=*jOmk-|qsPhKgZ-&Vld^702>m$$_HC7wrRfuq-uAGq6f;lGtLQXay>G&19d?$%OIDVIFlh*AT|2958^KTG@@8audiv!o(iM*! ztSoW@D2RR*s8`^krl|u33-hJ`beF&VyEBj7()#Ke;UgV`oYjE#$3q!poNY3C>+<#`FS!HOjqUZ zZaG_+6-ix`3|bhJgk^ZC(b+nT>F*y9DU69~=e802R41n38BDg|mYqho2X&gtbmVD&O#;;}q&Rj{n7ML~dz(Wn^}baj}lWU@eGV z%J>j!2d=6xJ$hIt2*s5fZW7oHv&!^mH^d*ysTI z(t6?kjee{%HfFZ|d3crqGXDOVwnMK3oEU5Hl-p=k`u5@dp9ij{GktxF=xfO5azMjG zkvLF8V7?G0&gT)ygQ2ldY+xYfN`WB<0$jwF#ZfGg2K9pzmm@w|9>-(}NtM%!vNZ92 zI!#v(IhWvufVKFZa-t*xtF48%5&Trj;OR(2kMLTCR86Mq7C}RBEZ4�TX|OE&?zC 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 Hu0mjflD$j_ literal 0 HcmV?d00001 diff --git a/web/news.rst b/web/news.rst index 327cf6cca..e8d97b69e 100644 --- a/web/news.rst +++ b/web/news.rst @@ -2,6 +2,9 @@ News ==== +`2016-11-20 Nim in Action is going into production! `_ +=================================== + `2016-10-23 Nim Version 0.15.2 released `_ =================================== diff --git a/web/news/e030_nim_in_action_in_production.rst b/web/news/e030_nim_in_action_in_production.rst new file mode 100644 index 000000000..b68b82801 --- /dev/null +++ b/web/news/e030_nim_in_action_in_production.rst @@ -0,0 +1,53 @@ +Nim in Action is going into production! +======================================= + +.. container:: metadata + + Posted by Dominik Picheta on 20/11/2016 + +.. raw::html + + + A printed copy of Nim in Action should be available in March 2017! + + +I am very happy to say that just last week I have put the finishing touches +on Nim in Action. The final manuscript has been submitted to Manning (the book's +publisher), and the printed version is expected to start shipping in March +2017 (give or take 1 month). + +The eBook is still available and now contains all of the book's chapters, +including new ones dealing with the foreign function interface and +metaprogramming. +That said, it may still take some time before the eBook is updated with the +latest corrections. + +I am incredibly thankful to everyone that purchased the book already. Many of +you have also given me a lot of `brilliant `_ +`feedback `_, +thank you very much for +taking the time to do so. I have done my best to act on this +feedback and I hope you will agree that the book has risen in quality as a +result. + +Writing this book has been both exhausting and incredible at the same time. +I look forward +to having a physical copy of it in my hands, and I'm sure many of you do as +well. I can safely say that without your support this book would not have +happened, even if you did not purchase a copy your interest in Nim has made it +possible and I thank you for that. + +As always, you can make a purchase on +`Manning's website `_. +Both eBook's and printed books are available, and purchasing a printed book will +get you an eBook for free. +You can now also pre-order Nim in Action on +`Amazon `_! + +If you would like updates about the book then please feel free to +follow either `myself `_ or +`@nim_lang `_ on Twitter. Finally, if you have any +questions, do get in touch via `Twitter, NimForum, +IRC or Gitter `_. + +Thanks for reading! diff --git a/web/ticker.html b/web/ticker.html index b59c0a3e9..86dc97c14 100644 --- a/web/ticker.html +++ b/web/ticker.html @@ -1,8 +1,18 @@ + +

November 20, 2016

+

Nim in Action is going into production!

+
+

October 23, 2016

Nim version 0.15.2 has been released!

+ +

September 30, 2016

+

Nim version 0.15.0 has been released!

+
+

September 3, 2016

Nim Community Survey results

@@ -13,14 +23,4 @@

BountySource Update: The Road to v1.0

- -

June 23, 2016

-

Launching the 2016 Nim community survey!

-
- - -

June 11, 2016

-

Nim version 0.14.2 has been released!

-
- See All News... From 4711f6c3985b54bfab0e1b6a3b239ba7cf0d57a7 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 20 Nov 2016 21:11:25 +0100 Subject: [PATCH 31/58] Put Nim in Action news in the slider on the front page. --- tools/website.tmpl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/website.tmpl b/tools/website.tmpl index 2801fea96..9aa64310d 100644 --- a/tools/website.tmpl +++ b/tools/website.tmpl @@ -61,13 +61,13 @@
From fa101a722f80646bcbf86b94b4c2ce2a4dce93a8 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Mon, 21 Nov 2016 12:04:33 +0100 Subject: [PATCH 32/58] updated sqlite3 wrapper slightly --- lib/wrappers/sqlite3.nim | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/wrappers/sqlite3.nim b/lib/wrappers/sqlite3.nim index e7fd2bc36..d76fb61ad 100644 --- a/lib/wrappers/sqlite3.nim +++ b/lib/wrappers/sqlite3.nim @@ -96,10 +96,6 @@ const #define SQLITE_TRANSIENT ((void(*)(void *))-1) SQLITE_DETERMINISTIC* = 0x800 -const - SQLITE_STATIC* = nil - SQLITE_TRANSIENT* = cast[pointer](- 1) - type Sqlite3 {.pure, final.} = object PSqlite3* = ptr Sqlite3 @@ -132,6 +128,10 @@ type Tresult_func: Result_func, Tcreate_collation_func: Create_collation_func, Tcollation_needed_func: Collation_needed_func].} +const + SQLITE_STATIC* = nil + SQLITE_TRANSIENT* = cast[Tbind_destructor_func](- 1) + proc close*(para1: PSqlite3): int32{.cdecl, dynlib: Lib, importc: "sqlite3_close".} proc exec*(para1: PSqlite3, sql: cstring, para3: Callback, para4: pointer, errmsg: var cstring): int32{.cdecl, dynlib: Lib, @@ -234,7 +234,8 @@ proc bind_parameter_name*(para1: Pstmt, para2: int32): cstring{.cdecl, dynlib: Lib, importc: "sqlite3_bind_parameter_name".} proc bind_parameter_index*(para1: Pstmt, zName: cstring): int32{.cdecl, dynlib: Lib, importc: "sqlite3_bind_parameter_index".} - #function sqlite3_clear_bindings(_para1:Psqlite3_stmt):longint;cdecl; external Sqlite3Lib name 'sqlite3_clear_bindings'; +proc clear_bindings*(para1: Pstmt): int32 {.cdecl, + dynlib: Lib, importc: "sqlite3_clear_bindings".} proc column_count*(pStmt: Pstmt): int32{.cdecl, dynlib: Lib, importc: "sqlite3_column_count".} proc column_name*(para1: Pstmt, para2: int32): cstring{.cdecl, dynlib: Lib, From 02a2180a6a7f819add2e53053ccabfd80a59718d Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Mon, 21 Nov 2016 12:07:17 +0100 Subject: [PATCH 33/58] first version of the new memory tracking feature --- compiler/ccgexprs.nim | 9 +++++ compiler/cgendata.nim | 1 + compiler/commands.nim | 5 +++ compiler/options.nim | 3 +- compiler/pragmas.nim | 3 +- compiler/wordrecg.nim | 4 +-- doc/advopt.txt | 1 + lib/pure/nimtracker.nim | 71 +++++++++++++++++++++++++++++++++++++++ lib/system.nim | 10 ++++++ lib/system/excpt.nim | 2 ++ lib/system/memtracker.nim | 62 ++++++++++++++++++++++++++++++++++ 11 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 lib/pure/nimtracker.nim create mode 100644 lib/system/memtracker.nim diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 00a60fe1b..2761f888b 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -355,6 +355,14 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src)) else: internalError("genAssignment: " & $ty.kind) + if optMemTracker in p.options and dest.s in {OnHeap, OnUnknown}: + #writeStackTrace() + #echo p.currLineInfo, " requesting" + linefmt(p, cpsStmts, "#memTrackerWrite((void*)$1, $2, $3, $4);$n", + addrLoc(dest), rope getSize(dest.t), + makeCString(p.currLineInfo.toFullPath), + rope p.currLineInfo.safeLineNm) + proc genDeepCopy(p: BProc; dest, src: TLoc) = var ty = skipTypes(dest.t, abstractVarRange) case ty.kind @@ -1946,6 +1954,7 @@ proc exprComplexConst(p: BProc, n: PNode, d: var TLoc) = d.s = OnStatic proc expr(p: BProc, n: PNode, d: var TLoc) = + p.currLineInfo = n.info case n.kind of nkSym: var sym = n.sym diff --git a/compiler/cgendata.nim b/compiler/cgendata.nim index a94950029..faeea7afb 100644 --- a/compiler/cgendata.nim +++ b/compiler/cgendata.nim @@ -68,6 +68,7 @@ type beforeRetNeeded*: bool # true iff 'BeforeRet' label for proc is needed threadVarAccessed*: bool # true if the proc already accessed some threadvar lastLineInfo*: TLineInfo # to avoid generating excessive 'nimln' statements + currLineInfo*: TLineInfo # AST codegen will make this superfluous nestedTryStmts*: seq[PNode] # in how many nested try statements we are # (the vars must be volatile then) inExceptBlock*: int # are we currently inside an except block? diff --git a/compiler/commands.nim b/compiler/commands.nim index e545e7ab5..590c4871d 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -242,6 +242,7 @@ proc testCompileOption*(switch: string, info: TLineInfo): bool = of "linetrace": result = contains(gOptions, optLineTrace) of "debugger": result = contains(gOptions, optEndb) of "profiler": result = contains(gOptions, optProfiler) + of "memtracker": result = contains(gOptions, optMemTracker) of "checks", "x": result = gOptions * ChecksOptions == ChecksOptions of "floatchecks": result = gOptions * {optNaNCheck, optInfCheck} == {optNaNCheck, optInfCheck} @@ -446,6 +447,10 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo) = processOnOffSwitch({optProfiler}, arg, pass, info) if optProfiler in gOptions: defineSymbol("profiler") else: undefSymbol("profiler") + of "memtracker": + processOnOffSwitch({optMemTracker}, arg, pass, info) + if optMemTracker in gOptions: defineSymbol("memtracker") + else: undefSymbol("memtracker") of "checks", "x": processOnOffSwitch(ChecksOptions, arg, pass, info) of "floatchecks": processOnOffSwitch({optNaNCheck, optInfCheck}, arg, pass, info) diff --git a/compiler/options.nim b/compiler/options.nim index 7cf707945..f8db3927a 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -34,7 +34,8 @@ type # please make sure we have under 32 options optProfiler, # profiler turned on optImplicitStatic, # optimization: implicit at compile time # evaluation - optPatterns # en/disable pattern matching + optPatterns, # en/disable pattern matching + optMemTracker TOptions* = set[TOption] TGlobalOption* = enum # **keep binary compatible** diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index f4109b26d..e11a8d08b 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -323,7 +323,8 @@ proc processOption(c: PContext, n: PNode): bool = of wStacktrace: onOff(c, n, {optStackTrace}) of wLinetrace: onOff(c, n, {optLineTrace}) of wDebugger: onOff(c, n, {optEndb}) - of wProfiler: onOff(c, n, {optProfiler}) + of wProfiler: onOff(c, n, {optProfiler, optMemTracker}) + of wMemTracker: onOff(c, n, {optMemTracker}) of wByRef: onOff(c, n, {optByRef}) of wDynlib: processDynLib(c, n, nil) of wOptimization: diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim index 04376892f..cf66b6358 100644 --- a/compiler/wordrecg.nim +++ b/compiler/wordrecg.nim @@ -34,7 +34,7 @@ type wColon, wColonColon, wEquals, wDot, wDotDot, wStar, wMinus, - wMagic, wThread, wFinal, wProfiler, wObjChecks, + wMagic, wThread, wFinal, wProfiler, wMemTracker, wObjChecks, wIntDefine, wStrDefine, wDestroy, @@ -121,7 +121,7 @@ const ":", "::", "=", ".", "..", "*", "-", - "magic", "thread", "final", "profiler", "objchecks", "intdefine", "strdefine", + "magic", "thread", "final", "profiler", "memtracker", "objchecks", "intdefine", "strdefine", "destroy", diff --git a/doc/advopt.txt b/doc/advopt.txt index b8980fa9c..c434391ce 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -61,6 +61,7 @@ Advanced options: --taintMode:on|off turn taint mode on|off --implicitStatic:on|off turn implicit compile time evaluation on|off --patterns:on|off turn pattern matching on|off + --memTracker:on|off turn memory tracker on|off --skipCfg do not read the general configuration file --skipUserCfg do not read the user's configuration file --skipParentCfg do not read the parent dirs' configuration files diff --git a/lib/pure/nimtracker.nim b/lib/pure/nimtracker.nim new file mode 100644 index 000000000..e3d9832c6 --- /dev/null +++ b/lib/pure/nimtracker.nim @@ -0,0 +1,71 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2016 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Memory tracking support for Nim. + +when isMainModule: + import db_sqlite + var db = open("memtrack.db", "", "", "") + db.exec sql""" + create table if not exists Tracking( + id integer primary key, + op varchar not null, + address integer not null, + size integer not null, + file varchar not null, + line integer not null + )""" + db.close() +else: + when not defined(memTracker): + {.error: "Memory tracking support is turned off!".} + + {.push memtracker: off.} + # we import the low level wrapper and are careful not to use Nim's + # memory manager for anything here. + import sqlite3 + + var + dbHandle: PSqlite3 + insertStmt: Pstmt + + template sbind(x: int; value) = + when value is cstring: + let ret = insertStmt.bindText(x, value, value.len.int32, SQLITE_TRANSIENT) + if ret != SQLITE_OK: + quit "could not bind value" + else: + let ret = insertStmt.bindInt64(x, value) + if ret != SQLITE_OK: + quit "could not bind value" + + proc logEntries(log: TrackLog) {.nimcall.} = + for i in 0..log.count-1: + var success = false + let e = log.data[i] + discard sqlite3.reset(insertStmt) + discard clearBindings(insertStmt) + sbind 1, e.op + sbind(2, cast[int](e.address)) + sbind 3, e.size + sbind 4, e.file + sbind 5, e.line + if step(insertStmt) == SQLITE_DONE: + success = true + if not success: + quit "could not write to database!" + + if sqlite3.open("memtrack.db", dbHandle) == SQLITE_OK: + const query = "INSERT INTO tracking(op, address, size, file, line) values (?, ?, ?, ?, ?)" + if prepare_v2(dbHandle, query, + query.len, insertStmt, nil) == SQLITE_OK: + setTrackLogger logEntries + else: + quit "could not prepare statement" + {.pop.} diff --git a/lib/system.nim b/lib/system.nim index 9547673a5..69d3db291 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -1272,12 +1272,14 @@ const seqShallowFlag = low(int) +{.push profiler: off.} when defined(nimKnowsNimvm): let nimvm* {.magic: "Nimvm".}: bool = false ## may be used only in "when" expression. ## It is true in Nim VM context and false otherwise else: const nimvm*: bool = false +{.pop.} proc compileOption*(option: string): bool {. magic: "CompileOption", noSideEffect.} @@ -2544,6 +2546,7 @@ when hostOS == "standalone": include "$projectpath/panicoverride" when not declared(sysFatal): + {.push profiler: off.} when hostOS == "standalone": proc sysFatal(exceptn: typedesc, message: string) {.inline.} = panic(message) @@ -2563,6 +2566,7 @@ when not declared(sysFatal): new(e) e.msg = message & arg raise e + {.pop.} proc getTypeInfo*[T](x: T): pointer {.magic: "GetTypeInfo", benign.} ## get type information for `x`. Ordinary code should not use this, but @@ -2616,8 +2620,10 @@ when not defined(JS): #and not defined(nimscript): when declared(setStackBottom): setStackBottom(locals) + {.push profiler: off.} var strDesc = TNimType(size: sizeof(string), kind: tyString, flags: {ntfAcyclic}) + {.pop.} # ----------------- IO Part ------------------------------------------------ @@ -2950,6 +2956,8 @@ when not defined(JS): #and not defined(nimscript): ## lead to the ``raise`` statement. This only works for debug builds. {.push stack_trace: off, profiler:off.} + when defined(memtracker): + include "system/memtracker" when hostOS == "standalone": include "system/embedded" else: @@ -2992,7 +3000,9 @@ when not defined(JS): #and not defined(nimscript): else: result = n.sons[n.len] + {.push profiler:off.} when hasAlloc: include "system/mmdisp" + {.pop.} {.push stack_trace: off, profiler:off.} when hasAlloc: include "system/sysstr" {.pop.} diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index dcf41b67d..d00ab64b1 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -339,6 +339,8 @@ when not defined(noSignalHandler): action("unknown signal\n") # print stack trace and quit + when defined(memtracker): + logPendingOps() when hasSomeStackTrace: GC_disable() var buf = newStringOfCap(2000) diff --git a/lib/system/memtracker.nim b/lib/system/memtracker.nim new file mode 100644 index 000000000..b4a5460fa --- /dev/null +++ b/lib/system/memtracker.nim @@ -0,0 +1,62 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2016 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Memory tracking support for Nim. + +when not defined(memTracker): + {.error: "Memory tracking support is turned off! Enable memory tracking by passing `--memtracker:on` to the compiler (see the Nim Compiler User Guide for more options).".} + +when defined(noSignalHandler): + {.error: "Memory tracking works better with the default signal handler.".} + +# We don't want to memtrack the tracking code ... +{.push memtracker: off.} + +type + LogEntry* = object + op*: cstring + address*: pointer + size*: int + file*: cstring + line*: int + TrackLog* = object + count*: int + data*: array[4000, LogEntry] + TrackLogger* = proc (log: TrackLog) {.nimcall.} + +var + gLog*: TrackLog + gLogger*: TrackLogger = proc (log: TrackLog) = discard + +proc setTrackLogger*(logger: TrackLogger) = + gLogger = logger + +proc addEntry(entry: LogEntry) = + if gLog.count > high(gLog.data): + gLogger(gLog) + gLog.count = 0 + gLog.data[gLog.count] = entry + inc gLog.count + +proc memTrackerWrite(address: pointer; size: int; file: cstring; line: int) {.compilerProc.} = + addEntry LogEntry(op: "write", address: address, + size: size, file: file, line: line) + +proc memTrackerOp*(op: cstring; address: pointer; size: int) = + addEntry LogEntry(op: op, address: address, size: size, + file: "", line: 0) + +proc logPendingOps() {.noconv.} = + # forward declared and called from Nim's signal handler. + gLogger(gLog) + gLog.count = 0 + +addQuitProc logPendingOps + +{.pop.} From 9ca15ad369c70f87f4b3a5ff0fb1230ef113f6cf Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Mon, 21 Nov 2016 13:53:55 +0100 Subject: [PATCH 34/58] string.add for floats and ints for more performance (JS not yet supported) --- lib/system/sysstr.nim | 51 +++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim index 3a93221e0..11034006a 100644 --- a/lib/system/sysstr.nim +++ b/lib/system/sysstr.nim @@ -263,27 +263,32 @@ proc setLengthSeq(seq: PGenericSeq, elemSize, newLen: int): PGenericSeq {. result.len = newLen # --------------- other string routines ---------------------------------- -proc nimIntToStr(x: int): string {.compilerRtl.} = - result = newString(sizeof(x)*4) +proc add*(result: var string; x: int64) = + let base = result.len + setLen(result, base + sizeof(x)*4) var i = 0 var y = x while true: var d = y div 10 - result[i] = chr(abs(int(y - d*10)) + ord('0')) + result[base+i] = chr(abs(int(y - d*10)) + ord('0')) inc(i) y = d if y == 0: break if x < 0: - result[i] = '-' + result[base+i] = '-' inc(i) - setLen(result, i) + setLen(result, base+i) # mirror the string: for j in 0..i div 2 - 1: - swap(result[j], result[i-j-1]) + swap(result[base+j], result[base+i-j-1]) -proc nimFloatToStr(f: float): string {.compilerproc.} = +proc nimIntToStr(x: int): string {.compilerRtl.} = + result = newStringOfCap(sizeof(x)*4) + result.add x + +proc add*(result: var string; x: float) = var buf: array[0..64, char] - var n: int = c_sprintf(buf, "%.16g", f) + var n: int = c_sprintf(buf, "%.16g", x) var hasDot = false for i in 0..n-1: if buf[i] == ',': @@ -298,14 +303,18 @@ proc nimFloatToStr(f: float): string {.compilerproc.} = # On Windows nice numbers like '1.#INF', '-1.#INF' or '1.#NAN' are produced. # We want to get rid of these here: if buf[n-1] in {'n', 'N'}: - result = "nan" + result.add "nan" elif buf[n-1] == 'F': if buf[0] == '-': - result = "-inf" + result.add "-inf" else: - result = "inf" + result.add "inf" else: - result = $buf + result.add buf + +proc nimFloatToStr(f: float): string {.compilerproc.} = + result = newStringOfCap(8) + result.add f proc c_strtod(buf: cstring, endptr: ptr cstring): float64 {. importc: "strtod", header: "", noSideEffect.} @@ -469,22 +478,8 @@ proc nimParseBiggestFloat(s: string, number: var BiggestFloat, number = c_strtod(t, nil) proc nimInt64ToStr(x: int64): string {.compilerRtl.} = - result = newString(sizeof(x)*4) - var i = 0 - var y = x - while true: - var d = y div 10 - result[i] = chr(abs(int(y - d*10)) + ord('0')) - inc(i) - y = d - if y == 0: break - if x < 0: - result[i] = '-' - inc(i) - setLen(result, i) - # mirror the string: - for j in 0..i div 2 - 1: - swap(result[j], result[i-j-1]) + result = newStringOfCap(sizeof(x)*4) + result.add x proc nimBoolToStr(x: bool): string {.compilerRtl.} = return if x: "true" else: "false" From 18690d4a6199804f2c5ad3322e2a6a3c33665eff Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Mon, 21 Nov 2016 13:54:16 +0100 Subject: [PATCH 35/58] speed up json core module --- lib/pure/json.nim | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 0b7908c02..b7f58c55d 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -954,9 +954,11 @@ proc newIndent(curr, indent: int, ml: bool): int = proc nl(s: var string, ml: bool) = if ml: s.add("\n") -proc escapeJson*(s: string): string = +proc escapeJson*(s: string; result: var string) = ## Converts a string `s` to its JSON representation. - result = newStringOfCap(s.len + s.len shr 3) + ## Appends to ``result``. + const + HexChars = "0123456789ABCDEF" result.add("\"") for x in runes(s): var r = int(x) @@ -967,10 +969,19 @@ proc escapeJson*(s: string): string = of '\\': result.add("\\\\") else: result.add(c) else: - result.add("\\u") - result.add(toHex(r, 4)) + # toHex inlined for more speed (saves stupid string allocations): + result.add("\\u0000") + let start = result.len - 4 + for j in countdown(3, 0): + result[j+start] = HexChars[r and 0xF] + r = r shr 4 result.add("\"") +proc escapeJson*(s: string): string = + ## Converts a string `s` to its JSON representation. + result = newStringOfCap(s.len + s.len shr 3) + escapeJson(s, result) + proc toPretty(result: var string, node: JsonNode, indent = 2, ml = true, lstArr = false, currIndent = 0) = case node.kind @@ -988,7 +999,7 @@ proc toPretty(result: var string, node: JsonNode, indent = 2, ml = true, inc i # Need to indent more than { result.indent(newIndent(currIndent, indent, ml)) - result.add(escapeJson(key)) + escapeJson(key, result) result.add(": ") toPretty(result, val, indent, ml, false, newIndent(currIndent, indent, ml)) @@ -999,16 +1010,19 @@ proc toPretty(result: var string, node: JsonNode, indent = 2, ml = true, result.add("{}") of JString: if lstArr: result.indent(currIndent) - result.add(escapeJson(node.str)) + escapeJson(node.str, result) of JInt: if lstArr: result.indent(currIndent) - result.add($node.num) + when defined(js): result.add($node.num) + else: result.add(node.num) of JFloat: if lstArr: result.indent(currIndent) - result.add($node.fnum) + # Fixme: implement new system.add ops for the JS target + when defined(js): result.add($node.fnum) + else: result.add(node.fnum) of JBool: if lstArr: result.indent(currIndent) - result.add($node.bval) + result.add(if node.bval: "true" else: "false") of JArray: if lstArr: result.indent(currIndent) if len(node.elems) != 0: @@ -1057,12 +1071,12 @@ proc toUgly*(result: var string, node: JsonNode) = for key, value in pairs(node.fields): if comma: result.add "," else: comma = true - result.add key.escapeJson() + key.escapeJson(result) result.add ":" result.toUgly value result.add "}" of JString: - result.add node.str.escapeJson() + node.str.escapeJson(result) of JInt: result.add($node.num) of JFloat: @@ -1394,4 +1408,6 @@ when isMainModule: var parsed2 = parseFile("tests/testdata/jsontest2.json") doAssert(parsed2{"repository", "description"}.str=="IRC Library for Haskell", "Couldn't fetch via multiply nested key using {}") + doAssert escapeJson("\10FoobarÄ") == "\"\\u000AFoobar\\u00C4\"" + echo("Tests succeeded!") From 585a970106c7c726280a90f0431c8fc4a72a8acb Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Mon, 21 Nov 2016 15:16:54 +0100 Subject: [PATCH 36/58] json.toUgly also uses optimized string routines --- lib/pure/json.nim | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index b7f58c55d..5fff7352f 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -1078,9 +1078,11 @@ proc toUgly*(result: var string, node: JsonNode) = of JString: node.str.escapeJson(result) of JInt: - result.add($node.num) + when defined(js): result.add($node.num) + else: result.add(node.num) of JFloat: - result.add($node.fnum) + when defined(js): result.add($node.fnum) + else: result.add(node.fnum) of JBool: result.add(if node.bval: "true" else: "false") of JNull: From e6f6323e770d326e633c5f1d7e5205d7cb2c4ca3 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Mon, 21 Nov 2016 20:55:37 +0100 Subject: [PATCH 37/58] Fix inaccuracy surrounding Nimble in doc/nims.rst. --- doc/nims.rst | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/doc/nims.rst b/doc/nims.rst index 7c76efe42..12d86a905 100644 --- a/doc/nims.rst +++ b/doc/nims.rst @@ -75,22 +75,8 @@ done: Nimble integration ================== -A ``project.nims`` file can also be used as an alternative to -a ``project.nimble`` file to specify the meta information (for example, author, -description) and dependencies of a Nimble package. This means you can easily -have platform specific dependencies: - -.. code-block:: nim - - version = "1.0" - author = "The green goo." - description = "Lexer generation and regex implementation for Nim." - license = "MIT" - - when defined(windows): - requires "oldwinapi >= 1.0" - else: - requires "gtk2 >= 1.0" +See the `Nimble readme `_ +for more information. From c538e1ae084ed7a1888c37b7c267449f166b9c11 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Mon, 21 Nov 2016 23:44:38 +0100 Subject: [PATCH 38/58] Fixes asyncdispatch.all completing its res future more than once. --- lib/pure/includes/asyncfutures.nim | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/pure/includes/asyncfutures.nim b/lib/pure/includes/asyncfutures.nim index dfcfa37a0..029c5f157 100644 --- a/lib/pure/includes/asyncfutures.nim +++ b/lib/pure/includes/asyncfutures.nim @@ -263,13 +263,13 @@ proc all*[T](futs: varargs[Future[T]]): auto = for fut in futs: fut.callback = proc(f: Future[T]) = - if f.failed: - retFuture.fail(f.error) - elif not retFuture.finished: - inc(completedFutures) - - if completedFutures == totalFutures: - retFuture.complete() + inc(completedFutures) + if not retFuture.finished: + if f.failed: + retFuture.fail(f.error) + else: + if completedFutures == totalFutures: + retFuture.complete() if totalFutures == 0: retFuture.complete() @@ -285,14 +285,15 @@ proc all*[T](futs: varargs[Future[T]]): auto = for i, fut in futs: proc setCallback(i: int) = fut.callback = proc(f: Future[T]) = - if f.failed: - retFuture.fail(f.error) - elif not retFuture.finished: - retValues[i] = f.read() - inc(completedFutures) + inc(completedFutures) + if not retFuture.finished: + if f.failed: + retFuture.fail(f.error) + else: + retValues[i] = f.read() - if completedFutures == len(retValues): - retFuture.complete(retValues) + if completedFutures == len(retValues): + retFuture.complete(retValues) setCallback(i) From 9a4f2225ce9901191f35fbf49fdbf6a0fea793c9 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 22 Nov 2016 08:23:10 +0100 Subject: [PATCH 39/58] documents --excessiveStackTrace switch --- doc/advopt.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/advopt.txt b/doc/advopt.txt index c434391ce..991f06397 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -62,6 +62,8 @@ Advanced options: --implicitStatic:on|off turn implicit compile time evaluation on|off --patterns:on|off turn pattern matching on|off --memTracker:on|off turn memory tracker on|off + --excessiveStackTrace:on|off + stack traces use full file paths --skipCfg do not read the general configuration file --skipUserCfg do not read the user's configuration file --skipParentCfg do not read the parent dirs' configuration files From 439f43fc52c62d6d2c980645f76c30b1d2cb573d Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 22 Nov 2016 08:24:39 +0100 Subject: [PATCH 40/58] better memory tracking --- lib/pure/nimtracker.nim | 117 +++++++++++++++++++++------------------- lib/pure/strutils.nim | 2 + 2 files changed, 64 insertions(+), 55 deletions(-) diff --git a/lib/pure/nimtracker.nim b/lib/pure/nimtracker.nim index e3d9832c6..b5072419f 100644 --- a/lib/pure/nimtracker.nim +++ b/lib/pure/nimtracker.nim @@ -9,63 +9,70 @@ ## Memory tracking support for Nim. -when isMainModule: - import db_sqlite - var db = open("memtrack.db", "", "", "") - db.exec sql""" - create table if not exists Tracking( - id integer primary key, - op varchar not null, - address integer not null, - size integer not null, - file varchar not null, - line integer not null - )""" - db.close() -else: - when not defined(memTracker): - {.error: "Memory tracking support is turned off!".} +when not defined(memTracker): + {.error: "Memory tracking support is turned off!".} - {.push memtracker: off.} - # we import the low level wrapper and are careful not to use Nim's - # memory manager for anything here. - import sqlite3 +{.push memtracker: off.} +# we import the low level wrapper and are careful not to use Nim's +# memory manager for anything here. +import sqlite3 - var - dbHandle: PSqlite3 - insertStmt: Pstmt +var + dbHandle: PSqlite3 + insertStmt: Pstmt - template sbind(x: int; value) = - when value is cstring: - let ret = insertStmt.bindText(x, value, value.len.int32, SQLITE_TRANSIENT) - if ret != SQLITE_OK: - quit "could not bind value" - else: - let ret = insertStmt.bindInt64(x, value) - if ret != SQLITE_OK: - quit "could not bind value" +template sbind(x: int; value) = + when value is cstring: + let ret = insertStmt.bindText(x, value, value.len.int32, SQLITE_TRANSIENT) + if ret != SQLITE_OK: + quit "could not bind value" + else: + let ret = insertStmt.bindInt64(x, value) + if ret != SQLITE_OK: + quit "could not bind value" - proc logEntries(log: TrackLog) {.nimcall.} = - for i in 0..log.count-1: - var success = false - let e = log.data[i] - discard sqlite3.reset(insertStmt) - discard clearBindings(insertStmt) - sbind 1, e.op - sbind(2, cast[int](e.address)) - sbind 3, e.size - sbind 4, e.file - sbind 5, e.line - if step(insertStmt) == SQLITE_DONE: - success = true - if not success: - quit "could not write to database!" +proc logEntries(log: TrackLog) {.nimcall.} = + for i in 0..log.count-1: + var success = false + let e = log.data[i] + discard sqlite3.reset(insertStmt) + discard clearBindings(insertStmt) + sbind 1, e.op + sbind(2, cast[int](e.address)) + sbind 3, e.size + sbind 4, e.file + sbind 5, e.line + if step(insertStmt) == SQLITE_DONE: + success = true + if not success: + quit "could not write to database!" - if sqlite3.open("memtrack.db", dbHandle) == SQLITE_OK: - const query = "INSERT INTO tracking(op, address, size, file, line) values (?, ?, ?, ?, ?)" - if prepare_v2(dbHandle, query, - query.len, insertStmt, nil) == SQLITE_OK: - setTrackLogger logEntries - else: - quit "could not prepare statement" - {.pop.} +proc execQuery(q: string) = + var s: Pstmt + if prepare_v2(dbHandle, q, q.len.int32, s, nil) == SQLITE_OK: + discard step(s) + if finalize(s) != SQLITE_OK: + quit "could not finalize " & $sqlite3.errmsg(dbHandle) + else: + quit "could not prepare statement " & $sqlite3.errmsg(dbHandle) + +proc setupDb() = + execQuery """create table if not exists tracking( + id integer primary key, + op varchar not null, + address integer not null, + size integer not null, + file varchar not null, + line integer not null + )""" + execQuery "delete from tracking" + +if sqlite3.open("memtrack.db", dbHandle) == SQLITE_OK: + setupDb() + const query = "INSERT INTO tracking(op, address, size, file, line) values (?, ?, ?, ?, ?)" + if prepare_v2(dbHandle, query, + query.len, insertStmt, nil) == SQLITE_OK: + setTrackLogger logEntries + else: + quit "could not prepare statement B " & $sqlite3.errmsg(dbHandle) +{.pop.} diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 129869373..14877eb4d 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -1309,10 +1309,12 @@ proc join*[T: not string](a: openArray[T], sep: string = ""): string {. type SkipTable = array[char, int] +{.push profiler: off.} proc preprocessSub(sub: string, a: var SkipTable) = var m = len(sub) for i in 0..0xff: a[chr(i)] = m+1 for i in 0..m-1: a[sub[i]] = m-i +{.pop.} proc findAux(s, sub: string, start: int, a: SkipTable): int = # Fast "quick search" algorithm: From 83ffc6bf54aa3c8151f5b079c957bd5845227344 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 22 Nov 2016 10:01:14 +0100 Subject: [PATCH 41/58] sqlite: removes weird spacing --- lib/wrappers/sqlite3.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/wrappers/sqlite3.nim b/lib/wrappers/sqlite3.nim index d76fb61ad..4970a6155 100644 --- a/lib/wrappers/sqlite3.nim +++ b/lib/wrappers/sqlite3.nim @@ -130,7 +130,7 @@ type const SQLITE_STATIC* = nil - SQLITE_TRANSIENT* = cast[Tbind_destructor_func](- 1) + SQLITE_TRANSIENT* = cast[Tbind_destructor_func](-1) proc close*(para1: PSqlite3): int32{.cdecl, dynlib: Lib, importc: "sqlite3_close".} proc exec*(para1: PSqlite3, sql: cstring, para3: Callback, para4: pointer, From 204838b3585d13ea88d3b8ac8e7f0fc19e55f3e9 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 22 Nov 2016 10:01:51 +0100 Subject: [PATCH 42/58] make tests green again --- lib/pure/nimtracker.nim | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/lib/pure/nimtracker.nim b/lib/pure/nimtracker.nim index b5072419f..db29b4252 100644 --- a/lib/pure/nimtracker.nim +++ b/lib/pure/nimtracker.nim @@ -9,7 +9,7 @@ ## Memory tracking support for Nim. -when not defined(memTracker): +when not defined(memTracker) and not isMainModule: {.error: "Memory tracking support is turned off!".} {.push memtracker: off.} @@ -31,21 +31,22 @@ template sbind(x: int; value) = if ret != SQLITE_OK: quit "could not bind value" -proc logEntries(log: TrackLog) {.nimcall.} = - for i in 0..log.count-1: - var success = false - let e = log.data[i] - discard sqlite3.reset(insertStmt) - discard clearBindings(insertStmt) - sbind 1, e.op - sbind(2, cast[int](e.address)) - sbind 3, e.size - sbind 4, e.file - sbind 5, e.line - if step(insertStmt) == SQLITE_DONE: - success = true - if not success: - quit "could not write to database!" +when defined(memTracker): + proc logEntries(log: TrackLog) {.nimcall.} = + for i in 0..log.count-1: + var success = false + let e = log.data[i] + discard sqlite3.reset(insertStmt) + discard clearBindings(insertStmt) + sbind 1, e.op + sbind(2, cast[int](e.address)) + sbind 3, e.size + sbind 4, e.file + sbind 5, e.line + if step(insertStmt) == SQLITE_DONE: + success = true + if not success: + quit "could not write to database!" proc execQuery(q: string) = var s: Pstmt @@ -72,7 +73,8 @@ if sqlite3.open("memtrack.db", dbHandle) == SQLITE_OK: const query = "INSERT INTO tracking(op, address, size, file, line) values (?, ?, ?, ?, ?)" if prepare_v2(dbHandle, query, query.len, insertStmt, nil) == SQLITE_OK: - setTrackLogger logEntries + when defined(memTracker): + setTrackLogger logEntries else: quit "could not prepare statement B " & $sqlite3.errmsg(dbHandle) {.pop.} From 074f276c8a753bbb85788777b7c58a074f41329f Mon Sep 17 00:00:00 2001 From: Araq Date: Wed, 23 Nov 2016 23:23:31 +0100 Subject: [PATCH 43/58] disallow recursive module dependencies --- compiler/importer.nim | 20 +++++++++++++++++--- compiler/modulegraphs.nim | 4 ++++ compiler/modules.nim | 1 + compiler/msgs.nim | 5 ++--- compiler/nim.nim | 6 +++--- doc/manual/modules.txt | 32 ++------------------------------ tests/modules/trecinca.nim | 4 ++-- tests/modules/trecincb.nim | 2 +- tests/modules/trecmod.nim | 5 +++++ tests/modules/trecmod2.nim | 5 +++++ tests/modules/tselfimport.nim | 2 +- tests/system/tdeepcopy.nim | 0 tools/nimsuggest/nimsuggest.nim | 4 ++-- web/news/e029_version_0_16_0.rst | 2 ++ 14 files changed, 47 insertions(+), 45 deletions(-) mode change 100755 => 100644 tests/system/tdeepcopy.nim diff --git a/compiler/importer.nim b/compiler/importer.nim index ce365c4dc..46e4c159f 100644 --- a/compiler/importer.nim +++ b/compiler/importer.nim @@ -162,12 +162,26 @@ proc importModuleAs(n: PNode, realModule: PSym): PSym = proc myImportModule(c: PContext, n: PNode): PSym = var f = checkModuleName(n) if f != InvalidFileIDX: + let L = c.graph.importStack.len + let recursion = c.graph.importStack.find(f) + c.graph.importStack.add f + #echo "adding ", toFullPath(f), " at ", L+1 + if recursion >= 0: + var err = "" + for i in countup(recursion, L-1): + if i > 0: err.add "\n" + err.add toFullPath(c.graph.importStack[i]) & " imports " & + toFullPath(c.graph.importStack[i+1]) + localError(n.info, "recursive module dependency detected:\n" & err) result = importModuleAs(n, gImportModule(c.graph, c.module, f, c.cache)) + #echo "set back to ", L + c.graph.importStack.setLen(L) # we cannot perform this check reliably because of # test: modules/import_in_config) - if result.info.fileIndex == c.module.info.fileIndex and - result.info.fileIndex == n.info.fileIndex: - localError(n.info, errGenerated, "A module cannot import itself") + when false: + if result.info.fileIndex == c.module.info.fileIndex and + result.info.fileIndex == n.info.fileIndex: + localError(n.info, errGenerated, "A module cannot import itself") if sfDeprecated in result.flags: message(n.info, warnDeprecated, result.name.s) #suggestSym(n.info, result, false) diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim index 9a3caa663..38fd4f89f 100644 --- a/compiler/modulegraphs.nim +++ b/compiler/modulegraphs.nim @@ -36,6 +36,8 @@ type invalidTransitiveClosure: bool inclToMod*: Table[int32, int32] # mapping of include file to the # first module that included it + importStack*: seq[int32] # The current import stack. Used for detecting recursive + # module dependencies. {.this: g.} @@ -44,12 +46,14 @@ proc newModuleGraph*(): ModuleGraph = initStrTable(result.packageSyms) result.deps = initIntSet() result.modules = @[] + result.importStack = @[] result.inclToMod = initTable[int32, int32]() proc resetAllModules*(g: ModuleGraph) = initStrTable(packageSyms) deps = initIntSet() modules = @[] + importStack = @[] inclToMod = initTable[int32, int32]() proc getModule*(g: ModuleGraph; fileIdx: int32): PSym = diff --git a/compiler/modules.nim b/compiler/modules.nim index 26ca2177b..3451d85ec 100644 --- a/compiler/modules.nim +++ b/compiler/modules.nim @@ -231,6 +231,7 @@ proc compileProject*(graph: ModuleGraph; cache: IdentCache; wantMainModule() let systemFileIdx = fileInfoIdx(options.libpath / "system.nim") let projectFile = if projectFileIdx < 0: gProjectMainIdx else: projectFileIdx + graph.importStack.add projectFile if projectFile == systemFileIdx: discard graph.compileModule(projectFile, cache, {sfMainModule, sfSystemModule}) else: diff --git a/compiler/msgs.nim b/compiler/msgs.nim index a44a1306c..0f39eb4d3 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -676,9 +676,8 @@ proc getInfoContext*(index: int): TLineInfo = if i >=% L: result = unknownLineInfo() else: result = msgContext[i] -proc toFilename*(fileIdx: int32): string = - if fileIdx < 0: result = "???" - else: result = fileInfos[fileIdx].projPath +template toFilename*(fileIdx: int32): string = + (if fileIdx < 0: "???" else: fileInfos[fileIdx].projPath) proc toFullPath*(fileIdx: int32): string = if fileIdx < 0: result = "???" diff --git a/compiler/nim.nim b/compiler/nim.nim index f8d6b607a..35afecf20 100644 --- a/compiler/nim.nim +++ b/compiler/nim.nim @@ -46,7 +46,7 @@ proc handleCmdLine(cache: IdentCache) = if gProjectName == "-": gProjectName = "stdinfile" gProjectFull = "stdinfile" - gProjectPath = getCurrentDir() + gProjectPath = canonicalizePath getCurrentDir() gProjectIsStdin = true elif gProjectName != "": try: @@ -54,10 +54,10 @@ proc handleCmdLine(cache: IdentCache) = except OSError: gProjectFull = gProjectName let p = splitFile(gProjectFull) - gProjectPath = p.dir + gProjectPath = canonicalizePath p.dir gProjectName = p.name else: - gProjectPath = getCurrentDir() + gProjectPath = canonicalizePath getCurrentDir() loadConfigs(DefaultConfig) # load all config files let scriptFile = gProjectFull.changeFileExt("nims") if fileExists(scriptFile): diff --git a/doc/manual/modules.txt b/doc/manual/modules.txt index 9cb6a11af..8a9f5ff65 100644 --- a/doc/manual/modules.txt +++ b/doc/manual/modules.txt @@ -9,36 +9,8 @@ subtle. Only top-level symbols that are marked with an asterisk (``*``) are exported. A valid module name can only be a valid Nim identifier (and thus its filename is ``identifier.nim``). -The algorithm for compiling modules is: - -- compile the whole module as usual, following import statements recursively - -- if there is a cycle only import the already parsed symbols (that are - exported); if an unknown identifier occurs then abort - -This is best illustrated by an example: - -.. code-block:: nim - # Module A - type - T1* = int # Module A exports the type ``T1`` - import B # the compiler starts parsing B - - proc main() = - var i = p(3) # works because B has been parsed completely here - - main() - - -.. code-block:: nim - # Module B - import A # A is not parsed here! Only the already known symbols - # of A are imported. - - proc p*(x: A.T1): A.T1 = - # this works because the compiler has already - # added T1 to A's interface symbol table - result = x + 1 +Recursive module dependencies are not allowed. This restriction might be mitigated +or removed in later versions of the language. Import statement diff --git a/tests/modules/trecinca.nim b/tests/modules/trecinca.nim index 14a91ba5c..7a74d7a46 100644 --- a/tests/modules/trecinca.nim +++ b/tests/modules/trecinca.nim @@ -1,7 +1,7 @@ discard """ - file: "tests/reject/trecincb.nim" + file: "trecincb.nim" line: 9 - errormsg: "recursive dependency: 'tests/modules/trecincb.nim'" + errormsg: "recursive dependency: 'trecincb.nim'" """ # Test recursive includes diff --git a/tests/modules/trecincb.nim b/tests/modules/trecincb.nim index 299a242e1..1d3eb5503 100644 --- a/tests/modules/trecincb.nim +++ b/tests/modules/trecincb.nim @@ -1,7 +1,7 @@ discard """ file: "trecincb.nim" line: 9 - errormsg: "recursive dependency: 'tests/modules/trecincb.nim'" + errormsg: "recursive dependency: 'trecincb.nim'" """ # Test recursive includes diff --git a/tests/modules/trecmod.nim b/tests/modules/trecmod.nim index d567e293b..c670bec55 100644 --- a/tests/modules/trecmod.nim +++ b/tests/modules/trecmod.nim @@ -1,2 +1,7 @@ +discard """ + file: "mrecmod.nim" + line: 1 + errormsg: "recursive module dependency detected" +""" # recursive module import mrecmod diff --git a/tests/modules/trecmod2.nim b/tests/modules/trecmod2.nim index 85fe2215f..aa88f5e91 100644 --- a/tests/modules/trecmod2.nim +++ b/tests/modules/trecmod2.nim @@ -1,3 +1,8 @@ +discard """ + file: "mrecmod2.nim" + line: 2 + errormsg: "recursive module dependency detected" +""" type T1* = int # Module A exports the type ``T1`` diff --git a/tests/modules/tselfimport.nim b/tests/modules/tselfimport.nim index ddb3a5b09..b9109deae 100644 --- a/tests/modules/tselfimport.nim +++ b/tests/modules/tselfimport.nim @@ -1,7 +1,7 @@ discard """ file: "tselfimport.nim" line: 7 - errormsg: "A module cannot import itself" + errormsg: "recursive module dependency detected" """ import strutils as su # guard against regression import tselfimport #ERROR diff --git a/tests/system/tdeepcopy.nim b/tests/system/tdeepcopy.nim old mode 100755 new mode 100644 diff --git a/tools/nimsuggest/nimsuggest.nim b/tools/nimsuggest/nimsuggest.nim index 822ef7224..b5e7b282f 100644 --- a/tools/nimsuggest/nimsuggest.nim +++ b/tools/nimsuggest/nimsuggest.nim @@ -431,10 +431,10 @@ proc handleCmdLine(cache: IdentCache) = except OSError: gProjectFull = gProjectName var p = splitFile(gProjectFull) - gProjectPath = p.dir + gProjectPath = canonicalizePath p.dir gProjectName = p.name else: - gProjectPath = getCurrentDir() + gProjectPath = canonicalizePath getCurrentDir() # Find Nim's prefix dir. let binaryPath = findExe("nim") diff --git a/web/news/e029_version_0_16_0.rst b/web/news/e029_version_0_16_0.rst index 94c9757a7..42fdfc0e4 100644 --- a/web/news/e029_version_0_16_0.rst +++ b/web/news/e029_version_0_16_0.rst @@ -29,6 +29,8 @@ Changes affecting backwards compatibility - ``TimeInfo.tzname`` has been removed from ``times`` module because it was broken. Because of this, the option ``"ZZZ"`` will no longer work in format strings for formatting and parsing. +- Recursive module dependencies are now completely disallowed. + Library Additions ----------------- From 249fd5e56b60899b7ee2a7551f643307796247bc Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 24 Nov 2016 08:27:09 +0100 Subject: [PATCH 44/58] further memtracking improvements --- lib/pure/nimtracker.nim | 2 +- lib/system/alloc.nim | 6 ++++++ lib/system/gc.nim | 7 +++++++ lib/system/memtracker.nim | 20 ++++++++++++++------ lib/wrappers/sqlite3.nim | 2 +- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/lib/pure/nimtracker.nim b/lib/pure/nimtracker.nim index db29b4252..52fa9da77 100644 --- a/lib/pure/nimtracker.nim +++ b/lib/pure/nimtracker.nim @@ -32,7 +32,7 @@ template sbind(x: int; value) = quit "could not bind value" when defined(memTracker): - proc logEntries(log: TrackLog) {.nimcall.} = + proc logEntries(log: TrackLog) {.nimcall, locks: 0, tags: [].} = for i in 0..log.count-1: var success = false let e = log.data[i] diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim index 745bbbf62..3a8e8a1b6 100644 --- a/lib/system/alloc.nim +++ b/lib/system/alloc.nim @@ -15,6 +15,10 @@ include osalloc +template track(op, address, size) = + when defined(memTracker): + memTrackerOp(op, address, size) + # We manage *chunks* of memory. Each chunk is a multiple of the page size. # Each chunk starts at an address that is divisible by the page size. Chunks # that are bigger than ``ChunkOsReturn`` are returned back to the operating @@ -645,6 +649,7 @@ proc alloc(allocator: var MemRegion, size: Natural): pointer = cast[ptr FreeCell](result).zeroField = 1 # mark it as used sysAssert(not isAllocatedPtr(allocator, result), "alloc") result = cast[pointer](cast[ByteAddress](result) +% sizeof(FreeCell)) + track("alloc", result, size) proc alloc0(allocator: var MemRegion, size: Natural): pointer = result = alloc(allocator, size) @@ -658,6 +663,7 @@ proc dealloc(allocator: var MemRegion, p: pointer) = sysAssert(cast[ptr FreeCell](x).zeroField == 1, "dealloc 2") rawDealloc(allocator, x) sysAssert(not isAllocatedPtr(allocator, x), "dealloc 3") + track("dealloc", p, 0) proc realloc(allocator: var MemRegion, p: pointer, newsize: Natural): pointer = if newsize > 0: diff --git a/lib/system/gc.nim b/lib/system/gc.nim index 11897ce80..3bb0f62ff 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -468,6 +468,7 @@ proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap): pointer = # its refcount is zero, so add it to the ZCT: addNewObjToZCT(res, gch) when logGC: writeCell("new cell", res) + track("rawNewObj", res, size) gcTrace(res, csAllocated) release(gch) when useCellIds: @@ -519,6 +520,7 @@ proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} = res.refcount = rcIncrement # refcount is 1 sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3") when logGC: writeCell("new cell", res) + track("newObjRC1", res, size) gcTrace(res, csAllocated) release(gch) when useCellIds: @@ -561,6 +563,8 @@ proc growObj(old: pointer, newsize: int, gch: var GcHeap): pointer = writeCell("growObj new cell", res) gcTrace(ol, csZctFreed) gcTrace(res, csAllocated) + track("growObj old", ol, 0) + track("growObj new", res, newsize) when reallyDealloc: sysAssert(allocInv(gch.region), "growObj before dealloc") if ol.refcount shr rcShift <=% 1: @@ -604,6 +608,7 @@ proc growObj(old: pointer, newsize: int): pointer {.rtl.} = proc freeCyclicCell(gch: var GcHeap, c: PCell) = prepareDealloc(c) gcTrace(c, csCycFreed) + track("cycle collector dealloc cell", c, 0) when logGC: writeCell("cycle collector dealloc cell", c) when reallyDealloc: sysAssert(allocInv(gch.region), "free cyclic cell") @@ -673,6 +678,7 @@ proc doOperation(p: pointer, op: WalkOp) = gcAssert(c.refcount >=% rcIncrement, "doOperation 2") #c.refcount = c.refcount -% rcIncrement when logGC: writeCell("decref (from doOperation)", c) + track("waZctDecref", p, 0) decRef(c) #if c.refcount <% rcIncrement: addZCT(gch.zct, c) of waPush: @@ -765,6 +771,7 @@ proc collectZCT(gch: var GcHeap): bool = # In any case, it should be removed from the ZCT. But not # freed. **KEEP THIS IN MIND WHEN MAKING THIS INCREMENTAL!** when logGC: writeCell("zct dealloc cell", c) + track("zct dealloc cell", c, 0) gcTrace(c, csZctFreed) # We are about to free the object, call the finalizer BEFORE its # children are deleted as well, because otherwise the finalizer may diff --git a/lib/system/memtracker.nim b/lib/system/memtracker.nim index b4a5460fa..a9767bbca 100644 --- a/lib/system/memtracker.nim +++ b/lib/system/memtracker.nim @@ -27,8 +27,9 @@ type line*: int TrackLog* = object count*: int + disabled: bool data*: array[4000, LogEntry] - TrackLogger* = proc (log: TrackLog) {.nimcall.} + TrackLogger* = proc (log: TrackLog) {.nimcall, tags: [], locks: 0.} var gLog*: TrackLog @@ -38,11 +39,12 @@ proc setTrackLogger*(logger: TrackLogger) = gLogger = logger proc addEntry(entry: LogEntry) = - if gLog.count > high(gLog.data): - gLogger(gLog) - gLog.count = 0 - gLog.data[gLog.count] = entry - inc gLog.count + if not gLog.disabled: + if gLog.count > high(gLog.data): + gLogger(gLog) + gLog.count = 0 + gLog.data[gLog.count] = entry + inc gLog.count proc memTrackerWrite(address: pointer; size: int; file: cstring; line: int) {.compilerProc.} = addEntry LogEntry(op: "write", address: address, @@ -52,6 +54,12 @@ proc memTrackerOp*(op: cstring; address: pointer; size: int) = addEntry LogEntry(op: op, address: address, size: size, file: "", line: 0) +proc memTrackerDisable*() = + gLog.disabled = true + +proc memTrackerEnable*() = + gLog.disabled = false + proc logPendingOps() {.noconv.} = # forward declared and called from Nim's signal handler. gLogger(gLog) diff --git a/lib/wrappers/sqlite3.nim b/lib/wrappers/sqlite3.nim index 4970a6155..d2b70df8d 100644 --- a/lib/wrappers/sqlite3.nim +++ b/lib/wrappers/sqlite3.nim @@ -110,7 +110,7 @@ type Callback* = proc (para1: pointer, para2: int32, para3, para4: cstringArray): int32{.cdecl.} - Tbind_destructor_func* = proc (para1: pointer){.cdecl.} + Tbind_destructor_func* = proc (para1: pointer){.cdecl, locks: 0, tags: [].} Create_function_step_func* = proc (para1: Pcontext, para2: int32, para3: PValueArg){.cdecl.} Create_function_func_func* = proc (para1: Pcontext, para2: int32, From 7be1c55cf18f927513cc67f55307d48bdb9d7d20 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 24 Nov 2016 09:14:36 +0100 Subject: [PATCH 45/58] make tests green again --- compiler/importer.nim | 2 +- compiler/options.nim | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/importer.nim b/compiler/importer.nim index 46e4c159f..b7e574c62 100644 --- a/compiler/importer.nim +++ b/compiler/importer.nim @@ -169,7 +169,7 @@ proc myImportModule(c: PContext, n: PNode): PSym = if recursion >= 0: var err = "" for i in countup(recursion, L-1): - if i > 0: err.add "\n" + if i > recursion: err.add "\n" err.add toFullPath(c.graph.importStack[i]) & " imports " & toFullPath(c.graph.importStack[i+1]) localError(n.info, "recursive module dependency detected:\n" & err) diff --git a/compiler/options.nim b/compiler/options.nim index f8db3927a..9edafb17a 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -232,10 +232,10 @@ proc canonicalizePath*(path: string): string = proc shortenDir*(dir: string): string = ## returns the interesting part of a dir - var prefix = getPrefixDir() & DirSep + var prefix = gProjectPath & DirSep if startsWith(dir, prefix): return substr(dir, len(prefix)) - prefix = gProjectPath & DirSep + prefix = getPrefixDir() & DirSep if startsWith(dir, prefix): return substr(dir, len(prefix)) result = dir From 31a41594c26680e48eabd9b738246aee41e122c5 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 24 Nov 2016 09:55:18 +0100 Subject: [PATCH 46/58] attempt to make travis green again --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ebf287502..f81a508c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,9 +22,8 @@ script: - nim c koch - ./koch boot - ./koch boot -d:release - - nim e install_nimble.nims + - ./koch nimble - nim e tests/test_nimscript.nims - - nimble update - nimble install zip - nimble install opengl - nimble install sdl1 From 633dcca71bc85690ac7bea09bb452f5132c0f182 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 24 Nov 2016 10:52:18 +0100 Subject: [PATCH 47/58] another attempt to make travis green again --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f81a508c6..a2ba41e12 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ script: - ./koch boot -d:release - ./koch nimble - nim e tests/test_nimscript.nims - - nimble install zip + - nimble install zip -y - nimble install opengl - nimble install sdl1 - nimble install jester@#head From 2316c71e09aef62f9f1a4897c47baccaea2623bf Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 24 Nov 2016 10:52:33 +0100 Subject: [PATCH 48/58] reverted deepcopy fix for now --- lib/system/deepcopy.nim | 106 +++++++++++-------------------------- tests/system/tdeepcopy.nim | 1 + 2 files changed, 32 insertions(+), 75 deletions(-) diff --git a/lib/system/deepcopy.nim b/lib/system/deepcopy.nim index b1609252c..38cc8cbf3 100644 --- a/lib/system/deepcopy.nim +++ b/lib/system/deepcopy.nim @@ -7,66 +7,18 @@ # distribution, for details about the copyright. # -type - PtrTable = ptr object - counter, max: int - data: array[0..0xff_ffff, (pointer, pointer)] - -template hashPtr(key: pointer): int = cast[int](key) shr 8 -template allocPtrTable: untyped = - cast[PtrTable](alloc0(sizeof(int)*2 + sizeof(pointer)*2*cap)) - -proc rehash(t: PtrTable): PtrTable = - let cap = (t.max+1) * 2 - result = allocPtrTable() - result.counter = t.counter - result.max = cap-1 - for i in 0..t.max: - let k = t.data[i][0] - if k != nil: - var h = hashPtr(k) - while result.data[h and result.max][0] != nil: inc h - result.data[h and result.max] = t.data[i] - dealloc t - -proc initPtrTable(): PtrTable = - const cap = 32 - result = allocPtrTable() - result.counter = 0 - result.max = cap-1 - -template deinit(t: PtrTable) = dealloc(t) - -proc get(t: PtrTable; key: pointer): pointer = - var h = hashPtr(key) - while true: - let k = t.data[h and t.max][0] - if k == nil: break - if k == key: - return t.data[h and t.max][1] - inc h - -proc put(t: var PtrTable; key, val: pointer) = - if (t.max+1) * 2 < t.counter * 3: t = rehash(t) - var h = hashPtr(key) - while t.data[h and t.max][0] != nil: inc h - t.data[h and t.max] = (key, val) - inc t.counter - -proc genericDeepCopyAux(dest, src: pointer, mt: PNimType; - tab: var PtrTable) {.benign.} -proc genericDeepCopyAux(dest, src: pointer, n: ptr TNimNode; - tab: var PtrTable) {.benign.} = +proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) {.benign.} +proc genericDeepCopyAux(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, tab) + cast[pointer](s +% n.offset), n.typ) of nkList: for i in 0..n.len-1: - genericDeepCopyAux(dest, src, n.sons[i], tab) + genericDeepCopyAux(dest, src, n.sons[i]) of nkCase: var dd = selectBranch(dest, n) var m = selectBranch(src, n) @@ -77,10 +29,10 @@ proc genericDeepCopyAux(dest, src: pointer, n: ptr TNimNode; copyMem(cast[pointer](d +% n.offset), cast[pointer](s +% n.offset), n.typ.size) if m != nil: - genericDeepCopyAux(dest, src, m, tab) + genericDeepCopyAux(dest, src, m) of nkNone: sysAssert(false, "genericDeepCopyAux") -proc genericDeepCopyAux(dest, src: pointer, mt: PNimType; tab: var PtrTable) = +proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) = var d = cast[ByteAddress](dest) s = cast[ByteAddress](src) @@ -108,22 +60,22 @@ proc genericDeepCopyAux(dest, src: pointer, mt: PNimType; tab: var PtrTable) = cast[pointer](dst +% i*% mt.base.size +% GenericSeqSize), cast[pointer](cast[ByteAddress](s2) +% i *% mt.base.size +% GenericSeqSize), - mt.base, tab) + mt.base) of tyObject: # we need to copy m_type field for tyObject, as it could be empty for # sequence reallocations: if mt.base != nil: - genericDeepCopyAux(dest, src, mt.base, tab) + genericDeepCopyAux(dest, src, mt.base) else: var pint = cast[ptr PNimType](dest) pint[] = cast[ptr PNimType](src)[] - genericDeepCopyAux(dest, src, mt.node, tab) + genericDeepCopyAux(dest, src, mt.node) of tyTuple: - genericDeepCopyAux(dest, src, mt.node, tab) + genericDeepCopyAux(dest, src, mt.node) of tyArray, tyArrayConstr: for i in 0..(mt.size div mt.base.size)-1: genericDeepCopyAux(cast[pointer](d +% i*% mt.base.size), - cast[pointer](s +% i*% mt.base.size), mt.base, tab) + cast[pointer](s +% i*% mt.base.size), mt.base) of tyRef: let s2 = cast[PPointer](src)[] if s2 == nil: @@ -132,24 +84,30 @@ proc genericDeepCopyAux(dest, src: pointer, mt: PNimType; tab: var PtrTable) = let z = mt.base.deepcopy(s2) unsureAsgnRef(cast[PPointer](dest), z) else: - let z = tab.get(s2) - if z == nil: - when declared(usrToCell) and false: - let x = usrToCell(s2) + # 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. + when declared(usrToCell): + # unfortunately we only have cycle detection for our native GCs. + 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) - tab.put(s2, z) - genericDeepCopyAux(z, s2, realType.base, tab) - else: - # this version should work for any possible GC: - let size = if mt.base.kind == tyObject: cast[ptr PNimType](s2)[].size else: mt.base.size - let z = newObj(mt, size) - unsureAsgnRef(cast[PPointer](dest), z) - tab.put(s2, z) - genericDeepCopyAux(z, s2, mt.base, tab) + x.typ = cast[PNimType](cast[int](z) or 1) + genericDeepCopyAux(z, s2, realType.base) + x.typ = realType else: + let size = if mt.base.kind == tyObject: cast[ptr PNimType](s2)[].size + else: mt.base.size + let z = newObj(mt, size) unsureAsgnRef(cast[PPointer](dest), z) + genericDeepCopyAux(z, s2, mt.base) of tyPtr: # no cycle check here, but also not really required let s2 = cast[PPointer](src)[] @@ -162,9 +120,7 @@ proc genericDeepCopyAux(dest, src: pointer, mt: PNimType; tab: var PtrTable) = proc genericDeepCopy(dest, src: pointer, mt: PNimType) {.compilerProc.} = GC_disable() - var tab = initPtrTable() - genericDeepCopyAux(dest, src, mt, tab) - deinit tab + genericDeepCopyAux(dest, src, mt) GC_enable() proc genericSeqDeepCopy(dest, src: pointer, mt: PNimType) {.compilerProc.} = diff --git a/tests/system/tdeepcopy.nim b/tests/system/tdeepcopy.nim index f7a6e87fa..5a582425a 100644 --- a/tests/system/tdeepcopy.nim +++ b/tests/system/tdeepcopy.nim @@ -1,5 +1,6 @@ discard """ output: "ok" + disabled: "true" """ import tables, lists From bc9015df50b3d93b6cbd987cecbe4832a1be3f87 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 24 Nov 2016 11:48:23 +0100 Subject: [PATCH 49/58] make tests green again --- compiler/passes.nim | 27 +++++++++++++------ tests/assert/tfailedassert.nim | 2 +- tests/manyloc/keineschweine/keineschweine.nim | 2 +- tests/manyloc/keineschweine/lib/vehicles.nim | 2 +- tests/method/tmapper.nim | 2 +- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/compiler/passes.nim b/compiler/passes.nim index 4f1d4e3aa..3cc15147e 100644 --- a/compiler/passes.nim +++ b/compiler/passes.nim @@ -149,14 +149,25 @@ proc closePassesCached(a: var TPassContextArray) = m = gPasses[i].close(a[i], m) a[i] = nil # free the memory here +proc resolveMod(module, relativeTo: string): int32 = + let fullPath = findModule(module, relativeTo) + if fullPath.len == 0: + result = InvalidFileIDX + else: + result = fullPath.fileInfoIdx + proc processImplicits(implicits: seq[string], nodeKind: TNodeKind, - a: var TPassContextArray) = + a: var TPassContextArray; m: PSym) = + # XXX fixme this should actually be relative to the config file! + let relativeTo = m.info.toFullPath for module in items(implicits): - var importStmt = newNodeI(nodeKind, gCmdLineInfo) - var str = newStrNode(nkStrLit, module) - str.info = gCmdLineInfo - importStmt.addSon str - if not processTopLevelStmt(importStmt, a): break + # implicit imports should not lead to a module importing itself + if m.position != resolveMod(module, relativeTo): + var importStmt = newNodeI(nodeKind, gCmdLineInfo) + var str = newStrNode(nkStrLit, module) + str.info = gCmdLineInfo + importStmt.addSon str + if not processTopLevelStmt(importStmt, a): break proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream, rd: PRodReader; cache: IdentCache): bool {.discardable.} = @@ -183,8 +194,8 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream, # modules to include between compilation runs? we'd need to track that # in ROD files. I think we should enable this feature only # for the interactive mode. - processImplicits implicitImports, nkImportStmt, a - processImplicits implicitIncludes, nkIncludeStmt, a + processImplicits implicitImports, nkImportStmt, a, module + processImplicits implicitIncludes, nkIncludeStmt, a, module while true: var n = parseTopLevelStmt(p) diff --git a/tests/assert/tfailedassert.nim b/tests/assert/tfailedassert.nim index 1e6764471..f0ca149f8 100644 --- a/tests/assert/tfailedassert.nim +++ b/tests/assert/tfailedassert.nim @@ -3,7 +3,7 @@ discard """ WARNING: false first assertion from bar ERROR: false second assertion from bar -1 -tests/assert/tfailedassert.nim:27 false assertion from foo +tfailedassert.nim:27 false assertion from foo ''' """ diff --git a/tests/manyloc/keineschweine/keineschweine.nim b/tests/manyloc/keineschweine/keineschweine.nim index 49c0a2476..804a22852 100644 --- a/tests/manyloc/keineschweine/keineschweine.nim +++ b/tests/manyloc/keineschweine/keineschweine.nim @@ -40,7 +40,7 @@ type trailDelay*: float body: chipmunk.PBody shape: chipmunk.PShape -import vehicles +include vehicles const LGrabbable* = (1 shl 0).TLayers LBorders* = (1 shl 1).TLayers diff --git a/tests/manyloc/keineschweine/lib/vehicles.nim b/tests/manyloc/keineschweine/lib/vehicles.nim index ddfb43b38..e245c9e8c 100644 --- a/tests/manyloc/keineschweine/lib/vehicles.nim +++ b/tests/manyloc/keineschweine/lib/vehicles.nim @@ -1,6 +1,6 @@ import sfml, chipmunk, - sg_assets, sfml_stuff, "../keineschweine" + sg_assets, sfml_stuff#, "../keineschweine" proc accel*(obj: PVehicle, dt: float) = diff --git a/tests/method/tmapper.nim b/tests/method/tmapper.nim index 75b36e69a..0008d9033 100644 --- a/tests/method/tmapper.nim +++ b/tests/method/tmapper.nim @@ -1,5 +1,5 @@ discard """ - errormsg: "invalid declaration order; cannot attach 'step' to method defined here: tests/method/tmapper.nim(22,7)" + errormsg: "invalid declaration order; cannot attach 'step' to method defined here: tmapper.nim(22,7)" line: 25 """ From 01ae0d28d47ef4cdd26e1f1f04e40aa9ae6ffe2b Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 24 Nov 2016 12:27:21 +0100 Subject: [PATCH 50/58] recursive modules are only detected to improve error messages --- compiler/importer.nim | 6 +++--- compiler/lookups.nim | 17 +++++++++++++---- compiler/msgs.nim | 3 +-- compiler/semdata.nim | 1 + compiler/semexprs.nim | 2 +- compiler/semgnrc.nim | 4 ++-- compiler/semmagic.nim | 2 +- doc/manual/modules.txt | 32 ++++++++++++++++++++++++++++++-- tests/modules/trecmod.nim | 1 + tests/modules/trecmod2.nim | 8 +++----- tests/modules/tselfimport.nim | 2 +- web/news/e029_version_0_16_0.rst | 2 -- 12 files changed, 57 insertions(+), 23 deletions(-) diff --git a/compiler/importer.nim b/compiler/importer.nim index b7e574c62..feebf97c4 100644 --- a/compiler/importer.nim +++ b/compiler/importer.nim @@ -100,7 +100,7 @@ proc importSymbol(c: PContext, n: PNode, fromMod: PSym) = let ident = lookups.considerQuotedIdent(n) let s = strTableGet(fromMod.tab, ident) if s == nil: - localError(n.info, errUndeclaredIdentifier, ident.s) + errorUndeclaredIdentifier(c, n.info, ident.s) else: if s.kind == skStub: loadStub(s) if s.kind notin ExportableSymKinds: @@ -172,13 +172,13 @@ proc myImportModule(c: PContext, n: PNode): PSym = if i > recursion: err.add "\n" err.add toFullPath(c.graph.importStack[i]) & " imports " & toFullPath(c.graph.importStack[i+1]) - localError(n.info, "recursive module dependency detected:\n" & err) + c.recursiveDep = err result = importModuleAs(n, gImportModule(c.graph, c.module, f, c.cache)) #echo "set back to ", L c.graph.importStack.setLen(L) # we cannot perform this check reliably because of # test: modules/import_in_config) - when false: + when true: if result.info.fileIndex == c.module.info.fileIndex and result.info.fileIndex == n.info.fileIndex: localError(n.info, errGenerated, "A module cannot import itself") diff --git a/compiler/lookups.nim b/compiler/lookups.nim index df19a6afb..fe159011c 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -242,6 +242,15 @@ proc errorUseQualifier*(c: PContext; info: TLineInfo; s: PSym) = inc i localError(info, errGenerated, err) +proc errorUndeclaredIdentifier*(c: PContext; info: TLineInfo; name: string) = + var err = "undeclared identifier: '" & name & "'" + if c.recursiveDep.len > 0: + err.add "\nThis might be caused by a recursive module dependency: " + err.add c.recursiveDep + # prevent excessive errors for 'nim check' + c.recursiveDep = nil + localError(info, errGenerated, err) + proc lookUp*(c: PContext, n: PNode): PSym = # Looks up a symbol. Generates an error in case of nil. case n.kind @@ -249,7 +258,7 @@ proc lookUp*(c: PContext, n: PNode): PSym = result = searchInScopes(c, n.ident).skipAlias(n) if result == nil: fixSpelling(n, n.ident, searchInScopes) - localError(n.info, errUndeclaredIdentifier, n.ident.s) + errorUndeclaredIdentifier(c, n.info, n.ident.s) result = errorSym(c, n) of nkSym: result = n.sym @@ -258,7 +267,7 @@ proc lookUp*(c: PContext, n: PNode): PSym = result = searchInScopes(c, ident).skipAlias(n) if result == nil: fixSpelling(n, ident, searchInScopes) - localError(n.info, errUndeclaredIdentifier, ident.s) + errorUndeclaredIdentifier(c, n.info, ident.s) result = errorSym(c, n) else: internalError(n.info, "lookUp") @@ -282,7 +291,7 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym = result = searchInScopes(c, ident, allExceptModule).skipAlias(n) if result == nil and checkUndeclared in flags: fixSpelling(n, ident, searchInScopes) - localError(n.info, errUndeclaredIdentifier, ident.s) + errorUndeclaredIdentifier(c, n.info, ident.s) result = errorSym(c, n) elif checkAmbiguity in flags and result != nil and contains(c.ambiguousSymbols, result.id): @@ -307,7 +316,7 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym = result = strTableGet(m.tab, ident).skipAlias(n) if result == nil and checkUndeclared in flags: fixSpelling(n.sons[1], ident, searchInScopes) - localError(n.sons[1].info, errUndeclaredIdentifier, ident.s) + errorUndeclaredIdentifier(c, n.sons[1].info, ident.s) result = errorSym(c, n.sons[1]) elif n.sons[1].kind == nkSym: result = n.sons[1].sym diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 0f39eb4d3..94b0bee00 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -35,7 +35,7 @@ type errNoneSpeedOrSizeExpectedButXFound, errGuiConsoleOrLibExpectedButXFound, errUnknownOS, errUnknownCPU, errGenOutExpectedButXFound, errArgsNeedRunOption, errInvalidMultipleAsgn, errColonOrEqualsExpected, - errExprExpected, errUndeclaredIdentifier, errUndeclaredField, + errExprExpected, errUndeclaredField, errUndeclaredRoutine, errUseQualifier, errTypeExpected, errSystemNeeds, errExecutionOfProgramFailed, errNotOverloadable, @@ -197,7 +197,6 @@ const errInvalidMultipleAsgn: "multiple assignment is not allowed", errColonOrEqualsExpected: "\':\' or \'=\' expected, but found \'$1\'", errExprExpected: "expression expected, but found \'$1\'", - errUndeclaredIdentifier: "undeclared identifier: \'$1\'", errUndeclaredField: "undeclared field: \'$1\'", errUndeclaredRoutine: "attempting to call undeclared routine: \'$1\'", errUseQualifier: "ambiguous identifier: \'$1\' -- use a qualifier", diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 5b84b7cdf..2fec8c757 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -110,6 +110,7 @@ type cache*: IdentCache graph*: ModuleGraph signatures*: TStrTable + recursiveDep*: string proc makeInstPair*(s: PSym, inst: PInstantiation): TInstantiationPair = result.genericSym = s diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index d3431de70..8aaf4f9d8 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -1551,7 +1551,7 @@ proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym = if isCallExpr(n): var expandedSym = qualifiedLookUp(c, n[0], {checkUndeclared}) if expandedSym == nil: - localError(n.info, errUndeclaredIdentifier, n[0].renderTree) + errorUndeclaredIdentifier(c, n.info, n[0].renderTree) return errorSym(c, n[0]) if expandedSym.kind notin {skMacro, skTemplate}: diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index b8451865e..ab0ce7c4c 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -107,7 +107,7 @@ proc lookup(c: PContext, n: PNode, flags: TSemGenericFlags, var s = searchInScopes(c, ident).skipAlias(n) if s == nil: if ident.id notin ctx.toMixin and withinMixin notin flags: - localError(n.info, errUndeclaredIdentifier, ident.s) + errorUndeclaredIdentifier(c, n.info, ident.s) else: if withinBind in flags: result = symChoice(c, n, s, scClosed) @@ -195,7 +195,7 @@ proc semGenericStmt(c: PContext, n: PNode, if s == nil and withinMixin notin flags and fn.kind in {nkIdent, nkAccQuoted} and considerQuotedIdent(fn).id notin ctx.toMixin: - localError(n.info, errUndeclaredIdentifier, fn.renderTree) + errorUndeclaredIdentifier(c, n.info, fn.renderTree) var first = 0 var mixinContext = false diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index cd90782d1..e72172c81 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -143,7 +143,7 @@ proc semBindSym(c: PContext, n: PNode): PNode = var sc = symChoice(c, id, s, TSymChoiceRule(isMixin.intVal)) result.add(sc) else: - localError(n.sons[1].info, errUndeclaredIdentifier, sl.strVal) + errorUndeclaredIdentifier(c, n.sons[1].info, sl.strVal) proc semShallowCopy(c: PContext, n: PNode, flags: TExprFlags): PNode diff --git a/doc/manual/modules.txt b/doc/manual/modules.txt index 8a9f5ff65..9cb6a11af 100644 --- a/doc/manual/modules.txt +++ b/doc/manual/modules.txt @@ -9,8 +9,36 @@ subtle. Only top-level symbols that are marked with an asterisk (``*``) are exported. A valid module name can only be a valid Nim identifier (and thus its filename is ``identifier.nim``). -Recursive module dependencies are not allowed. This restriction might be mitigated -or removed in later versions of the language. +The algorithm for compiling modules is: + +- compile the whole module as usual, following import statements recursively + +- if there is a cycle only import the already parsed symbols (that are + exported); if an unknown identifier occurs then abort + +This is best illustrated by an example: + +.. code-block:: nim + # Module A + type + T1* = int # Module A exports the type ``T1`` + import B # the compiler starts parsing B + + proc main() = + var i = p(3) # works because B has been parsed completely here + + main() + + +.. code-block:: nim + # Module B + import A # A is not parsed here! Only the already known symbols + # of A are imported. + + proc p*(x: A.T1): A.T1 = + # this works because the compiler has already + # added T1 to A's interface symbol table + result = x + 1 Import statement diff --git a/tests/modules/trecmod.nim b/tests/modules/trecmod.nim index c670bec55..5f053bcae 100644 --- a/tests/modules/trecmod.nim +++ b/tests/modules/trecmod.nim @@ -2,6 +2,7 @@ discard """ file: "mrecmod.nim" line: 1 errormsg: "recursive module dependency detected" + disabled: true """ # recursive module import mrecmod diff --git a/tests/modules/trecmod2.nim b/tests/modules/trecmod2.nim index aa88f5e91..03c8cf70d 100644 --- a/tests/modules/trecmod2.nim +++ b/tests/modules/trecmod2.nim @@ -1,15 +1,13 @@ discard """ - file: "mrecmod2.nim" - line: 2 - errormsg: "recursive module dependency detected" + output: "4" """ type T1* = int # Module A exports the type ``T1`` import mrecmod2 # the compiler starts parsing B - +# the manual says this should work proc main() = - var i = p(3) # works because B has been parsed completely here + echo p(3) # works because B has been parsed completely here main() diff --git a/tests/modules/tselfimport.nim b/tests/modules/tselfimport.nim index b9109deae..ddb3a5b09 100644 --- a/tests/modules/tselfimport.nim +++ b/tests/modules/tselfimport.nim @@ -1,7 +1,7 @@ discard """ file: "tselfimport.nim" line: 7 - errormsg: "recursive module dependency detected" + errormsg: "A module cannot import itself" """ import strutils as su # guard against regression import tselfimport #ERROR diff --git a/web/news/e029_version_0_16_0.rst b/web/news/e029_version_0_16_0.rst index 42fdfc0e4..94c9757a7 100644 --- a/web/news/e029_version_0_16_0.rst +++ b/web/news/e029_version_0_16_0.rst @@ -29,8 +29,6 @@ Changes affecting backwards compatibility - ``TimeInfo.tzname`` has been removed from ``times`` module because it was broken. Because of this, the option ``"ZZZ"`` will no longer work in format strings for formatting and parsing. -- Recursive module dependencies are now completely disallowed. - Library Additions ----------------- From 2c146445bcb38dbd17f9988d60efc354a20c2d5b Mon Sep 17 00:00:00 2001 From: Ruslan Mustakov Date: Tue, 22 Nov 2016 21:53:41 +0700 Subject: [PATCH 51/58] Added deques module, deprecating queues --- doc/lib.rst | 5 +- lib/pure/asyncdispatch.nim | 12 +- lib/pure/collections/deques.nim | 266 +++++++++++++++++++++++++++++++ lib/pure/collections/queues.nim | 4 +- lib/upcoming/asyncdispatch.nim | 10 +- tests/misc/tvarious1.nim | 8 +- tests/stdlib/tmitems.nim | 10 +- tests/test_nimscript.nims | 2 +- web/news/e029_version_0_16_0.rst | 3 + web/website.ini | 2 +- 10 files changed, 297 insertions(+), 25 deletions(-) create mode 100644 lib/pure/collections/deques.nim diff --git a/doc/lib.rst b/doc/lib.rst index 828889968..8bb602b78 100644 --- a/doc/lib.rst +++ b/doc/lib.rst @@ -77,8 +77,9 @@ Collections and algorithms * `lists `_ Nim linked list support. Contains singly and doubly linked lists and circular lists ("rings"). -* `queues `_ - Implementation of a queue. The underlying implementation uses a ``seq``. +* `deques `_ + Implementation of a double-ended queue. + The underlying implementation uses a ``seq``. * `intsets `_ Efficient implementation of a set of ints as a sparse bit set. * `critbits `_ diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index 06301b08d..01088c2e7 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -11,7 +11,7 @@ include "system/inclrtl" import os, oids, tables, strutils, times, heapqueue -import nativesockets, net, queues +import nativesockets, net, deques export Port, SocketFlag @@ -164,7 +164,7 @@ include includes/asyncfutures type PDispatcherBase = ref object of RootRef timers: HeapQueue[tuple[finishAt: float, fut: Future[void]]] - callbacks: Queue[proc ()] + callbacks: Deque[proc ()] proc processTimers(p: PDispatcherBase) {.inline.} = while p.timers.len > 0 and epochTime() >= p.timers[0].finishAt: @@ -172,7 +172,7 @@ proc processTimers(p: PDispatcherBase) {.inline.} = proc processPendingCallbacks(p: PDispatcherBase) = while p.callbacks.len > 0: - var cb = p.callbacks.dequeue() + var cb = p.callbacks.popFirst() cb() proc adjustedTimeout(p: PDispatcherBase, timeout: int): int {.inline.} = @@ -230,7 +230,7 @@ when defined(windows) or defined(nimdoc): result.ioPort = createIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1) result.handles = initSet[AsyncFD]() result.timers.newHeapQueue() - result.callbacks = initQueue[proc ()](64) + result.callbacks = initDeque[proc ()](64) var gDisp{.threadvar.}: PDispatcher ## Global dispatcher proc getGlobalDispatcher*(): PDispatcher = @@ -987,7 +987,7 @@ else: new result result.selector = newSelector() result.timers.newHeapQueue() - result.callbacks = initQueue[proc ()](64) + result.callbacks = initDeque[proc ()](64) var gDisp{.threadvar.}: PDispatcher ## Global dispatcher proc getGlobalDispatcher*(): PDispatcher = @@ -1417,7 +1417,7 @@ proc recvLine*(socket: AsyncFD): Future[string] {.async, deprecated.} = 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) + getGlobalDispatcher().callbacks.addLast(cbproc) proc runForever*() = ## Begins a never ending global dispatcher poll loop. diff --git a/lib/pure/collections/deques.nim b/lib/pure/collections/deques.nim new file mode 100644 index 000000000..c25429778 --- /dev/null +++ b/lib/pure/collections/deques.nim @@ -0,0 +1,266 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2012 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Implementation of a `deque`:idx: (double-ended queue). +## The underlying implementation uses a ``seq``. +## +## None of the procs that get an individual value from the deque can be used +## on an empty deque. +## 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 deque 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 deq = initDeque[int]() # initializes the object +## for i in 1 ..< a: deq.addLast i # populates the deque +## +## if b < deq.len: # checking before indexed access +## echo "The element at index position ", b, " is ", deq[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 deq.peekFirst == 1 +## assert deq.peekLast == a +## +## while deq.len > 0: # checking if the deque is empty +## echo deq.removeLast() +## +## Note: For inter thread communication use +## a `Channel `_ instead. + +import math + +type + Deque*[T] = object + ## A double-ended queue backed with a ringed seq buffer. + data: seq[T] + head, tail, count, mask: int + +proc initDeque*[T](initialSize: int = 4): Deque[T] = + ## Create a new deque. + ## Optionally, the initial capacity can be reserved via `initialSize` as a + ## performance optimization. The length of a newly created deque 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 `_ module. + assert isPowerOfTwo(initialSize) + result.mask = initialSize-1 + newSeq(result.data, initialSize) + +proc len*[T](deq: Deque[T]): int {.inline.} = + ## Return the number of elements of `deq`. + result = deq.count + +template emptyCheck(deq) = + # Bounds check for the regular deque access. + when compileOption("boundChecks"): + if unlikely(deq.count < 1): + raise newException(IndexError, "Empty deque.") + +template xBoundsCheck(deq, i) = + # Bounds check for the array like accesses. + when compileOption("boundChecks"): # d:release should disable this. + if unlikely(i >= deq.count): # x < deq.low is taken care by the Natural parameter + raise newException(IndexError, + "Out of bounds: " & $i & " > " & $(deq.count - 1)) + +proc `[]`*[T](deq: Deque[T], i: Natural) : T {.inline.} = + ## Access the i-th element of `deq` by order from first to last. + ## deq[0] is the first, deq[^1] is the last. + xBoundsCheck(deq, i) + return deq.data[(deq.first + i) and deq.mask] + +proc `[]`*[T](deq: var Deque[T], i: Natural): var T {.inline.} = + ## Access the i-th element of `deq` and returns a mutable + ## reference to it. + xBoundsCheck(deq, i) + return deq.data[(deq.head + i) and deq.mask] + +proc `[]=`* [T] (deq: var Deque[T], i: Natural, val : T) {.inline.} = + ## Change the i-th element of `deq`. + xBoundsCheck(deq, i) + deq.data[(deq.head + i) and deq.mask] = val + +iterator items*[T](deq: Deque[T]): T = + ## Yield every element of `deq`. + var i = deq.head + for c in 0 ..< deq.count: + yield deq.data[i] + i = (i + 1) and deq.mask + +iterator mitems*[T](deq: var Deque[T]): var T = + ## Yield every element of `deq`. + var i = deq.head + for c in 0 ..< deq.count: + yield deq.data[i] + i = (i + 1) and deq.mask + +iterator pairs*[T](deq: Deque[T]): tuple[key: int, val: T] = + ## Yield every (position, value) of `deq`. + var i = deq.head + for c in 0 ..< deq.count: + yield (c, deq.data[i]) + i = (i + 1) and deq.mask + +proc contains*[T](deq: Deque[T], item: T): bool {.inline.} = + ## Return true if `item` is in `deq` or false if not found. Usually used + ## via the ``in`` operator. It is the equivalent of ``deq.find(item) >= 0``. + ## + ## .. code-block:: Nim + ## if x in q: + ## assert q.contains x + for e in deq: + if e == item: return true + return false + +proc expandIfNeeded[T](deq: var Deque[T]) = + var cap = deq.mask + 1 + if unlikely(deq.count >= cap): + var n = newSeq[T](cap * 2) + for i, x in deq: # don't use copyMem because the GC and because it's slower. + shallowCopy(n[i], x) + shallowCopy(deq.data, n) + deq.mask = cap * 2 - 1 + deq.tail = deq.count + deq.head = 0 + +proc addFirst*[T](deq: var Deque[T], item: T) = + ## Add an `item` to the beginning of the `deq`. + expandIfNeeded(deq) + inc deq.count + deq.head = (deq.head - 1) and deq.mask + deq.data[deq.head] = item + +proc addLast*[T](deq: var Deque[T], item: T) = + ## Add an `item` to the end of the `deq`. + expandIfNeeded(deq) + inc deq.count + deq.data[deq.tail] = item + deq.tail = (deq.tail + 1) and deq.mask + +proc peekFirst*[T](deq: Deque[T]): T {.inline.}= + ## Returns the first element of `deq`, but does not remove it from the deque. + emptyCheck(deq) + result = deq.data[deq.head] + +proc peekLast*[T](deq: Deque[T]): T {.inline.} = + ## Returns the last element of `deq`, but does not remove it from the deque. + emptyCheck(deq) + result = deq.data[(deq.tail - 1) and deq.mask] + +proc default[T](t: typedesc[T]): T {.inline.} = discard +proc popFirst*[T](deq: var Deque[T]): T {.inline, discardable.} = + ## Remove and returns the first element of the `deq`. + emptyCheck(deq) + dec deq.count + result = deq.data[deq.head] + deq.data[deq.head] = default(type(result)) + deq.head = (deq.head + 1) and deq.mask + +proc popLast*[T](deq: var Deque[T]): T {.inline, discardable.} = + ## Remove and returns the last element of the `deq`. + emptyCheck(deq) + dec deq.count + deq.tail = (deq.tail - 1) and deq.mask + result = deq.data[deq.tail] + deq.data[deq.tail] = default(type(result)) + +proc `$`*[T](deq: Deque[T]): string = + ## Turn a deque into its string representation. + result = "[" + for x in deq: + if result.len > 1: result.add(", ") + result.add($x) + result.add("]") + +when isMainModule: + var deq = initDeque[int](1) + deq.addLast(4) + deq.addFirst(9) + deq.addFirst(123) + var first = deq.popFirst() + deq.addLast(56) + assert(deq.peekLast() == 56) + deq.addLast(6) + assert(deq.peekLast() == 6) + var second = deq.popFirst() + deq.addLast(789) + assert(deq.peekLast() == 789) + + assert first == 123 + assert second == 9 + assert($deq == "[4, 56, 6, 789]") + + assert deq[0] == deq.peekFirst and deq.peekFirst == 4 + assert deq[^1] == deq.peekLast and deq.peekLast == 789 + deq[0] = 42 + deq[^1] = 7 + + assert 6 in deq and 789 notin deq + assert deq.find(6) >= 0 + assert deq.find(789) < 0 + + for i in -2 .. 10: + if i in deq: + assert deq.contains(i) and deq.find(i) >= 0 + else: + assert(not deq.contains(i) and deq.find(i) < 0) + + when compileOption("boundChecks"): + try: + echo deq[99] + assert false + except IndexError: + discard + + try: + assert deq.len == 4 + for i in 0 ..< 5: deq.popFirst() + assert false + except IndexError: + discard + + # grabs some types of resize error. + deq = initDeque[int]() + for i in 1 .. 4: deq.addLast i + deq.popFirst() + deq.popLast() + for i in 5 .. 8: deq.addFirst i + assert $deq == "[8, 7, 6, 5, 2, 3]" + + # Similar to proc from the documentation example + proc foo(a, b: Positive) = # assume random positive values for `a` and `b`. + var deq = initDeque[int]() + assert deq.len == 0 + for i in 1 .. a: deq.addLast i + + if b < deq.len: # checking before indexed access. + assert deq[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 deq.peekFirst == 1 + assert deq.peekLast == a + + while deq.len > 0: # checking if the deque is empty + assert deq.popFirst() > 0 + + #foo(0,0) + foo(8,5) + foo(10,9) + foo(1,1) + foo(2,1) + foo(1,5) + foo(3,2) \ No newline at end of file diff --git a/lib/pure/collections/queues.nim b/lib/pure/collections/queues.nim index 399e4d413..e4d7eeef1 100644 --- a/lib/pure/collections/queues.nim +++ b/lib/pure/collections/queues.nim @@ -39,8 +39,10 @@ import math +{.warning: "`queues` module is deprecated - use `deques` instead".} + type - Queue*[T] = object ## A queue. + Queue* {.deprecated.} [T] = object ## A queue. data: seq[T] rd, wr, count, mask: int diff --git a/lib/upcoming/asyncdispatch.nim b/lib/upcoming/asyncdispatch.nim index e7dc4abcc..68ecbe81e 100644 --- a/lib/upcoming/asyncdispatch.nim +++ b/lib/upcoming/asyncdispatch.nim @@ -11,7 +11,7 @@ include "system/inclrtl" import os, oids, tables, strutils, times, heapqueue, lists -import nativesockets, net, queues +import nativesockets, net, deques export Port, SocketFlag @@ -135,7 +135,7 @@ include "../includes/asyncfutures" type PDispatcherBase = ref object of RootRef timers: HeapQueue[tuple[finishAt: float, fut: Future[void]]] - callbacks: Queue[proc ()] + callbacks: Deque[proc ()] proc processTimers(p: PDispatcherBase) {.inline.} = while p.timers.len > 0 and epochTime() >= p.timers[0].finishAt: @@ -143,7 +143,7 @@ proc processTimers(p: PDispatcherBase) {.inline.} = proc processPendingCallbacks(p: PDispatcherBase) = while p.callbacks.len > 0: - var cb = p.callbacks.dequeue() + var cb = p.callbacks.popFirst() cb() proc adjustedTimeout(p: PDispatcherBase, timeout: int): int {.inline.} = @@ -1114,7 +1114,7 @@ else: new result result.selector = newSelector[AsyncData]() result.timers.newHeapQueue() - result.callbacks = initQueue[proc ()](64) + result.callbacks = initDeque[proc ()](64) var gDisp{.threadvar.}: PDispatcher ## Global dispatcher proc getGlobalDispatcher*(): PDispatcher = @@ -1638,7 +1638,7 @@ proc recvLine*(socket: AsyncFD): Future[string] {.async.} = 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) + getGlobalDispatcher().callbacks.addLast(cbproc) proc runForever*() = ## Begins a never ending global dispatcher poll loop. diff --git a/tests/misc/tvarious1.nim b/tests/misc/tvarious1.nim index 1d5ad876a..595c77919 100644 --- a/tests/misc/tvarious1.nim +++ b/tests/misc/tvarious1.nim @@ -18,15 +18,15 @@ echo v[2] # bug #569 -import queues +import deques type TWidget = object - names: Queue[string] + names: Deque[string] -var w = TWidget(names: initQueue[string]()) +var w = TWidget(names: initDeque[string]()) -add(w.names, "Whopie") +addLast(w.names, "Whopie") for n in w.names: echo(n) diff --git a/tests/stdlib/tmitems.nim b/tests/stdlib/tmitems.nim index c713d91a4..17265e1f7 100644 --- a/tests/stdlib/tmitems.nim +++ b/tests/stdlib/tmitems.nim @@ -98,13 +98,13 @@ block: x += 10 echo sl -import queues +import deques block: - var q = initQueue[int]() - q.add(1) - q.add(2) - q.add(3) + var q = initDeque[int]() + q.addLast(1) + q.addLast(2) + q.addLast(3) for x in q.mitems: x += 10 echo q diff --git a/tests/test_nimscript.nims b/tests/test_nimscript.nims index 436e990ef..2500bac73 100644 --- a/tests/test_nimscript.nims +++ b/tests/test_nimscript.nims @@ -14,7 +14,7 @@ import ospaths # import parseopt import parseutils # import pegs -import queues +import deques import sequtils import strutils import subexes diff --git a/web/news/e029_version_0_16_0.rst b/web/news/e029_version_0_16_0.rst index 94c9757a7..a6c8aa20f 100644 --- a/web/news/e029_version_0_16_0.rst +++ b/web/news/e029_version_0_16_0.rst @@ -35,6 +35,9 @@ Library Additions - Added new parameter to ``error`` proc of ``macro`` module to provide better error message +- Added new ``deques`` module intended to replace ``queues``. + ``deques`` provides a superset of ``queues`` API with clear naming. + ``queues`` module is now deprecated and will be removed in the future. Tool Additions -------------- diff --git a/web/website.ini b/web/website.ini index 0d1be4b63..3b8203cc0 100644 --- a/web/website.ini +++ b/web/website.ini @@ -51,7 +51,7 @@ srcdoc2: "pure/ropes;pure/unidecode/unidecode;pure/xmldom;pure/xmldomparser" srcdoc2: "pure/xmlparser;pure/htmlparser;pure/xmltree;pure/colors;pure/mimetypes" srcdoc2: "pure/json;pure/base64;pure/scgi" srcdoc2: "pure/collections/tables;pure/collections/sets;pure/collections/lists" -srcdoc2: "pure/collections/intsets;pure/collections/queues;pure/encodings" +srcdoc2: "pure/collections/intsets;pure/collections/queues;pure/collections/deques;pure/encodings" srcdoc2: "pure/events;pure/collections/sequtils;pure/cookies" srcdoc2: "pure/memfiles;pure/subexes;pure/collections/critbits" srcdoc2: "deprecated/pure/asyncio;deprecated/pure/actors;core/locks;core/rlocks;pure/oids;pure/endians;pure/uri" From 41205493c0825d9329ee99b68a52d7684c33c14f Mon Sep 17 00:00:00 2001 From: Yuriy Glukhov Date: Sat, 26 Nov 2016 12:43:36 +0200 Subject: [PATCH 52/58] Fixes #5057 --- lib/pure/collections/tables.nim | 40 +++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index dfd822852..e423396ed 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -778,20 +778,22 @@ proc sort*[A, B](t: OrderedTableRef[A, B], proc del*[A, B](t: var OrderedTable[A, B], key: A) = ## deletes `key` from ordered hash table `t`. O(n) comlexity. - var prev = -1 + var n: OrderedKeyValuePairSeq[A, B] + newSeq(n, len(t.data)) + var h = t.first + t.first = -1 + t.last = -1 + swap(t.data, n) let hc = genHash(key) - forAllOrderedPairs: - if t.data[h].hcode == hc: - if t.first == h: - t.first = t.data[h].next + while h >= 0: + var nxt = n[h].next + if isFilled(n[h].hcode): + if n[h].hcode == hc and n[h].key == key: + dec t.counter 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 + var j = -1 - rawGetKnownHC(t, n[h].key, n[h].hcode) + rawInsert(t, t.data, n[h].key, n[h].val, n[h].hcode, j) + h = nxt proc del*[A, B](t: var OrderedTableRef[A, B], key: A) = ## deletes `key` from ordered hash table `t`. O(n) comlexity. @@ -1157,6 +1159,20 @@ when isMainModule: doAssert(prev < i) prev = i + block: # Deletion from OrederedTable should account for collision groups. See issue #5057. + # The bug is reproducible only with exact keys + const key1 = "boy_jackpot.inGamma1" + const key2 = "boy_jackpot.outBlack2" + + var t = { + key1: 0, + key2: 0 + }.toOrderedTable() + + t.del(key1) + assert(t.len == 1) + assert(key2 in t) + var t1 = initCountTable[string]() t2 = initCountTable[string]() From e83d11e8f17c384aa9f717936128d7a2d9c2b512 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Sat, 26 Nov 2016 14:15:37 +0100 Subject: [PATCH 53/58] deepcopy fix --- lib/system/deepcopy.nim | 106 ++++++++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 31 deletions(-) diff --git a/lib/system/deepcopy.nim b/lib/system/deepcopy.nim index 38cc8cbf3..b1609252c 100644 --- a/lib/system/deepcopy.nim +++ b/lib/system/deepcopy.nim @@ -7,18 +7,66 @@ # distribution, for details about the copyright. # -proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) {.benign.} -proc genericDeepCopyAux(dest, src: pointer, n: ptr TNimNode) {.benign.} = +type + PtrTable = ptr object + counter, max: int + data: array[0..0xff_ffff, (pointer, pointer)] + +template hashPtr(key: pointer): int = cast[int](key) shr 8 +template allocPtrTable: untyped = + cast[PtrTable](alloc0(sizeof(int)*2 + sizeof(pointer)*2*cap)) + +proc rehash(t: PtrTable): PtrTable = + let cap = (t.max+1) * 2 + result = allocPtrTable() + result.counter = t.counter + result.max = cap-1 + for i in 0..t.max: + let k = t.data[i][0] + if k != nil: + var h = hashPtr(k) + while result.data[h and result.max][0] != nil: inc h + result.data[h and result.max] = t.data[i] + dealloc t + +proc initPtrTable(): PtrTable = + const cap = 32 + result = allocPtrTable() + result.counter = 0 + result.max = cap-1 + +template deinit(t: PtrTable) = dealloc(t) + +proc get(t: PtrTable; key: pointer): pointer = + var h = hashPtr(key) + while true: + let k = t.data[h and t.max][0] + if k == nil: break + if k == key: + return t.data[h and t.max][1] + inc h + +proc put(t: var PtrTable; key, val: pointer) = + if (t.max+1) * 2 < t.counter * 3: t = rehash(t) + var h = hashPtr(key) + while t.data[h and t.max][0] != nil: inc h + t.data[h and t.max] = (key, val) + inc t.counter + +proc genericDeepCopyAux(dest, src: pointer, mt: PNimType; + tab: var PtrTable) {.benign.} +proc genericDeepCopyAux(dest, src: pointer, n: ptr TNimNode; + tab: var PtrTable) {.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) + cast[pointer](s +% n.offset), n.typ, tab) of nkList: for i in 0..n.len-1: - genericDeepCopyAux(dest, src, n.sons[i]) + genericDeepCopyAux(dest, src, n.sons[i], tab) of nkCase: var dd = selectBranch(dest, n) var m = selectBranch(src, n) @@ -29,10 +77,10 @@ proc genericDeepCopyAux(dest, src: pointer, n: ptr TNimNode) {.benign.} = copyMem(cast[pointer](d +% n.offset), cast[pointer](s +% n.offset), n.typ.size) if m != nil: - genericDeepCopyAux(dest, src, m) + genericDeepCopyAux(dest, src, m, tab) of nkNone: sysAssert(false, "genericDeepCopyAux") -proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) = +proc genericDeepCopyAux(dest, src: pointer, mt: PNimType; tab: var PtrTable) = var d = cast[ByteAddress](dest) s = cast[ByteAddress](src) @@ -60,22 +108,22 @@ proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) = cast[pointer](dst +% i*% mt.base.size +% GenericSeqSize), cast[pointer](cast[ByteAddress](s2) +% i *% mt.base.size +% GenericSeqSize), - mt.base) + mt.base, tab) of tyObject: # we need to copy m_type field for tyObject, as it could be empty for # sequence reallocations: if mt.base != nil: - genericDeepCopyAux(dest, src, mt.base) + genericDeepCopyAux(dest, src, mt.base, tab) else: var pint = cast[ptr PNimType](dest) pint[] = cast[ptr PNimType](src)[] - genericDeepCopyAux(dest, src, mt.node) + genericDeepCopyAux(dest, src, mt.node, tab) of tyTuple: - genericDeepCopyAux(dest, src, mt.node) + genericDeepCopyAux(dest, src, mt.node, tab) of tyArray, tyArrayConstr: for i in 0..(mt.size div mt.base.size)-1: genericDeepCopyAux(cast[pointer](d +% i*% mt.base.size), - cast[pointer](s +% i*% mt.base.size), mt.base) + cast[pointer](s +% i*% mt.base.size), mt.base, tab) of tyRef: let s2 = cast[PPointer](src)[] if s2 == nil: @@ -84,30 +132,24 @@ proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) = let z = mt.base.deepcopy(s2) unsureAsgnRef(cast[PPointer](dest), z) 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. - when declared(usrToCell): - # unfortunately we only have cycle detection for our native GCs. - 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 z = tab.get(s2) + if z == nil: + when declared(usrToCell) and false: + let x = usrToCell(s2) 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(z, s2, realType.base) - x.typ = realType + tab.put(s2, z) + genericDeepCopyAux(z, s2, realType.base, tab) + else: + # this version should work for any possible GC: + let size = if mt.base.kind == tyObject: cast[ptr PNimType](s2)[].size else: mt.base.size + let z = newObj(mt, size) + unsureAsgnRef(cast[PPointer](dest), z) + tab.put(s2, z) + genericDeepCopyAux(z, s2, mt.base, tab) else: - let size = if mt.base.kind == tyObject: cast[ptr PNimType](s2)[].size - else: mt.base.size - let z = newObj(mt, size) unsureAsgnRef(cast[PPointer](dest), z) - genericDeepCopyAux(z, s2, mt.base) of tyPtr: # no cycle check here, but also not really required let s2 = cast[PPointer](src)[] @@ -120,7 +162,9 @@ proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) = proc genericDeepCopy(dest, src: pointer, mt: PNimType) {.compilerProc.} = GC_disable() - genericDeepCopyAux(dest, src, mt) + var tab = initPtrTable() + genericDeepCopyAux(dest, src, mt, tab) + deinit tab GC_enable() proc genericSeqDeepCopy(dest, src: pointer, mt: PNimType) {.compilerProc.} = From 10292a2626ded66a369044392d670030ec699210 Mon Sep 17 00:00:00 2001 From: Yuriy Glukhov Date: Sun, 27 Nov 2016 13:34:24 +0200 Subject: [PATCH 54/58] Corrected test case for #5057. --- lib/pure/collections/tables.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index e423396ed..e6e72d9ed 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -1159,10 +1159,10 @@ when isMainModule: doAssert(prev < i) prev = i - block: # Deletion from OrederedTable should account for collision groups. See issue #5057. + block: # Deletion from OrderedTable should account for collision groups. See issue #5057. # The bug is reproducible only with exact keys - const key1 = "boy_jackpot.inGamma1" - const key2 = "boy_jackpot.outBlack2" + const key1 = "boy_jackpot.inGamma" + const key2 = "boy_jackpot.outBlack" var t = { key1: 0, From b67aa23de9286c7bce3500f43f5f2991b947e24b Mon Sep 17 00:00:00 2001 From: Aditya Siram Date: Sun, 27 Nov 2016 13:37:36 -0600 Subject: [PATCH 55/58] Fix compilation error in nimeval. --- compiler/nimeval.nim | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler/nimeval.nim b/compiler/nimeval.nim index 2bddb76e7..2872bdade 100644 --- a/compiler/nimeval.nim +++ b/compiler/nimeval.nim @@ -8,10 +8,9 @@ # ## exposes the Nim VM to clients. - import ast, modules, passes, passaux, condsyms, - options, nimconf, lists, sem, semdata, llstream, vm + options, nimconf, lists, sem, semdata, llstream, vm, modulegraphs, idents proc execute*(program: string) = passes.gIncludeFile = includeModule @@ -27,7 +26,9 @@ proc execute*(program: string) = registerPass(evalPass) appendStr(searchPaths, options.libpath) - compileSystemModule() - var m = makeStdinModule() + var graph = newModuleGraph() + var cache = newIdentCache() + var m = makeStdinModule(graph) incl(m.flags, sfMainModule) - processModule(m, llStreamOpen(program), nil) + compileSystemModule(graph,cache) + processModule(graph,m, llStreamOpen(program), nil, cache) From 7ca72a733c7960eee1c86a4c46b0aac47fe851fc Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Mon, 28 Nov 2016 10:56:45 +0100 Subject: [PATCH 56/58] use -d:nimTypeNames to create RTTI with type names --- compiler/ccgtypes.nim | 2 ++ lib/system/hti.nim | 2 ++ 2 files changed, 4 insertions(+) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 60ee0eaee..68e98e92e 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -816,6 +816,8 @@ proc genTypeInfoAuxBase(m: BModule; typ, origType: PType; name, base: Rope) = #else MessageOut("can contain a cycle: " & typeToString(typ)) if flags != 0: addf(m.s[cfsTypeInit3], "$1.flags = $2;$n", [name, rope(flags)]) + if isDefined("nimTypeNames"): + addf(m.s[cfsTypeInit3], "$1.name = $2;$n", [name, makeCstring typeToString origType]) discard cgsym(m, "TNimType") addf(m.s[cfsVars], "TNimType $1; /* $2 */$n", [name, rope(typeToString(typ))]) diff --git a/lib/system/hti.nim b/lib/system/hti.nim index 892a209df..d5cca7c1c 100644 --- a/lib/system/hti.nim +++ b/lib/system/hti.nim @@ -86,6 +86,8 @@ type finalizer: pointer # the finalizer for the type marker: proc (p: pointer, op: int) {.nimcall, benign.} # marker proc for GC deepcopy: proc (p: pointer): pointer {.nimcall, benign.} + when defined(nimTypeNames): + name: cstring PNimType = ptr TNimType # node.len may be the ``first`` element of a set From 734443d725724df0d328d27982fbc2c54d1ff683 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Mon, 28 Nov 2016 11:03:01 +0100 Subject: [PATCH 57/58] system.deepCopy should show old behaviour --- lib/system/deepcopy.nim | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/system/deepcopy.nim b/lib/system/deepcopy.nim index b1609252c..c137b3cf6 100644 --- a/lib/system/deepcopy.nim +++ b/lib/system/deepcopy.nim @@ -134,7 +134,7 @@ proc genericDeepCopyAux(dest, src: pointer, mt: PNimType; tab: var PtrTable) = else: let z = tab.get(s2) if z == nil: - when declared(usrToCell) and false: + when declared(usrToCell): let x = usrToCell(s2) let realType = x.typ let z = newObj(realType, realType.base.size) @@ -142,6 +142,11 @@ proc genericDeepCopyAux(dest, src: pointer, mt: PNimType; tab: var PtrTable) = tab.put(s2, z) genericDeepCopyAux(z, s2, realType.base, tab) else: + when false: + # addition check disabled + let x = usrToCell(s2) + let realType = x.typ + sysAssert realType == mt, " types do differ" # this version should work for any possible GC: let size = if mt.base.kind == tyObject: cast[ptr PNimType](s2)[].size else: mt.base.size let z = newObj(mt, size) From f9c184a4932eb839a6ec4b9293e37583cd33a89a Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 25 Nov 2016 11:14:57 +0100 Subject: [PATCH 58/58] minor website improvements --- tools/website.tmpl | 24 ++++++++++++------------ web/index.rst | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tools/website.tmpl b/tools/website.tmpl index 9aa64310d..344024ff0 100644 --- a/tools/website.tmpl +++ b/tools/website.tmpl @@ -60,17 +60,7 @@ # if currentTab == "index":
-
- - A printed copy of Nim in Action should be available in March 2017! - -
-
- - Meet our BountySource sponsors! - -
-
+

Nim is simple..

@@ -104,7 +94,7 @@ p.greet() # or greet(p)
 
-
+

C FFI is easy in Nim..

@@ -136,6 +126,16 @@ runForever()
               

View in browser at:
    localhost:5000

+
+ + A printed copy of Nim in Action should be available in March 2017! + +
+
+ + Meet our BountySource sponsors! + +
diff --git a/web/index.rst b/web/index.rst index 506453423..4b712fa3b 100644 --- a/web/index.rst +++ b/web/index.rst @@ -5,7 +5,7 @@ Home Welcome to Nim -------------- -**Nim** (formerly known as "Nimrod") is a statically typed, imperative +**Nim** is a statically typed, imperative programming language that tries to give the programmer ultimate power without compromises on runtime efficiency. This means it focuses on compile-time mechanisms in all their various forms.