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 bd2e2cdda3..0327d9b82e 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 52de60969c..a75016ed74 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 0327d9b82e..a9c27217a4 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 eb1beb549f..5d9ed03ab6 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 a75016ed74..818bff462b 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 a9c27217a4..60ee0eaeeb 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 11897ce806..c8623f2f11 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 57b8a68d48..f5b8e4826a 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 f5b8e4826a..50fa9a431b 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 1e869d301c..4260179ed8 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 204e5bb40a..241ad17aed 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 4260179ed8..f740038209 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 5d3c8325e4..c1559ec7a2 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 85951a28fa..e545e7ab5a 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 ffe6a391e7..0241e47960 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 f740038209..cfc39bc553 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 c1559ec7a2..6d3d7c93ab 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 cfc39bc553..b6b9fad5ba 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 6d3d7c93ab..39106f1e04 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 b6b9fad5ba..ef23c4cf9a 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 ef23c4cf9a..0ab14e1839 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 39106f1e04..b28d8aecde 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 0ab14e1839..1767a37be3 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 b23b1e5bb9..5544a4b3f4 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 12553e3da1..cdca02ed79 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 58f5e5777a..863a8a6f41 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 38cc8cbf3c..0a661d0cdc 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 0a661d0cdc..b1609252cb 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 a3dfd43a15..674fdddd2f 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 bee0a41b20..dfd8228522 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 4f286d0edb..ef5ed92f57 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 0000000000..f7a6e87fa4 --- /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 36e6cf52fe..5eb8f7c1c4 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 5eb8f7c1c4..c4c731acf9 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 8c4a0e41d9..58028847e5 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 58028847e5..b93390221d 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 b93390221d..06301b08d9 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 731ef52dcc..e7dc4abcc6 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 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

6@#6SkMe=BuXS<7_Gu32O=*|h)k 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 +=================================== + `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 0000000000..b68b828013 --- /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 b59c0a3e90..86dc97c14c 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 2801fea96a..9aa64310dd 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 e7fd2bc360..d76fb61ad5 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 00a60fe1bd..2761f888b9 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 a949500296..faeea7afbe 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 e545e7ab5a..590c4871de 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 7cf7079450..f8db3927af 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 f4109b26d9..e11a8d08b4 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 04376892ff..cf66b6358f 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 b8980fa9c6..c434391ce0 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 0000000000..e3d9832c6e --- /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 9547673a57..69d3db291d 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 dcf41b67de..d00ab64b13 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 0000000000..b4a5460fa9 --- /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 3a93221e0c..11034006af 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 0b7908c020..b7f58c55d5 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 b7f58c55d5..5fff7352f4 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 7c76efe423..12d86a9056 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 dfcfa37a0c..029c5f157b 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 c434391ce0..991f06397c 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 e3d9832c6e..b5072419f0 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 129869373d..14877eb4d5 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 d76fb61ad5..4970a6155b 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 b5072419f0..db29b4252c 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 ce365c4dca..46e4c159f8 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 9a3caa6632..38fd4f89fc 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 26ca2177b6..3451d85ecb 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 a44a1306c9..0f39eb4d3b 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 f8d6b607af..35afecf205 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 9cb6a11af8..8a9f5ff655 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 14a91ba5c3..7a74d7a465 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 299a242e1c..1d3eb55035 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 d567e293b3..c670bec558 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 85fe2215fd..aa88f5e91c 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 ddb3a5b093..b9109deaec 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 822ef7224a..b5e7b282f5 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 94c9757a73..42fdfc0e4d 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 db29b4252c..52fa9da77e 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 745bbbf622..3a8e8a1b69 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 11897ce806..3bb0f62ff5 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 b4a5460fa9..a9767bbca6 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 4970a6155b..d2b70df8d0 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 46e4c159f8..b7e574c629 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 f8db3927af..9edafb17a2 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 ebf2875026..f81a508c62 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 f81a508c62..a2ba41e12c 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 b1609252cb..38cc8cbf3c 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 f7a6e87fa4..5a582425ab 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 4f1d4e3aa3..3cc15147e3 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 1e67644712..f0ca149f8d 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 49c0a24764..804a22852c 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 ddfb43b386..e245c9e8c4 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 75b36e69af..0008d90337 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 b7e574c629..feebf97c43 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 df19a6afb4..fe159011cd 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 0f39eb4d3b..94b0bee00a 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 5b84b7cdf0..2fec8c757d 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 d3431de702..8aaf4f9d85 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 b8451865ee..ab0ce7c4c2 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 cd90782d16..e72172c815 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 8a9f5ff655..9cb6a11af8 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 c670bec558..5f053bcae8 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 aa88f5e91c..03c8cf70d3 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 b9109deaec..ddb3a5b093 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 42fdfc0e4d..94c9757a73 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 828889968f..8bb602b788 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 06301b08d9..01088c2e7c 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 0000000000..c254297783 --- /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 399e4d4136..e4d7eeef1c 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 e7dc4abcc6..68ecbe81ee 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 1d5ad876a5..595c779193 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 c713d91a41..17265e1f7e 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 436e990ef7..2500bac738 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 94c9757a73..a6c8aa20fe 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 0d1be4b638..3b8203cc0f 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 dfd8228522..e423396ed9 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 38cc8cbf3c..b1609252cb 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 e423396ed9..e6e72d9edc 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 2bddb76e7a..2872bdade5 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 60ee0eaeeb..68e98e92e2 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 892a209df8..d5cca7c1c6 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 b1609252cb..c137b3cf61 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 9aa64310dd..344024ff00 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 5064534233..4b712fa3b6 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.